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.
Files changed (226) hide show
  1. package/.claude/settings.local.json +11 -0
  2. package/.claude/skills/license-updater/SKILL.md +149 -0
  3. package/.claude/skills/license-updater/references/compatibility.md +68 -0
  4. package/.claude/skills/license-updater/references/templates.md +151 -0
  5. package/.claude/skills/license-updater/scripts/generate_license_files.py +467 -0
  6. package/CLAUDE.md +104 -0
  7. package/LICENSE +1 -1
  8. package/README.MD +121 -27
  9. package/THIRD_PARTY_LICENSES +135 -0
  10. package/docs/assets/hierarchy.js +1 -1
  11. package/docs/assets/navigation.js +1 -1
  12. package/docs/assets/search.js +1 -1
  13. package/docs/classes/BasicModelPipelineCreator.html +4 -4
  14. package/docs/classes/Callback.html +5 -5
  15. package/docs/classes/CyclicModelPipelineCreator.html +4 -4
  16. package/docs/classes/IterCheckerCallback.html +5 -5
  17. package/docs/classes/PipelineCreator.html +4 -4
  18. package/docs/classes/TimeCheckerCallback.html +5 -5
  19. package/docs/classes/UpdatesModelPipelineCreator.html +4 -4
  20. package/docs/functions/ab4.html +8 -0
  21. package/docs/functions/ab5.html +8 -0
  22. package/docs/functions/applyPipeline.html +2 -2
  23. package/docs/functions/getCallback.html +2 -2
  24. package/docs/functions/getIVP.html +2 -2
  25. package/docs/functions/getInputVector.html +2 -2
  26. package/docs/functions/getIvp2WebWorker.html +2 -2
  27. package/docs/functions/getJScode.html +2 -2
  28. package/docs/functions/getOutputNames.html +2 -2
  29. package/docs/functions/getPipelineCreator.html +2 -2
  30. package/docs/functions/lsoda.html +8 -0
  31. package/docs/functions/mrt.html +2 -2
  32. package/docs/functions/printE5.html +2 -2
  33. package/docs/functions/printHires.html +2 -2
  34. package/docs/functions/printOrego.html +2 -2
  35. package/docs/functions/printPollution.html +2 -2
  36. package/docs/functions/printRobertson.html +2 -2
  37. package/docs/functions/printVdpol.html +2 -2
  38. package/docs/functions/rk3.html +8 -0
  39. package/docs/functions/rk4.html +8 -0
  40. package/docs/functions/rkdp.html +8 -0
  41. package/docs/functions/ros34prw.html +2 -2
  42. package/docs/functions/ros3prw.html +2 -2
  43. package/docs/functions/solveIvp.html +2 -2
  44. package/docs/hierarchy.html +1 -1
  45. package/docs/index.html +100 -15
  46. package/docs/media/E5.png +0 -0
  47. package/docs/media/HIRES.png +0 -0
  48. package/docs/media/OREGO.png +0 -0
  49. package/docs/media/POLL-full.png +0 -0
  50. package/docs/media/ROBER.png +0 -0
  51. package/docs/media/VDP0L.png +0 -0
  52. package/docs/media/cyclic-model.js +3 -1
  53. package/docs/media/cyclic-model.js.map +1 -1
  54. package/docs/media/cyclic-model.ts +3 -1
  55. package/docs/modules.html +1 -1
  56. package/docs/types/Arg.html +7 -7
  57. package/docs/types/DifEqs.html +4 -4
  58. package/docs/types/Func.html +2 -2
  59. package/docs/types/IVP.html +20 -20
  60. package/docs/types/IVP2WebWorker.html +7 -7
  61. package/docs/types/Input.html +4 -4
  62. package/docs/types/Loop.html +4 -4
  63. package/docs/types/ODEs.html +8 -8
  64. package/docs/types/Output.html +4 -4
  65. package/docs/types/Pipeline.html +4 -4
  66. package/docs/types/SolverOptions.html +5 -5
  67. package/docs/types/Update.html +5 -5
  68. package/docs/types/Wrapper.html +5 -5
  69. package/images/E5.png +0 -0
  70. package/images/HIRES.png +0 -0
  71. package/images/OREGO.png +0 -0
  72. package/images/POLL-full.png +0 -0
  73. package/images/ROBER.png +0 -0
  74. package/images/VDP0L.png +0 -0
  75. package/index.js +1 -1
  76. package/index.js.map +1 -1
  77. package/index.ts +2 -1
  78. package/jest.config.js +1 -0
  79. package/package.json +4 -2
  80. package/src/examples/check-methods.js +15 -3
  81. package/src/examples/check-methods.js.map +1 -1
  82. package/src/examples/check-methods.ts +18 -3
  83. package/src/examples/cyclic-model.js +3 -1
  84. package/src/examples/cyclic-model.js.map +1 -1
  85. package/src/examples/cyclic-model.ts +3 -1
  86. package/src/solver-tools/ab4-method.js +332 -0
  87. package/src/solver-tools/ab4-method.js.map +1 -0
  88. package/src/solver-tools/ab4-method.ts +395 -0
  89. package/src/solver-tools/ab5-method.js +334 -0
  90. package/src/solver-tools/ab5-method.js.map +1 -0
  91. package/src/solver-tools/ab5-method.ts +397 -0
  92. package/src/solver-tools/cvode/common.js +487 -0
  93. package/src/solver-tools/cvode/common.js.map +1 -0
  94. package/src/solver-tools/cvode/common.ts +549 -0
  95. package/src/solver-tools/cvode/cvode.js +1062 -0
  96. package/src/solver-tools/cvode/cvode.js.map +1 -0
  97. package/src/solver-tools/cvode/cvode.ts +1226 -0
  98. package/src/solver-tools/cvode/cvode_adams.js +124 -0
  99. package/src/solver-tools/cvode/cvode_adams.js.map +1 -0
  100. package/src/solver-tools/cvode/cvode_adams.ts +142 -0
  101. package/src/solver-tools/cvode/cvode_bdf.js +153 -0
  102. package/src/solver-tools/cvode/cvode_bdf.js.map +1 -0
  103. package/src/solver-tools/cvode/cvode_bdf.ts +164 -0
  104. package/src/solver-tools/cvode/cvode_class.js +158 -0
  105. package/src/solver-tools/cvode/cvode_class.js.map +1 -0
  106. package/src/solver-tools/cvode/cvode_class.ts +271 -0
  107. package/src/solver-tools/cvode/cvode_hin.js +119 -0
  108. package/src/solver-tools/cvode/cvode_hin.js.map +1 -0
  109. package/src/solver-tools/cvode/cvode_hin.ts +150 -0
  110. package/src/solver-tools/cvode/cvode_io.js +203 -0
  111. package/src/solver-tools/cvode/cvode_io.js.map +1 -0
  112. package/src/solver-tools/cvode/cvode_io.ts +258 -0
  113. package/src/solver-tools/cvode/cvode_ls.js +210 -0
  114. package/src/solver-tools/cvode/cvode_ls.js.map +1 -0
  115. package/src/solver-tools/cvode/cvode_ls.ts +249 -0
  116. package/src/solver-tools/cvode/cvode_nls.js +220 -0
  117. package/src/solver-tools/cvode/cvode_nls.js.map +1 -0
  118. package/src/solver-tools/cvode/cvode_nls.ts +259 -0
  119. package/src/solver-tools/cvode/cvode_root.js +415 -0
  120. package/src/solver-tools/cvode/cvode_root.js.map +1 -0
  121. package/src/solver-tools/cvode/cvode_root.ts +503 -0
  122. package/src/solver-tools/cvode/dense_linalg.js +131 -0
  123. package/src/solver-tools/cvode/dense_linalg.js.map +1 -0
  124. package/src/solver-tools/cvode/dense_linalg.ts +144 -0
  125. package/src/solver-tools/cvode/index.js +26 -0
  126. package/src/solver-tools/cvode/index.js.map +1 -0
  127. package/src/solver-tools/cvode/index.ts +64 -0
  128. package/src/solver-tools/cvode-method.js +114 -0
  129. package/src/solver-tools/cvode-method.js.map +1 -0
  130. package/src/solver-tools/cvode-method.ts +129 -0
  131. package/src/solver-tools/index.js +7 -0
  132. package/src/solver-tools/index.js.map +1 -1
  133. package/src/solver-tools/index.ts +7 -0
  134. package/src/solver-tools/lsoda/blas.js +257 -0
  135. package/src/solver-tools/lsoda/blas.js.map +1 -0
  136. package/src/solver-tools/lsoda/blas.ts +279 -0
  137. package/src/solver-tools/lsoda/cfode.js +84 -0
  138. package/src/solver-tools/lsoda/cfode.js.map +1 -0
  139. package/src/solver-tools/lsoda/cfode.ts +95 -0
  140. package/src/solver-tools/lsoda/common.js +94 -0
  141. package/src/solver-tools/lsoda/common.js.map +1 -0
  142. package/src/solver-tools/lsoda/common.ts +135 -0
  143. package/src/solver-tools/lsoda/corfailure.js +36 -0
  144. package/src/solver-tools/lsoda/corfailure.js.map +1 -0
  145. package/src/solver-tools/lsoda/corfailure.ts +42 -0
  146. package/src/solver-tools/lsoda/correction.js +112 -0
  147. package/src/solver-tools/lsoda/correction.js.map +1 -0
  148. package/src/solver-tools/lsoda/correction.ts +131 -0
  149. package/src/solver-tools/lsoda/dense.js +121 -0
  150. package/src/solver-tools/lsoda/dense.js.map +1 -0
  151. package/src/solver-tools/lsoda/dense.ts +141 -0
  152. package/src/solver-tools/lsoda/index.js +15 -0
  153. package/src/solver-tools/lsoda/index.js.map +1 -0
  154. package/src/solver-tools/lsoda/index.ts +17 -0
  155. package/src/solver-tools/lsoda/intdy.js +54 -0
  156. package/src/solver-tools/lsoda/intdy.js.map +1 -0
  157. package/src/solver-tools/lsoda/intdy.ts +62 -0
  158. package/src/solver-tools/lsoda/lsoda.js +578 -0
  159. package/src/solver-tools/lsoda/lsoda.js.map +1 -0
  160. package/src/solver-tools/lsoda/lsoda.ts +644 -0
  161. package/src/solver-tools/lsoda/methodswitch.js +108 -0
  162. package/src/solver-tools/lsoda/methodswitch.js.map +1 -0
  163. package/src/solver-tools/lsoda/methodswitch.ts +119 -0
  164. package/src/solver-tools/lsoda/orderswitch.js +97 -0
  165. package/src/solver-tools/lsoda/orderswitch.js.map +1 -0
  166. package/src/solver-tools/lsoda/orderswitch.ts +107 -0
  167. package/src/solver-tools/lsoda/prja.js +54 -0
  168. package/src/solver-tools/lsoda/prja.js.map +1 -0
  169. package/src/solver-tools/lsoda/prja.ts +61 -0
  170. package/src/solver-tools/lsoda/scaleh.js +41 -0
  171. package/src/solver-tools/lsoda/scaleh.js.map +1 -0
  172. package/src/solver-tools/lsoda/scaleh.ts +45 -0
  173. package/src/solver-tools/lsoda/solsy.js +25 -0
  174. package/src/solver-tools/lsoda/solsy.js.map +1 -0
  175. package/src/solver-tools/lsoda/solsy.ts +30 -0
  176. package/src/solver-tools/lsoda/stoda.js +270 -0
  177. package/src/solver-tools/lsoda/stoda.js.map +1 -0
  178. package/src/solver-tools/lsoda/stoda.ts +298 -0
  179. package/src/solver-tools/lsoda-method.js +134 -0
  180. package/src/solver-tools/lsoda-method.js.map +1 -0
  181. package/src/solver-tools/lsoda-method.ts +154 -0
  182. package/src/solver-tools/mrt-method.js +81 -18
  183. package/src/solver-tools/mrt-method.js.map +1 -1
  184. package/src/solver-tools/mrt-method.ts +111 -21
  185. package/src/solver-tools/rk3-method.js +210 -0
  186. package/src/solver-tools/rk3-method.js.map +1 -0
  187. package/src/solver-tools/rk3-method.ts +257 -0
  188. package/src/solver-tools/rk4-method.js +236 -0
  189. package/src/solver-tools/rk4-method.js.map +1 -0
  190. package/src/solver-tools/rk4-method.ts +286 -0
  191. package/src/solver-tools/rkdp-method.js +247 -0
  192. package/src/solver-tools/rkdp-method.js.map +1 -0
  193. package/src/solver-tools/rkdp-method.ts +300 -0
  194. package/src/solver-tools/ros34prw-method.js +1 -1
  195. package/src/solver-tools/ros34prw-method.js.map +1 -1
  196. package/src/solver-tools/ros34prw-method.ts +1 -1
  197. package/src/solver-tools/ros3prw-method.js +1 -1
  198. package/src/solver-tools/ros3prw-method.js.map +1 -1
  199. package/src/solver-tools/ros3prw-method.ts +1 -1
  200. package/src/solver-tools/solver-defs.js +7 -0
  201. package/src/solver-tools/solver-defs.js.map +1 -1
  202. package/src/solver-tools/solver-defs.ts +7 -0
  203. package/src/tests/correctness.test.js +6 -4
  204. package/src/tests/correctness.test.js.map +1 -1
  205. package/src/tests/correctness.test.ts +6 -4
  206. package/src/tests/cvode-lib-correctness.test.js +404 -0
  207. package/src/tests/cvode-lib-correctness.test.js.map +1 -0
  208. package/src/tests/cvode-lib-correctness.test.ts +505 -0
  209. package/src/tests/cvode-lib-performance.test.js +103 -0
  210. package/src/tests/cvode-lib-performance.test.js.map +1 -0
  211. package/src/tests/cvode-lib-performance.test.ts +128 -0
  212. package/src/tests/lsoda-lib-correctness.test.js +223 -0
  213. package/src/tests/lsoda-lib-correctness.test.js.map +1 -0
  214. package/src/tests/lsoda-lib-correctness.test.ts +283 -0
  215. package/src/tests/lsoda-lib-performance.test.js +95 -0
  216. package/src/tests/lsoda-lib-performance.test.js.map +1 -0
  217. package/src/tests/lsoda-lib-performance.test.ts +126 -0
  218. package/src/tests/performance.test.js +12 -8
  219. package/src/tests/performance.test.js.map +1 -1
  220. package/src/tests/performance.test.ts +12 -8
  221. package/src/tests/test-defs.js +20 -1
  222. package/src/tests/test-defs.js.map +1 -1
  223. package/src/tests/test-defs.ts +22 -1
  224. package/src/worker-tools/solving.js +15 -1
  225. package/src/worker-tools/solving.js.map +1 -1
  226. package/src/worker-tools/solving.ts +23 -1
@@ -0,0 +1,644 @@
1
+ /**
2
+ * TypeScript port of liblsoda (https://github.com/sdwfrost/liblsoda)
3
+ * Original LSODA algorithm by Linda Petzold & Alan Hindmarsh (1983)
4
+ *
5
+ * Copyright (c) 2011 Yu Feng, McWilliam Cosmology Center, Carnegie Mellon University
6
+ * Copyright (c) 2016 Simon Frost
7
+ * Copyright (c) 2026 Datagrok
8
+ *
9
+ * Licensed under the MIT License. See THIRD_PARTY_LICENSES for the full
10
+ * license text and provenance chain of the original code.
11
+ */
12
+
13
+ import {
14
+ LsodaContext, LsodaCommon, LsodaOpt, LsodaFunc, NordsieckSnapshot, ETA,
15
+ } from './common';
16
+ import {vmnorm} from './blas';
17
+ import {intdy} from './intdy';
18
+ import {stoda} from './stoda';
19
+ import {DenseOutput} from './dense';
20
+
21
+ // ---- Internal helpers ----
22
+
23
+ function captureSnapshot(c: LsodaCommon, neq: number): NordsieckSnapshot {
24
+ const nq = c.nq;
25
+ const yh: Float64Array[] = new Array(nq + 1);
26
+ for (let j = 0; j <= nq; j++) {
27
+ const row = new Float64Array(neq);
28
+ for (let i = 0; i < neq; i++)
29
+ row[i] = c.yh[j + 1][i + 1]; // convert 1-based to 0-based
30
+ yh[j] = row;
31
+ }
32
+ return {tn: c.tn, h: c.h, hu: c.hu, nq, yh};
33
+ }
34
+
35
+ function ewset(ctx: LsodaContext, ycur: Float64Array): void {
36
+ const c = ctx.common!;
37
+ const neq = ctx.neq;
38
+ const rtol = ctx.opt!.rtol;
39
+ const atol = ctx.opt!.atol;
40
+ for (let i = 1; i <= neq; i++)
41
+ c.ewt[i] = rtol[i] * Math.abs(ycur[i]) + atol[i];
42
+ for (let i = 1; i <= neq; i++)
43
+ c.ewt[i] = 1. / c.ewt[i];
44
+ }
45
+
46
+ function checkOpt(ctx: LsodaContext, opt: LsodaOpt): boolean {
47
+ const mxstp0 = 500;
48
+ const mord = [0, 12, 5];
49
+
50
+ if (ctx.state === 0) ctx.state = 1;
51
+ if (ctx.state === 1) {
52
+ opt.h0 = 0.;
53
+ opt.mxordn = mord[1];
54
+ opt.mxords = mord[2];
55
+ }
56
+
57
+ if (ctx.neq <= 0) {
58
+ ctx.error = `[lsoda] neq = ${ctx.neq} is less than 1`;
59
+ return false;
60
+ }
61
+
62
+ // Check rtol and atol (1-indexed)
63
+ if (ctx.state === 1 || ctx.state === 3) {
64
+ for (let i = 1; i <= ctx.neq; i++) {
65
+ if (opt.rtol[i] < 0.) {
66
+ ctx.error = `[lsoda] rtol = ${opt.rtol[i]} is less than 0.`;
67
+ return false;
68
+ }
69
+ if (opt.atol[i] < 0.) {
70
+ ctx.error = `[lsoda] atol = ${opt.atol[i]} is less than 0.`;
71
+ return false;
72
+ }
73
+ }
74
+ }
75
+
76
+ if (opt.itask === 0) opt.itask = 1;
77
+ if (opt.itask < 1 || opt.itask > 5) {
78
+ ctx.error = `[lsoda] illegal itask = ${opt.itask}`;
79
+ return false;
80
+ }
81
+ if (opt.ixpr < 0 || opt.ixpr > 1) {
82
+ ctx.error = `[lsoda] ixpr = ${opt.ixpr} is illegal`;
83
+ return false;
84
+ }
85
+ if (opt.mxstep < 0) {
86
+ ctx.error = '[lsoda] mxstep < 0';
87
+ return false;
88
+ }
89
+ if (opt.mxstep === 0) opt.mxstep = mxstp0;
90
+ if (opt.mxhnil < 0) {
91
+ ctx.error = '[lsoda] mxhnil < 0';
92
+ return false;
93
+ }
94
+
95
+ if (ctx.state === 1) {
96
+ if (opt.mxordn < 0) {
97
+ ctx.error = `[lsoda] mxordn = ${opt.mxordn} is less than 0`;
98
+ return false;
99
+ }
100
+ if (opt.mxordn === 0) opt.mxordn = 100;
101
+ opt.mxordn = Math.min(opt.mxordn, mord[1]);
102
+ if (opt.mxords < 0) {
103
+ ctx.error = `[lsoda] mxords = ${opt.mxords} is less than 0`;
104
+ return false;
105
+ }
106
+ if (opt.mxords === 0) opt.mxords = 100;
107
+ opt.mxords = Math.min(opt.mxords, mord[2]);
108
+ }
109
+
110
+ if (opt.hmax < 0.) {
111
+ ctx.error = '[lsoda] hmax < 0.';
112
+ return false;
113
+ }
114
+ opt.hmxi = 0.;
115
+ if (opt.hmax > 0)
116
+ opt.hmxi = 1. / opt.hmax;
117
+ if (opt.hmin < 0.) {
118
+ ctx.error = '[lsoda] hmin < 0.';
119
+ return false;
120
+ }
121
+ return true;
122
+ }
123
+
124
+ function allocMem(ctx: LsodaContext): boolean {
125
+ const c = ctx.common!;
126
+ const nyh = ctx.neq;
127
+ const lenyh = 1 + Math.max(ctx.opt!.mxordn, ctx.opt!.mxords);
128
+
129
+ // yh: array of (lenyh+1) Float64Arrays, each of size (nyh+1)
130
+ c.yh = new Array(lenyh + 1);
131
+ for (let i = 0; i <= lenyh; i++)
132
+ c.yh[i] = new Float64Array(nyh + 1);
133
+
134
+ // wm: array of (nyh+1) Float64Arrays, each of size (nyh+1)
135
+ c.wm = new Array(nyh + 1);
136
+ for (let i = 0; i <= nyh; i++)
137
+ c.wm[i] = new Float64Array(nyh + 1);
138
+
139
+ c.ewt = new Float64Array(nyh + 1);
140
+ c.savf = new Float64Array(nyh + 1);
141
+ c.acor = new Float64Array(nyh + 1);
142
+ c.ipvt = new Int32Array(nyh + 1);
143
+
144
+ // Allocate elco and tesco
145
+ c.elco = new Array(13);
146
+ for (let i = 0; i < 13; i++)
147
+ c.elco[i] = new Float64Array(14);
148
+ c.tesco = new Array(13);
149
+ for (let i = 0; i < 13; i++)
150
+ c.tesco[i] = new Float64Array(4);
151
+
152
+ return true;
153
+ }
154
+
155
+ // ---- Public API ----
156
+
157
+ /**
158
+ * Prepare a context with options. Must be called before lsoda().
159
+ * Returns true on success.
160
+ */
161
+ export function lsodaPrepare(ctx: LsodaContext, opt: LsodaOpt): boolean {
162
+ ctx.common = new LsodaCommon();
163
+ ctx.opt = opt;
164
+ if (!checkOpt(ctx, opt)) return false;
165
+ return allocMem(ctx);
166
+ }
167
+
168
+ /**
169
+ * Reset context for reuse with a different problem.
170
+ * All options are preserved, internal state is zeroed.
171
+ */
172
+ export function lsodaReset(ctx: LsodaContext): void {
173
+ const c = ctx.common!;
174
+ // Zero all scalar state
175
+ c.h = 0; c.hu = 0; c.rc = 0; c.tn = 0; c.tsw = 0; c.pdnorm = 0;
176
+ c.crate = 0; c.hold = 0; c.rmax = 0; c.pdest = 0; c.pdlast = 0;
177
+ c.ialth = 0; c.ipup = 0; c.nslp = 0; c.icount = 0; c.irflag = 0;
178
+ c.imxer = 0; c.illin = 0; c.nhnil = 0; c.nslast = 0; c.jcur = 0;
179
+ c.meth = 0; c.mused = 0; c.nq = 0; c.nst = 0; c.ncf = 0;
180
+ c.nfe = 0; c.nje = 0; c.nqu = 0; c.miter = 0;
181
+ c.el.fill(0);
182
+ // Zero arrays
183
+ for (let i = 0; i < c.yh.length; i++) c.yh[i].fill(0);
184
+ for (let i = 0; i < c.wm.length; i++) c.wm[i].fill(0);
185
+ c.ewt.fill(0); c.savf.fill(0); c.acor.fill(0); c.ipvt.fill(0);
186
+ for (let i = 0; i < c.elco.length; i++) c.elco[i].fill(0);
187
+ for (let i = 0; i < c.tesco.length; i++) c.tesco[i].fill(0);
188
+ ctx.state = 1;
189
+ ctx.error = null;
190
+ }
191
+
192
+ /**
193
+ * Free context resources. In JS/TS, this is mostly a no-op (GC handles it).
194
+ */
195
+ export function lsodaFree(ctx: LsodaContext): void {
196
+ if (ctx.error)
197
+ console.error(`unhandled error message: ${ctx.error}`);
198
+
199
+ ctx.common = null;
200
+ }
201
+
202
+ /**
203
+ * Main solver. Integrates from t towards tout.
204
+ * y: 1-indexed Float64Array (y[0] unused, y[1..neq] are state values)
205
+ * Returns { t, state } — updated time and solver state.
206
+ */
207
+ export function lsoda(
208
+ ctx: LsodaContext, y: Float64Array, tIn: number, tout: number,
209
+ ): { t: number; state: number } {
210
+ const c = ctx.common!;
211
+ const opt = ctx.opt!;
212
+ const neq = ctx.neq;
213
+ let t = tIn;
214
+
215
+ let kflag: number;
216
+ let jstart: number = 0;
217
+ let ihit = 0;
218
+
219
+ // Helper functions matching C macros
220
+ function hardfailure(msg: string): { t: number; state: number } {
221
+ ctx.error = msg;
222
+ ctx.state = -3;
223
+ return {t, state: ctx.state};
224
+ }
225
+
226
+ function softfailure(code: number, msg: string): { t: number; state: number } {
227
+ ctx.error = msg;
228
+ for (let i = 1; i <= neq; i++)
229
+ y[i] = c.yh[1][i];
230
+ t = c.tn;
231
+ ctx.state = code;
232
+ return {t, state: ctx.state};
233
+ }
234
+
235
+ function successreturn(): { t: number; state: number } {
236
+ for (let i = 1; i <= neq; i++)
237
+ y[i] = c.yh[1][i];
238
+ t = c.tn;
239
+ if (itask === 4 || itask === 5) {
240
+ if (ihit)
241
+ t = tcrit;
242
+ }
243
+ ctx.state = 2;
244
+ return {t, state: ctx.state};
245
+ }
246
+
247
+ function intdyreturn(): { t: number; state: number } {
248
+ const iflag = intdy(ctx, tout, 0, y);
249
+ if (iflag !== 0) {
250
+ ctx.error = `[lsoda] trouble from intdy, itask = ${itask}, tout = ${tout}`;
251
+ for (let i = 1; i <= neq; i++)
252
+ y[i] = c.yh[1][i];
253
+ t = c.tn;
254
+ }
255
+ t = tout;
256
+ ctx.state = 2;
257
+ return {t, state: ctx.state};
258
+ }
259
+
260
+ if (c === null)
261
+ return hardfailure('[lsoda] illegal common block did you call lsoda_prepare?');
262
+
263
+
264
+ // Block b: initial/continuation call setup
265
+ let h0: number = 0;
266
+ let tcrit: number = 0;
267
+ const rtol = opt.rtol;
268
+ const atol = opt.atol;
269
+
270
+ if (ctx.state === 1 || ctx.state === 3) {
271
+ h0 = opt.h0;
272
+ if (ctx.state === 1) {
273
+ if ((tout - t) * h0 < 0.)
274
+ return hardfailure(`[lsoda] tout = ${tout} behind t = ${t}. integration direction is given by ${h0}`);
275
+ }
276
+ }
277
+
278
+ const itask = opt.itask;
279
+
280
+ if (ctx.state === 3)
281
+ jstart = -1;
282
+
283
+
284
+ // Block c: initial call only (state == 1)
285
+ if (ctx.state === 1) {
286
+ c.meth = 1;
287
+ c.tn = t;
288
+ c.tsw = t;
289
+
290
+ if (itask === 4 || itask === 5) {
291
+ tcrit = opt.tcrit;
292
+ if ((tcrit - tout) * (tout - t) < 0.)
293
+ return hardfailure('[lsoda] itask = 4 or 5 and tcrit behind tout');
294
+
295
+ if (h0 !== 0. && (t + h0 - tcrit) * h0 > 0.)
296
+ h0 = tcrit - t;
297
+ }
298
+
299
+ jstart = 0;
300
+ c.nq = 1;
301
+
302
+ // Initial call to f
303
+ ctx.func(t, y.subarray(1), c.yh[2].subarray(1), ctx.data);
304
+ c.nfe = 1;
305
+
306
+ // Load initial value vector
307
+ for (let i = 1; i <= neq; i++)
308
+ c.yh[1][i] = y[i];
309
+
310
+ // Load and invert ewt
311
+ ewset(ctx, y);
312
+ for (let i = 1; i <= neq; i++) {
313
+ if (c.ewt[i] <= 0.)
314
+ return hardfailure(`[lsoda] ewt[${i}] = ${c.ewt[i]} <= 0.`);
315
+ }
316
+
317
+ // Compute initial step size h0 if not given
318
+ if (h0 === 0.) {
319
+ const tdist = Math.abs(tout - t);
320
+ const w0 = Math.max(Math.abs(t), Math.abs(tout));
321
+ if (tdist < 2. * ETA * w0)
322
+ return hardfailure('[lsoda] tout too close to t to start integration');
323
+
324
+ let tol = 0.;
325
+ for (let i = 1; i <= neq; i++)
326
+ tol = Math.max(tol, rtol[i]);
327
+ if (tol <= 0.) {
328
+ for (let i = 1; i <= neq; i++) {
329
+ const atoli = atol[i];
330
+ const ayi = Math.abs(y[i]);
331
+ if (ayi !== 0.)
332
+ tol = Math.max(tol, atoli / ayi);
333
+ }
334
+ }
335
+ tol = Math.max(tol, 100. * ETA);
336
+ tol = Math.min(tol, 0.001);
337
+ const sum = vmnorm(neq, c.yh[2], c.ewt);
338
+ const sumSq = 1. / (tol * w0 * w0) + tol * sum * sum;
339
+ h0 = 1. / Math.sqrt(sumSq);
340
+ h0 = Math.min(h0, tdist);
341
+ h0 = h0 * ((tout - t >= 0.) ? 1. : -1.);
342
+ }
343
+
344
+ // Adjust h0 for hmax
345
+ const rh = Math.abs(h0) * opt.hmxi;
346
+ if (rh > 1.) h0 /= rh;
347
+
348
+ // Load h and scale yh[2]
349
+ c.h = h0;
350
+ for (let i = 1; i <= neq; i++)
351
+ c.yh[2][i] *= h0;
352
+ }
353
+
354
+ // Block d: continuation calls (state == 2 or 3)
355
+ if (ctx.state === 2 || ctx.state === 3) {
356
+ jstart = 1;
357
+ c.nslast = c.nst;
358
+
359
+ switch (itask) {
360
+ case 1:
361
+ if ((c.tn - tout) * c.h >= 0.)
362
+ return intdyreturn();
363
+
364
+ break;
365
+ case 2:
366
+ break;
367
+ case 3: {
368
+ const tp = c.tn - c.hu * (1. + 100. * ETA);
369
+ if ((tp - tout) * c.h > 0.)
370
+ return hardfailure(`[lsoda] itask = ${itask} and tout behind tcur - hu`);
371
+
372
+ if ((c.tn - tout) * c.h < 0.) break;
373
+ return successreturn();
374
+ }
375
+ case 4:
376
+ tcrit = opt.tcrit;
377
+ if ((c.tn - tcrit) * c.h > 0.)
378
+ return hardfailure('[lsoda] itask = 4 or 5 and tcrit behind tcur');
379
+
380
+ if ((tcrit - tout) * c.h < 0.)
381
+ return hardfailure('[lsoda] itask = 4 or 5 and tcrit behind tout');
382
+
383
+ if ((c.tn - tout) * c.h >= 0.)
384
+ return intdyreturn();
385
+
386
+ // fall through to case 5 logic
387
+ // eslint-disable-next-line no-fallthrough
388
+ case 5:
389
+ if (itask === 5) {
390
+ tcrit = opt.tcrit;
391
+ if ((c.tn - tcrit) * c.h > 0.)
392
+ return hardfailure('[lsoda] itask = 4 or 5 and tcrit behind tcur');
393
+ }
394
+ {
395
+ const hmx = Math.abs(c.tn) + Math.abs(c.h);
396
+ ihit = Math.abs(c.tn - tcrit) <= (100. * ETA * hmx) ? 1 : 0;
397
+ if (ihit) {
398
+ t = tcrit;
399
+ return successreturn();
400
+ }
401
+ const tnext = c.tn + c.h * (1. + 4. * ETA);
402
+ if ((tnext - tcrit) * c.h <= 0.) break;
403
+ c.h = (tcrit - c.tn) * (1. - 4. * ETA);
404
+ if (ctx.state === 2) jstart = -2;
405
+ }
406
+ break;
407
+ }
408
+ }
409
+
410
+ // Block e: main integration loop
411
+ while (true) {
412
+ if (ctx.state !== 1 || c.nst !== 0) {
413
+ if ((c.nst - c.nslast) >= opt.mxstep)
414
+ return softfailure(-1, `[lsoda] ${opt.mxstep} steps taken before reaching tout`);
415
+
416
+ ewset(ctx, c.yh[1]);
417
+ for (let i = 1; i <= neq; i++) {
418
+ if (c.ewt[i] <= 0.)
419
+ return softfailure(-6, `[lsoda] ewt[${i}] = ${c.ewt[i]} <= 0.`);
420
+ }
421
+ }
422
+
423
+ const tolsf = ETA * vmnorm(neq, c.yh[1], c.ewt);
424
+ if (tolsf > 0.01) {
425
+ const scaled = tolsf * 200.;
426
+ if (c.nst === 0) {
427
+ return hardfailure(
428
+ 'lsoda -- at start of problem, too much accuracy requested' +
429
+ ` for precision of machine, suggested scaling factor = ${scaled}`,
430
+ );
431
+ }
432
+ return softfailure(-2,
433
+ `lsoda -- at t = ${t}, too much accuracy requested` +
434
+ ` for precision of machine, suggested scaling factor = ${scaled}`,
435
+ );
436
+ }
437
+
438
+ if ((c.tn + c.h) === c.tn) {
439
+ c.nhnil++;
440
+ if (c.nhnil <= opt.mxhnil) {
441
+ console.error(`lsoda -- warning..internal t = ${c.tn} and h = ${c.h} are` +
442
+ ' such that t + h = t on the next step');
443
+ if (c.nhnil === opt.mxhnil) {
444
+ console.error(`lsoda -- above warning has been issued ${c.nhnil}` +
445
+ ' times, it will not be issued again for this problem');
446
+ }
447
+ }
448
+ }
449
+
450
+ // Call stoda
451
+ kflag = stoda(ctx, y, jstart);
452
+
453
+ if (kflag === 0) {
454
+ // Block f: successful return from stoda
455
+ // Dense output: capture snapshot
456
+ if (ctx.snapshots)
457
+ ctx.snapshots.push(captureSnapshot(c, neq));
458
+
459
+ jstart = 1;
460
+ if (c.meth !== c.mused) {
461
+ c.tsw = c.tn;
462
+ jstart = -1;
463
+ if (opt.ixpr) {
464
+ if (c.meth === 2) {
465
+ console.error('[lsoda] a switch to the stiff method has occurred' +
466
+ ` at t = ${c.tn}, tentative step size h = ${c.h}, step nst = ${c.nst}`);
467
+ }
468
+ if (c.meth === 1) {
469
+ console.error('[lsoda] a switch to the nonstiff method has occurred' +
470
+ ` at t = ${c.tn}, tentative step size h = ${c.h}, step nst = ${c.nst}`);
471
+ }
472
+ }
473
+ }
474
+
475
+ if (itask === 1) {
476
+ if ((c.tn - tout) * c.h < 0.) continue;
477
+ return intdyreturn();
478
+ }
479
+ if (itask === 2)
480
+ return successreturn();
481
+
482
+ if (itask === 3) {
483
+ if ((c.tn - tout) * c.h >= 0.)
484
+ return successreturn();
485
+
486
+ continue;
487
+ }
488
+ if (itask === 4) {
489
+ tcrit = opt.tcrit;
490
+ if ((c.tn - tout) * c.h >= 0.)
491
+ return intdyreturn();
492
+ else {
493
+ const hmx = Math.abs(c.tn) + Math.abs(c.h);
494
+ ihit = Math.abs(c.tn - tcrit) <= (100. * ETA * hmx) ? 1 : 0;
495
+ if (ihit)
496
+ return successreturn();
497
+
498
+ const tnext = c.tn + c.h * (1. + 4. * ETA);
499
+ if ((tnext - tcrit) * c.h <= 0.) continue;
500
+ c.h = (tcrit - c.tn) * (1. - 4. * ETA);
501
+ jstart = -2;
502
+ continue;
503
+ }
504
+ }
505
+ if (itask === 5) {
506
+ tcrit = opt.tcrit;
507
+ const hmx = Math.abs(c.tn) + Math.abs(c.h);
508
+ ihit = Math.abs(c.tn - tcrit) <= (100. * ETA * hmx) ? 1 : 0;
509
+ return successreturn();
510
+ }
511
+ }
512
+
513
+ // kflag == -1 or -2: error test / convergence failures
514
+ if (kflag === -1 || kflag === -2) {
515
+ let big = 0.;
516
+ c.imxer = 1;
517
+ for (let i = 1; i <= neq; i++) {
518
+ const size = Math.abs(c.acor[i]) * c.ewt[i];
519
+ if (big < size) {
520
+ big = size;
521
+ c.imxer = i;
522
+ }
523
+ }
524
+ if (kflag === -1) {
525
+ return softfailure(-4,
526
+ `lsoda -- at t = ${c.tn} and step size h = ${c.h},` +
527
+ ' the error test failed repeatedly or with abs(h) = hmin',
528
+ );
529
+ }
530
+ if (kflag === -2) {
531
+ return softfailure(-5,
532
+ `lsoda -- at t = ${c.tn} and step size h = ${c.h},` +
533
+ ' the corrector convergence failed repeatedly or with abs(h) = hmin',
534
+ );
535
+ }
536
+ }
537
+ }
538
+ }
539
+
540
+ // ---- High-level convenience API ----
541
+
542
+ /**
543
+ * User-facing ODE function signature (0-based arrays).
544
+ */
545
+ export type OdeFunction = (t: number, y: Float64Array, ydot: Float64Array, data?: any) => number;
546
+
547
+ export interface LsodaSolveResult {
548
+ y: Float64Array;
549
+ t: number;
550
+ }
551
+
552
+ /**
553
+ * High-level LSODA solver class with 0-based public interface.
554
+ */
555
+ export class Lsoda {
556
+ private ctx: LsodaContext;
557
+ private internalY: Float64Array;
558
+ private denseEnabled: boolean = false;
559
+
560
+ constructor(
561
+ f: OdeFunction, neq: number,
562
+ opt?: Partial<LsodaOpt & { dense: boolean }>, data?: any,
563
+ ) {
564
+ // Wrap user's 0-based function to internal 1-based convention
565
+ const wrappedFunc: LsodaFunc = (t, y, ydot, data) => {
566
+ return f(t, y, ydot, data);
567
+ };
568
+
569
+ this.ctx = new LsodaContext(wrappedFunc, neq, data);
570
+ this.internalY = new Float64Array(neq + 1); // 1-indexed
571
+
572
+ // Build full options with defaults
573
+ const fullOpt: LsodaOpt = {
574
+ ixpr: 0, mxstep: 0, mxhnil: 0, mxordn: 0, mxords: 0,
575
+ tcrit: 0, h0: 0, hmax: 0, hmin: 0, hmxi: 0, itask: 1,
576
+ rtol: new Float64Array(neq + 1),
577
+ atol: new Float64Array(neq + 1),
578
+ ...opt,
579
+ };
580
+
581
+ // If rtol/atol provided as partial, make sure they're 1-indexed Float64Arrays
582
+ if (opt?.rtol) {
583
+ if (opt.rtol.length === neq) {
584
+ // User provided 0-based, convert to 1-based
585
+ const r = new Float64Array(neq + 1);
586
+ for (let i = 0; i < neq; i++) r[i + 1] = opt.rtol[i];
587
+ fullOpt.rtol = r;
588
+ } else
589
+ fullOpt.rtol = opt.rtol;
590
+ }
591
+ if (opt?.atol) {
592
+ if (opt.atol.length === neq) {
593
+ const a = new Float64Array(neq + 1);
594
+ for (let i = 0; i < neq; i++) a[i + 1] = opt.atol[i];
595
+ fullOpt.atol = a;
596
+ } else
597
+ fullOpt.atol = opt.atol;
598
+ }
599
+
600
+ lsodaPrepare(this.ctx, fullOpt);
601
+
602
+ if (opt?.dense) {
603
+ this.denseEnabled = true;
604
+ this.ctx.snapshots = [];
605
+ }
606
+ }
607
+
608
+ /**
609
+ * Integrate from current t to tout.
610
+ * y: 0-based array of state values.
611
+ * Returns updated { y, t }.
612
+ */
613
+ solve(y: ArrayLike<number>, t: number, tout: number): LsodaSolveResult {
614
+ const neq = this.ctx.neq;
615
+ // Copy 0-based user y into 1-based internal array
616
+ for (let i = 0; i < neq; i++)
617
+ this.internalY[i + 1] = y[i];
618
+
619
+ const result = lsoda(this.ctx, this.internalY, t, tout);
620
+
621
+ // Copy back to 0-based
622
+ const out = new Float64Array(neq);
623
+ for (let i = 0; i < neq; i++)
624
+ out[i] = this.internalY[i + 1];
625
+
626
+ return {y: out, t: result.t};
627
+ }
628
+
629
+ get state(): number {return this.ctx.state;}
630
+ get error(): string | null {return this.ctx.error;}
631
+
632
+ /** Returns a DenseOutput interpolator from collected snapshots. */
633
+ getDenseOutput(): DenseOutput {
634
+ if (!this.ctx.snapshots || this.ctx.snapshots.length === 0)
635
+ throw new Error('[Lsoda] dense output not enabled or no steps taken — pass { dense: true } in options');
636
+ return new DenseOutput(this.ctx.snapshots, this.ctx.neq);
637
+ }
638
+
639
+ reset(): void {
640
+ lsodaReset(this.ctx);
641
+ if (this.denseEnabled)
642
+ this.ctx.snapshots = [];
643
+ }
644
+ }