// James Brink, 5/6/2021
// In this program mode referes to initial value mode or boundary value mode
// mode = 0: intial value
// mode = 1: boundary value
let can1;
let can2;
let spacer;
let mode = 0; // start in rectangular mode - the only mode in this program
let s2; // span for showing mode;
let f = ["", "", "", "", "", "", ""];
let fModified = []; // f after y0, y1, and y2 are replaced by y, v, w
let fDefined = []; // the function i is defined
let noFDefined;
let specialDefined = false; // true if a special like x=, o=, L, ... defined
let fLabel = [];
let fInput = [];
let methodMsg = "";
let x, y, t;
let v, w; // y1, y2, and y3 are changed to y, v, and w so they can be used
// by eval
let minX, maxX, minY, maxY, minT, maxT; // used before checking equalSpace
let miniX, maxiX, miniY, maxiY; // in case equalSpace true
let xAxisLoc, yAxisLoc;
let xVals = []; // x values to be displayed in graph
let yVals = []; // y values to be displayed in graph
let ff = []; // function values for the Adams-Moulton method
let fun, funP, funPP, funPPP; // function and its derivatives
let maxIterations; // global for sake of check function
let xMaxV = [];
let xMinV = [];
let yMaxV = [];
let yMinV = [];
let yMaxVals;
let yMinVals;
let beginningX;
let endingX;
let beginningY = [];
let endingY = [];
let shotNum;
let endingYResult = [];
let endingXResult;
let results = []; // array of past y values at ending x
results[0] = [];
results[1] = [];
results[2] = [];
let doingAdams = false;
let s3aDiv; // for ending y values - only for boundary value problems
let s3Div; // for update - only for boundary value problems
let starting = []; //starting y or y' for boundary value problems
starting[0] = []; // starting y values
starting[1] = []; // starting y' values
let xMinLabel, xMaxLabel, yMinLabel, yMaxLabel;
let xMinInput, xMaxInput, yMinInput, yMaxInput;
let tMinLabel, tMinInput, tMaxLabel, tMaxInput;
let hashSpacingX, hashSpacingY;
let beginningXInp;
let beginningYInp = [];
let endingYInp = [];
let showFP; // check box for showing f'
let showFPP; // check box for showing f''
let equalSpace; // check box for Equal Spacing
let allowLoop; // check box for Allow motion
let zoomInBtn; // enlarge picture around last iteration
let zoomOutBtn; // zoom out
let zoomRatio = 5; // multiplier for zoom
let initialRadio, boundaryRadio; // used to determine initial or boundary mode
let moveBackBtn; //
let numPtsLabel, numPtsInput; // number of evaluation pts
let showExampleDataBtn;
let loopCnt = 0;
let p5left;
let p5right;
let big; // enlarged canvas
let valuesLab = ["a", "b", "c", "d", "e", "s"];
let a = 0, b = 0, c = 0, d = 0, e = 0, s;
let theValues = [0, 0, 0, 0, 0, 0];
let savedValues = [0, 0, 0, 0, 0, 0];
let g = 0; // Beginning x
let G = 0; // Ending x // these caps are not constants - for ending valu
let H = 0; // Step size - calculated - not box value
let p = 0; // Beginning y0
let q = 0; // Beginning y1 (y')
let r = 0; // Beginning y2 (y")
let M = 0; // y0 boundary condition at ending x
let N = 0; // y1 boundary condition at ending x
let P = 0; // y0 at ending x after iteration
let Q = 0; // y1 at ending x after iteration (y')
let R = 0; // y2 at ending x after iteration(y")
let colors = ["red", "blue", "green", "maroon", "magenta", "navy"];
// Number of colors determines NUM_F_INPUT
let useColor = []; // used to allow specifying color with words
let valLab = [];
let valInp = [];
let numDiv = 200;
let numPtsOffset = .05 * numDiv; // 2 * numPtsOffset + numDiv = numPts
let errorMsg = ""; // error message shown in the canvas
let functionError = false; // these are types of error that may occur
let minMaxError = false;
let valueError = false;
let numberEvalsError = false;
let beginningXError = false;
let initializationError = false;
const locOffset = 19;
const ERROR = "ERROR";
const RECT_MODE = 0;
const REALY_BIG = 1.2345E100;
const NUM_F_INPUT = colors.length;
const NUM_BOUNDARY = 2; // Boundary ODEs must be order 2
const MODE_LAB ='
Mode: Type of problem
';
// FUNCTION_LABn format = [Label for mode 0, label for mode 1, label for mode2]
// Only mode 0 is used in DiffiEq
const FUNCTION_LAB0 = ['
'+ "y0'(x, y) = ",
'
'+ "y0'(x, y) = "];
const FUNCTION_LAB1 = ['
' + "y1'(x, y) = ",
'
' + "y0''(x, y) = "];
const FUNCTION_LAB2 = ['
' + "y2'(x, y) = ",
'
' + "y2'(x, y) = "];
const FUNCTION_LAB3 = ['
' + "y3(x) = ",
'
' + "y3(x) = "];
const FUNCTION_LAB4 = ['
' + "y4(x) = ",
'
' + "y4(x) = "];
const FUNCTION_LAB5 = ['
' + "y5(x) = ",
'
' + "y5(x) = "];
const FUNCTION_LAB = [FUNCTION_LAB0, FUNCTION_LAB1, FUNCTION_LAB2,
FUNCTION_LAB3, FUNCTION_LAB4];
const T_MIN_LAB = ['', '
Minimum θ ',
'
Maximum t '];
const T_MAX_LAB = ['', '
Maximum θ ',
'
Maximum t '];
const T_MIN_VAL = [0, 0, 0];
const T_MAX_VAL = [1, 'TWO_PI', 10];
const SPECIAL_SYMBOLS = ['^a', '^b', '^c', '^d', '^e'];
const MODE_COLORS = ["Black"]; // for examples
// for enlargement
let enlarge = false;
let enlargeRatio = 1.5;
let enlargeChk;
let enlargeSpan; // label for enlargeSlider
let enlargeSlider;
// Examples:
let example = [];
let exampleRadio; // radio buttons for the examples // not used
let examplesRadio = []
const EXAMPLE_NAME = 31; // location of the name //Changed for DiffiEq
let numPts; // Math.floor is required because
// p5.js functions not allowed in the "introductory" assignments.
function setup() {
p5left = document.getElementById("p5-left"); // used with radio
p5right = document.getElementById("p5-right"); // used with radio
// start left hand side with a canvas
numPts = /*Math.*/floor(1.1 * numDiv); ///// Math.floor is required because
/////// p5.js functions not allowed in the "introductory" assignments.
// Now uses p5 floor function
can1 = createCanvasClass(430, 430); // must be square
can1.doubleClicked(dblClick);
// When these programs were written p5js radio buttons didn't work.
// So regular Javascript buttons were used instead.
can1.parent(p5left);
can1.mouseClicked(clearErrorMsg);
can2 = createCanvasClass(430 * enlargeRatio, 430 * enlargeRatio);
big = document.getElementById("big");
can2.parent(big);
can2.mouseClicked(clearErrorMsg);
can2.style("display: none");
myTextAlign(CENTER, BASELINE);
spacer = createDiv(" ");
spacer.parent(big);
spacer.style("display: none");
let radio;
// start left hand info display below the graph
let s0 = createSpan("
Values");
s0.parent(p5left);
for (i = 0; i < valuesLab.length; i++) {
if (i == 3 ) { // start a new line
let s1 = createSpan("
"
+ " ");
s1.parent(p5left);
}
valLab[i] = createSpan(" " + valuesLab[i] + " ")
valLab[i].parent(p5left);
valInp[i] = createInput("", "text");
valInp[i].parent(p5left);
valInp[i].size(109);
valInp[i].changed(valueChanged);
}
// Handle s input in a special way
valInp[5].input(sInput);
// create slider for s
sleft = createSpan("
"
+ " "
+ " Adjust value of s (0 - 100) ");
sleft.parent(p5left);
sValueSlider = createSlider(0, 100, 50);
sValueSlider.parent(p5left);
sValueSlider.changed(sSliderChanged);
// create display for values for a ... e
s1a = createSpan("
");
s1a.parent(p5left);
valDisplay = createSpan("");
valDisplay.parent(p5left);
// start right hand info display
// display function labels and inputs
// first the labels and radio buttons for the initial and boundary value
if (trim(valInp[3].value()) != "") {
d = eval(valInp[3].value()); // eval of blank returns NaN
} else {
d = 0;
}
s2 = createSpan(MODE_LAB);
s2.parent("p5-right");
// s2a = createSpan("
");
// s2a.parent("p5-right");
radio = HTMLforRadioButton("modeResult",
''
+ 'Initial value ',
"getRadioValueMode()", true, 0);
initialRadio = displayHTMLElements(radio, p5right, "inline-block");
radio = HTMLforRadioButton("modeResult",
''
+ 'Boundary value ',
"getRadioValueMode()", false, 1);
boundaryRadio = displayHTMLElements(radio, p5right, "inline-block");
let s3 = createSpan("
To change a value in any input box, type"
+ ' the
desired value and then press "Enter".');
s3.parent("p5-right");
for (i = 0; i < NUM_F_INPUT; i++) {
myFill(colors[i]);
fLabel[i] = createSpan(""); // create label
functionLab(i, mode); // provide an initial label
fLabel[i].parent("p5-right");
fInput[i] = createInput(f[i]);
fInput[i].parent("p5-right");
fInput[i].changed(inputF);
fInput[i].size(240);
}
// values for solving
beginningXLab = createSpan("
Beginning x ");
beginningXLab.parent("p5-right");
beginningXInp = createInput("");
beginningXInp.parent("p5-right");
beginningXInp.size(50);
beginningXInp.changed(checkStartEnd);
endingXLab = createSpan(" Ending x ");
endingXLab.parent("p5-right");
endingXInp = createInput("");
endingXInp.parent("p5-right");
endingXInp.size(50);
endingXInp.changed(checkStartEnd);
beginningYLab = createSpan("
Beginning y values ");
beginningYLab.parent("p5-right");
for (j = 0; j < NUM_F_INPUT/2; j++) {
beginningYInp[j] = createInput("");
beginningYInp[j].parent("p5-right");
beginningYInp[j].size(50);
beginningYInp[j].changed(checkYBoundaries);
}
s3aDiv = createDiv();
s3aDiv.parent("p5-right");
s3aDiv.style("display: none; background-color: lightblue");
let endingYLab = createSpan("Ending y values ");
endingYLab.parent(s3aDiv);
for (j = 0; j < NUM_BOUNDARY; j++) {
endingYInp[j] = createInput("");
endingYInp[j].parent(s3aDiv);
endingYInp[j].size(50);
endingYInp[j].changed(checkYBoundaries);
}
shotNum = createSpan();
shotNum.parent(s3aDiv);
stepSizeLab = createSpan("
Step size "); ////////
stepSizeLab.parent("p5-right");
stepSizeInp = createInput("50");
stepSizeInp.parent("p5-right");
stepSizeInp.size(50);
// solve buttons
let s3a = createSpan("
");
s3a.parent("p5-right");
let eulersSolveBtn = createButton("Use Euler's method");
eulersSolveBtn.parent("p5-right");
eulersSolveBtn.mousePressed(eulersSolve);
let s3b = createSpan("
");
s3b.parent("p5-right");
let modifiedEulersSolveBtn = createButton("Use Modified Euler's method");
modifiedEulersSolveBtn.parent("p5-right");
modifiedEulersSolveBtn.mousePressed(modifiedEulersSolve);
let s3c = createSpan("
");
s3c.parent("p5-right");
let rungeKutttaSolveBtn = createButton("Use Runge-Kutta 4th order method");
rungeKutttaSolveBtn.parent("p5-right");
rungeKutttaSolveBtn.mousePressed(rungeKuttaSolve);
let s3d = createSpan("
");
s3d.parent("p5-right");
let adamsMoultonSolveBtn = createButton(
"Use Adams-Moulton 4th order method");
adamsMoultonSolveBtn.parent("p5-right");
adamsMoultonSolveBtn.mousePressed(adamsMoultonSolve);
let s3e = createSpan("
Result at x = ");
s3e.parent("p5-right");
endingXResult = createInput("");
endingXResult.parent("p5-right");
endingXResult.size(50);
let s3f = createSpan(" ");
s3f.parent("p5-right");
for (j = 0; j < NUM_F_INPUT/2; j++) {
endingYResult[j] = createInput("");
endingYResult[j].parent("p5-right");
endingYResult[j].size(50);
}
// create the "update" buton. It will be hidden until
// boundary value is choosen.
let s3g = createSpan("
");
s3g.parent("p5-right");
s3Div = createDiv();
s3Div.parent("p5-right");
s3Div.style("display: none; background-color: lightblue");
let updateBtn1 = createButton("Update y' at beginning x");
updateBtn1.parent(s3Div);
updateBtn1.mousePressed(update1);
let updateBtn0 = createButton("Update y at beginning x");
updateBtn0.parent(s3Div);
updateBtn0.mousePressed(update0);
// create a help link
let s4 = createSpan('
'
+ 'Help with DiffiEq2 app.
');
s4.parent("p5-right");
// create input for max and mins for x and y
xMinLabel = createSpan("
Maximum x ");
xMinLabel.parent("p5-right");
xMinInput = createInput("-5", "text");
xMinInput.parent("p5-right");
xMinInput.changed(checkMinMax);
xMaxLabel = createSpan("
Maximum x ");
xMaxLabel.parent("p5-right");
xMaxInput = createInput("5", "text");
xMaxInput.parent("p5-right");
xMaxInput.changed(checkMinMax);
instLabel = createSpan("
The minimum and maximum y values will"
+ " be
calculated automatically if you leave them blank.");
instLabel.parent("p5-right");
yMinLabel = createSpan("
Minimum y ");
yMinLabel.parent("p5-right");
yMinInput = createInput("", "text");
yMinInput.parent("p5-right");
yMinInput.changed(checkMinMax);
yMaxLabel = createSpan("
Maximum y ");
yMaxLabel.parent("p5-right");
yMaxInput = createInput("", "text");
yMaxInput.parent("p5-right");
yMaxInput.changed(checkMinMax);
// zoom buttons
let s4a = createSpan("
Doubleclick to set center of graph.
");
s4a.parent("p5-right");
zoomInBtn = createButton("Zoom in");
zoomInBtn.parent("p5-right");
zoomInBtn.mousePressed(zoomIn);
zoomOutBtn = createButton("Zoom out");
zoomOutBtn.parent("p5-right");
zoomOutBtn.mousePressed(zoomOut);
// create Equal spacing, Allow motion, Show enlarged canvas
// and Radians/degrees inputs
equalSpace = createCheckbox("Equal spacing ", false);
equalSpace.parent("p5-right");
equalSpace.changed(equalSpaceChanged);
allowLoop = createCheckbox("Allow motion", false);
allowLoop.parent("p5-right");
allowLoop.changed(allowLoopChanged);
enlargeChk = createCheckbox("Show enlarged canvas", false);
enlargeChk.parent(p5right);
enlargeChk.changed(enlargeChanged);
enlargeSpan = createSpan("Enlargement ratio ("
+ (round(10 * enlargeRatio)/10) + ") ");
enlargeSpan.parent(p5right);
enlargeSlider = createSlider(430, 1000, 645);
enlargeSlider.parent(enlargeSpan);
enlargeSlider.changed(enlargeChanged);
let s5a = createSpan("
");
s5a.parent(p5right);
// create number of evaluations inputs
numPtsLabel = createSpan("Number of evaluation points ");
numPtsLabel.parent("p5-right");
numPtsInput = createInput(200, "text");
numPtsInput.parent("p5-right");
numPtsInput.size(87);
numPtsInput.changed(numPtsChanged);
// create buttons for displaying and reading examples
let s6 = createSpan("
");
s6.parent("p5-right");
showExampleDataBtn = createButton("Show current setup");
showExampleDataBtn.parent("p5-right");
showExampleDataBtn.mousePressed(showExampleData);
saveExampleBtn = createButton("Save as a temp example");
saveExampleBtn.parent("p5-right");
saveExampleBtn.mousePressed(saveExample);
// create button for saving graph as an image
let s7 = createSpan("
");
s7.parent("p5-right");
saveImageBtn = createButton("Save as an image file");
saveImageBtn.parent("p5-right");
saveImageBtn.mousePressed(saveImage);
// create buttons to show graph values
let s8 = createSpan("
Show data ")
s8.parent("p5-right");
consoleValuesBtn = createButton("Console");
consoleValuesBtn.parent("p5-right");
consoleValuesBtn.mousePressed(consoleValues);
fileValuesBtn = createButton("File");
fileValuesBtn.parent("p5-right");
fileValuesBtn.mousePressed(fileValues);
alertValuesBtn = createButton("Alert");
alertValuesBtn.parent("p5-right");
alertValuesBtn.mousePressed(alertValues);
// initialize arrays xVals and yVals which hold points to be plotted
for (let i = 0; i < NUM_F_INPUT; i++) { // ????????
xVals[i] = [];
yVals[i] = [];
}
// -----------------------------------------
// some initial values (temporary)
fInput[0].value("x + y * exp(-y)"); //********
fInput[1].value(""); //********
fInput[5].value("") ; //********
//fInput[2].value("6 * x - 4"); //********
xMinInput.value("");//******** Initialize
xMaxInput.value("");//********
beginningXInp.value(0);//********
endingXInp.value(2);//********
beginningYInp[0].value(0); //********
beginningYInp[1].value(""); //********
stepSizeInp.value(.1);//********
valInp[1].value(eval(beginningXInp.value())); //Initial x value
valInp[4].value(eval(endingXInp.value())); // final x value
/* */ // some additional initialization
inputF();
valueChanged();
checkMinMax();
frameRate(20);
// finally, initialize the examples
setupExamples();
displayExamples();
fDefined[0] = false;
fDefined[1] = false;
fDefined[2] = false;
} // setup
function draw() {
// set background color for plot area
myBackground("lightblue");
// warning if no function defined and hence no graph can be drawn
if (noFDefined) {
// Has there been an error?
if (errorMsg != "") {
displayError();
} else {
// noFDefined would be true if no yn(x) defined and no method selected
if (yVals[0].length + yVals[1].length + yVals[2].length == 0) {
myText("You need to select a solution method"
+ " and/or a new problem.\n",
can1.width/2, can1.height/2);
} else {
myText("You need to define a derivative or function.", can1.width/2, can1.height/2);
}
}
noLoop();
return;
}
g = eval(beginningXInp.value());
p = eval(beginningYInp[0].value());
q = eval(beginningYInp[1].value());
r = eval(beginningYInp[2].value());
M = eval(endingYInp[0].value());
N = eval(endingYInp[1].value());
if (errorMsg != "") {
displayError();
return;
}
// *** for each function determine points to be plotted ***
// Display backwards so y0, y1, y2 are plotted over the exact
// curves if given.
for (let i = NUM_F_INPUT - 1; i >= 0; i--) { //for each function
// The exact curves are evaluated later, while y0, y1, y2, are
// already evaluated.
if (i >= NUM_F_INPUT/2) {
xVals[i] = [];
yVals[i] = [];
}
// initials values, mins, and maxs
xMaxV[i] = -Infinity; // for i = 3-5: regular functions
xMinV[i] = Infinity;
yMaxV[i] = -Infinity;
yMinV[i] = Infinity;
// determine points to be ploted - y3, y4, y5
if (!fDefined[i]) {continue;}
specialDefined = false;
if (f[i] != "") { // only process if function is not blank
let first2Char = f[i].substring(0, 2).toLowerCase(); // ***
let first3Char = f[i].substring(0, 3).toLowerCase(); // ***
if (first3Char == "!x=") { // ***
gVerticalLine(i);
} else if (first3Char == "!o=") { // ray ***
gTheta(i);
} else if (first3Char == "!r=") { // circle ***
gCircle(i);
} else if (first2Char == "!l") { // line segment ***
gLineSegment(i);
} else if (first2Char == "!q") { // quadratic ***
gQuad(i);
} else if (first2Char == "!p") { // point ***
gPoint(i);
} else if (first2Char == "!w") { //word (text, label) ***
gWord(i);
} else {
evalRectangular(i);
}
}
} // end of evaluation of points loop
// start determining the min and max of x and y
xMaxVals = max(xMaxV);
xMinVals = min(xMinV);
yMaxVals = max(yMaxV);
yMinVals = min(yMinV);
if (abs(xMaxVals) == Infinity || abs(xMinVals) == Infinity
|| abs(yMaxVals) == Infinity || abs(yMinVals) == Infinity)
{
if (!specialDefined) {
// If special defined, an attempt at a special such x=
// but no the max and mins were not set so no graph can be drawn.
// If there was an error, it should already registered.
// The following should only happen if no values are valid points
// or no values calculated because the only function is illegal
let s = "The values cannot be calculated. Possible division by 0"
+ " or invalid \nsquare root."
+ " Or only function is illegal or incomplete.";
setErrorMsg(s);
}
} else { // process anything that has been defined
// check to see if max and min are too close or the same)
if (abs(xMaxVals - xMinVals) < .01 * xMaxVals) {
if (xMaxVals > 0) {
xMaxVals = 1.1 * xMaxVals;
xMinVals = 0;
} else if (xMaxVals == 0) {
xMaxVals = 1;
xMinVals = -1;
} else {
xMaxVals = 0;
xMinVals = 1.1 * xMinVals;
}
}
if (yMaxVals == yMinVals) {
if (yMaxVals > 0) {
yMaxVals = 1.1 * yMaxVals;
yMinVals = 0;
} else if (yMaxVals == 0) {
yMaxVals = 0.1;
yMinVals = -0.1;
} else {
yMaxVals = 0;
yMinVals = 1.1 * yMinVals;
}
}
// determine maxX, minX, maxY and minY
if (maxX === "") {
maxX = ceil(xMaxVals);
}
if (minX === "") { // Why is === needed??????
minX = floor(xMinVals);
}
if (maxY === "") {
maxY = ceil(yMaxVals);
}
if (minY === "") {
minY = floor(yMinVals);
}
// check for equal spacing setting maxiX, miniX, maxiY, miniY
maxiX = maxX;
miniX = minX;
maxiY = maxY;
miniY = minY;
if (equalSpace.checked()) {
if (maxX - minX > maxY - minY) {
let dif = maxX - minX - maxY + minY ;
maxiY += dif/2;
miniY -= dif/2;
} else {
let dif = maxY - minY - maxX + minX;
maxiX += dif/2;
miniX -= dif/2;
}
}
// locate and draw axis and hashmarks
locateAxes();
myStroke("black");
myLine(0, xAxisLoc, can1.width, xAxisLoc);
myLine(yAxisLoc, 0, yAxisLoc, can1.height);
myFill("blue");
xHashMarks();
yHashMarks();
myNoStroke();
// lastPx and lastPy are used to detect vertical asymptotes
let lastPx, lastPy;
let px, py;
// *** start drawing for each function ***
for (i = 0; i < NUM_F_INPUT; i++) { //for each function
// first check for vertical lines
if (!fDefined[i]) { continue; }
myStroke(colors[i]);
lastPy = NaN;
if (xVals[i][0] == "vLine") { // check for line
let xx = xVals[i][1];
let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset);
let yHigh, yLow;
if (yVals[i][0] >= 1) {
yLow = yVals[i][1];
} else {
yLow = miniY - 10;
}
if (yVals[i][0] >= 2) {
yHigh = yVals[i][2];
} else {
yHigh = maxiY + 10;
}
let py1 = map(yLow, miniY, maxiY, can1.height - locOffset,
locOffset);
let py2 = map(yHigh, miniY, maxiY, can1.height - locOffset,
locOffset);
myLine(px, py1, px, py2);
if (i < NUM_F_INPUT/2) {
myPoint
}
// then for rays through the origin. The endpoints have been determined
// also check for lineSegments
} else if (xVals[i][0] == "oLine" || xVals[i][0] == "l") {
for (let j = 0; j < 2 * yVals[i][0]; j = j+2) {
let px1 = map(xVals[i][j + 1], miniX, maxiX, locOffset, can1.width - locOffset);
let py1 = map(yVals[i][j + 1], miniY, maxiY, can1.height - locOffset, locOffset);
let px2 = map(xVals[i][j + 2], miniX, maxiX, locOffset, can1.width - locOffset);
let py2 = map(yVals[i][j + 2], miniY, maxiY, can1.height - locOffset, locOffset);
myLine(px1, py1, px2, py2);
}
// check for circular curves
} else if (xVals[i][0] == "r") {
lastPx = xVals[i][1];
lastPy = yVals[i][1]
for (let j = 1; j < xVals[i].length; j++) {
let xx = xVals[i][j];
let yy = yVals[i][j];
let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset);
let py = map(yy, miniY, maxiY, can1.height - locOffset, locOffset);
if (j > 1) {
myLine(px, py, lastPx, lastPy);
}
lastPx = px;
lastPy = py;
}
// check for points
} else if (xVals[i][0] == "p") {
for (let j = 0; j < yVals[i][0]; j++) {
let xx = xVals[i][j+1];
let yy = yVals[i][j+1];
let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset);
let py = map(yy, miniY, maxiY, can1.height - locOffset, locOffset);
myStrokeWeight(5);
myPoint(px, py);
myStrokeWeight(1);
}
// check for words
} else if (xVals[i][0]== "w") {
for (let j = 0; j < yVals[i][0]; j++) {
let xx = xVals[i][2*j+1];
let yy = yVals[i][2*j+1];
let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset);
let py = map(yy, miniY, maxiY, can1.height - locOffset, locOffset);
myNoStroke();
myFill(useColor[i]);
myText(yVals[i][2*j + 2], px, py);
}
// having taken care of special cases, plot normal curves
} else { // normal curves
for (let ix = 0; ix < yVals[i].length; ix++) {
// the first time in this loop is just to initialize lastPx and lastPy
if (!isNaN(yVals[i][ix])) {
let xx = xVals[i][ix];
let yy = yVals[i][ix];
if (isNaN(xx) || isNaN(yy)
|| abs(xx) == Infinity || abs(yy) == Infinity) {
lastPy = NaN;
} else {
let px = map(xx, miniX, maxiX, locOffset, can1.width - locOffset);
let py = map(yy, miniY, maxiY, can1.height - locOffset, locOffset);
if (!isNaN(lastPy)
&& abs(py - lastPy) <= .3 * can1.height && py * lastPy >= 0) {
myLine(px, py, lastPx, lastPy);
if (i < NUM_F_INPUT/2) { // special option for DiffiEq
myStrokeWeight(4);
myPoint(px, py); // put a point at each iteration
if (ix == 1) {
myPoint(lastPx, lastPy);
}
myStrokeWeight(1);
}
} else {
lastPy = NaN;
}
lastPx = px;
lastPy = py;
}
}
}
}
}
} // Some function was defined and processed
// Has there been an error?
if (errorMsg != "") {
displayError();
}
// check to see if we are looping
myTextAlign("left");
myNoStroke();
can1.text(loopCnt + " " + methodMsg, 30, 15);
can2.text(methodMsg, 30, 15);// don't display loopCnt in enlarged can2
myTextAlign("center");
if (loopCnt > 1 && !allowLoop.checked()) {// used to be 5 for 6 draws
noLoop();
loopCnt = 0;
} else {
++loopCnt;
}
} // draw
function eulersSolve() {
let xn;
let yn = [];
let test;
let returnValues = [];
try {
let returnValues = initializeIteration();
xn = returnValues[0];
endingX = returnValues[1];
yn = returnValues[2];
h = returnValues[3];
test = returnValues[4];
let ynNew = [];
let n = 0;
while (eval(test)) {
n++;
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") {
ynNew[i] = yn[i] + h * func(i, xn, yn);
xVals[i][n] = xn + h; // for graphing
yVals[i][n] = ynNew[i];
}
}
xn += h;
yn = ynNew;
}
methodMsg = "Euler's method with step size = " + h.toFixed(4);
loop(); // make sure to redraw the graph
afterIteration();
} catch (e) {
alert("Required information (A function, a beginning value or the step size"
+ " is missing or invalid. "
+ e + " (line " + e.lineNumber + ")");
}
} // eulersSolve
function modifiedEulersSolve() {
// Uses only one prediction
let xn;
let yn = [];
let test;
let returnValues = [];
try {
let returnValues = initializeIteration();
xn = returnValues[0];
endingX = returnValues[1];
yn = returnValues[2];
h = returnValues[3];
test = returnValues[4];
let fn = [];
let ynPred = [];
let ynCorr = [];
let n = 0;
while (eval(test)) {
n++;
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") { // if y[i]' exists
fn[i] = func(i, xn, yn);
ynPred[i] = yn[i] + h * fn[i];
}
}
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") {
ynCorr[i] = yn[i] + h * (fn[i] + func(i, xn + h, ynPred))/2;
xVals[i][n] = xn + h; // for graphing
yVals[i][n] = ynCorr[i];
}
}
xn += h;
yn = ynCorr;
}
methodMsg = "Modified Euler's method with step size = " + h.toFixed(4);
loop(); // make sure to redraw the graph
afterIteration();
} catch (e) {
alert("Required information (A function, a beginning value or the step size "
+ " is missing or invalid. "
+ e + " (line " + e.lineNumber + ")");
}
} // modifiedEulersSolve
function rungeKuttaSolve(){
let xn;
let yn = [];
let ySave = [];
let yNext = [];
let test;
let returnValues = [];
try {
let returnValues = initializeIteration();
xn = returnValues[0];
endingX = returnValues[1];
yn = returnValues[2];
h = returnValues[3];
test = returnValues[4];
//let fn = [];
let k1 = [];
let k2 = [];
let k3 = [];
let k4 = [];
let n = 0;
while (eval(test)) {
n++;
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") {// if y[i]' exists
ySave[i] = yn[i]
k1[i] = h * func(i, xn, yn);
yNext[i] = ySave[i] + .5 * k1[i];
}
}
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") {
k2[i] = h * func(i, xn + .5 * h, yNext);
yn[i] = ySave[i] + .5 * k2[i]
}
}
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") {
k3[i] = h * func(i, xn + .5 * h, yn);
yNext[i] = ySave[i] + k3[i]
}
}
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") {
k4[i] = h * func(i, xn + h, yNext);
}
}
xn += h;
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") {
yn[i] = ySave[i] + (k1[i] + 2 * k2[i] + 2 * k3[i] + k4[i])/6;
xVals[i][n] = xn;
yVals[i][n] = yn[i];
}
}
}
methodMsg = "Runge-Kutta method with step size = " + h.toFixed(4);
loop(); // make sure to redraw the graph
afterIteration();
} catch (e) {
alert("Required information (A function, a beginning value or the step size"
+ " is missing or invalid. "
+ e + " (line " + e.lineNumber + ")");
}
} // rungeKuttaSolve
function adamsMoultonSolve() {
let xn;
let yn = [];
let test;
let returnValues = [];
try {
let returnValues = initializeIteration();
xn = returnValues[0];
endingX = returnValues[1];
yn = returnValues[2];
h = returnValues[3];
let saveH = h;
test = returnValues[4];
ff[0] = []; // function values at previous yn's
ff[1] = [];
ff[2] = [];
ff[3] = [];
ff[4] = [];
let n = 0;
// do 3 Runge-Kutta steps (including starting point)
stepSizeInp.value(h);
let saveEndingX = endingXInp.value(); // Runge-Kutta would use a
// different value the optional step size is being used
doingAdams = true; // we are doing RungeKutta as part of AdamsMoulti
endingXInp.value(xn + 3*h);
rungeKuttaSolve();
stepSizeInp.value(saveH); // restore h input box
endingXInp.value(saveEndingX);
let yVals_j = [];
for (let j = 0; j < 4; j++) { // for each of the 4 points so far
yVals_j = determineY(j, yVals);
for (i = 0; i < NUM_F_INPUT/2; i++) {
// determine function values at 4 starting points
if (fModified[i] != "") { // if y[i]' exists
ff[j][i] = func(i, xVals[0][j], yVals_j);
// all xVals[i][j] are the same
}
}
if (j == 3) {
xn = xVals[0][3];
yn = yVals_j;
}
}
n = 3;
h = saveH;
endingX = eval(saveEndingX); // eval needed if saveEndingX is a formula
doingAdams = false; // we have finished doing RungeKutta
let yPred = [];
while (eval(test)) {
// calculate predicted values
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") { // if y[i]' exists
yPred[i] = yn[i] + h * (55 * ff[3][i] - 59 * ff[2][i] + 37 * ff[1][i]
- 9 * ff[0][i])/24;
}
}
// calculate ff for predicted values
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") { // if y[i]' exists
ff[4][i] = func(i, xn + h, yPred);
}
}
// calculate corrected values
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") { // if y[i]' exists
yn[i] = yn[i] + h * (9 * ff[4][i] + 19 * ff[3][i] - 5 * ff[2][i]
+ ff[1][i])/24;
}
}
// rotate subscripts on old ff's and calculate a new one
n++;
xn += h;
for (let i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") { // if y[i]' exists
ff[0][i] = ff[1][i];
ff[1][i] = ff[2][i];
ff[2][i] = ff[3][i];
ff[3][i] = func(i, xn, yn);
xVals[i][n] = xn;
yVals[i][n] = yn[i];
xMaxV[i] = xn;
}
}
}
// update graph
methodMsg = "Adams-Moulton method with step size = " + h.toFixed(4);
maxX = ""; // reset to allow normal processing of max/min
// It was set by the Runge-Kutta initial processing
loop(); // make sure to redraw the graph
afterIteration();
} catch (e) {
alert("Required information (A function, a beginning value or the step size"
+ " is missing or invalid. "
+ e + " (line " + e.lineNumber + ")");
}
} // adamsMoultonSolve
function determineY(j, yy) {
// returns yy[.][j]
let yy_ = [];
for (i = 0; i < NUM_F_INPUT/2; i++) {
if (fModified[i] != "") { // if y[i]' exists
yy_[i] = yy[i][j];
}
}
return yy_;
} // determineY
// Called by all of the methods to initialize
function initializeIteration() {
if (initializationError) {
inititializationError = false;
}
let returnVal = [];
let erMsg = "";
try {
noFDefined = false;
let yn = [];
let test; // test for end of iteration
checkMinMax(); // because sometimes a new "function" might be plotted
if (beginningXInp.value() == "") {
erMsg = "The beginning value must be given a value. ";
}
if (endingXInp.value() == "") {
erMsg += "The ending value must be given a value. ";
}
beginningX = eval(beginningXInp.value());
endingX = eval(endingXInp.value());
let h = stepSizeInp.value();
if (h != "") {
h = eval(h);
} else if (a > 0 && a == floor(a)) { // use value a as number of
// subdivisions when the step size is not specified.
h = (endingX - beginningX)/a;
} else {
erMsg += "Either step size must be defined or value 'a' must be a"
+ " positive integer.";
}
// make sure that the step is in the right direction
if (h * (endingX - beginningX) < 0) {
h = -h;
}
let n = 0;
xn = beginningX;
fReplace(); // replace y0, y1, and y2 in functions by y, v, and w
for (j = 0; j < NUM_F_INPUT/2; j++) {
if (f[j] != "") {
xVals[j] = []; // for graphing
yVals[j] = [];
if (trim(fInput[j].value()) != "") { ////// is this the best?
fDefined[j] = true;
beginningY[j] = eval(beginningYInp[j].value());
yn[j] = beginningY[j];
xVals[j][n] = beginningX; // for graphing
yVals[j][n] = beginningY[j];
} else {
yn[j] = NaN;
}
}
}
if (h > 0) {
test = "xn < endingX - 0.5 * h";
} else {
test = "xn > endingX - 0.5 * h";
}
returnVal[0] = xn; // basically the beginning X
returnVal[1] = endingX;
returnVal[2] = yn;
returnVal[3] = h;
returnVal[4] = test;
errorMsg = "";
if (erMsg != "") {
setErrorMsg(erMsg);
}
H = h; // calculated value
return returnVal;
} catch (err) {
setErrorMsg("Initialization error: check beginning and ending"
+ " values.");
initializationError = true;
}
} // initializeIteration
function afterIteration() {
// shows and saves results and determines maxs and mins
// first find last value and save final x value
let numValues = yVals[0].length - 1;
G = xVals[0][numValues];
endingXResult.value(myNumberFormat(G, -6)); // left justify
for (j = 0; j < NUM_F_INPUT/2; j++) {
if (f[j] != "") {
// determine maxs and mins
xMinV[j] = min(xVals[j]);
xMaxV[j] = max(xVals[j]);
yMinV[j] = min(yVals[j]);
yMaxV[j] = max(yVals[j]);
checkMinMax();
// store results in endingYs and save in results
let val = yVals[j][numValues];
endingY[j] = val;
endingYResult[j].value(myNumberFormat(val, -6)); // left justify 6 digits
if (!doingAdams) { // This isn't runge-kutta for adams-moulton
results[j][results[j].length] = yVals[j][numValues];
}
}
}
shotNum.html(" " + results[0].length + " shot(s)");
// afterIteration
if (!doingAdams) {
starting[1][starting[1].length] = beginningY[1]; // save y' values
starting[0][starting[0].length] = beginningY[0]; // save y values
}
// evaluate final y values
P = yVals[0][numValues];
Q = yVals[1][numValues];
R = yVals[2][numValues];
} // afterIteration
function update1() {
update(1);
} // update1
function update0() {
update(0);
} // update0
function update(yOrYPrime) {
// correct y or y' at starting pt
if (mode == 1) {
let lastBracket, prevBracket; // aiming values
let lastVal, prevVal; // resulting values at the brackets
let targetVal; // desired value
let j = 0; // j determines changing y or y'. 0 specifies y at ending value
if (endingYInp[0].value() == "" ) {
j = 1; // specifying y' at ending value
}
if (results[j].length == 0) {
alert("You must use one of the methods before you can update.");
return;
} else if (results[j].length > 1) {
// use linear interpretation
let lastBracket = starting[yOrYPrime][starting[yOrYPrime].length - 1];
// starting has y or y' (0 means y, 1 means y')
let prevBracket = starting[yOrYPrime][starting[yOrYPrime].length - 2];
if (endingYInp[j].value() != "") {
lastVal = results[j][results[j].length - 1];
prevVal = results[j][results[j].length - 2];
targetVal = eval(endingYInp[j].value());
}
let temp = interpolate(targetVal, lastVal, prevVal, lastBracket,
prevBracket);
if (temp != NaN) {
beginningY[yOrYPrime] = temp;
}
} else { // length = 1
if (beginningY[yOrYPrime] == 0) {
beginningY[yOrYPrime] = .4 * random(); // make sure that beginningY gets changed
} else if (eval(endingYInp[j].value()) < endingY[j]) {
// hopefully in moves in the right direction but maybe not
beginningY[yOrYPrime] = .9 * beginningY[yOrYPrime];
} else {
beginningY[yOrYPrime] = 1.1 * beginningY[yOrYPrime];
}
}
beginningYInp[yOrYPrime].value(beginningY[yOrYPrime]);
}
} // update
function interpolate(target, above, below, aboveValue, belowValue) {
// target: The value for which a interpolated value is desired
// above: The next value in the table
// below: The previous value in the table
// aboveValue: The value at above
// belowValue: The value at below
// Returns the interpolated value for target
// Example: x xSquared
// 6 36
// above ---> 5 25 <--- above value
// target---> 4.3 18.7 <--- returned value
// below 4 16 <--- below value
// 3 9
// Calculation: 16 + (25-16) * (4.3 - 4)/ (5 - 4)
// 16 + 9 * .3 / 1
// 18.7
if (above != below) {
return belowValue + (aboveValue - belowValue) * (target - below)
/(above - below);
} else {
alert("Can't update because last two shots are the same.");
}
} // interpolate
function zoomIn() {
// zooms out with the ratio of 5. Centers graph on x.
// If x is not defined, then it uses the center of the current graph for x
if (typeof x == "undefined") {
x =(minX + maxX)/2;
y = 0;
}
centerGraph(x, 0, zoomRatio);
} // zoomIn
function zoomOut() {
// zooms out with the ratio of 1/5. Centers graph on ined, then it uses the center of the current graph for x
if (typeof x == "undefined") {
x =(minX + maxX)/2;
y = 0;
} centerGraph(x, 0, 1/zoomRatio);
} // zoomOut
function dblClick(event) {
// centers the graph at the double clicked point.
// the mins and maxs are offset by locOffset from the borders
let xx = map(event.offsetX, locOffset, can1.width - locOffset, minX, maxX);
let yy = map(event.offsetY, locOffset, can1.height - locOffset, maxY, minY);
centerGraph(xx, yy, 1);
} // dblClick
function centerGraph(xx, yy, zoom) {
// centers the graph at (xx, yy) and zooms if zoom != 1
// zoom in if zoom > 1, zoom out if 0 < zoom < 1
x = xx;
let currentWidth = maxX - minX;
let newWidth = currentWidth/zoom;
let maxX1 = xx + newWidth/2;
let minX1 = xx - newWidth/2;
xMinInput.value(minX1);
xMaxInput.value(maxX1);
y == yy;
if (yMinInput.value() != "" && yMaxInput.value() != "") {
let currentHeight = maxY - minY;
let newHeight = currentHeight/zoom;
let maxY1 = yy + newHeight/2;
let minY1 = yy - newHeight/2;
yMinInput.value(minY1);
yMaxInput.value(maxY1);
}
checkMinMax();
loop(); // causes a redraw
} // centerGraph
function formCubic(a, b, c, d) {
let s = "";
if (a != 0) {
if (a != 1) {
s += a + " * ";
}
s += "x**3 ";
}
if (b != 0) {
if (b > 0) {
if (b == 1) {
s += "+ ";
} else {
s += " +" + b + " * ";
}
} else {
s += b + " * ";
}
s += "x**2 ";
}
if (c != 0) {
if (c > 0) {
if (b == 1) {
s += "+ ";
} else {
s += " +" + c + " * ";
}
} else {
s += c + " * ";
}
s += "x ";
}
if (d != 0) {
if (d > 0) {
s += "+ ";
}
s += d;
}
return s;
} // formCubic
function evalRectangular(i) {
// evaluates x and y values for y3, y4, and y5. In all cases
// it determines mins and maxs.
let num;
try {
if (i >= NUM_F_INPUT/2) {
num = numPts; // points must be determined
} else {
num = xVals[i].length; // points already determined
if (num <= 0) { // there are no points for i = 0, 1, or 2
yMaxV[i] = -Infinity;
yMinV[i] = Infinity;
xMaxV[i] = -Infinity;
xMinV[i] = Infinity;
}
}
// check data for mins and maxs
let yyy;
for (let ix = 0; ix < num; ix++) {
if (i >= NUM_F_INPUT/2) {
// determine x and y values for y3, y4, and y5
xVals[i][ix] = float(minX) + float((ix - numPtsOffset)
* (maxX - minX) / numDiv);
yyy = func(i, xVals[i][ix], 0); // the zero is ignored
if (yyy == ERROR) {
break;
}
yVals[i][ix] = yyy;
} else {
yyy = yVals[i][ix]; // already determined
}
// determine mins and maxs for y values
if (!isNaN(yyy) && yyy !== Infinity) {
yMaxV[i] = max(yMaxV[i], yyy);
yMinV[i] = min(yMinV[i], yyy);
}
}
// the min and max for x are at the endpoints of x values
if (num > 0) {
xMinV[i] = min(xVals[i][0], xVals[i][num - 1]);
xMaxV[i] = max(xVals[i][0], xVals[i][num - 1]);
}
} catch(err) {
setErrorMsg("Error in function f" + i + ".\n Namely: " + err);
}
} // evalRectangular
function gVerticalLine(i) {
// Draws vertical lines (can be used in all modes)
// Format:
// != xVal, yLow (optional), yHigh (optional) ***
// Draws a vertical line at xVal from yHigh to yLow.
// If yHigh and yLow are not provide the line normally will go from the
// top of the graph to the bottom.
// Result:
// xVals[i][0] = "vLine"
// xVals[i][1] = xVal
// yVals[i][0] = 0, 1, 2 depending on the number of y values provided
// yVals[i][1] = yLow (if provided) // this allows having the line for y>=0
// yVals[i][2] = yHigh (if provided)
// yMaxV[i]
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
let st = f[i].substring(3, 10000); // ***
sts = st.split(";");
let theXVal = eval(sts[0]);
if (sts.length == 1 && sts[0] == "") {
setErrorMsg("In f" + i + ", !x= must be followed by the x value\n and"
+ " optionally by ; yLow; yHigh"); // ***
return;
}
xVals[i][0] = "vLine";
xVals[i][1] = theXVal;
xMinV[i] = theXVal;
xMaxV[i] = theXVal;
yVals[i][0] = sts.length - 1;
if (sts.length >= 2) {
yVals[i][1] = eval(sts[1]);
yMinV[i] = yVals[i][1];
if (sts.length >= 3) {
yVals[i][2] = eval(sts[2]);
yMinV[i] = min(yVals[i][2], yVals[i][1]);
yMaxV[i] = max(yVals[i][2], yVals[i][1]);
}
}
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i] + ")"
+ ".\nNamely: " + err);
functionError = true;
loop;
}
} // gVerticalLine
function gTheta(i) {
// Draws rays at angle theta (o)
// Assumes polar coordiantes but may be used in all modes
// format:
// !o= thetaVal, rLow (optional),rHigh (optional)
// Draws a ray at angle thetaVal from (rLow, theta) to (rHigh, theta)
// (Assumes polar coordinates.)
// If rHigh and rLow are not provided, the rLow = -10, rHigh = 10
// Result:
// xVals[i][0] = "oLine"
// (xVals[i][1], yVals[i][1]): one end point of ray
// (xVals[i][2], yVals[i][2]): other end point of ray
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
let st = f[i].substring(3, 10000);
let sts = st.split(";");
if (sts.length == 1 && sts[0] == "") {
setErrorMsg("!o= must be followed by the theta value\n and optionally"
+ " by ; rLow; rHigh");
return;
}
xVals[i][0] = "oLine";
yVals[i][0] = 1;/// could be changed....
let thetaVal = eval(sts[0]);
let rMin = -10; // these are arbitrary
let rMax = 10;
if (sts.length >= 2) {
rMin = eval(sts[1]); // this may actually be the max
if (sts.length >= 3) {
rMin = min(rMin, eval(sts[2]));
rMax = max(eval(sts[1]), eval(sts[2]));// rMin may have changed
}
}
let x1 = rMin * cos(thetaVal);
let y1 = rMin * sin(thetaVal);
let x2 = rMax * cos(thetaVal);
let y2 = rMax * sin(thetaVal);
xVals[i][1] = x1;
xVals[i][2] = x2;
yVals[i][1] = y1;
yVals[i][2] = y2;
xMinV[i] = min(x1, x2);
xMaxV[i] = max(x1, x2);
yMinV[i] = min(y1, y2);
yMaxV[i] = max(y1, y2);
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i]
+ ".\nNamely: " + err);
functionError = true;
loop;
specialDefined = true;
}
} // gTheta
function gLineSegment(i) {
// Draws a straight line betweein two points.
// May be used in all modes.
// format:
// !l = x1; y1; x2; y2; ...
// draws a straight line from (x1, y1) to (x2, y2).
// Result:
// xVals[i][0] = "l"
// yVals[i][0] = number of line segments
// xVals[i][j+1]: x value of one end point
// xVals[i][j+1]: y value of one end point
// xVals[i][j+2]: x value of other end point
// xVals[i][j+3]: y value of other end point
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
let st = f[i].substring(2, 10000);
let sts = st.split(";");
// check to see if there is sufficient info
noFDefined = false;
if (floor(sts.length/4) == 0) {
setErrorMsg('In f' + i + '= "' + f[i]
+ '",\n!L or !lmust be followed by xVal; yVal; xVal; yVal'
+ '\nDid you use ";"?');
return;
}
// process the points
xVals[i][0] = "l";
yVals[i][0] = floor(sts.length/4);
let xxx2, yyy2;
for (let j = 0; j < floor(sts.length/4); j++) {
xxx = eval(sts[4 * j]);
yyy = eval(sts[4 * j + 1]);
xxx2 = eval(sts[4 * j + 2]);
yyy2 = eval(sts[4 * j + 3]);
xVals[i][2 * j + 1] = xxx;
yVals[i][2 * j + 1] = yyy;
xVals[i][2 * j + 2] = xxx2;
yVals[i][2 * j + 2] = yyy2;
xMinV[i] = min(xxx, xxx2, xMinV[i]);
xMaxV[i] = max(xxx, xxx2, xMaxV[i]);
yMinV[i] = min(yyy, yyy2, yMinV[i]);
yMaxV[i] = max(yyy, yyy2, yMaxV[i]);
}
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i]
+ ".\nNamely: " + err);
functionError = true;
}
} // gLineSegment
function gCircle(i) {
// Draws circles with radius r)
// May be used in all modes.
// format:
// !r = thetaVal, center x (optional), center y (optional)
// Draws a circle with center at (centerX, centerY)
// If the centers are not provided, the center is the orgin,
// Result:
// xVals[i][0] = "r"
// xVals[i][j+1]: x value of point on the curve (number of evaluation pts.)
// xVals[i][j+1]: y value of point on the curve
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
let st = f[i].substring(3, 10000);
let sts = st.split(";");
if (sts.length == 1 && sts[0] == "") {
setErrorMsg("!r= must be followed by the r value\n and optionally"
+ " by both ; center x; center theta");
return;
}
let rVal = eval(sts[0]);
let centerX = 0; // default center at origin
let centerY = 0;
if (sts.length >= 3) { // If option used, both low and high required
centerX = eval(sts[1]);
centerY = eval(sts[2]);
}
let range = TWO_PI;
xVals[i][0] = "r";
for (let j = 0; j <= numPts; j++) {
let theta = j * range/numPts;
xVals[i][j+1] = rVal * cos(theta) + centerX;
yVals[i][j+1] = rVal * sin(theta) + centerY;
}
xMinV[i] = -rVal + centerX;
xMaxV[i] = rVal + centerX;
yMinV[i] = -rVal + centerY;
yMaxV[i] = rVal + centerY;
} catch (err) {
setErrorMsg("Error in function " + i + " (" +f[i] + ")"
+ ".\nNamely: " + err);
functionError = true;
loop;
}
} // gCircle
function gPoint(i) {
// graph points
// Format:
// !p xVal, yVal, xVal, yVal, ... multiple points allowed
// Plots a point at each aVal, yVal pair. If yVal is missing, the
// xVal is ignored.
// Result:
// xVals[i][0] = "p"
// yVals[i][0] = number of points
// xVals[i][1], yVals[i][1]: the first point
// xVals[i][2], yVals[i][2]: second point, if specified
// ... (likewise for any additional points)
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
let st = f[i].substring(2, 10000);
let sts = st.split(";");
// check to see if there is sufficient info
if (floor(sts.length/2) == 0) {
setErrorMsg('In f' + i + '= "' + f[i]
+ '", !p must be followed by xVal; yVal'
+ '\nDid you use ";"?');
loop();
return;
}
// process the points
xVals[i][0] = "p";
yVals[i][0] = floor(sts.length/2);
xMinV[i] = Infinity; //make sure these maxs and mins are reset
xMaxV[i] = -Infinity;
yMinV[i] = Infinity;
yMaxV[i] = -Infinity;
for (let j = 0; j < floor(sts.length/2); j++) {
xxx = eval(sts[2 * j]);
yyy = eval(sts[2 * j + 1]);
xVals[i][j + 1] = xxx;
yVals[i][j + 1] = yyy;
xMinV[i] = min(xxx, xMinV[i]);
xMaxV[i] = max(xxx, xMaxV[i]);
yMinV[i] = min(yyy, yMinV[i]);
yMaxV[i] = max(yyy, yMaxV[i]);
}
} catch(e) {
errorMsg = "Error in point specication. Namely " + e;
functionError = true;
loop;
}
} // gPoint
function gWord(i) {
// graph words (or labels)
// (w for word instead of l for label as l looks too much like 1)
// Format:
// !w xVal, yVal, word, xVal, yVal, word... multiple words allowed
// Types a word or label at each aVal, yVal pair. If yVal or word is
// missing, thexVal is ignored.
// Result:
// xVals[i][0] = "w"
// yVals[i][0] = number of words
// xVals[i][1], yVals[i][1]: location of the first word
// yVals[i][2] the first word
// xVals[i][3], yVals[i][3]: location of second word, if specified
// yVals[i][4] the second word, if specified
// ... (likewise for any additional words)
// Notes: each word uses 2 items in the xVals and yVals arrays.
// The word is centered horizontally just above the specified y value.
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
let st = f[i].substring(2, 10000);
let sts = st.split(";");
// check to make at least 1 word is declared
if (floor(sts.length/3) == 0) {
setErrorMsg('In f' + i + '= "' + f[i]
+ '",\nw must be followed by xVal; yVal; word');
loop();
return;
}
// process the words
xVals[i][0] = "w";
yVals[i][0] = floor(sts.length/3);
let xxx, yyy;
useColor[i] = colors[i]; // assume normal color
for (let j = 0; j < floor(sts.length/3); j++) {
if (trim(sts[3 * j]) == "color") {
try {
useColor[i] = colors[floor(eval(sts[3 * j + 1]))];
} catch (err) {
setErrorMsg("Illegal color (" + sts[3 * j + 1]
+ ") for word(s) in function box " + i + ",\nnamely " + err);
}
return;
}
xxx = eval(sts[3 * j]);
yyy = eval(sts[3 * j + 1]);
xVals[i][2 * j + 1] = xxx;
yVals[i][2 * j + 1] = yyy;
xMinV[i] = min(xxx, xMinV[i]);
xMaxV[i] = max(xxx, xMaxV[i]);
yMinV[i] = min(yyy, yMinV[i]);
yMaxV[i] = max(yyy, yMaxV[i]);
let st = trim(sts[3 * j + 2]);
for (let i = 0; i < valuesLab.length; i++) {
st = myReplaceAll(st, SPECIAL_SYMBOLS[i], theValues[i]);
}
st = myReplaceAll(st, '^$', ';');
yVals[i][2 * j + 2] = st;
}
} catch (err) {
errorMsg = 'Error in declaring a "word". \nNamely: ' + err;
functionError = true;
loop;
}
} // gWord
function gQuad(i) {
// Draws pieces of quadrics (parabolas).
// Format:
// q a; b; c; x1; x2; a; b; c; x1; x2; .....
// The quadratic is ax**2 + bx +c and it is drawn between x1 and x2.
// The 5 values are required but can be repeated. Unlike the
// other special g options, it justs produces x and y values
// that can be plotted like any other curve.
// i is the curve number
if (functionError) {
errorMsg = "";
functionError = false;
}
try {
specialDefined = true;
let st = f[i].substring(2, 10000);
let sts = st.split(";");
// check to see if there is sufficient info
let numQuad = floor(sts.length/5);
if (numQuad == 0) {
setErrorMsg('In f' + i + '= "' + f[i]
+ '", q must be followed by a, b, c, x1, and x2'
+ '\nDid you use ";"?');
}
xMinV[i] = Infinity;
xMaxV[i] = -Infinity;
yMinV[i] = Infinity;
yMaxV[i] = -Infinity;
let iii = 0; // counter for points to be plotted
for (let j = 0; j < numQuad; j++) {
let j5 = j * 5;
let aaa = eval(sts[j5]); // is float needed????
let bbb = eval(sts[j5 + 1]);
let ccc = eval(sts[j5 + 2]);
let xxx1 = eval(sts[j5 + 3]);
let xxx2 = eval(sts[j5 + 4]);
let numPts = abs(numDiv * (xxx2 - xxx1)/(maxX - minX));
let spacing = (xxx2 - xxx1)/numPts;
for (let ix = 0; ix <= numPts; ix++) {
let xxx = xxx1 + ix * spacing;
let yyy = (aaa * xxx + bbb) * xxx + ccc;
xVals[i][iii] = xxx;
yVals[i][iii] = yyy;
iii++;
xMinV[i] = min(xMinV[i], xxx);
xMaxV[i] = max(xMaxV[i], xxx);
yMaxV[i] = max(yMaxV[i], yyy);
yMinV[i] = min(yMinV[i], yyy);
// provide separator between curves
}
xVals[i][iii] = Infinity;
yVals[i][iii] = Infinity;
iii++;
}
} catch (err) {
errorMsg = 'Error in declaring a quadratic. \nNamely: ' + err;
functionError = true;
loop;
}
} // gQuad
function checkStartEnd() {
checkMinMax(); // checks for invalid input
valInp[1].value(eval(beginningXInp.value()));
valInp[4].value(eval(endingXInp.value()));
checkYBoundaries(); // for boundary value problems
valueChanged();
} // checkStartEnd
function checkYBoundaries() {
// resets the number of shots
results[0] = []; // revised problem, start keeping new results
results[1] = []; // these are intended for boundary value mode
shotNum.html(" " + results[0].length + " shot(s)");
} // checkYBoundaries
// This called when any MinInput or MaxInput changes.
// Both x and y axis location must be reset in case there is a blank.
function checkMinMax() {
// check for previous minMaxError. Clear it. It will be reset if needed
if (minMaxError) {
errorMsg = "";
minMaxError = false;
}
try {
// check x min/max
minX = xMinInput.value();
if (minX == "") {
minX = eval(beginningXInp.value()); // "" in solver
} else {
minX = eval(minX);
}
maxX = xMaxInput.value();
if (maxX == "") {
maxX = eval(endingXInp.value());
} else {
maxX = eval(maxX);
}
// make sure minX and maxX are in the right order
if (minX > maxX) {
let temp = minX;
minX = maxX;
maxX = temp;
}
// check y min/max
minY = yMinInput.value();
if (minY != "") {
minY = eval(minY);
}
maxY = yMaxInput.value();
if (maxY != "") {
maxY = eval(maxY);
}
// check beginning and ending
eval(beginningXInp.value());
eval(endingXInp.value());
} catch(err) {
minMaxError = true;
setErrorMsg("Invalid beginningX, endingX, min or max.\nNamely: " + err);
}
loop();
} // checkMinMax
// locateAxes called during draw()
function locateAxes() {
// locate x axis
if (miniY >= 0) {
xAxisLoc = can1.height - locOffset;
} else if (maxiY <= 0) {
xAxisLoc = locOffset;
} else {
xAxisLoc = map(0, miniY, maxiY, can1.height - locOffset, locOffset);
}
if (miniX >= 0) {
yAxisLoc = locOffset;
} else if (maxiX <= 0) {
yAxisLoc = can1.width - locOffset;
} else {
yAxisLoc = map(0, miniX, maxiX, locOffset, can1.width - locOffset);
}
} // locateAxes
function inputF() {
// called by setup and everytime a function is changed
noFDefined = true;
errorMsg = "";
for (let i = 0; i < NUM_F_INPUT; i++) {
f[i] = trim(fInput[i].value());
fDefined[i] = (f[i] != '');
if (fDefined[i]) {
xVals[i] = [];
yVals[i] = [];
if (i >= NUM_F_INPUT/2) {
noFDefined = false;
}
}
}
results[0] = []; // new problem, start keeping new results
results[1] = [];
starting = [];
starting[0] = [];
starting[1] = [];
endingXResult.value("");
for (j = 0; j < NUM_F_INPUT/2; j++) {
endingY[j] = "";
}
// recalculate
checkMinMax()
loop();
} // inputf
function fReplace() {
// Trim f. Replace y0, y1, and y2 by y, v, w in derivative equations.
// The later is needed as eval only replaces single letter variables.
for (i = 0; i < NUM_F_INPUT; i++) {
if (i < NUM_F_INPUT/2) {
fModified[i] = trim(f[i]).replaceAll("y0", "y");
fModified[i] = fModified[i].replaceAll("y1", "v");
fModified[i] = fModified[i].replaceAll("y2", "w");
} else {
fModified[i] = trim(f[i]);
}
}
} // fReplace
// evaluate fModified[i] at xx, yy
function func(i, xx, yy) {
if (functionError) {
errorMsg = "";
functionError = false;
}
let val;
try {
x = xx;
if (i < NUM_F_INPUT/2) {
y = yy[0];
v = yy[1];
w = yy[2];
val = eval(fModified[i]);
} else {
val = eval(f[i]);
}
return val;
} catch(err) {
if (i < NUM_F_INPUT/2) {
setErrorMsg("The function for y" + i + "' = " + '"' + f[i] + '" is invalid:\n'
+ err);
} else {
setErrorMsg("The function for y" + i + " = " + '"' + f[i] + '" is invalid:\n'
+ err);
}
functionError = true;
loop;
return ERROR;
}
} // end func
// draw x hashMarks and label them
function xHashMarks() {
let currentWidth = maxiX - miniX;
let logCW = floor(Math.log10(currentWidth));
let roundOffFactor = 100; // up to 2 decimal places
let spacingMin = min(.5, .5 * 10 ** logCW);
let spacing = max(round(currentWidth/10), spacingMin);
if (spacing < currentWidth/10) {
spacing = 2 * spacing;
}
let rMiniX = miniX;
if (spacing >= spacingMin) {
rMiniX = round(spacingMin * rMiniX)/spacingMin;
}
if (spacing < .01) {
roundOffFactor = 10000; // 4 decimal places
} else if (spacing < .1) {
roundOffFactor = 1000; // 3 decimal places
}
let num = max(currentWidth/spacing, 5); // provide at least 5 hash marks
for (let i = 0; i <= num; i++) {
let xxx = float(miniX) + float(i * currentWidth / num);
if (xxx != 0) {
let px = map(xxx, miniX, maxiX, locOffset, can1.width - locOffset);
myLine(px, xAxisLoc - 3, px, xAxisLoc + 3);
myText(round(roundOffFactor * xxx)/roundOffFactor, px, xAxisLoc + 14);
}
}
} // xHashMarks
// draw y hashMarks and label them
function yHashMarks() {
let currentHeight = maxiY - miniY;
let logCW = floor(Math.log10(currentHeight));
let roundOffFactor = 100; // up to 2 decimal places
let spacingMin = min(.5, .5 * 10 ** logCW);
let spacing = max(round(currentHeight/10), spacingMin);
if (spacing < currentHeight/10) {
spacing = 2 * spacing;
}
let rMiniY = miniY;
if (spacing >= spacingMin) {
rMiniX = round(spacingMin * rMiniY)/spacingMin;
}
if (spacing < .01) {
roundOffFactor = 10000;
} else if (spacing < .1) {
roundOffFactor = 1000;
}
let num = max(currentHeight/spacing, 5); // provide at least 5 hash marks
for (let i = 0; i <= num; i++) {
let yyy = float(rMiniY) + float(i * currentHeight / num);
if (yyy != 0) {
let py = map(yyy, miniY, maxiY, can1.height - locOffset, locOffset);
myLine(yAxisLoc - 3, py, yAxisLoc + 3, py);
if (yAxisLoc <= 25) {
myText(round(roundOffFactor * yyy)/roundOffFactor, yAxisLoc + 20,
py + 5); // put y value right of axis
} else {
myText(round(roundOffFactor * yyy)/roundOffFactor, yAxisLoc - 20,
py + 5); // put y value left of axis
}
}
}
} // yHashMarks
function equalSpaceChanged() {
// called when equalSpace is changed
loop();
} // equalSpaceChanged
function allowLoopChanged() {
// called when allowLoop is changed
loop();
} // allowLoopChanged
function valueChanged() {
// called by valueChanged or whenever it is appropriate check values
if (valueError) {
errorMsg = "";
valueError = false;
}
try {
valInp[5].value(sValueSlider.value());
s = valInp[5].value();
if (trim(valInp[4].value()) != "") {
e = eval(valInp[4].value()); // eval of blank returns NaN
} else {
e = 0;
}
if (trim(valInp[3].value()) != "") {
d = eval(valInp[3].value()); // eval of blank returns NaN
if (abs(d) > 1E9) { // in case c unreasonable large - e.g. divide by ~0
d = 0;
valInp[3].value("");
}
} else {
d = 0;
}
if (trim(valInp[2].value()) != "") {
if (abs(d) > 1E9) { // in case d unreasonable large - e.g. divide by ~0
d = 0;
}
} else {
c = 0;
}
if (trim(valInp[1].value()) != "") {
b = eval(valInp[1].value());
} else {
b = 0;
}
if (trim(valInp[0].value()) != "") {
a = eval(valInp[0].value());
} else {
a = 0;
}
// The above evaluate the value e, d, ..., a in that order so the value
// of e does not reflect a new value in d and so on.
// So now evaluate them in the order b, c, ..., e
if (trim(valInp[1].value()) != "") {
b = eval(valInp[1].value());
}
if (trim(valInp[2].value()) != "") {
c = eval(valInp[2].value());
}
if (trim(valInp[3].value()) != "") {
d = eval(valInp[3].value());
}
if (trim(valInp[4].value()) != "") {
e = eval(valInp[4].value());
}
// theValues used for word w and to check for changed values.
theValues[5] = s;
theValues[4] = e;
theValues[3] = d;
theValues[2] = c;
theValues[1] = b;
theValues[0] = a;
for (let ii = 0; ii < theValues.length; ii++) {
if (theValues[ii] != savedValues[ii]) {
results[0] = []; // Reset number of shots if a value changed
results[1] = []; // Needed for boundary value problems.
shotNum.html(" " + results[0].length + " shot(s)");
}
savedValues[ii] = theValues[ii];
}
valDisplay.html("a = " + a.toFixed(3)
+ ", b = " + b.toFixed(3)
+ ", c = " + c.toFixed(3)
+ ", d = " + d.toFixed(3)
+ ", e = " + e.toFixed(3));
} catch(err) {
setErrorMsg("Invalid value.\nNamely: " + err);
valueError = true;
loop();
return;
}
checkMinMax();
loop();
} // valueChanged
function numPtsChanged() {
// called when the number of points is changed
// check for previous numberEvalError. Clear it. It will be reset if needed
if (numberEvalsError) {
errorMsg = "";
numberEvalsError = false;
}
try {
numDiv = eval(numPtsInput.value());
if (numDiv < 50 || numPtsInput.value() == "") {
throw "Number of evaluation points is blank or too small";
}
numPts = Math.floor(1.1 * numDiv);
numPtsOffset = 0.05 * numDiv;
} catch(err) {
setErrorMsg("Invalid number of evaluation points."
+ "\nNamely " + err);
numberEvalsError = true;
}
loop();
} // numPtsChanged
function modeChanged() {
// called when the initial or boundary value mode is changed
getRadioValueMode();
// update most everything when the mode changes and do a loop
valueChanged();
checkMinMax();
inputF();
loop();
} // modeChanged
function functionLab(i, aMode) {
// Provide an appropriate label for function boxes.
// Called by setup(). There must a case for every possible curve.
let u;
switch (i) {
case 0: u = FUNCTION_LAB0[aMode];
break;
case 1: u = FUNCTION_LAB1[aMode];
break;
case 2: u = FUNCTION_LAB2[aMode];
break;
case 3: u = FUNCTION_LAB3[aMode];
break;
case 4: u = FUNCTION_LAB4[aMode];
break;
case 5: u = FUNCTION_LAB5[aMode];
break;
}
fLabel[i].html(u);
} // functionLab
function sSliderChanged() {
valInp[5].value(sValueSlider.value());
valueChanged();
} // sSliderChanged
function sInput() {
let s = this.value();
let sVal = int(s);
if (sVal == s && sVal >= 0 && sVal <= 100) {
sValueSlider.value(sVal);
}
} // sInput
function setErrorMsg(msg) {
// this is designed to prevent repeated alerts for the same error
// expecially while looping. Repeats of the same alert msg may make
// difficult to fix the error. In some cases a very similar msg may
// be shown. For exampl, if "abc" is an illegal variable, the alert
// would repeat if the variable is changed to "ab" when the "c" deleted.
errorMsg = msg;
} // setErrorMsg
function displayError() {
myFill("wheat");
myRect(10, 10, can1.width - 20, 90);
myFill("black");
myNoStroke();
myText(errorMsg + "\n\nClick canvas to remove this message after"
+ " correcting the problem.", can1.width/2, 25);
}
function clearErrorMsg() {
errorMsg = "";
loop();
}
function myReplaceAll(st, from, to) {
// replaces every occurance of "from" in st with "to" and
// returns the result.
let array = st.split(from);
let s = array[0];
for (let i = 1; i < array.length; i++) {
s += to + array[i];
}
return s;
} // myReplaceAll
function myNumberFormat(v, n) {
// returns a string of width 10 containing v formatted to abs(n) digits
// unless v is not a number. It then returns a blank string.
// If abs(v) < 1 then abs(n)-1 decimal digits are returned.
// if n < 0, return the value left justified.
let display;
let nn = abs(n);
try {
if (typeof v != "number") {
display = "";
} else if (abs(v) < 1) {
display = v.toFixed(nn-1);
} else {
display = v.toPrecision(nn);
}
if (n >= 0) {
display = " " + display;
return display.substr(display.length - 10, 10);
} else {
return display;
}
} catch(e) {
}
} // myNumberformat
function myStringFormat(s, num) {
// returns a string cantain s formatted to width num. Assumes s has
// one or two characters.
let display = " " + s;
return display.substr(display.length - num, num);
} // myStringFormat
// creates a string containing values used by alertValues, consoleValues
// and fileVvalues.
function showValues(includeHeading) {
let str = methodMsg; // the heading on top of the graph
let numPts = 0;
for (let i = 0; i < NUM_F_INPUT/2; i++) {
if (f[i] != "" ) {
beginningY[i] = eval(beginningYInp[i].value()); // case no method used yet
str += "\ny" + i + "' = " + f[i] + ", y" + i + "(" + b.toFixed(2)
+ ") = " + beginningY[i].toFixed(3);
numPts = yVals[i].length;
}
if (mode == 1 && i == 0) { // boundary value problems
if (endingYInp[0].value() != ""){
str += ", y" + i + "(" + endingX.toFixed(2) + ") = "
+ eval(endingYInp[0].value()).toFixed(3);
} else if (endingYInp[1].value() != "" ) {
str += ", y" + i + "'(" + endingX.toFixed(2) + ") = "
+ eval(endingYInp[1].value()).toFixed(3);
}
}
}
for (let i = NUM_F_INPUT/2; i < NUM_F_INPUT; i++) {
if (f[i] != "" && f[i].charAt(0) != "!") {
str += "\ny" + i + " = " + f[i];
}
}
// column headings
if (methodMsg != "") {
str += "\n\n" + myStringFormat("x", 8);
let star = "";
let show = true;
let display = [];
for (let i = 0; i < NUM_F_INPUT; i++) {
display[i] = f[i] != "" && (f[i].charAt(0) != "!");
// specials begin with !. E.g. !w 2, 3, word
if (display[i]) {
if (i >= NUM_F_INPUT/2 && show) {
star = "*";
show = false;
}
str += myStringFormat(" " + star + " y" + i, 11);
}
}
// list data values
for (let j = 0; j < numPts; j++) {
let show = true;
str += "\n ";
for (let i = 0; i < NUM_F_INPUT/2; i++) {
if (display[i]) { // second condition says to
// ignore things "w" and "p" special conditions
if (show) {
x = xVals[i][j]; // changes value of x so yVals will be printed
str += myNumberFormat(x, 7);
show = false;
}
str += "," + myNumberFormat(yVals[i][j], 7);
}
}
// print function values
for (let i = NUM_F_INPUT/2; i < NUM_F_INPUT; i++) {
if (display[i]) {
str += "," + myNumberFormat(eval(f[i]), 7);
}
}
}
}
if (mode == 1) {
if (endingYInp[0].value() != "") {
if (abs(endingYInp[0].value() - yVals[0][yVals[0].length - 1]) > .001) {
str += "\nBoundary values not satisfied";
}
} else if (endingYInp[1].value() != "") {
if (abs(endingYInp[1].value() - yVals[1][yVals[1].length - 1]) > .001) {
str += "\nBoundary values not satisfied";
}
}
}
return str;
} // showValues
// print the values in an alert. Columns may not lineup because alerts
// typically use a variable spacing font.
function alertValues() {
alert(showValues(true)); // alertValues
} // showValues
// print values to the console
function consoleValues() {
print(showValues(false));
} // consoleValues
// print values to a file
function fileValues() {
let writer = createWriter("DiffiEq2.txt");
writer.write(showValues(false));
writer.close();
} // fileValues
// an alert with data needed for an example
// based on the current graph
function showExampleData() {
// set data to be displayed
let s = " example[??] = ["
for (let i = 0; i < NUM_F_INPUT; i++) {
s += '"' + myReplaceAll(fInput[i].value(), ',', '^$') + '",';
}
s += '\n ';
s += mode + ',\n '; // add spaces to indent output
s += '"' + 0 + '", "' + 0 + '", ';
s += '"' + xMinInput.value() + '", "' + xMaxInput.value() + '", ';
s += '"' + yMinInput.value() + '", "' + yMaxInput.value() + '", ';
s += equalSpace.checked() + ', ';
s += allowLoop.checked() + ',\n ';
for (let i = 0; i < 5; i++) {
s += '"' + valInp[i].value() + '", ';
}
s += '"' + sValueSlider.value() + '", ';
s += '"", '; //mode angle replaced by a blank
s += '"' + numPtsInput.value() + '", \n ';
// Make sure that beginning and ending values agree with what is displayed.
// They may not be if a method has not been selected since they were changed.
s += '"' + eval(stepSizeInp.value()) + '", "'
+ eval(beginningXInp.value()) + '", "'
+ eval(endingXInp.value()) + '", \n ';
s += '"' + eval(beginningYInp[0].value())
+ '", "' + eval(beginningYInp[1].value())
+ '", "' + eval(beginningYInp[2].value()) + '", ';
s += '"' + eval(endingYInp[0].value())
+ '", "' + eval(endingYInp[1].value()) + '", \n ';
s += '"???? Example name ????"];';
s = s.replaceAll("undefined",""); // eval("") may give "undefined"
s += '\n\n';
s += 'You can copy the above and paste it into setupExamples().\n'
+ 'Replace the example number [??] and example name appropriately.\n'
+ 'Examples are printed in the order of the example numbers.';
alert(s);
} // showExampleData
function saveExample() {
// copies current data in order to make a new temporary example
let newName = prompt("What do you want the setup to be called?");
if (newName == "" ||newName == null) {
return;
}
let ex = example.length;
example[ex] = [];
for (let i = 0; i < NUM_F_INPUT; i++) {
example[ex][i] = fInput[i].value();
}
example[ex][6] = mode;
let theMode = int(example[ex][6]);
example[ex][7] = "";
example[ex][8] = "";
example[ex][9] = xMinInput.value();
example[ex][10] = xMaxInput.value();
example[ex][11] = yMinInput.value();
example[ex][12] = yMaxInput.value();
example[ex][13] = equalSpace.checked();
example[ex][14] = allowLoop.checked();
for (let i = 0; i < 6; i++) { // values
example[ex][15+i] = trim(valInp[i].value()); // includes s
}
example[ex][21] = ""; // mode angle not used
example[ex][22] = numPtsInput.value();
example[ex][23] = stepSizeInp.value();
example[ex][24] = beginningXInp.value();
example[ex][25] = endingXInp.value();
for (let i = 0; i < 3; i++) {
example[ex][26+i] = beginningYInp[i].value();
}
example[ex][29] = endingYInp[0].value();
example[ex][30] = endingYInp[1].value();
example[ex][EXAMPLE_NAME] = newName;
let radio = HTMLforRadioButton("examplesResult",
''
+ example[ex][EXAMPLE_NAME] +'',
"getRadioValueExamples()", true, ex);
examplesRadio[ex] = displayHTMLElements(radio, p5left, "block");
useExample(ex);
} // saveExample
// save the canvas as an image file
function saveImage() {
mySaveCanvas("diffiEq2", "jpg");
} // saveImage
function setupExamples() {
// Examples must be numbered consecutively starting a 0
// the format of the example[i] vector:
// [0] .. [5]: functions f0 .. f4 (strings)
// [6]: the mode - 0: initial value, 1: boundary value
// [7] .. [8]: min and max t (number or string) (not used)
// t is not used in DiffEq so these values are replaced by 0's
// [9] .. [10]: min and max x (number or string)
// [11] .. [12]: min and max y (number or string)
// [13]: equal spacing (true or false)
// [14]: allow motion (true or false)
// [15] .. [19]: values a.. e (number or string)
// [20]: value of s (sets slider);
// [21]: radians/degrees option ("radians" or "degrees")
// replaced by "" in diffiEq2
// [22]: number of evaluation points (number or string)
// [23]: stepSize (The following have been added for diffiEq)
// [24]: beginningX
// [25]: endingX
// [26] .. [28]: BeginningY
// [29] .. [30]: EndingY
// [31]: display title (string) (EXAMPLE_NAME = 31)(Renumbered for DiffiEq)
example[0] = ["x - y","","","","","x - 1 + d * exp(-x)",
0,
"0", "1", "-2", "10", "", "", false, false,
"", "-2", "1", "(c - b + 1) * exp(b)", "10", "50", "", "200",
// following line added for DiffiEq and DiffiEq2
".1", "b", "10", "c", "", "", "", "",
"1: 1st order: y' = x - y, y(0) = 1"];
example[1] = ["y1","x - y0","","","cos(x) + 1","sin(x) + x",
0,
"0", "1", "", "", "", "", false, false,
"30", "0", "", "", "12.566370614359172", "50", "", "200",
"", "0", "12.566370614359172",
"0", "2", "", "", "",
"2: 2nd order: y'' = x - y, y(0) = 0, y'(0) = 2"];
example[2] = ["y1","y2","-y0 + x * x","exp(-x) + x*x","-exp(-x) + 2 * x",
"exp(-x) + 2",
0,
"0", "1", "", "", "0", "10", false, false,
"", "0", "", "", "4", "50", "", "200",
"0.1", "0", "4",
"exp(-b) + b*b", "-exp(-b) + 2 * b", "exp(-b) + 2", "", "",
"3: 3rd order: y''' = exp(-x) + 2 * x;
y(b) = exp(-b) + b * b;"
+ "y'(b) = -exp(-b) + 2 * b; y''(b) = exp(-b) + 2"];
example[3] = ["5 * c * x**4 + 4 * x**3", "4 * y**(3/4)", "", "", "",
"c * x**5 + x**4",
0,
"0", "1", "", "", "", "", false, false,
"", "1", "0", "", "2", "50", "", "200",
"0.25", "1", "4",
"1", "1", "", "", "",
"4: Two 1st order: y' = 4 * x**3 and y' = 4 * y**(3/4)
"
+ "Solution to both y(x) = x**4.
"
+ "Illustrates exact for 4th order but"
+ " not exact for 5th order when c != 0"];
example[4] = ["2 * x", "2 * sqrt(y)", "", "", "x**2", "",
0,
"0", "1", "", "", "", "", false, false,
"", "0", "", "", "4", "50", "", "200",
"0.25", "2", "4",
"4", "4", "", "", "",
"5: Two 1st order: y' = 2 * x and y' = 2 * sqrt(y)
"
+ "Solution to both y(x) = x*x.
"
+ "Illustrates exact for 2nd order methods"];
example[5] = ["y1","-y","","!p e; c","!w e; c; target",
"d * sin(x)",
1,
"0", "0", "", "", "", "", false, false,
"", "", ".8", "c/sin(e)", "2", "50", "", "200",
"0.1", "0", "e",
"0", "1", "", "c", "",
"6: Boundary value problem: y'' = -y; y(0) = 0; y(2) = .8,"
+ " solution y = c/sin(e)*sin(x)."
+ "
(To modify, use c to change ending y value and use"
+ " e for ending x.)"];
example[6] = ["y1"," -3*x**2 + 2 - x*y1 - y","",
"","!p G; M","!w G; M; target",
1,
"0", "0", "", "", "", "", false, false,
"", "", ".8", "", "2", "50", "", "200",
"0.1", "0", "e",
"0", "1", "", "c", "",
"7: Boundary value problem: y'' = -3*x**2 + 2 - x*y' - y,"
+ " y(0) = 0; y(1) = 1."
+ "
(To modify, use c to change ending y value and use"
+ " e for ending x.)"];
example[7] = ["y1","-y","","","","",
1,
"0", "0", "", "", "", "", false, false,
"", "0", "", "", "2", "50", "", "200",
"0.1", "0", "e",
"0", "1", "", "", "-0.5",
"8: Boundary value problem with ending slope specified,"
+ " y'' = -y;
y'(0) = .5; y'(2) = -.5 ."
+ ' Use "Update y at beginning x to adjust y\'.'
+ ' Check y\' in "Result at x =" line to see result.' ];
example[8] = ["y1","exp(x) + y * y1","","!p 1; 1","!w 1; 1; target","",
1,
"0", "0", "", "", "", "", false, false,
"30", "0", "", "", "1", "50", "", "200",
"0.1", "0", "1",
"1", "0", "", "1", "",
"9: Nonlinear boudary value problem y'' = ex + y * y',"
+ "
y(0) = 0, y(1) = 1"
+ "
Several shots and updates will be needed."];
} // setupExamples
function displayExamples() {
let heading = createElement("h3","Examples");
heading.parent(p5left);
let radio;
for (let i = 0; i < example.length; i++) {
// s2a = createSpan("
1");
// s2a.parent(p5left);
radio = HTMLforRadioButton("examplesResult",
''
+ example[i][EXAMPLE_NAME] +'',
"getRadioValueExamples()", false, i);
examplesRadio[i] = displayHTMLElements(radio, p5left, "block");
}
} // displayExamples
function useExample(ex) {
methodMsg = "";
noFDefined = true;
for (let i = 0; i < NUM_F_INPUT; i++) {
fInput[i].value(myReplaceAll(example[ex][i], "^$", ","));
}
if (example[ex][6] == 0) {
initialRadio.checked = true;
} else {
boundaryRadio.checked = true;
}
modeChanged(); // must be done before setting values
xMinInput.value(example[ex][9]);
xMaxInput.value(example[ex][10]);
yMinInput.value(example[ex][11]);
yMaxInput.value(example[ex][12]);
equalSpace.checked(example[ex][13]);
allowLoop.checked(example[ex][14]);
for (let i = 0; i < 5; i++) {
valInp[i].value(example[ex][15 + i]);
}
sValueSlider.value(example[ex][20]);
// 21 is unused in diffiEq2
numPtsInput.value(example[ex][22]);
// the following lines have been added for DiffiEq
stepSizeInp.value(example[ex][23]);
beginningXInp.value(example[ex][24]);
endingXInp.value(example[ex][25]);
for (let i = 0; i < NUM_F_INPUT/2; i++) {// handle beginning & ending values in one loop
beginningYInp[i].value(example[ex][26 + i]);
}
for (let i = 0; i < NUM_BOUNDARY; i++) {// handle beginning & ending values in one loop
endingYInp[i].value(example[ex][29 + i]);
endingY[i] = example[ex][29 + i];
}
endingX = eval(endingXInp.value());
updateMode();
// The name is not displayed anywhere except in example list
// so it is not used here.
inputF();
valueChanged();
errorMsg = "";
loop();
} // useExample
function getRadioValueMode() {
mode = int(getRadioValue("modeResult"));
updateMode();
} // getRadioValueMode
function getRadioValueExamples() {
let ex = int(getRadioValue("examplesResult"));
useExample(ex);
} // getRadioValueExamples
function updateMode() {
// called when the mode is changed
// label the function box inputs correctly
for (let i = 0; i < NUM_F_INPUT; i++) {
functionLab(i, mode);
}
if (mode == 0) {
s3aDiv.style("display: none");
s3Div.style("display: none");
shotNum.html(" ");
} else {
s3aDiv.style("display: block");
s3Div.style("display: block");
fInput[0].value("y1");
shotNum.html(" " + results[0].length + " shot(s)");
}
// update most everything when the mode changes and do a loop
valueChanged();
checkMinMax();
inputF();
loop();
} // updateMode
// The following my.... functions are intended to work both the normal
// canvas and an enlarged canvas. Simple attribute setting functions
// like myFill always set both canvases. This means that they will work
// even if the enlarged canvas is not currently activated. The more
// complicated ones like myText always effect the normal small canvas but
// only work on the enlarged canvas if enlarge is true.
function myText(msg, xLoc, yLoc) {
can1.text(msg, xLoc, yLoc);
if (enlarge) {
can2.text(msg, xLoc * enlargeRatio, yLoc * enlargeRatio);
}
} // myText
function myFill(color) {
can1.fill(color);
can2.fill(color);
} // myFill
function myStroke(color) {
can1.stroke(color);
can2.stroke(color);
} // myStroke
function myBackground(color) {
can1.background(color);
can2.background(color);
} // myBackground
function myNoStroke(){
can1.noStroke();
can2.noStroke();
} // myNoStroke
function myTextAlign(horizAlign, vertAlign) {
// vertAlign is probably required
can1.textAlign(horizAlign, vertAlign);
can2.textAlign(horizAlign, vertAlign);
} // myTextAlign
function myStrokeWeight(val) {
can1.strokeWeight(val);
can2.strokeWeight(val);
} // myStrokeWeight
function myLine(x1, y1, x2, y2) {
can1.line(x1, y1, x2, y2);
if (enlarge) {
can2.line(x1 * enlargeRatio, y1 * enlargeRatio,
x2 * enlargeRatio, y2 * enlargeRatio);
}
} // myLine
function myRect(x, y, w, h) {
can1.rect(x, y, w, h);
if (enlarge) {
can2.rect(x * enlargeRatio, y * enlargeRatio, w * enlargeRatio,
h * enlargeRatio);
}
} // myRect
function myPoint(x, y) {
can1.point(x, y);
if (enlarge) {
can2.point(x * enlargeRatio, y * enlargeRatio);
}
} // myPoint
/* myArk is not used in diffEq2
function myArc(x,y, w, h, start, stop, mode) {
// mode is probably required
can1.arc(x,y, w, h, start, stop, mode);
if (enlarge) {
can2.arc(x * enlargeRatio, y * enlargeRatio, w * enlargeRatio,
h * enlargeRatio, start, stop, mode);
}
} // myArc */
// this functions save can2 if enlarge is true, can1 otherwise
function mySaveCanvas(name, tag) {
if (enlarge) {
can2.saveCanvas(name, tag);
} else {
can1.saveCanvas(name, tag);
}
} // mySaveCanvas
/**
* Changes the size of can2 in response to the enlarge slider
*/
function enlargeChanged() {
if (enlargeChk.checked()) {
let newSize = enlargeSlider.value();
can2.resizeCanvas(newSize, newSize);
enlargeRatio = newSize/can1.width;
enlargeSpan.html("Enlargement ratio ("
+ (round(10 * enlargeRatio)/10) + ") ");
enlargeSlider.parent(enlargeSpan);
enlargeChk.checked(true);
inputF()
enlarge = true;
can2.style("display: block");
spacer.style("display: block");
inputF();
} else {
enlarge = false;
can2.style("display: none");
spacer.style("display:none");
}
} // enlargeChanged