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 )301n>;];(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> ;