1 /*
  2     Copyright 2008-2023
  3         Matthias Ehmann,
  4         Michael Gerhaeuser,
  5         Carsten Miller,
  6         Bianca Valentin,
  7         Alfred Wassermann,
  8         Peter Wilfahrt
  9 
 10     This file is part of JSXGraph.
 11 
 12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
 13 
 14     You can redistribute it and/or modify it under the terms of the
 15 
 16       * GNU Lesser General Public License as published by
 17         the Free Software Foundation, either version 3 of the License, or
 18         (at your option) any later version
 19       OR
 20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
 21 
 22     JSXGraph is distributed in the hope that it will be useful,
 23     but WITHOUT ANY WARRANTY; without even the implied warranty of
 24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 25     GNU Lesser General Public License for more details.
 26 
 27     You should have received a copy of the GNU Lesser General Public License and
 28     the MIT License along with JSXGraph. If not, see <https://www.gnu.org/licenses/>
 29     and <https://opensource.org/licenses/MIT/>.
 30  */
 31 
 32 /*global JXG: true, define: true*/
 33 /*jslint nomen: true, plusplus: true*/
 34 /*eslint no-loss-of-precision: off */
 35 
 36 /**
 37  * @fileoverview In this file the namespace Math.Numerics is defined, which holds numerical
 38  * algorithms for solving linear equations etc.
 39  */
 40 
 41 import JXG from "../jxg";
 42 import Type from "../utils/type";
 43 import Env from "../utils/env";
 44 import Mat from "./math";
 45 
 46 // Predefined butcher tableaus for the common Runge-Kutta method (fourth order), Heun method (second order), and Euler method (first order).
 47 var predefinedButcher = {
 48     rk4: {
 49         s: 4,
 50         A: [
 51             [0, 0, 0, 0],
 52             [0.5, 0, 0, 0],
 53             [0, 0.5, 0, 0],
 54             [0, 0, 1, 0]
 55         ],
 56         b: [1.0 / 6.0, 1.0 / 3.0, 1.0 / 3.0, 1.0 / 6.0],
 57         c: [0, 0.5, 0.5, 1]
 58     },
 59     heun: {
 60         s: 2,
 61         A: [
 62             [0, 0],
 63             [1, 0]
 64         ],
 65         b: [0.5, 0.5],
 66         c: [0, 1]
 67     },
 68     euler: {
 69         s: 1,
 70         A: [[0]],
 71         b: [1],
 72         c: [0]
 73     }
 74 };
 75 
 76 /**
 77  * The JXG.Math.Numerics namespace holds numerical algorithms, constants, and variables.
 78  * @name JXG.Math.Numerics
 79  * @exports Mat.Numerics as JXG.Math.Numerics
 80  * @namespace
 81  */
 82 Mat.Numerics = {
 83     //JXG.extend(Mat.Numerics, /** @lends JXG.Math.Numerics */ {
 84     /**
 85      * Solves a system of linear equations given by A and b using the Gauss-Jordan-elimination.
 86      * The algorithm runs in-place. I.e. the entries of A and b are changed.
 87      * @param {Array} A Square matrix represented by an array of rows, containing the coefficients of the lineare equation system.
 88      * @param {Array} b A vector containing the linear equation system's right hand side.
 89      * @throws {Error} If a non-square-matrix is given or if b has not the right length or A's rank is not full.
 90      * @returns {Array} A vector that solves the linear equation system.
 91      * @memberof JXG.Math.Numerics
 92      */
 93     Gauss: function (A, b) {
 94         var i,
 95             j,
 96             k,
 97             // copy the matrix to prevent changes in the original
 98             Acopy,
 99             // solution vector, to prevent changing b
100             x,
101             eps = Mat.eps,
102             // number of columns of A
103             n = A.length > 0 ? A[0].length : 0;
104 
105         if (n !== b.length || n !== A.length) {
106             throw new Error(
107                 "JXG.Math.Numerics.Gauss: Dimensions don't match. A must be a square matrix and b must be of the same length as A."
108             );
109         }
110 
111         // initialize solution vector
112         Acopy = [];
113         x = b.slice(0, n);
114 
115         for (i = 0; i < n; i++) {
116             Acopy[i] = A[i].slice(0, n);
117         }
118 
119         // Gauss-Jordan-elimination
120         for (j = 0; j < n; j++) {
121             for (i = n - 1; i > j; i--) {
122                 // Is the element which is to eliminate greater than zero?
123                 if (Math.abs(Acopy[i][j]) > eps) {
124                     // Equals pivot element zero?
125                     if (Math.abs(Acopy[j][j]) < eps) {
126                         // At least numerically, so we have to exchange the rows
127                         Type.swap(Acopy, i, j);
128                         Type.swap(x, i, j);
129                     } else {
130                         // Saves the L matrix of the LR-decomposition. unnecessary.
131                         Acopy[i][j] /= Acopy[j][j];
132                         // Transform right-hand-side b
133                         x[i] -= Acopy[i][j] * x[j];
134 
135                         // subtract the multiple of A[i][j] / A[j][j] of the j-th row from the i-th.
136                         for (k = j + 1; k < n; k++) {
137                             Acopy[i][k] -= Acopy[i][j] * Acopy[j][k];
138                         }
139                     }
140                 }
141             }
142 
143             // The absolute values of all coefficients below the j-th row in the j-th column are smaller than JXG.Math.eps.
144             if (Math.abs(Acopy[j][j]) < eps) {
145                 throw new Error(
146                     "JXG.Math.Numerics.Gauss(): The given matrix seems to be singular."
147                 );
148             }
149         }
150 
151         this.backwardSolve(Acopy, x, true);
152 
153         return x;
154     },
155 
156     /**
157      * Solves a system of linear equations given by the right triangular matrix R and vector b.
158      * @param {Array} R Right triangular matrix represented by an array of rows. All entries a_(i,j) with i < j are ignored.
159      * @param {Array} b Right hand side of the linear equation system.
160      * @param {Boolean} [canModify=false] If true, the right hand side vector is allowed to be changed by this method.
161      * @returns {Array} An array representing a vector that solves the system of linear equations.
162      * @memberof JXG.Math.Numerics
163      */
164     backwardSolve: function (R, b, canModify) {
165         var x, m, n, i, j;
166 
167         if (canModify) {
168             x = b;
169         } else {
170             x = b.slice(0, b.length);
171         }
172 
173         // m: number of rows of R
174         // n: number of columns of R
175         m = R.length;
176         n = R.length > 0 ? R[0].length : 0;
177 
178         for (i = m - 1; i >= 0; i--) {
179             for (j = n - 1; j > i; j--) {
180                 x[i] -= R[i][j] * x[j];
181             }
182             x[i] /= R[i][i];
183         }
184 
185         return x;
186     },
187 
188     /**
189      * @private
190      * Gauss-Bareiss algorithm to compute the
191      * determinant of matrix without fractions.
192      * See Henri Cohen, "A Course in Computational
193      * Algebraic Number Theory (Graduate texts
194      * in mathematics; 138)", Springer-Verlag,
195      * ISBN 3-540-55640-0 / 0-387-55640-0
196      * Third, Corrected Printing 1996
197      * "Algorithm 2.2.6", pg. 52-53
198      * @memberof JXG.Math.Numerics
199      */
200     gaussBareiss: function (mat) {
201         var k,
202             c,
203             s,
204             i,
205             j,
206             p,
207             n,
208             M,
209             t,
210             eps = Mat.eps;
211 
212         n = mat.length;
213 
214         if (n <= 0) {
215             return 0;
216         }
217 
218         if (mat[0].length < n) {
219             n = mat[0].length;
220         }
221 
222         // Copy the input matrix to M
223         M = [];
224 
225         for (i = 0; i < n; i++) {
226             M[i] = mat[i].slice(0, n);
227         }
228 
229         c = 1;
230         s = 1;
231 
232         for (k = 0; k < n - 1; k++) {
233             p = M[k][k];
234 
235             // Pivot step
236             if (Math.abs(p) < eps) {
237                 for (i = k + 1; i < n; i++) {
238                     if (Math.abs(M[i][k]) >= eps) {
239                         break;
240                     }
241                 }
242 
243                 // No nonzero entry found in column k -> det(M) = 0
244                 if (i === n) {
245                     return 0.0;
246                 }
247 
248                 // swap row i and k partially
249                 for (j = k; j < n; j++) {
250                     t = M[i][j];
251                     M[i][j] = M[k][j];
252                     M[k][j] = t;
253                 }
254                 s = -s;
255                 p = M[k][k];
256             }
257 
258             // Main step
259             for (i = k + 1; i < n; i++) {
260                 for (j = k + 1; j < n; j++) {
261                     t = p * M[i][j] - M[i][k] * M[k][j];
262                     M[i][j] = t / c;
263                 }
264             }
265 
266             c = p;
267         }
268 
269         return s * M[n - 1][n - 1];
270     },
271 
272     /**
273      * Computes the determinant of a square nxn matrix with the
274      * Gauss-Bareiss algorithm.
275      * @param {Array} mat Matrix.
276      * @returns {Number} The determinant pf the matrix mat.
277      *                   The empty matrix returns 0.
278      * @memberof JXG.Math.Numerics
279      */
280     det: function (mat) {
281         var n = mat.length;
282 
283         if (n === 2 && mat[0].length === 2) {
284             return mat[0][0] * mat[1][1] - mat[1][0] * mat[0][1];
285         }
286 
287         return this.gaussBareiss(mat);
288     },
289 
290     /**
291      * Compute the Eigenvalues and Eigenvectors of a symmetric 3x3 matrix with the Jacobi method
292      * Adaption of a FORTRAN program by Ed Wilson, Dec. 25, 1990
293      * @param {Array} Ain A symmetric 3x3 matrix.
294      * @returns {Array} [A,V] the matrices A and V. The diagonal of A contains the Eigenvalues, V contains the Eigenvectors.
295      * @memberof JXG.Math.Numerics
296      */
297     Jacobi: function (Ain) {
298         var i,
299             j,
300             k,
301             aa,
302             si,
303             co,
304             tt,
305             ssum,
306             amax,
307             eps = Mat.eps * Mat.eps,
308             sum = 0.0,
309             n = Ain.length,
310             V = [
311                 [0, 0, 0],
312                 [0, 0, 0],
313                 [0, 0, 0]
314             ],
315             A = [
316                 [0, 0, 0],
317                 [0, 0, 0],
318                 [0, 0, 0]
319             ],
320             nloops = 0;
321 
322         // Initialization. Set initial Eigenvectors.
323         for (i = 0; i < n; i++) {
324             for (j = 0; j < n; j++) {
325                 V[i][j] = 0.0;
326                 A[i][j] = Ain[i][j];
327                 sum += Math.abs(A[i][j]);
328             }
329             V[i][i] = 1.0;
330         }
331 
332         // Trivial problems
333         if (n === 1) {
334             return [A, V];
335         }
336 
337         if (sum <= 0.0) {
338             return [A, V];
339         }
340 
341         sum /= n * n;
342 
343         // Reduce matrix to diagonal
344         do {
345             ssum = 0.0;
346             amax = 0.0;
347             for (j = 1; j < n; j++) {
348                 for (i = 0; i < j; i++) {
349                     // Check if A[i][j] is to be reduced
350                     aa = Math.abs(A[i][j]);
351 
352                     if (aa > amax) {
353                         amax = aa;
354                     }
355 
356                     ssum += aa;
357 
358                     if (aa >= eps) {
359                         // calculate rotation angle
360                         aa = Math.atan2(2.0 * A[i][j], A[i][i] - A[j][j]) * 0.5;
361                         si = Math.sin(aa);
362                         co = Math.cos(aa);
363 
364                         // Modify 'i' and 'j' columns
365                         for (k = 0; k < n; k++) {
366                             tt = A[k][i];
367                             A[k][i] = co * tt + si * A[k][j];
368                             A[k][j] = -si * tt + co * A[k][j];
369                             tt = V[k][i];
370                             V[k][i] = co * tt + si * V[k][j];
371                             V[k][j] = -si * tt + co * V[k][j];
372                         }
373 
374                         // Modify diagonal terms
375                         A[i][i] = co * A[i][i] + si * A[j][i];
376                         A[j][j] = -si * A[i][j] + co * A[j][j];
377                         A[i][j] = 0.0;
378 
379                         // Make 'A' matrix symmetrical
380                         for (k = 0; k < n; k++) {
381                             A[i][k] = A[k][i];
382                             A[j][k] = A[k][j];
383                         }
384                         // A[i][j] made zero by rotation
385                     }
386                 }
387             }
388             nloops += 1;
389         } while (Math.abs(ssum) / sum > eps && nloops < 2000);
390 
391         return [A, V];
392     },
393 
394     /**
395      * Calculates the integral of function f over interval using Newton-Cotes-algorithm.
396      * @param {Array} interval The integration interval, e.g. [0, 3].
397      * @param {function} f A function which takes one argument of type number and returns a number.
398      * @param {Object} [config] The algorithm setup. Accepted properties are number_of_nodes of type number and integration_type
399      * with value being either 'trapez', 'simpson', or 'milne'.
400      * @param {Number} [config.number_of_nodes=28]
401      * @param {String} [config.integration_type='milne'] Possible values are 'milne', 'simpson', 'trapez'
402      * @returns {Number} Integral value of f over interval
403      * @throws {Error} If config.number_of_nodes doesn't match config.integration_type an exception is thrown. If you want to use
404      * simpson rule respectively milne rule config.number_of_nodes must be dividable by 2 respectively 4.
405      * @example
406      * function f(x) {
407      *   return x*x;
408      * }
409      *
410      * // calculates integral of <tt>f</tt> from 0 to 2.
411      * var area1 = JXG.Math.Numerics.NewtonCotes([0, 2], f);
412      *
413      * // the same with an anonymous function
414      * var area2 = JXG.Math.Numerics.NewtonCotes([0, 2], function (x) { return x*x; });
415      *
416      * // use trapez rule with 16 nodes
417      * var area3 = JXG.Math.Numerics.NewtonCotes([0, 2], f,
418      *                                   {number_of_nodes: 16, integration_type: 'trapez'});
419      * @memberof JXG.Math.Numerics
420      */
421     NewtonCotes: function (interval, f, config) {
422         var evaluation_point,
423             i,
424             number_of_intervals,
425             integral_value = 0.0,
426             number_of_nodes =
427                 config && Type.isNumber(config.number_of_nodes) ? config.number_of_nodes : 28,
428             available_types = { trapez: true, simpson: true, milne: true },
429             integration_type =
430                 config &&
431                 config.integration_type &&
432                 available_types.hasOwnProperty(config.integration_type) &&
433                 available_types[config.integration_type]
434                     ? config.integration_type
435                     : "milne",
436             step_size = (interval[1] - interval[0]) / number_of_nodes;
437 
438         switch (integration_type) {
439             case "trapez":
440                 integral_value = (f(interval[0]) + f(interval[1])) * 0.5;
441                 evaluation_point = interval[0];
442 
443                 for (i = 0; i < number_of_nodes - 1; i++) {
444                     evaluation_point += step_size;
445                     integral_value += f(evaluation_point);
446                 }
447 
448                 integral_value *= step_size;
449                 break;
450             case "simpson":
451                 if (number_of_nodes % 2 > 0) {
452                     throw new Error(
453                         "JSXGraph:  INT_SIMPSON requires config.number_of_nodes dividable by 2."
454                     );
455                 }
456 
457                 number_of_intervals = number_of_nodes / 2.0;
458                 integral_value = f(interval[0]) + f(interval[1]);
459                 evaluation_point = interval[0];
460 
461                 for (i = 0; i < number_of_intervals - 1; i++) {
462                     evaluation_point += 2.0 * step_size;
463                     integral_value += 2.0 * f(evaluation_point);
464                 }
465 
466                 evaluation_point = interval[0] - step_size;
467 
468                 for (i = 0; i < number_of_intervals; i++) {
469                     evaluation_point += 2.0 * step_size;
470                     integral_value += 4.0 * f(evaluation_point);
471                 }
472 
473                 integral_value *= step_size / 3.0;
474                 break;
475             default:
476                 if (number_of_nodes % 4 > 0) {
477                     throw new Error(
478                         "JSXGraph: Error in INT_MILNE: config.number_of_nodes must be a multiple of 4"
479                     );
480                 }
481 
482                 number_of_intervals = number_of_nodes * 0.25;
483                 integral_value = 7.0 * (f(interval[0]) + f(interval[1]));
484                 evaluation_point = interval[0];
485 
486                 for (i = 0; i < number_of_intervals - 1; i++) {
487                     evaluation_point += 4.0 * step_size;
488                     integral_value += 14.0 * f(evaluation_point);
489                 }
490 
491                 evaluation_point = interval[0] - 3.0 * step_size;
492 
493                 for (i = 0; i < number_of_intervals; i++) {
494                     evaluation_point += 4.0 * step_size;
495                     integral_value +=
496                         32.0 * (f(evaluation_point) + f(evaluation_point + 2 * step_size));
497                 }
498 
499                 evaluation_point = interval[0] - 2.0 * step_size;
500 
501                 for (i = 0; i < number_of_intervals; i++) {
502                     evaluation_point += 4.0 * step_size;
503                     integral_value += 12.0 * f(evaluation_point);
504                 }
505 
506                 integral_value *= (2.0 * step_size) / 45.0;
507         }
508         return integral_value;
509     },
510 
511     /**
512      * Calculates the integral of function f over interval using Romberg iteration.
513      * @param {Array} interval The integration interval, e.g. [0, 3].
514      * @param {function} f A function which takes one argument of type number and returns a number.
515      * @param {Object} [config] The algorithm setup. Accepted properties are max_iterations of type number and precision eps.
516      * @param {Number} [config.max_iterations=20]
517      * @param {Number} [config.eps=0.0000001]
518      * @returns {Number} Integral value of f over interval
519      * @example
520      * function f(x) {
521      *   return x*x;
522      * }
523      *
524      * // calculates integral of <tt>f</tt> from 0 to 2.
525      * var area1 = JXG.Math.Numerics.Romberg([0, 2], f);
526      *
527      * // the same with an anonymous function
528      * var area2 = JXG.Math.Numerics.Romberg([0, 2], function (x) { return x*x; });
529      *
530      * // use trapez rule with maximum of 16 iterations or stop if the precision 0.0001 has been reached.
531      * var area3 = JXG.Math.Numerics.Romberg([0, 2], f,
532      *                                   {max_iterations: 16, eps: 0.0001});
533      * @memberof JXG.Math.Numerics
534      */
535     Romberg: function (interval, f, config) {
536         var a,
537             b,
538             h,
539             s,
540             n,
541             k,
542             i,
543             q,
544             p = [],
545             integral = 0.0,
546             last = Infinity,
547             m = config && Type.isNumber(config.max_iterations) ? config.max_iterations : 20,
548             eps = config && Type.isNumber(config.eps) ? config.eps : config.eps || 0.0000001;
549 
550         a = interval[0];
551         b = interval[1];
552         h = b - a;
553         n = 1;
554 
555         p[0] = 0.5 * h * (f(a) + f(b));
556 
557         for (k = 0; k < m; ++k) {
558             s = 0;
559             h *= 0.5;
560             n *= 2;
561             q = 1;
562 
563             for (i = 1; i < n; i += 2) {
564                 s += f(a + i * h);
565             }
566 
567             p[k + 1] = 0.5 * p[k] + s * h;
568 
569             integral = p[k + 1];
570             for (i = k - 1; i >= 0; --i) {
571                 q *= 4;
572                 p[i] = p[i + 1] + (p[i + 1] - p[i]) / (q - 1.0);
573                 integral = p[i];
574             }
575 
576             if (Math.abs(integral - last) < eps * Math.abs(integral)) {
577                 break;
578             }
540 + c[1+  n;zdota[kp1[1+                // circle circleA: [+ 11+                = p[k + 1][1]if (+                =             P, {
Pstart].
Pstart
Psts="PUNC">] =                 kp1[board, <= - 1b, t, sts="PUNC">] integrald2,
Pvmultc[k]ass="NAME" class="NAME">vmultc[ <= vmultc[k]ass="NAME" class="NAME">vmultc[ <= vmultc[= 0span>;
1101> ()<   //     the gradient of a constraint. Therefore the two stages arelass="PUNC">|| n !== vmultc[k]ass,> 1,
// an>(n
186   
186   
18>,
// an>{
2465 [
186   ; i
18>,)1002      * @returns s.board.jc.snippet(1+           n>i)vmultc[ )i
18>,AME">p[A1[first s = HIT"> sibrnch=   el2, i)a k             class="NUMB">4;
2465  (span class="NAME">k]ass="NAME" class="NAME">vmultc[ integral  integral) {an class="WHIT"> <">, parents[0A1 2.0 2.0 
18>,) f(evaluation_point);
489                  *) f()
454(lass="WHIT">                    );
455 ;=  284             return s ==   
489                         available_types454(lass="WHIT">                 span>             2.0 *2023  *             color: 'red'
20242 n class="PUNC">(evaluation_pointavailable_types454 -4.0 ];
return2pan class="WHIT">                integral_value<2.0= <=" <ös="PUNYltal -
454(lass="WHIT">                    );];integral_value<2.0=                    );];(lass="WHIT">                    );] =+ 1 +=2pan class="WHIT">       ,1n_point)2pan class="WHIT">       ,1n_pointi)] = integral_value<2.0= <=" <ös="   ; <ös="  -;integral_vne'>455 ;=        s="NUMB""PUNC">;iPUNC">) f(     ="PUNC">*= 455  Type.isNumber( 455= 455  Type.isNumber( 455=<4span> 455  Type.isNumber( 455=<4span> class='line'>544                         "Ppan>  Tys="WHIT"> el2, iiPUNC">),    /**
3> Type.isNumber(      * // use tr= 0.0,* i( = * i<2r( pan>(  =* i(Number( Number                    );];(lass="WHIT">                    ss="WHIT"> pan>(>)] ];(lass="WHIT">                    ss="WHIT"> ];(lass="span class="WHIT"> a;, i n>      * // use tr= ]; pan>    /**
(lass="span class="WHIT"> a/**
3> Type.isNum="WHIT"> pan>  WHIT"> (span class="NAME">n class="WHIT"> el2(a)   iPUNC">) ass='line'>493                 ; pan>pan class="NAME">i(  i n>UNC">= integral_value<2.0{
2465 ;(lass="> =]
360 <   WHIT"> (span /s>]span /s>]if  <(lass="> =]an class="PUNC">,
5ý0             n,
541 ,
5ýe'>541 span /s>]if iPUNC">) (object (object step_size; (object ispan /s>] + i nss="WHIT">                    )P a config a config a) ass=n>span /s>]step_size;integral_value<2.0=,ispan /s>] pan>ss="WHIspan class="PUNC">)            an class="WHIT"> =(+           n>i)2/span>vmultc[ ) n>)2/span>vmul="PUNC">= intervalvmultc[s="WHIT"> ) i(        h =k]ass,>)] =="PUNC">)2/span>vmultc[ <=" <ös=" vmultc[s="WHIT"> (        ha// calculate rotadieprmZTTepan>                 ;