// 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