diff-grok 1.0.10 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/settings.local.json +11 -0
- package/.claude/skills/license-updater/SKILL.md +149 -0
- package/.claude/skills/license-updater/references/compatibility.md +68 -0
- package/.claude/skills/license-updater/references/templates.md +151 -0
- package/.claude/skills/license-updater/scripts/generate_license_files.py +467 -0
- package/CLAUDE.md +104 -0
- package/LICENSE +1 -1
- package/README.MD +121 -27
- package/THIRD_PARTY_LICENSES +135 -0
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/BasicModelPipelineCreator.html +4 -4
- package/docs/classes/Callback.html +5 -5
- package/docs/classes/CyclicModelPipelineCreator.html +4 -4
- package/docs/classes/IterCheckerCallback.html +5 -5
- package/docs/classes/PipelineCreator.html +4 -4
- package/docs/classes/TimeCheckerCallback.html +5 -5
- package/docs/classes/UpdatesModelPipelineCreator.html +4 -4
- package/docs/functions/ab4.html +8 -0
- package/docs/functions/ab5.html +8 -0
- package/docs/functions/applyPipeline.html +2 -2
- package/docs/functions/getCallback.html +2 -2
- package/docs/functions/getIVP.html +2 -2
- package/docs/functions/getInputVector.html +2 -2
- package/docs/functions/getIvp2WebWorker.html +2 -2
- package/docs/functions/getJScode.html +2 -2
- package/docs/functions/getOutputNames.html +2 -2
- package/docs/functions/getPipelineCreator.html +2 -2
- package/docs/functions/lsoda.html +8 -0
- package/docs/functions/mrt.html +2 -2
- package/docs/functions/printE5.html +2 -2
- package/docs/functions/printHires.html +2 -2
- package/docs/functions/printOrego.html +2 -2
- package/docs/functions/printPollution.html +2 -2
- package/docs/functions/printRobertson.html +2 -2
- package/docs/functions/printVdpol.html +2 -2
- package/docs/functions/rk3.html +8 -0
- package/docs/functions/rk4.html +8 -0
- package/docs/functions/rkdp.html +8 -0
- package/docs/functions/ros34prw.html +2 -2
- package/docs/functions/ros3prw.html +2 -2
- package/docs/functions/solveIvp.html +2 -2
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +100 -15
- package/docs/media/E5.png +0 -0
- package/docs/media/HIRES.png +0 -0
- package/docs/media/OREGO.png +0 -0
- package/docs/media/POLL-full.png +0 -0
- package/docs/media/ROBER.png +0 -0
- package/docs/media/VDP0L.png +0 -0
- package/docs/media/cyclic-model.js +3 -1
- package/docs/media/cyclic-model.js.map +1 -1
- package/docs/media/cyclic-model.ts +3 -1
- package/docs/modules.html +1 -1
- package/docs/types/Arg.html +7 -7
- package/docs/types/DifEqs.html +4 -4
- package/docs/types/Func.html +2 -2
- package/docs/types/IVP.html +20 -20
- package/docs/types/IVP2WebWorker.html +7 -7
- package/docs/types/Input.html +4 -4
- package/docs/types/Loop.html +4 -4
- package/docs/types/ODEs.html +8 -8
- package/docs/types/Output.html +4 -4
- package/docs/types/Pipeline.html +4 -4
- package/docs/types/SolverOptions.html +5 -5
- package/docs/types/Update.html +5 -5
- package/docs/types/Wrapper.html +5 -5
- package/images/E5.png +0 -0
- package/images/HIRES.png +0 -0
- package/images/OREGO.png +0 -0
- package/images/POLL-full.png +0 -0
- package/images/ROBER.png +0 -0
- package/images/VDP0L.png +0 -0
- package/index.js +1 -1
- package/index.js.map +1 -1
- package/index.ts +2 -1
- package/jest.config.js +1 -0
- package/package.json +4 -2
- package/src/examples/check-methods.js +15 -3
- package/src/examples/check-methods.js.map +1 -1
- package/src/examples/check-methods.ts +18 -3
- package/src/examples/cyclic-model.js +3 -1
- package/src/examples/cyclic-model.js.map +1 -1
- package/src/examples/cyclic-model.ts +3 -1
- package/src/solver-tools/ab4-method.js +332 -0
- package/src/solver-tools/ab4-method.js.map +1 -0
- package/src/solver-tools/ab4-method.ts +395 -0
- package/src/solver-tools/ab5-method.js +334 -0
- package/src/solver-tools/ab5-method.js.map +1 -0
- package/src/solver-tools/ab5-method.ts +397 -0
- package/src/solver-tools/cvode/common.js +487 -0
- package/src/solver-tools/cvode/common.js.map +1 -0
- package/src/solver-tools/cvode/common.ts +549 -0
- package/src/solver-tools/cvode/cvode.js +1062 -0
- package/src/solver-tools/cvode/cvode.js.map +1 -0
- package/src/solver-tools/cvode/cvode.ts +1226 -0
- package/src/solver-tools/cvode/cvode_adams.js +124 -0
- package/src/solver-tools/cvode/cvode_adams.js.map +1 -0
- package/src/solver-tools/cvode/cvode_adams.ts +142 -0
- package/src/solver-tools/cvode/cvode_bdf.js +153 -0
- package/src/solver-tools/cvode/cvode_bdf.js.map +1 -0
- package/src/solver-tools/cvode/cvode_bdf.ts +164 -0
- package/src/solver-tools/cvode/cvode_class.js +158 -0
- package/src/solver-tools/cvode/cvode_class.js.map +1 -0
- package/src/solver-tools/cvode/cvode_class.ts +271 -0
- package/src/solver-tools/cvode/cvode_hin.js +119 -0
- package/src/solver-tools/cvode/cvode_hin.js.map +1 -0
- package/src/solver-tools/cvode/cvode_hin.ts +150 -0
- package/src/solver-tools/cvode/cvode_io.js +203 -0
- package/src/solver-tools/cvode/cvode_io.js.map +1 -0
- package/src/solver-tools/cvode/cvode_io.ts +258 -0
- package/src/solver-tools/cvode/cvode_ls.js +210 -0
- package/src/solver-tools/cvode/cvode_ls.js.map +1 -0
- package/src/solver-tools/cvode/cvode_ls.ts +249 -0
- package/src/solver-tools/cvode/cvode_nls.js +220 -0
- package/src/solver-tools/cvode/cvode_nls.js.map +1 -0
- package/src/solver-tools/cvode/cvode_nls.ts +259 -0
- package/src/solver-tools/cvode/cvode_root.js +415 -0
- package/src/solver-tools/cvode/cvode_root.js.map +1 -0
- package/src/solver-tools/cvode/cvode_root.ts +503 -0
- package/src/solver-tools/cvode/dense_linalg.js +131 -0
- package/src/solver-tools/cvode/dense_linalg.js.map +1 -0
- package/src/solver-tools/cvode/dense_linalg.ts +144 -0
- package/src/solver-tools/cvode/index.js +26 -0
- package/src/solver-tools/cvode/index.js.map +1 -0
- package/src/solver-tools/cvode/index.ts +64 -0
- package/src/solver-tools/cvode-method.js +114 -0
- package/src/solver-tools/cvode-method.js.map +1 -0
- package/src/solver-tools/cvode-method.ts +129 -0
- package/src/solver-tools/index.js +7 -0
- package/src/solver-tools/index.js.map +1 -1
- package/src/solver-tools/index.ts +7 -0
- package/src/solver-tools/lsoda/blas.js +257 -0
- package/src/solver-tools/lsoda/blas.js.map +1 -0
- package/src/solver-tools/lsoda/blas.ts +279 -0
- package/src/solver-tools/lsoda/cfode.js +84 -0
- package/src/solver-tools/lsoda/cfode.js.map +1 -0
- package/src/solver-tools/lsoda/cfode.ts +95 -0
- package/src/solver-tools/lsoda/common.js +94 -0
- package/src/solver-tools/lsoda/common.js.map +1 -0
- package/src/solver-tools/lsoda/common.ts +135 -0
- package/src/solver-tools/lsoda/corfailure.js +36 -0
- package/src/solver-tools/lsoda/corfailure.js.map +1 -0
- package/src/solver-tools/lsoda/corfailure.ts +42 -0
- package/src/solver-tools/lsoda/correction.js +112 -0
- package/src/solver-tools/lsoda/correction.js.map +1 -0
- package/src/solver-tools/lsoda/correction.ts +131 -0
- package/src/solver-tools/lsoda/dense.js +121 -0
- package/src/solver-tools/lsoda/dense.js.map +1 -0
- package/src/solver-tools/lsoda/dense.ts +141 -0
- package/src/solver-tools/lsoda/index.js +15 -0
- package/src/solver-tools/lsoda/index.js.map +1 -0
- package/src/solver-tools/lsoda/index.ts +17 -0
- package/src/solver-tools/lsoda/intdy.js +54 -0
- package/src/solver-tools/lsoda/intdy.js.map +1 -0
- package/src/solver-tools/lsoda/intdy.ts +62 -0
- package/src/solver-tools/lsoda/lsoda.js +578 -0
- package/src/solver-tools/lsoda/lsoda.js.map +1 -0
- package/src/solver-tools/lsoda/lsoda.ts +644 -0
- package/src/solver-tools/lsoda/methodswitch.js +108 -0
- package/src/solver-tools/lsoda/methodswitch.js.map +1 -0
- package/src/solver-tools/lsoda/methodswitch.ts +119 -0
- package/src/solver-tools/lsoda/orderswitch.js +97 -0
- package/src/solver-tools/lsoda/orderswitch.js.map +1 -0
- package/src/solver-tools/lsoda/orderswitch.ts +107 -0
- package/src/solver-tools/lsoda/prja.js +54 -0
- package/src/solver-tools/lsoda/prja.js.map +1 -0
- package/src/solver-tools/lsoda/prja.ts +61 -0
- package/src/solver-tools/lsoda/scaleh.js +41 -0
- package/src/solver-tools/lsoda/scaleh.js.map +1 -0
- package/src/solver-tools/lsoda/scaleh.ts +45 -0
- package/src/solver-tools/lsoda/solsy.js +25 -0
- package/src/solver-tools/lsoda/solsy.js.map +1 -0
- package/src/solver-tools/lsoda/solsy.ts +30 -0
- package/src/solver-tools/lsoda/stoda.js +270 -0
- package/src/solver-tools/lsoda/stoda.js.map +1 -0
- package/src/solver-tools/lsoda/stoda.ts +298 -0
- package/src/solver-tools/lsoda-method.js +134 -0
- package/src/solver-tools/lsoda-method.js.map +1 -0
- package/src/solver-tools/lsoda-method.ts +154 -0
- package/src/solver-tools/mrt-method.js +81 -18
- package/src/solver-tools/mrt-method.js.map +1 -1
- package/src/solver-tools/mrt-method.ts +111 -21
- package/src/solver-tools/rk3-method.js +210 -0
- package/src/solver-tools/rk3-method.js.map +1 -0
- package/src/solver-tools/rk3-method.ts +257 -0
- package/src/solver-tools/rk4-method.js +236 -0
- package/src/solver-tools/rk4-method.js.map +1 -0
- package/src/solver-tools/rk4-method.ts +286 -0
- package/src/solver-tools/rkdp-method.js +247 -0
- package/src/solver-tools/rkdp-method.js.map +1 -0
- package/src/solver-tools/rkdp-method.ts +300 -0
- package/src/solver-tools/ros34prw-method.js +1 -1
- package/src/solver-tools/ros34prw-method.js.map +1 -1
- package/src/solver-tools/ros34prw-method.ts +1 -1
- package/src/solver-tools/ros3prw-method.js +1 -1
- package/src/solver-tools/ros3prw-method.js.map +1 -1
- package/src/solver-tools/ros3prw-method.ts +1 -1
- package/src/solver-tools/solver-defs.js +7 -0
- package/src/solver-tools/solver-defs.js.map +1 -1
- package/src/solver-tools/solver-defs.ts +7 -0
- package/src/tests/correctness.test.js +6 -4
- package/src/tests/correctness.test.js.map +1 -1
- package/src/tests/correctness.test.ts +6 -4
- package/src/tests/cvode-lib-correctness.test.js +404 -0
- package/src/tests/cvode-lib-correctness.test.js.map +1 -0
- package/src/tests/cvode-lib-correctness.test.ts +505 -0
- package/src/tests/cvode-lib-performance.test.js +103 -0
- package/src/tests/cvode-lib-performance.test.js.map +1 -0
- package/src/tests/cvode-lib-performance.test.ts +128 -0
- package/src/tests/lsoda-lib-correctness.test.js +223 -0
- package/src/tests/lsoda-lib-correctness.test.js.map +1 -0
- package/src/tests/lsoda-lib-correctness.test.ts +283 -0
- package/src/tests/lsoda-lib-performance.test.js +95 -0
- package/src/tests/lsoda-lib-performance.test.js.map +1 -0
- package/src/tests/lsoda-lib-performance.test.ts +126 -0
- package/src/tests/performance.test.js +12 -8
- package/src/tests/performance.test.js.map +1 -1
- package/src/tests/performance.test.ts +12 -8
- package/src/tests/test-defs.js +20 -1
- package/src/tests/test-defs.js.map +1 -1
- package/src/tests/test-defs.ts +22 -1
- package/src/worker-tools/solving.js +15 -1
- package/src/worker-tools/solving.js.map +1 -1
- package/src/worker-tools/solving.ts +23 -1
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import {Cvode, CvodeOptions, CvodeSolveResult} from '../solver-tools/cvode';
|
|
2
|
+
import {ODEs} from '../../index';
|
|
3
|
+
import {robertson, robertsonReferencePoint} from '../examples/robertson';
|
|
4
|
+
import {hires, hiresReferencePoint} from '../examples/hires';
|
|
5
|
+
import {vdpol, vdpolReferencePoint} from '../examples/vdpol';
|
|
6
|
+
import {orego, oregoReferencePoint} from '../examples/orego';
|
|
7
|
+
import {e5, e5ReferencePoint} from '../examples/e5';
|
|
8
|
+
import {pollution, pollutionReferencePoint} from '../examples/pollution';
|
|
9
|
+
|
|
10
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
11
|
+
|
|
12
|
+
interface SolverOpts {
|
|
13
|
+
mxstep?: number;
|
|
14
|
+
hmax?: number;
|
|
15
|
+
rtol?: Float64Array;
|
|
16
|
+
atol?: Float64Array;
|
|
17
|
+
/** Small initial tout to bootstrap very stiff problems before the main loop. */
|
|
18
|
+
warmupTout?: number;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** Solves a benchmark problem using CVODE, checks accuracy against the reference point. */
|
|
22
|
+
function solveBenchmarkAndCheck(
|
|
23
|
+
ode: ODEs, referencePoint: Float64Array, relTol: number, absTol: number, opts?: SolverOpts,
|
|
24
|
+
): void {
|
|
25
|
+
const neq = ode.initial.length;
|
|
26
|
+
const tol = ode.tolerance;
|
|
27
|
+
|
|
28
|
+
// Build CvodeOptions
|
|
29
|
+
const cvodeOpts: CvodeOptions = {lmm: 'bdf'};
|
|
30
|
+
|
|
31
|
+
// Handle tolerances
|
|
32
|
+
if (opts?.rtol && opts?.atol) {
|
|
33
|
+
cvodeOpts.rtol = opts.rtol[0];
|
|
34
|
+
cvodeOpts.atol = opts.atol;
|
|
35
|
+
} else if (opts?.rtol)
|
|
36
|
+
cvodeOpts.rtol = opts.rtol[0];
|
|
37
|
+
else if (opts?.atol)
|
|
38
|
+
cvodeOpts.atol = opts.atol;
|
|
39
|
+
else {
|
|
40
|
+
cvodeOpts.rtol = tol;
|
|
41
|
+
cvodeOpts.atol = tol;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (opts?.mxstep) cvodeOpts.maxSteps = opts.mxstep;
|
|
45
|
+
if (opts?.hmax) cvodeOpts.maxStep = opts.hmax;
|
|
46
|
+
|
|
47
|
+
const y0 = Float64Array.from(ode.initial);
|
|
48
|
+
const solver = new Cvode(ode.func, neq, ode.arg.start, y0, cvodeOpts);
|
|
49
|
+
|
|
50
|
+
// Warmup phase: bootstrap very stiff problems with a small initial tout
|
|
51
|
+
if (opts?.warmupTout !== undefined) {
|
|
52
|
+
const result = solver.solve(opts.warmupTout);
|
|
53
|
+
expect(result.flag).toBeGreaterThanOrEqual(0);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const t = opts?.warmupTout ?? ode.arg.start;
|
|
57
|
+
let tout = t + ode.arg.step;
|
|
58
|
+
const finish = ode.arg.finish;
|
|
59
|
+
let lastResult: CvodeSolveResult;
|
|
60
|
+
|
|
61
|
+
while (tout <= finish + ode.arg.step / 2) {
|
|
62
|
+
lastResult = solver.solve(tout);
|
|
63
|
+
if (lastResult.flag < 0)
|
|
64
|
+
throw new Error(`${ode.name}: solver failed at t=${lastResult.t} with flag=${lastResult.flag}`);
|
|
65
|
+
tout += ode.arg.step;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Check final solution against reference point
|
|
69
|
+
const y = lastResult!.y;
|
|
70
|
+
for (let j = 0; j < neq; j++) {
|
|
71
|
+
const absErr = Math.abs(y[j] - referencePoint[j]);
|
|
72
|
+
const allowed = relTol * Math.abs(referencePoint[j]) + absTol;
|
|
73
|
+
expect(absErr).toBeLessThan(allowed);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// ─── Performance tests ──────────────────────────────────────────────────────
|
|
78
|
+
|
|
79
|
+
// Robertson: components span many orders of magnitude; use component-wise atol
|
|
80
|
+
describe('Robertson', () => {
|
|
81
|
+
it('should solve correctly', () => {
|
|
82
|
+
solveBenchmarkAndCheck(robertson, robertsonReferencePoint, 1e-2, 1e-7, {
|
|
83
|
+
mxstep: 50000,
|
|
84
|
+
atol: new Float64Array([1e-8, 1e-14, 1e-8]),
|
|
85
|
+
rtol: new Float64Array([1e-7, 1e-7, 1e-7]),
|
|
86
|
+
});
|
|
87
|
+
}, 60000);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('HIRES', () => {
|
|
91
|
+
it('should solve correctly', () => {
|
|
92
|
+
solveBenchmarkAndCheck(hires, hiresReferencePoint, 1e-3, 1e-12);
|
|
93
|
+
}, 60000);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
// Van der Pol (mu=1000): very stiff, needs many internal steps
|
|
97
|
+
describe('van der Pol', () => {
|
|
98
|
+
it('should solve correctly', () => {
|
|
99
|
+
solveBenchmarkAndCheck(vdpol, vdpolReferencePoint, 1e-3, 1e-6, {
|
|
100
|
+
mxstep: 50000,
|
|
101
|
+
});
|
|
102
|
+
}, 60000);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe('OREGO', () => {
|
|
106
|
+
it('should solve correctly', () => {
|
|
107
|
+
solveBenchmarkAndCheck(orego, oregoReferencePoint, 1e-2, 1e-6);
|
|
108
|
+
}, 60000);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// E5: extremely stiff (rate constants span 20 orders of magnitude).
|
|
112
|
+
// Known limitation: CVODE BDF with dense direct solver hits convergence failures
|
|
113
|
+
// on this extreme problem. LSODA handles it via automatic stiff/non-stiff switching.
|
|
114
|
+
// TODO: revisit when band/iterative solvers or preconditioners are added.
|
|
115
|
+
describe('E5', () => {
|
|
116
|
+
it.skip('should solve correctly (known limitation: CV_CONV_FAILURE)', () => {
|
|
117
|
+
solveBenchmarkAndCheck(e5, e5ReferencePoint, 1, 1e-6, {
|
|
118
|
+
mxstep: 50000,
|
|
119
|
+
warmupTout: 1e-5,
|
|
120
|
+
});
|
|
121
|
+
}, 60000);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe('Pollution', () => {
|
|
125
|
+
it('should solve correctly', () => {
|
|
126
|
+
solveBenchmarkAndCheck(pollution, pollutionReferencePoint, 1e-2, 1e-10);
|
|
127
|
+
}, 60000);
|
|
128
|
+
});
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
import { Lsoda } from '../solver-tools/lsoda';
|
|
2
|
+
import { corrProbs } from '../examples/corr-probs';
|
|
3
|
+
// ─── Robertson chemical kinetics (original test) ────────────────────────────
|
|
4
|
+
function fex(t, y, ydot) {
|
|
5
|
+
ydot[0] = 1.0e4 * y[1] * y[2] - 0.04 * y[0];
|
|
6
|
+
ydot[2] = 3.0e7 * y[1] * y[1];
|
|
7
|
+
ydot[1] = -(ydot[0] + ydot[2]);
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
const expected = [
|
|
11
|
+
{ t: 4.0000e-01, y: [9.851712e-01, 3.386380e-05, 1.479493e-02] },
|
|
12
|
+
{ t: 4.0000e+00, y: [9.055333e-01, 2.240655e-05, 9.444430e-02] },
|
|
13
|
+
{ t: 4.0000e+01, y: [7.158403e-01, 9.186334e-06, 2.841505e-01] },
|
|
14
|
+
{ t: 4.0000e+02, y: [4.505250e-01, 3.222964e-06, 5.494717e-01] },
|
|
15
|
+
{ t: 4.0000e+03, y: [1.831976e-01, 8.941773e-07, 8.168015e-01] },
|
|
16
|
+
{ t: 4.0000e+04, y: [3.898729e-02, 1.621940e-07, 9.610125e-01] },
|
|
17
|
+
{ t: 4.0000e+05, y: [4.936362e-03, 1.984221e-08, 9.950636e-01] },
|
|
18
|
+
{ t: 4.0000e+06, y: [5.161833e-04, 2.065787e-09, 9.994838e-01] },
|
|
19
|
+
{ t: 4.0000e+07, y: [5.179804e-05, 2.072027e-10, 9.999482e-01] },
|
|
20
|
+
{ t: 4.0000e+08, y: [5.283675e-06, 2.113481e-11, 9.999947e-01] },
|
|
21
|
+
{ t: 4.0000e+09, y: [4.658667e-07, 1.863468e-12, 9.999995e-01] },
|
|
22
|
+
{ t: 4.0000e+10, y: [1.431100e-08, 5.724404e-14, 1.000000e+00] },
|
|
23
|
+
];
|
|
24
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
25
|
+
function relError(computed, reference) {
|
|
26
|
+
if (reference === 0)
|
|
27
|
+
return Math.abs(computed);
|
|
28
|
+
return Math.abs((computed - reference) / reference);
|
|
29
|
+
}
|
|
30
|
+
/** Wraps an ODEs func into an OdeFunction compatible with Lsoda (returns 0). */
|
|
31
|
+
function wrapFunc(ode) {
|
|
32
|
+
return (t, y, ydot) => {
|
|
33
|
+
ode.func(t, y, ydot);
|
|
34
|
+
return 0;
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Solves an ODE problem and checks against the exact solution at the final time point.
|
|
39
|
+
* Uses combined absolute + relative tolerance: |computed - exact| < checkTol * |exact| + checkAbsTol.
|
|
40
|
+
*/
|
|
41
|
+
function solveAndCheck(ode, exactSolution, checkTol = 1e-3, checkAbsTol = 1e-6) {
|
|
42
|
+
const neq = ode.initial.length;
|
|
43
|
+
const tol = ode.tolerance;
|
|
44
|
+
const rtol = new Float64Array(neq).fill(tol);
|
|
45
|
+
const atol = new Float64Array(neq).fill(tol);
|
|
46
|
+
const solver = new Lsoda(wrapFunc(ode), neq, { rtol, atol, itask: 1 });
|
|
47
|
+
let y = [...ode.initial];
|
|
48
|
+
let t = ode.arg.start;
|
|
49
|
+
const step = ode.arg.step;
|
|
50
|
+
const finish = ode.arg.finish;
|
|
51
|
+
let tout = t + step;
|
|
52
|
+
while (tout <= finish + step / 2) {
|
|
53
|
+
const result = solver.solve(y, t, tout);
|
|
54
|
+
y = result.y;
|
|
55
|
+
t = result.t;
|
|
56
|
+
expect(solver.state).toBeGreaterThan(0);
|
|
57
|
+
tout += step;
|
|
58
|
+
}
|
|
59
|
+
// Check final solution against exact using combined abs + rel tolerance
|
|
60
|
+
const exact = exactSolution(t);
|
|
61
|
+
for (let j = 0; j < neq; j++) {
|
|
62
|
+
const absErr = Math.abs(y[j] - exact[j]);
|
|
63
|
+
const allowed = checkTol * Math.abs(exact[j]) + checkAbsTol;
|
|
64
|
+
expect(absErr).toBeLessThan(allowed);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
68
|
+
const nonStiffProbs = corrProbs.filter((p) => p.odes.name.startsWith('non-stiff'));
|
|
69
|
+
const stiffProbs = corrProbs.filter((p) => p.odes.name.startsWith('stiff'));
|
|
70
|
+
describe('LSODA LIB', () => {
|
|
71
|
+
it('should solve Robertson chemical kinetics with expected accuracy', () => {
|
|
72
|
+
const neq = 3;
|
|
73
|
+
const rtol = new Float64Array([1.0e-4, 1.0e-4, 1.0e-4]);
|
|
74
|
+
const atol = new Float64Array([1.0e-6, 1.0e-10, 1.0e-6]);
|
|
75
|
+
const solver = new Lsoda(fex, neq, {
|
|
76
|
+
rtol,
|
|
77
|
+
atol,
|
|
78
|
+
itask: 1,
|
|
79
|
+
});
|
|
80
|
+
let y = [1.0, 0.0, 0.0];
|
|
81
|
+
let t = 0.0;
|
|
82
|
+
let tout = 0.4;
|
|
83
|
+
for (let iout = 0; iout < 12; iout++) {
|
|
84
|
+
const result = solver.solve(y, t, tout);
|
|
85
|
+
y = result.y;
|
|
86
|
+
t = result.t;
|
|
87
|
+
expect(solver.state).toBeGreaterThan(0);
|
|
88
|
+
const exp = expected[iout];
|
|
89
|
+
for (let j = 0; j < 3; j++) {
|
|
90
|
+
const err = relError(y[j], exp.y[j]);
|
|
91
|
+
expect(err).toBeLessThanOrEqual(5e-3);
|
|
92
|
+
}
|
|
93
|
+
tout *= 10.0;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
describe('non-stiff problems', () => {
|
|
97
|
+
nonStiffProbs.forEach(({ odes, exact }) => {
|
|
98
|
+
it(`should solve ${odes.name} problem`, () => {
|
|
99
|
+
solveAndCheck(odes, exact);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
});
|
|
103
|
+
describe('stiff problems', () => {
|
|
104
|
+
stiffProbs.forEach(({ odes, exact }) => {
|
|
105
|
+
it(`should solve ${odes.name} problem`, () => {
|
|
106
|
+
solveAndCheck(odes, exact);
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
// ─── Dense output tests ───────────────────────────────────────────────────
|
|
111
|
+
describe('dense output', () => {
|
|
112
|
+
// Helper: run Robertson to completion with dense output enabled
|
|
113
|
+
function solveRobertsonDense() {
|
|
114
|
+
const neq = 3;
|
|
115
|
+
const rtol = new Float64Array([1.0e-4, 1.0e-4, 1.0e-4]);
|
|
116
|
+
const atol = new Float64Array([1.0e-6, 1.0e-10, 1.0e-6]);
|
|
117
|
+
const solver = new Lsoda(fex, neq, { rtol, atol, itask: 1, dense: true });
|
|
118
|
+
let y = [1.0, 0.0, 0.0];
|
|
119
|
+
let t = 0.0;
|
|
120
|
+
let tout = 0.4;
|
|
121
|
+
for (let iout = 0; iout < 12; iout++) {
|
|
122
|
+
const result = solver.solve(y, t, tout);
|
|
123
|
+
y = result.y;
|
|
124
|
+
t = result.t;
|
|
125
|
+
tout *= 10.0;
|
|
126
|
+
}
|
|
127
|
+
return solver.getDenseOutput();
|
|
128
|
+
}
|
|
129
|
+
it('consistency: dense output should match direct solver results (Robertson)', () => {
|
|
130
|
+
const dense = solveRobertsonDense();
|
|
131
|
+
// Query dense output at the same tout points used in the reference test
|
|
132
|
+
const tPoints = Float64Array.from(expected.map((e) => e.t));
|
|
133
|
+
const results = dense.solveAtTimes(tPoints);
|
|
134
|
+
for (let i = 0; i < expected.length; i++) {
|
|
135
|
+
for (let j = 0; j < 3; j++) {
|
|
136
|
+
const err = relError(results[j][i], expected[i].y[j]);
|
|
137
|
+
expect(err).toBeLessThanOrEqual(5e-3);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
it('monotonicity: uniform query should have increasing t and finite y', () => {
|
|
142
|
+
const dense = solveRobertsonDense();
|
|
143
|
+
// Query at 1000 uniform points across the full range
|
|
144
|
+
const tMin = dense.tMin;
|
|
145
|
+
const tMax = dense.tMax;
|
|
146
|
+
const n = 1000;
|
|
147
|
+
const tArray = new Float64Array(n);
|
|
148
|
+
for (let i = 0; i < n; i++)
|
|
149
|
+
tArray[i] = tMin + (tMax - tMin) * i / (n - 1);
|
|
150
|
+
const results = dense.solveAtTimes(tArray);
|
|
151
|
+
expect(results.length).toBe(3);
|
|
152
|
+
for (let j = 0; j < 3; j++)
|
|
153
|
+
expect(results[j].length).toBe(n);
|
|
154
|
+
for (let i = 1; i < n; i++)
|
|
155
|
+
expect(tArray[i]).toBeGreaterThan(tArray[i - 1]);
|
|
156
|
+
for (let i = 0; i < n; i++) {
|
|
157
|
+
for (let j = 0; j < 3; j++)
|
|
158
|
+
expect(Number.isFinite(results[j][i])).toBe(true);
|
|
159
|
+
}
|
|
160
|
+
});
|
|
161
|
+
it('boundary: query at tMin and tMax should succeed', () => {
|
|
162
|
+
const dense = solveRobertsonDense();
|
|
163
|
+
const yMin = dense.evaluateAt(dense.tMin);
|
|
164
|
+
const yMax = dense.evaluateAt(dense.tMax);
|
|
165
|
+
expect(yMin.length).toBe(3);
|
|
166
|
+
expect(yMax.length).toBe(3);
|
|
167
|
+
for (let j = 0; j < 3; j++) {
|
|
168
|
+
expect(Number.isFinite(yMin[j])).toBe(true);
|
|
169
|
+
expect(Number.isFinite(yMax[j])).toBe(true);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
it('solveOnGrid: should return correct grid dimensions and quality', () => {
|
|
173
|
+
const dense = solveRobertsonDense();
|
|
174
|
+
const { t, y } = dense.solveOnGrid(0, 4e10, 1e9);
|
|
175
|
+
expect(t.length).toBe(41);
|
|
176
|
+
expect(y.length).toBe(3);
|
|
177
|
+
for (let j = 0; j < 3; j++)
|
|
178
|
+
expect(y[j].length).toBe(41);
|
|
179
|
+
expect(t[0]).toBeCloseTo(0, 10);
|
|
180
|
+
expect(t[t.length - 1]).toBeCloseTo(4e10, 0);
|
|
181
|
+
// Check solution quality at a sampled point (t=4e10)
|
|
182
|
+
const refLast = expected[expected.length - 1].y;
|
|
183
|
+
for (let j = 0; j < 3; j++) {
|
|
184
|
+
const err = relError(y[j][t.length - 1], refLast[j]);
|
|
185
|
+
expect(err).toBeLessThanOrEqual(5e-3);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
function denseExactCheck(ode, exactSolution, checkTol = 1e-3, checkAbsTol = 1e-6) {
|
|
189
|
+
const neq = ode.initial.length;
|
|
190
|
+
const tol = ode.tolerance;
|
|
191
|
+
const rtol = new Float64Array(neq).fill(tol);
|
|
192
|
+
const atol = new Float64Array(neq).fill(tol);
|
|
193
|
+
const solver = new Lsoda(wrapFunc(ode), neq, { rtol, atol, itask: 1, dense: true });
|
|
194
|
+
let y = [...ode.initial];
|
|
195
|
+
let t = ode.arg.start;
|
|
196
|
+
const finish = ode.arg.finish;
|
|
197
|
+
const result = solver.solve(y, t, finish);
|
|
198
|
+
y = result.y;
|
|
199
|
+
t = result.t;
|
|
200
|
+
const dense = solver.getDenseOutput();
|
|
201
|
+
// Query at 100 points and compare against exact solution
|
|
202
|
+
const nPts = 100;
|
|
203
|
+
const tArr = new Float64Array(nPts + 1);
|
|
204
|
+
for (let i = 0; i <= nPts; i++)
|
|
205
|
+
tArr[i] = ode.arg.start + (finish - ode.arg.start) * i / nPts;
|
|
206
|
+
const results = dense.solveAtTimes(tArr);
|
|
207
|
+
for (let i = 0; i < tArr.length; i++) {
|
|
208
|
+
const exact = exactSolution(tArr[i]);
|
|
209
|
+
for (let j = 0; j < neq; j++) {
|
|
210
|
+
const absErr = Math.abs(results[j][i] - exact[j]);
|
|
211
|
+
const allowed = checkTol * Math.abs(exact[j]) + checkAbsTol;
|
|
212
|
+
expect(absErr).toBeLessThan(allowed);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
corrProbs.forEach(({ odes, exact }) => {
|
|
217
|
+
it(`dense output on ${odes.name} problem should match exact solution`, () => {
|
|
218
|
+
denseExactCheck(odes, exact);
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
//# sourceMappingURL=lsoda-lib-correctness.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lsoda-lib-correctness.test.js","sourceRoot":"","sources":["lsoda-lib-correctness.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import {Lsoda} from '../solver-tools/lsoda';
|
|
2
|
+
import {ODEs} from '../../index';
|
|
3
|
+
import {corrProbs} from '../examples/corr-probs';
|
|
4
|
+
|
|
5
|
+
// ─── Robertson chemical kinetics (original test) ────────────────────────────
|
|
6
|
+
|
|
7
|
+
function fex(t: number, y: Float64Array, ydot: Float64Array): number {
|
|
8
|
+
ydot[0] = 1.0e4 * y[1] * y[2] - 0.04 * y[0];
|
|
9
|
+
ydot[2] = 3.0e7 * y[1] * y[1];
|
|
10
|
+
ydot[1] = -(ydot[0] + ydot[2]);
|
|
11
|
+
return 0;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const expected = [
|
|
15
|
+
{t: 4.0000e-01, y: [9.851712e-01, 3.386380e-05, 1.479493e-02]},
|
|
16
|
+
{t: 4.0000e+00, y: [9.055333e-01, 2.240655e-05, 9.444430e-02]},
|
|
17
|
+
{t: 4.0000e+01, y: [7.158403e-01, 9.186334e-06, 2.841505e-01]},
|
|
18
|
+
{t: 4.0000e+02, y: [4.505250e-01, 3.222964e-06, 5.494717e-01]},
|
|
19
|
+
{t: 4.0000e+03, y: [1.831976e-01, 8.941773e-07, 8.168015e-01]},
|
|
20
|
+
{t: 4.0000e+04, y: [3.898729e-02, 1.621940e-07, 9.610125e-01]},
|
|
21
|
+
{t: 4.0000e+05, y: [4.936362e-03, 1.984221e-08, 9.950636e-01]},
|
|
22
|
+
{t: 4.0000e+06, y: [5.161833e-04, 2.065787e-09, 9.994838e-01]},
|
|
23
|
+
{t: 4.0000e+07, y: [5.179804e-05, 2.072027e-10, 9.999482e-01]},
|
|
24
|
+
{t: 4.0000e+08, y: [5.283675e-06, 2.113481e-11, 9.999947e-01]},
|
|
25
|
+
{t: 4.0000e+09, y: [4.658667e-07, 1.863468e-12, 9.999995e-01]},
|
|
26
|
+
{t: 4.0000e+10, y: [1.431100e-08, 5.724404e-14, 1.000000e+00]},
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
30
|
+
|
|
31
|
+
function relError(computed: number, reference: number): number {
|
|
32
|
+
if (reference === 0) return Math.abs(computed);
|
|
33
|
+
return Math.abs((computed - reference) / reference);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Wraps an ODEs func into an OdeFunction compatible with Lsoda (returns 0). */
|
|
37
|
+
function wrapFunc(ode: ODEs): (t: number, y: Float64Array, ydot: Float64Array) => number {
|
|
38
|
+
return (t, y, ydot) => {
|
|
39
|
+
ode.func(t, y, ydot);
|
|
40
|
+
return 0;
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Solves an ODE problem and checks against the exact solution at the final time point.
|
|
46
|
+
* Uses combined absolute + relative tolerance: |computed - exact| < checkTol * |exact| + checkAbsTol.
|
|
47
|
+
*/
|
|
48
|
+
function solveAndCheck(
|
|
49
|
+
ode: ODEs,
|
|
50
|
+
exactSolution: (t: number) => Float64Array,
|
|
51
|
+
checkTol: number = 1e-3,
|
|
52
|
+
checkAbsTol: number = 1e-6,
|
|
53
|
+
) {
|
|
54
|
+
const neq = ode.initial.length;
|
|
55
|
+
const tol = ode.tolerance;
|
|
56
|
+
const rtol = new Float64Array(neq).fill(tol);
|
|
57
|
+
const atol = new Float64Array(neq).fill(tol);
|
|
58
|
+
|
|
59
|
+
const solver = new Lsoda(wrapFunc(ode), neq, {rtol, atol, itask: 1});
|
|
60
|
+
|
|
61
|
+
let y: ArrayLike<number> = [...ode.initial];
|
|
62
|
+
let t = ode.arg.start;
|
|
63
|
+
const step = ode.arg.step;
|
|
64
|
+
const finish = ode.arg.finish;
|
|
65
|
+
|
|
66
|
+
let tout = t + step;
|
|
67
|
+
while (tout <= finish + step / 2) {
|
|
68
|
+
const result = solver.solve(y, t, tout);
|
|
69
|
+
y = result.y;
|
|
70
|
+
t = result.t;
|
|
71
|
+
|
|
72
|
+
expect(solver.state).toBeGreaterThan(0);
|
|
73
|
+
tout += step;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check final solution against exact using combined abs + rel tolerance
|
|
77
|
+
const exact = exactSolution(t);
|
|
78
|
+
for (let j = 0; j < neq; j++) {
|
|
79
|
+
const absErr = Math.abs(y[j] - exact[j]);
|
|
80
|
+
const allowed = checkTol * Math.abs(exact[j]) + checkAbsTol;
|
|
81
|
+
expect(absErr).toBeLessThan(allowed);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Tests ───────────────────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
const nonStiffProbs = corrProbs.filter((p) => p.odes.name.startsWith('non-stiff'));
|
|
88
|
+
const stiffProbs = corrProbs.filter((p) => p.odes.name.startsWith('stiff'));
|
|
89
|
+
|
|
90
|
+
describe('LSODA LIB', () => {
|
|
91
|
+
it('should solve Robertson chemical kinetics with expected accuracy', () => {
|
|
92
|
+
const neq = 3;
|
|
93
|
+
const rtol = new Float64Array([1.0e-4, 1.0e-4, 1.0e-4]);
|
|
94
|
+
const atol = new Float64Array([1.0e-6, 1.0e-10, 1.0e-6]);
|
|
95
|
+
|
|
96
|
+
const solver = new Lsoda(fex, neq, {
|
|
97
|
+
rtol,
|
|
98
|
+
atol,
|
|
99
|
+
itask: 1,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
let y: ArrayLike<number> = [1.0, 0.0, 0.0];
|
|
103
|
+
let t = 0.0;
|
|
104
|
+
let tout = 0.4;
|
|
105
|
+
|
|
106
|
+
for (let iout = 0; iout < 12; iout++) {
|
|
107
|
+
const result = solver.solve(y, t, tout);
|
|
108
|
+
y = result.y;
|
|
109
|
+
t = result.t;
|
|
110
|
+
|
|
111
|
+
expect(solver.state).toBeGreaterThan(0);
|
|
112
|
+
|
|
113
|
+
const exp = expected[iout];
|
|
114
|
+
for (let j = 0; j < 3; j++) {
|
|
115
|
+
const err = relError(y[j], exp.y[j]);
|
|
116
|
+
expect(err).toBeLessThanOrEqual(5e-3);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
tout *= 10.0;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
describe('non-stiff problems', () => {
|
|
124
|
+
nonStiffProbs.forEach(({odes, exact}) => {
|
|
125
|
+
it(`should solve ${odes.name} problem`, () => {
|
|
126
|
+
solveAndCheck(odes, exact);
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe('stiff problems', () => {
|
|
132
|
+
stiffProbs.forEach(({odes, exact}) => {
|
|
133
|
+
it(`should solve ${odes.name} problem`, () => {
|
|
134
|
+
solveAndCheck(odes, exact);
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// ─── Dense output tests ───────────────────────────────────────────────────
|
|
140
|
+
|
|
141
|
+
describe('dense output', () => {
|
|
142
|
+
// Helper: run Robertson to completion with dense output enabled
|
|
143
|
+
function solveRobertsonDense() {
|
|
144
|
+
const neq = 3;
|
|
145
|
+
const rtol = new Float64Array([1.0e-4, 1.0e-4, 1.0e-4]);
|
|
146
|
+
const atol = new Float64Array([1.0e-6, 1.0e-10, 1.0e-6]);
|
|
147
|
+
|
|
148
|
+
const solver = new Lsoda(fex, neq, {rtol, atol, itask: 1, dense: true});
|
|
149
|
+
|
|
150
|
+
let y: ArrayLike<number> = [1.0, 0.0, 0.0];
|
|
151
|
+
let t = 0.0;
|
|
152
|
+
let tout = 0.4;
|
|
153
|
+
|
|
154
|
+
for (let iout = 0; iout < 12; iout++) {
|
|
155
|
+
const result = solver.solve(y, t, tout);
|
|
156
|
+
y = result.y;
|
|
157
|
+
t = result.t;
|
|
158
|
+
tout *= 10.0;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return solver.getDenseOutput();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
it('consistency: dense output should match direct solver results (Robertson)', () => {
|
|
165
|
+
const dense = solveRobertsonDense();
|
|
166
|
+
|
|
167
|
+
// Query dense output at the same tout points used in the reference test
|
|
168
|
+
const tPoints = Float64Array.from(expected.map((e) => e.t));
|
|
169
|
+
const results = dense.solveAtTimes(tPoints);
|
|
170
|
+
|
|
171
|
+
for (let i = 0; i < expected.length; i++) {
|
|
172
|
+
for (let j = 0; j < 3; j++) {
|
|
173
|
+
const err = relError(results[j][i], expected[i].y[j]);
|
|
174
|
+
expect(err).toBeLessThanOrEqual(5e-3);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it('monotonicity: uniform query should have increasing t and finite y', () => {
|
|
180
|
+
const dense = solveRobertsonDense();
|
|
181
|
+
|
|
182
|
+
// Query at 1000 uniform points across the full range
|
|
183
|
+
const tMin = dense.tMin;
|
|
184
|
+
const tMax = dense.tMax;
|
|
185
|
+
const n = 1000;
|
|
186
|
+
const tArray = new Float64Array(n);
|
|
187
|
+
for (let i = 0; i < n; i++)
|
|
188
|
+
tArray[i] = tMin + (tMax - tMin) * i / (n - 1);
|
|
189
|
+
|
|
190
|
+
const results = dense.solveAtTimes(tArray);
|
|
191
|
+
expect(results.length).toBe(3);
|
|
192
|
+
for (let j = 0; j < 3; j++)
|
|
193
|
+
expect(results[j].length).toBe(n);
|
|
194
|
+
|
|
195
|
+
for (let i = 1; i < n; i++)
|
|
196
|
+
expect(tArray[i]).toBeGreaterThan(tArray[i - 1]);
|
|
197
|
+
|
|
198
|
+
for (let i = 0; i < n; i++) {
|
|
199
|
+
for (let j = 0; j < 3; j++)
|
|
200
|
+
expect(Number.isFinite(results[j][i])).toBe(true);
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('boundary: query at tMin and tMax should succeed', () => {
|
|
205
|
+
const dense = solveRobertsonDense();
|
|
206
|
+
|
|
207
|
+
const yMin = dense.evaluateAt(dense.tMin);
|
|
208
|
+
const yMax = dense.evaluateAt(dense.tMax);
|
|
209
|
+
|
|
210
|
+
expect(yMin.length).toBe(3);
|
|
211
|
+
expect(yMax.length).toBe(3);
|
|
212
|
+
for (let j = 0; j < 3; j++) {
|
|
213
|
+
expect(Number.isFinite(yMin[j])).toBe(true);
|
|
214
|
+
expect(Number.isFinite(yMax[j])).toBe(true);
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it('solveOnGrid: should return correct grid dimensions and quality', () => {
|
|
219
|
+
const dense = solveRobertsonDense();
|
|
220
|
+
|
|
221
|
+
const {t, y} = dense.solveOnGrid(0, 4e10, 1e9);
|
|
222
|
+
|
|
223
|
+
expect(t.length).toBe(41);
|
|
224
|
+
expect(y.length).toBe(3);
|
|
225
|
+
for (let j = 0; j < 3; j++)
|
|
226
|
+
expect(y[j].length).toBe(41);
|
|
227
|
+
expect(t[0]).toBeCloseTo(0, 10);
|
|
228
|
+
expect(t[t.length - 1]).toBeCloseTo(4e10, 0);
|
|
229
|
+
|
|
230
|
+
// Check solution quality at a sampled point (t=4e10)
|
|
231
|
+
const refLast = expected[expected.length - 1].y;
|
|
232
|
+
for (let j = 0; j < 3; j++) {
|
|
233
|
+
const err = relError(y[j][t.length - 1], refLast[j]);
|
|
234
|
+
expect(err).toBeLessThanOrEqual(5e-3);
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
function denseExactCheck(
|
|
239
|
+
ode: ODEs,
|
|
240
|
+
exactSolution: (t: number) => Float64Array,
|
|
241
|
+
checkTol: number = 1e-3,
|
|
242
|
+
checkAbsTol: number = 1e-6,
|
|
243
|
+
) {
|
|
244
|
+
const neq = ode.initial.length;
|
|
245
|
+
const tol = ode.tolerance;
|
|
246
|
+
const rtol = new Float64Array(neq).fill(tol);
|
|
247
|
+
const atol = new Float64Array(neq).fill(tol);
|
|
248
|
+
|
|
249
|
+
const solver = new Lsoda(wrapFunc(ode), neq, {rtol, atol, itask: 1, dense: true});
|
|
250
|
+
|
|
251
|
+
let y: ArrayLike<number> = [...ode.initial];
|
|
252
|
+
let t = ode.arg.start;
|
|
253
|
+
const finish = ode.arg.finish;
|
|
254
|
+
const result = solver.solve(y, t, finish);
|
|
255
|
+
y = result.y;
|
|
256
|
+
t = result.t;
|
|
257
|
+
|
|
258
|
+
const dense = solver.getDenseOutput();
|
|
259
|
+
|
|
260
|
+
// Query at 100 points and compare against exact solution
|
|
261
|
+
const nPts = 100;
|
|
262
|
+
const tArr = new Float64Array(nPts + 1);
|
|
263
|
+
for (let i = 0; i <= nPts; i++)
|
|
264
|
+
tArr[i] = ode.arg.start + (finish - ode.arg.start) * i / nPts;
|
|
265
|
+
|
|
266
|
+
const results = dense.solveAtTimes(tArr);
|
|
267
|
+
for (let i = 0; i < tArr.length; i++) {
|
|
268
|
+
const exact = exactSolution(tArr[i]);
|
|
269
|
+
for (let j = 0; j < neq; j++) {
|
|
270
|
+
const absErr = Math.abs(results[j][i] - exact[j]);
|
|
271
|
+
const allowed = checkTol * Math.abs(exact[j]) + checkAbsTol;
|
|
272
|
+
expect(absErr).toBeLessThan(allowed);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
corrProbs.forEach(({odes, exact}) => {
|
|
278
|
+
it(`dense output on ${odes.name} problem should match exact solution`, () => {
|
|
279
|
+
denseExactCheck(odes, exact);
|
|
280
|
+
});
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|