diff-grok 1.0.8

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 (136) hide show
  1. package/.eslintrc.json +45 -0
  2. package/.github/scripts/bump-js-api-version.sh +106 -0
  3. package/.github/scripts/check-output.sh +20 -0
  4. package/.github/scripts/iac-cfn-deploy.sh +90 -0
  5. package/.github/scripts/slack_api_request.sh +65 -0
  6. package/.github/scripts/test_package.sh +270 -0
  7. package/.github/workflows/libraries.yaml +305 -0
  8. package/LICENSE +21 -0
  9. package/README.MD +396 -0
  10. package/eslintrc.json +45 -0
  11. package/images/acid-prod.gif +0 -0
  12. package/index.js +6 -0
  13. package/index.js.map +1 -0
  14. package/index.ts +15 -0
  15. package/package.json +45 -0
  16. package/src/examples/basic-use.js +40 -0
  17. package/src/examples/basic-use.js.map +1 -0
  18. package/src/examples/basic-use.ts +44 -0
  19. package/src/examples/check-methods.js +45 -0
  20. package/src/examples/check-methods.js.map +1 -0
  21. package/src/examples/check-methods.ts +62 -0
  22. package/src/examples/corr-probs.js +130 -0
  23. package/src/examples/corr-probs.js.map +1 -0
  24. package/src/examples/corr-probs.ts +151 -0
  25. package/src/examples/cyclic-model.js +100 -0
  26. package/src/examples/cyclic-model.js.map +1 -0
  27. package/src/examples/cyclic-model.ts +110 -0
  28. package/src/examples/e5.js +30 -0
  29. package/src/examples/e5.js.map +1 -0
  30. package/src/examples/e5.ts +29 -0
  31. package/src/examples/hires.js +29 -0
  32. package/src/examples/hires.js.map +1 -0
  33. package/src/examples/hires.ts +29 -0
  34. package/src/examples/index.js +17 -0
  35. package/src/examples/index.js.map +1 -0
  36. package/src/examples/index.ts +18 -0
  37. package/src/examples/model-updates.js +112 -0
  38. package/src/examples/model-updates.js.map +1 -0
  39. package/src/examples/model-updates.ts +122 -0
  40. package/src/examples/orego.js +19 -0
  41. package/src/examples/orego.js.map +1 -0
  42. package/src/examples/orego.ts +19 -0
  43. package/src/examples/pipeline-use.js +87 -0
  44. package/src/examples/pipeline-use.js.map +1 -0
  45. package/src/examples/pipeline-use.ts +98 -0
  46. package/src/examples/pollution.js +110 -0
  47. package/src/examples/pollution.js.map +1 -0
  48. package/src/examples/pollution.ts +110 -0
  49. package/src/examples/robertson.js +14 -0
  50. package/src/examples/robertson.js.map +1 -0
  51. package/src/examples/robertson.ts +13 -0
  52. package/src/examples/scripting.js +31 -0
  53. package/src/examples/scripting.js.map +1 -0
  54. package/src/examples/scripting.ts +36 -0
  55. package/src/examples/vdpol.js +13 -0
  56. package/src/examples/vdpol.js.map +1 -0
  57. package/src/examples/vdpol.ts +12 -0
  58. package/src/pipeline/basic-pipeline-creator.js +21 -0
  59. package/src/pipeline/basic-pipeline-creator.js.map +1 -0
  60. package/src/pipeline/basic-pipeline-creator.ts +24 -0
  61. package/src/pipeline/constants.js +21 -0
  62. package/src/pipeline/constants.js.map +1 -0
  63. package/src/pipeline/constants.ts +21 -0
  64. package/src/pipeline/index.js +8 -0
  65. package/src/pipeline/index.js.map +1 -0
  66. package/src/pipeline/index.ts +7 -0
  67. package/src/pipeline/input.js +45 -0
  68. package/src/pipeline/input.js.map +1 -0
  69. package/src/pipeline/input.ts +55 -0
  70. package/src/pipeline/loops-pipeline-creator.js +107 -0
  71. package/src/pipeline/loops-pipeline-creator.js.map +1 -0
  72. package/src/pipeline/loops-pipeline-creator.ts +138 -0
  73. package/src/pipeline/output.js +146 -0
  74. package/src/pipeline/output.js.map +1 -0
  75. package/src/pipeline/output.ts +196 -0
  76. package/src/pipeline/pipeline-creator.js +10 -0
  77. package/src/pipeline/pipeline-creator.js.map +1 -0
  78. package/src/pipeline/pipeline-creator.ts +16 -0
  79. package/src/pipeline/pipeline.js +71 -0
  80. package/src/pipeline/pipeline.js.map +1 -0
  81. package/src/pipeline/pipeline.ts +104 -0
  82. package/src/pipeline/updates-pipeline-creator.js +98 -0
  83. package/src/pipeline/updates-pipeline-creator.js.map +1 -0
  84. package/src/pipeline/updates-pipeline-creator.ts +125 -0
  85. package/src/scripting-tools/constants.js +53 -0
  86. package/src/scripting-tools/constants.js.map +1 -0
  87. package/src/scripting-tools/constants.ts +51 -0
  88. package/src/scripting-tools/index.js +4 -0
  89. package/src/scripting-tools/index.js.map +1 -0
  90. package/src/scripting-tools/index.ts +7 -0
  91. package/src/scripting-tools/model-error.js +17 -0
  92. package/src/scripting-tools/model-error.js.map +1 -0
  93. package/src/scripting-tools/model-error.ts +19 -0
  94. package/src/scripting-tools/scripting-tools.js +1032 -0
  95. package/src/scripting-tools/scripting-tools.js.map +1 -0
  96. package/src/scripting-tools/scripting-tools.ts +1375 -0
  97. package/src/solver-tools/callbacks/callback-base.js +10 -0
  98. package/src/solver-tools/callbacks/callback-base.js.map +1 -0
  99. package/src/solver-tools/callbacks/callback-base.ts +8 -0
  100. package/src/solver-tools/callbacks/callback-tools.js +14 -0
  101. package/src/solver-tools/callbacks/callback-tools.js.map +1 -0
  102. package/src/solver-tools/callbacks/callback-tools.ts +20 -0
  103. package/src/solver-tools/callbacks/iter-checker-callback.js +16 -0
  104. package/src/solver-tools/callbacks/iter-checker-callback.js.map +1 -0
  105. package/src/solver-tools/callbacks/iter-checker-callback.ts +21 -0
  106. package/src/solver-tools/callbacks/time-checker-callback.js +17 -0
  107. package/src/solver-tools/callbacks/time-checker-callback.js.map +1 -0
  108. package/src/solver-tools/callbacks/time-checker-callback.ts +19 -0
  109. package/src/solver-tools/index.js +6 -0
  110. package/src/solver-tools/index.js.map +1 -0
  111. package/src/solver-tools/index.ts +6 -0
  112. package/src/solver-tools/lin-alg-tools.js +56 -0
  113. package/src/solver-tools/lin-alg-tools.js.map +1 -0
  114. package/src/solver-tools/lin-alg-tools.ts +75 -0
  115. package/src/solver-tools/mrt-method.js +210 -0
  116. package/src/solver-tools/mrt-method.js.map +1 -0
  117. package/src/solver-tools/mrt-method.ts +269 -0
  118. package/src/solver-tools/ros34prw-method.js +257 -0
  119. package/src/solver-tools/ros34prw-method.js.map +1 -0
  120. package/src/solver-tools/ros34prw-method.ts +335 -0
  121. package/src/solver-tools/ros3prw-method.js +233 -0
  122. package/src/solver-tools/ros3prw-method.js.map +1 -0
  123. package/src/solver-tools/ros3prw-method.ts +304 -0
  124. package/src/solver-tools/solver-defs.js +58 -0
  125. package/src/solver-tools/solver-defs.js.map +1 -0
  126. package/src/solver-tools/solver-defs.ts +100 -0
  127. package/src/worker-tools/index.js +3 -0
  128. package/src/worker-tools/index.js.map +1 -0
  129. package/src/worker-tools/index.ts +2 -0
  130. package/src/worker-tools/scripting.js +68 -0
  131. package/src/worker-tools/scripting.js.map +1 -0
  132. package/src/worker-tools/scripting.ts +95 -0
  133. package/src/worker-tools/solving.js +48 -0
  134. package/src/worker-tools/solving.js.map +1 -0
  135. package/src/worker-tools/solving.ts +60 -0
  136. package/tsconfig.json +71 -0
@@ -0,0 +1,1375 @@
1
+ /* eslint-disable max-len */
2
+ /* Scripting tools for the Initial Value Problem (IVP) solver:
3
+ - parser of formulas defining IVP;
4
+ - JS-script generator.
5
+
6
+ The parser processes IVP formulas given in the declarative form (see README.md).
7
+
8
+ JS-script generator creates DATAGROK JavaScript script: annotation & code.
9
+ */
10
+
11
+ import {CONTROL_TAG, CONTROL_TAG_LEN, CONTROL_EXPR, LOOP, UPDATE, MAX_LINE_CHART,
12
+ SOLVER_OPTIONS_RANGES, TINY, STEP_RATIO} from './constants';
13
+
14
+ import {ModelError} from './model-error';
15
+
16
+ // Scripting specific constants
17
+ export const CONTROL_SEP = ':';
18
+ const COMMA = ',';
19
+ const EQUAL_SIGN = '=';
20
+ const DIV_SIGN = '/';
21
+ const SERVICE = '_';
22
+ export const BRACE_OPEN = '{';
23
+ export const BRACE_CLOSE = '}';
24
+ export const BRACKET_OPEN = '[';
25
+ export const BRACKET_CLOSE = ']';
26
+ export const ANNOT_SEPAR = ';';
27
+ const DEFAULT_TOL = '0.00005';
28
+ export const DEFAULT_SOLVER_SETTINGS: string = '{}';
29
+ const COMMENT_SEQ = '//';
30
+ export const STAGE_COL_NAME = `_Stage`;
31
+ const INCEPTION = 'Inception';
32
+
33
+ /** Elementary math tools */
34
+ export const MATH_FUNCS = ['pow', 'sin', 'cos', 'tan', 'asin', 'acos', 'atan', 'sqrt', 'exp', 'log', 'sinh', 'cosh', 'tanh'];
35
+ export const POW_IDX = MATH_FUNCS.indexOf('pow');
36
+ export const MATH_CONSTS = ['PI', 'E'];
37
+
38
+ /** Default meta */
39
+ const defaultMetas = `//meta.runOnOpen: true
40
+ //meta.runOnInput: true
41
+ //meta.features: {"sens-analysis": true, "fitting": true}`;
42
+
43
+ /** Numerical input specification */
44
+ export type Input = {
45
+ value: number,
46
+ annot: string | null,
47
+ };
48
+
49
+ /** Argument of IVP specification */
50
+ type Arg = {
51
+ name: string,
52
+ initial: Input,
53
+ final: Input,
54
+ step: Input,
55
+ updateName: string | undefined,
56
+ };
57
+
58
+ /** Input keys of Arg */
59
+ export const ARG_INPUT_KEYS = ['initial', 'final', 'step'];
60
+
61
+ /** Scripting specific constants */
62
+ export enum SCRIPTING {
63
+ ARG_NAME = 'name',
64
+ COUNT = 'count',
65
+ DURATION = 'duration',
66
+ };
67
+
68
+ /** Differential equations specification */
69
+ type DifEqs = {
70
+ equations: Map<string, string>,
71
+ solutionNames: string[]
72
+ };
73
+
74
+ /** Loop specification */
75
+ type Loop = {
76
+ count: Input,
77
+ updates: string[],
78
+ };
79
+
80
+ /** Update specification */
81
+ type Update = {
82
+ name: string,
83
+ durationFormula: string,
84
+ updates: string[],
85
+ };
86
+
87
+ /** Output specification */
88
+ type Output = {
89
+ caption: string,
90
+ formula: string | null,
91
+ };
92
+
93
+ /** Initial Value Problem (IVP) specification type */
94
+ export type IVP = {
95
+ name: string,
96
+ tags: string | null,
97
+ descr: string | null,
98
+ deqs: DifEqs,
99
+ exprs: Map<string, string> | null,
100
+ arg: Arg,
101
+ inits: Map<string, Input>,
102
+ consts: Map<string, Input> | null,
103
+ params: Map<string, Input> | null,
104
+ tolerance: string,
105
+ usedMathFuncs: number[],
106
+ usedMathConsts: number[],
107
+ loop: Loop | null,
108
+ updates: Update[] | null,
109
+ metas: string[],
110
+ outputs: Map<string, Output> | null,
111
+ solverSettings: string,
112
+ inputsLookup: string | null,
113
+ };
114
+
115
+ /** Help links for model errors */
116
+ enum ERROR_LINK {
117
+ MAIN_DOCS = '/help/compute/diff-studio',
118
+ CORE_BLOCKS = `/help/compute/diff-studio#core-blocks`,
119
+ COMPS_SYNTAX = `/help/compute/diff-studio#model-components-and-syntax`,
120
+ LOOP = `/help/compute/diff-studio#cyclic-processes`,
121
+ UPDATE = `/help/compute/diff-studio#multistage-processes`,
122
+ UI_OPTS = `/help/compute/diff-studio#user-interface-options`,
123
+ LOOP_VS_UPDATE = `/help/compute/diff-studio#advanced-features`,
124
+ SOLVER_CONFIG = `/help/compute/diff-studio#solver-configuration`,
125
+ MODEL_PARAMS = `/help/compute/diff-studio#model-parameters`,
126
+ };
127
+
128
+ /** Specific error messages */
129
+ enum ERROR_MSG {
130
+ CTRL_EXPR = `Unsupported control expression with the tag **"#"**`,
131
+ ARG = `'The **#argument** block must consist of 3 lines specifying initial and final time, and solution grid step.`,
132
+ LOOP = `The **#loop** block must contain at least one line.`,
133
+ COUNT = 'Incorrect loop count',
134
+ LOOP_VS_UPDATE = `The **#loop** and **'#update'** blocks cannot be used simultaneously.`,
135
+ UPDATE_LINES_COUNT = `The **'#update'** block must contain at least one line.`,
136
+ DURATION = 'Incorrect update duration',
137
+ BRACES = ' Missing one of the braces (**{**, **}**).',
138
+ COLON = `Incorrect position of **":"**.`,
139
+ CASE_INSENS = 'Non-unique name (case-insensitive): use different caption for ',
140
+ MISSING_INIT = `Correct the **#inits** block.`,
141
+ UNDEF_NAME = `Model name missing. Specify the model name in the **#name** block.`,
142
+ UNDEF_DEQS = `Differential equation(s) are required for this model. Add equation(s) under the **#equations** block.`,
143
+ UNDEF_INITS = `Initial conditions are required for this model. Add initial conditions under the **#inits** block.`,
144
+ UNDEF_ARG = `Argument specification is required for this model. Specify an argument, its range, and a grid step in the **#argument** block.`,
145
+ CORRECT_ARG_LIM = `Correct limits in the **#argument** block.`,
146
+ INTERVAL = `Incorrect range for`,
147
+ NEGATIVE_STEP = `Solution grid step must be positive. Correct the **#argument** block.`,
148
+ INCOR_STEP = `Grid step must less than the length of solution interval. Correct the **#argument** block.`,
149
+ MISS_COLON = `Missing **":"**`,
150
+ NAN = `is not a valid number. Correct the line`,
151
+ SERVICE_START = `Variable names must not begin with **"_"**.`,
152
+ REUSE_NAME = 'Variable reuse (case-insensitive): rename ',
153
+ SOLVER = `Incorrect solver options. Correct the **#meta.solver** line.`,
154
+ } // ERROR_MSG
155
+
156
+ /** Datagrok annotations */
157
+ enum ANNOT {
158
+ NAME = '//name:',
159
+ DESCR = '//description:',
160
+ TAGS = '//tags:',
161
+ LANG = '//language: javascript',
162
+ DOUBLE_INPUT = '//input: double',
163
+ INT_INPUT = '//input: int',
164
+ OUTPUT = `//output: dataframe df`,
165
+ EDITOR = '//editor: Compute:RichFunctionViewEditor',
166
+ SIDEBAR = '//sidebar: @compute',
167
+ CAPTION = 'caption:',
168
+ ARG_INIT = '{caption: Initial; category: Argument}',
169
+ ARG_FIN = '{caption: Final; category: Argument}',
170
+ ARG_STEP = '{caption: Step; category: Argument}',
171
+ INITS = 'category: Initial values',
172
+ PARAMS = 'category: Parameters',
173
+ }
174
+
175
+ /** JS-scripting components */
176
+ enum SCRIPT {
177
+ CONSTS = '// constants',
178
+ ODE_COM = '// the problem definition',
179
+ ODE = 'let odes = {',
180
+ SOLVER_COM = '// solve the problem',
181
+ SOLVER = `const solver = await grok.functions.eval('DiffStudio:solveEquations');`,
182
+ PREPARE = 'let call = solver.prepare({problem: odes, options: opts});',
183
+ CALL = 'await call.call();',
184
+ OUTPUT = `let df = call.getParamValue('df');`,
185
+ SPACE2 = ' ',
186
+ SPACE4 = ' ',
187
+ SPACE6 = ' ',
188
+ SPACE8 = ' ',
189
+ FUNC_VALS = '// extract function values',
190
+ EVAL_EXPR = '// evaluate expressions',
191
+ COMP_OUT = '// compute output',
192
+ MATH_FUNC_COM = '// used Math-functions',
193
+ MATH_CONST_COM = '// used Math-constants',
194
+ ONE_STAGE_COM = '\n// one stage solution',
195
+ ONE_STAGE_BEGIN = 'let _oneStage = async (',
196
+ ONE_STAGE_END = ') => {',
197
+ ASYNC_OUTPUT = `let df = await _oneStage(`,
198
+ RETURN_OUTPUT = `return call.getParamValue('df');`,
199
+ EMPTY_OUTPUT = `let df = DG.DataFrame.create();`,
200
+ APPEND = `df.append(`,
201
+ SOLUTION_DF_COM = '// solution dataframe',
202
+ LOOP_INTERVAL_COM = '// loop interval',
203
+ LOOP_INTERVAL = `_interval`,
204
+ LAST_IDX = `_lastIdx`,
205
+ UPDATE_COM = '// update ',
206
+ CUSTOM_OUTPUT_COM = '// create custom output',
207
+ CUSTOM_COLUMNS = `let _columns = [`,
208
+ ONE_STAGE = 'await _oneStage(',
209
+ SEGMENT_COM = '// add segment category',
210
+ }
211
+
212
+ /** Limits of the problem specification */
213
+ type Block = {
214
+ begin: number,
215
+ end: number
216
+ }
217
+
218
+ /** Get start of the problem skipping note-lines */
219
+ function getStartOfProblemDef(lines: string[]): number {
220
+ const linesCount = lines.length;
221
+ let idx = 0;
222
+
223
+ while (!lines[idx].startsWith(CONTROL_TAG)) {
224
+ ++idx;
225
+
226
+ if (idx === linesCount)
227
+ throw new ModelError(ERROR_MSG.UNDEF_NAME, ERROR_LINK.CORE_BLOCKS);
228
+ }
229
+
230
+ return idx;
231
+ }
232
+
233
+ /** Get limits of blocks specifying IVP */
234
+ function getBlocks(lines: string[], start: number): Block[] {
235
+ const linesCount = lines.length;
236
+ let beg = start;
237
+ let idx = start + 1;
238
+ const blocks = [] as Block[];
239
+
240
+ while (true) {
241
+ if (idx === linesCount) {
242
+ blocks.push({begin: beg, end: idx});
243
+ break;
244
+ }
245
+
246
+ if (lines[idx].startsWith(CONTROL_TAG)) {
247
+ blocks.push({begin: beg, end: idx});
248
+ beg = idx;
249
+ }
250
+
251
+ ++idx;
252
+ }
253
+
254
+ return blocks;
255
+ }
256
+
257
+ /** Concatenate multi-line formulas & expressions */
258
+ function concatMultilineFormulas(source: string[]): string[] {
259
+ const res: string[] = [source[0]];
260
+
261
+ let idxRes = 0;
262
+ let idxSource = 1;
263
+ const size = source.length;
264
+
265
+ while (idxSource < size) {
266
+ const str = source[idxSource];
267
+
268
+ if (!str.includes(EQUAL_SIGN))
269
+ res[idxRes] += str;
270
+ else {
271
+ res.push(str);
272
+ ++idxRes;
273
+ }
274
+
275
+ ++idxSource;
276
+ }
277
+
278
+ return res;
279
+ }
280
+
281
+ /** Get indeces of math functions used in the current text */
282
+ function getUsedMathIds(text: string, mathIds: string[]) {
283
+ const res = [] as number[];
284
+ const size = mathIds.length;
285
+
286
+ for (let i = 0; i < size; ++i) {
287
+ if (text.includes(`${mathIds[i]}`))
288
+ res.push(i);
289
+ }
290
+
291
+ return res;
292
+ }
293
+
294
+ /** Get differential equations */
295
+ function getDifEquations(lines: string[]): DifEqs {
296
+ const deqs = new Map<string, string>();
297
+ const names = [] as string[];
298
+
299
+ let divIdx = 0;
300
+ let eqIdx = 0;
301
+
302
+ for (const line of lines) {
303
+ if (line === undefined)
304
+ throw new ModelError(ERROR_MSG.UNDEF_DEQS, ERROR_LINK.CORE_BLOCKS, CONTROL_EXPR.DIF_EQ);
305
+
306
+ divIdx = line.indexOf(DIV_SIGN);
307
+ eqIdx = line.indexOf(EQUAL_SIGN);
308
+ const name = line.slice(line.indexOf('d') + 1, divIdx).replace(/[() ]/g, '');
309
+ names.push(name);
310
+ deqs.set(name, line.slice(eqIdx + 1).trim());
311
+ }
312
+
313
+ return {
314
+ equations: deqs,
315
+ solutionNames: names,
316
+ };
317
+ }
318
+
319
+ /** Get expressions of IVP */
320
+ function getExpressions(lines: string[]): Map<string, string> {
321
+ const exprs = new Map<string, string>();
322
+ let eqIdx = 0;
323
+
324
+ for (const line of lines) {
325
+ if (line === undefined)
326
+ break;
327
+
328
+ eqIdx = line.indexOf(EQUAL_SIGN);
329
+ exprs.set(line.slice(0, eqIdx).replaceAll(' ', ''), line.slice(eqIdx + 1).trim());
330
+ }
331
+
332
+ return exprs;
333
+ }
334
+
335
+ /** Get input specification */
336
+ function getInput(line: string) : Input {
337
+ const str = line.slice(line.indexOf(EQUAL_SIGN) + 1).trim();
338
+ const braceIdx = str.indexOf(BRACE_OPEN);
339
+ let val: number;
340
+
341
+ if (braceIdx === -1) { // no annotation
342
+ val = Number(str);
343
+
344
+ // Check right-hand side
345
+ if (isNaN(val))
346
+ throw new ModelError(`**${str}** ${ERROR_MSG.NAN}\n **${line}**.`, ERROR_LINK.COMPS_SYNTAX, line);
347
+
348
+ return {
349
+ value: val,
350
+ annot: null,
351
+ };
352
+ }
353
+
354
+ // There is an annotation
355
+ val = Number(str.slice(0, braceIdx));
356
+
357
+ // Check right-hand side
358
+ if (isNaN(val))
359
+ throw new ModelError(`**${str.slice(0, braceIdx)}** ${ERROR_MSG.NAN}: **${line}**`, ERROR_LINK.COMPS_SYNTAX, line);
360
+
361
+ return {
362
+ value: val,
363
+ annot: str.slice(braceIdx),
364
+ };
365
+ }
366
+
367
+ /** Get argument (independent variable) of IVP */
368
+ function getArg(lines: string[]): Arg {
369
+ if (lines.length !== 4)
370
+ throw new ModelError(ERROR_MSG.ARG, ERROR_LINK.CORE_BLOCKS, CONTROL_EXPR.ARG);
371
+
372
+ const sepIdx = lines[0].indexOf(CONTROL_SEP);
373
+ if (sepIdx === -1)
374
+ throw new ModelError(`${ERROR_MSG.MISS_COLON}. Correct the line **${lines[0]}**`, ERROR_LINK.CORE_BLOCKS, lines[0]);
375
+
376
+ const commaIdx = lines[0].indexOf(COMMA);
377
+
378
+ return {
379
+ name: ((commaIdx > -1) ? lines[0].slice(sepIdx + 1, commaIdx) : lines[0].slice(sepIdx + 1)).trim(),
380
+ initial: getInput(lines[1]),
381
+ final: getInput(lines[2]),
382
+ step: getInput(lines[3]),
383
+ updateName: (commaIdx > -1) ? lines[0].slice(commaIdx + 1).trim() : undefined,
384
+ };
385
+ }
386
+
387
+ /** Get equalities specification */
388
+ function getEqualities(lines: string[], begin: number, end: number): Map<string, Input> {
389
+ const source = concatMultilineFormulas(lines.slice(begin, end));
390
+ const eqs = new Map<string, Input>();
391
+ let eqIdx = 0;
392
+
393
+ for (const line of source) {
394
+ if (line === undefined)
395
+ break;
396
+
397
+ eqIdx = line.indexOf(EQUAL_SIGN);
398
+ eqs.set(line.slice(0, eqIdx).replaceAll(' ', ''), getInput(line));
399
+ }
400
+
401
+ return eqs;
402
+ }
403
+
404
+ /** Get loop specification */
405
+ function getLoop(lines: string[], begin: number, end: number): Loop {
406
+ if (begin >= end)
407
+ throw new ModelError(ERROR_MSG.LOOP, ERROR_LINK.LOOP, CONTROL_EXPR.LOOP);
408
+
409
+ const source = concatMultilineFormulas(lines.slice(begin, end));
410
+ const size = source.length;
411
+
412
+ if (size < LOOP.MIN_LINES_COUNT)
413
+ throw new ModelError(ERROR_MSG.LOOP, ERROR_LINK.LOOP, CONTROL_EXPR.LOOP);
414
+
415
+ const count = getInput(source[LOOP.COUNT_IDX]);
416
+ if (count.value < LOOP.MIN_COUNT)
417
+ throw new ModelError(`${ERROR_MSG.COUNT}: **${count.value}**.`, ERROR_LINK.LOOP, source[LOOP.COUNT_IDX]);
418
+
419
+ return {count: count, updates: source.slice(LOOP.COUNT_IDX + 1)};
420
+ }
421
+
422
+ /** Get update specification */
423
+ function getUpdate(lines: string[], begin: number, end: number): Update {
424
+ const colonIdx = lines[begin].indexOf(CONTROL_SEP);
425
+ if (colonIdx === -1) {
426
+ throw new ModelError(
427
+ `${ERROR_MSG.MISS_COLON}. Correct the line: **${lines[begin]}**`,
428
+ ERROR_LINK.UPDATE,
429
+ lines[begin],
430
+ );
431
+ }
432
+
433
+ const source = concatMultilineFormulas(lines.slice(begin + 1, end));
434
+ const size = source.length;
435
+
436
+ if (size < UPDATE.MIN_LINES_COUNT)
437
+ throw new ModelError(ERROR_MSG.UPDATE_LINES_COUNT, ERROR_LINK.UPDATE, CONTROL_EXPR.UPDATE);
438
+
439
+ if (source[UPDATE.DURATION_IDX] == undefined)
440
+ throw new ModelError(ERROR_MSG.UPDATE_LINES_COUNT, ERROR_LINK.UPDATE, CONTROL_EXPR.UPDATE);
441
+
442
+ const eqIdx = source[UPDATE.DURATION_IDX].indexOf(EQUAL_SIGN);
443
+
444
+ if (eqIdx === -1) {
445
+ throw new ModelError(
446
+ `Missing **${EQUAL_SIGN}**. Correct the line: **${source[UPDATE.DURATION_IDX]}**`,
447
+ ERROR_LINK.UPDATE,
448
+ source[UPDATE.DURATION_IDX],
449
+ );
450
+ }
451
+
452
+ return {
453
+ name: lines[begin].slice(colonIdx + 1),
454
+ durationFormula: source[UPDATE.DURATION_IDX].slice(eqIdx + 1).trim(),
455
+ updates: source.slice(UPDATE.DURATION_IDX + 1),
456
+ };
457
+ } // getUpdate
458
+
459
+ /** Get output specification */
460
+ function getOutput(lines: string[], begin: number, end: number): Map<string, Output> {
461
+ const res = new Map<string, Output>();
462
+
463
+ let line: string;
464
+ let token: string;
465
+ let eqIdx: number;
466
+ let openIdx: number;
467
+ let closeIdx: number;
468
+ let colonIdx: number;
469
+
470
+ for (let i = begin; i < end; ++i) {
471
+ line = lines[i].trim();
472
+
473
+ if (line.length < 1)
474
+ continue;
475
+
476
+ eqIdx = line.indexOf(EQUAL_SIGN);
477
+ openIdx = line.indexOf(BRACE_OPEN);
478
+ closeIdx = line.indexOf(BRACE_CLOSE);
479
+ colonIdx = line.indexOf(CONTROL_SEP);
480
+
481
+ // check braces
482
+ if (openIdx * closeIdx <= 0) {
483
+ throw new ModelError(
484
+ `${ERROR_MSG.BRACES} Correct the line **${line}** in the **${CONTROL_EXPR.OUTPUT}** block.`,
485
+ ERROR_LINK.UI_OPTS,
486
+ line,
487
+ );
488
+ }
489
+
490
+ // check ":"
491
+ if (openIdx * colonIdx <= 0) {
492
+ throw new ModelError(
493
+ `${ERROR_MSG.COLON} Correct the line **${line}** in the **#output** block.`,
494
+ ERROR_LINK.UI_OPTS,
495
+ line,
496
+ );
497
+ }
498
+
499
+ if (eqIdx === -1) {// no formula
500
+ if (openIdx === -1) { // no annotation
501
+ token = line.trim();
502
+ res.set(token, {
503
+ caption: token,
504
+ formula: null,
505
+ });
506
+ } else { // there is an annotation
507
+ token = line.slice(0, openIdx).trim();
508
+ res.set(token, {
509
+ caption: line.slice(colonIdx + 1, closeIdx).trim(),
510
+ formula: null,
511
+ });
512
+ }
513
+ } else // there is a formula
514
+ if (openIdx === -1) { // no annotation
515
+ token = line.slice(0, eqIdx).trim();
516
+ res.set(token, {
517
+ caption: token,
518
+ formula: line.slice(eqIdx + 1),
519
+ });
520
+ } else { // there is an annotation
521
+ token = line.slice(0, eqIdx).trim();
522
+ res.set(token, {
523
+ caption: line.slice(colonIdx + 1, closeIdx),
524
+ formula: line.slice(eqIdx + 1, openIdx),
525
+ });
526
+ }
527
+ } // for i
528
+
529
+ return res;
530
+ } // getOutput
531
+
532
+ /** Get initial value problem specification given in the text */
533
+ export function getIVP(text: string): IVP {
534
+ // The current Initial Value Problem (IVP) specification
535
+ let name: string;
536
+ let tags: string | null = null;
537
+ let descr: string | null = null;
538
+ let deqs: DifEqs;
539
+ let exprs: Map<string, string> | null = null;
540
+ let arg: Arg;
541
+ let inits: Map<string, Input>;
542
+ let consts: Map<string, Input> | null = null;
543
+ let params: Map<string, Input> | null = null;
544
+ let tolerance = DEFAULT_TOL;
545
+ let loop: Loop | null = null;
546
+ const updates = [] as Update[];
547
+ const metas = [] as string[];
548
+ let outputs: Map<string, Output> | null = null;
549
+ let solverSettings = DEFAULT_SOLVER_SETTINGS;
550
+ let inputsLookup: string | null = null;
551
+
552
+ // 0. Split text into lines & remove comments
553
+ const lines = text.replaceAll('\t', ' ').split('\n')
554
+ .map((s) => {
555
+ const idx = s.indexOf(COMMENT_SEQ);
556
+ return s.slice(0, (idx >= 0) ? idx : undefined).trimStart();
557
+ })
558
+ .filter((s) => s !== '');
559
+
560
+ // 1. Skip first lines without the control tag
561
+ const start = getStartOfProblemDef(lines);
562
+
563
+ // 2. Get blocks limits
564
+ const blocks = getBlocks(lines, start);
565
+
566
+ // 3. Process blocks
567
+ for (const block of blocks) {
568
+ const firstLine = lines[block.begin];
569
+
570
+ if (firstLine.startsWith(CONTROL_EXPR.NAME)) { // the 'name' block
571
+ name = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
572
+ } else if (firstLine.startsWith(CONTROL_EXPR.TAGS)) { // the 'tags' block
573
+ tags = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
574
+ } else if (firstLine.startsWith(CONTROL_EXPR.DESCR)) { // the 'description' block
575
+ descr = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
576
+ } else if (firstLine.startsWith(CONTROL_EXPR.DIF_EQ)) { // the 'differential equations' block
577
+ deqs = getDifEquations(concatMultilineFormulas(lines.slice(block.begin + 1, block.end)));
578
+ } else if (firstLine.startsWith(CONTROL_EXPR.EXPR)) { // the 'expressions' block
579
+ exprs = getExpressions(concatMultilineFormulas(lines.slice(block.begin + 1, block.end)));
580
+ } else if (firstLine.startsWith(CONTROL_EXPR.ARG)) { // the 'argument' block
581
+ arg = getArg(concatMultilineFormulas(lines.slice(block.begin, block.end)));
582
+ } else if (firstLine.startsWith(CONTROL_EXPR.INITS)) { // the 'initial values' block
583
+ inits = getEqualities(lines, block.begin + 1, block.end);
584
+ } else if (firstLine.startsWith(CONTROL_EXPR.CONSTS)) { // the 'constants' block
585
+ consts = getEqualities(lines, block.begin + 1, block.end);
586
+ } else if (firstLine.startsWith(CONTROL_EXPR.PARAMS)) { // the 'parameters' block
587
+ params = getEqualities(lines, block.begin + 1, block.end);
588
+ } else if (firstLine.startsWith(CONTROL_EXPR.TOL)) { // the 'tolerance' block
589
+ tolerance = firstLine.slice( firstLine.indexOf(CONTROL_SEP) + 1).trim();
590
+ } else if (firstLine.startsWith(CONTROL_EXPR.SOLVER)) { // the 'solver settings' block
591
+ solverSettings = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
592
+ } else if (firstLine.startsWith(CONTROL_EXPR.LOOP)) { // the 'loop' block
593
+ loop = getLoop(lines, block.begin + 1, block.end);
594
+ } else if (firstLine.startsWith(CONTROL_EXPR.UPDATE)) { // the 'update' block
595
+ updates.push(getUpdate(lines, block.begin, block.end));
596
+ } else if (firstLine.startsWith(CONTROL_EXPR.OUTPUT)) { // the 'output' block
597
+ outputs = getOutput(lines, block.begin + 1, block.end);
598
+ } else if (firstLine.startsWith(CONTROL_EXPR.INPUTS)) { // the 'inputs' block
599
+ inputsLookup = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
600
+ } else if (firstLine.startsWith(CONTROL_EXPR.COMMENT)) { // the 'comment' block
601
+ // just skip it
602
+ } else
603
+ metas.push(firstLine.slice(CONTROL_TAG_LEN));
604
+ } // for
605
+
606
+ // check loop- & update-features: just one is supported
607
+ if ( (loop !== null) && (updates.length > 0))
608
+ throw new ModelError(ERROR_MSG.LOOP_VS_UPDATE, ERROR_LINK.LOOP_VS_UPDATE, CONTROL_EXPR.LOOP);
609
+
610
+ // obtained model
611
+ const ivp = {
612
+ name: name!,
613
+ tags: tags,
614
+ descr: descr,
615
+ deqs: deqs!,
616
+ exprs: exprs,
617
+ arg: arg!,
618
+ inits: inits!,
619
+ consts: consts,
620
+ params: params,
621
+ tolerance: tolerance,
622
+ usedMathFuncs: getUsedMathIds(text, MATH_FUNCS),
623
+ usedMathConsts: getUsedMathIds(text, MATH_CONSTS),
624
+ loop: loop,
625
+ updates: (updates.length === 0) ? null : updates,
626
+ metas: metas,
627
+ outputs: outputs,
628
+ solverSettings: solverSettings,
629
+ inputsLookup: inputsLookup,
630
+ };
631
+
632
+ checkCorrectness(ivp);
633
+
634
+ return ivp;
635
+ } // getIVP
636
+
637
+ /** Return input specification, required for annotation generating */
638
+ function getInputSpec(inp: Input): string {
639
+ if (inp.annot)
640
+ return `${inp.value} ${inp.annot}`;
641
+
642
+ return `${inp.value}`;
643
+ }
644
+
645
+ /** Return annotation line specifying viewers */
646
+ function getViewersLine(ivp: IVP): string {
647
+ const outputColsCount = (ivp.outputs) ? ivp.outputs.size : ivp.inits.size;
648
+ const multiAxis = (outputColsCount > MAX_LINE_CHART - 1) ? 'true' : 'false';
649
+
650
+ const segments = (ivp.updates) ? ` segmentColumnName: "${STAGE_COL_NAME}",` : '';
651
+
652
+ // eslint-disable-next-line max-len
653
+ return `viewer: Line chart(block: 100, multiAxis: "${multiAxis}",${segments} multiAxisLegendPosition: "RightCenter", autoLayout: "false", showAggrSelectors: "false") | Grid(block: 100)`;
654
+ }
655
+
656
+ /** Generate annotation lines */
657
+ function getAnnot(ivp: IVP, toAddViewers = true, toAddEditor = false): string[] {
658
+ const res = [] as string[];
659
+
660
+ // the 'name' line
661
+ res.push(`${ANNOT.NAME} ${ivp.name}`);
662
+
663
+ // the 'tags' line
664
+ if (ivp.tags)
665
+ res.push(`${ANNOT.TAGS} ${ivp.tags}`);
666
+
667
+ // the 'description' line
668
+ if (ivp.descr)
669
+ res.push(`${ANNOT.DESCR} ${ivp.descr}`);
670
+
671
+ // the 'language' line
672
+ res.push(ANNOT.LANG);
673
+
674
+ // the 'loop' lines
675
+ if (ivp.loop)
676
+ res.push(`${ANNOT.INT_INPUT} ${LOOP.COUNT_NAME} = ${getInputSpec(ivp.loop.count)}`);
677
+
678
+ // argument lines
679
+ const arg = ivp.arg;
680
+ const t0 = `_${arg.name}0`;
681
+ const t1 = `_${arg.name}1`;
682
+ const h = `_h`;
683
+ res.push(`${ANNOT.DOUBLE_INPUT} ${t0} = ${getInputSpec(arg.initial)}`);
684
+ res.push(`${ANNOT.DOUBLE_INPUT} ${t1} = ${getInputSpec(arg.final)}`);
685
+ res.push(`${ANNOT.DOUBLE_INPUT} ${h} = ${getInputSpec(arg.step)}`);
686
+
687
+ // initial values lines
688
+ ivp.inits.forEach((val, key) => res.push(`${ANNOT.DOUBLE_INPUT} ${key} = ${getInputSpec(val)}`));
689
+
690
+ // parameters lines
691
+ if (ivp.params !== null)
692
+ ivp.params.forEach((val, key) => res.push(`${ANNOT.DOUBLE_INPUT} ${key} = ${getInputSpec(val)}`));
693
+
694
+ // the 'output' line
695
+ if (toAddViewers)
696
+ res.push(`${ANNOT.OUTPUT} {${ANNOT.CAPTION} ${ivp.name}; ${getViewersLine(ivp)}}`);
697
+ else
698
+ res.push(`${ANNOT.OUTPUT} {${ANNOT.CAPTION} ${ivp.name}`);
699
+
700
+ // the 'editor' line
701
+ if (toAddEditor) {
702
+ res.push(ANNOT.EDITOR);
703
+ res.push(ANNOT.SIDEBAR);
704
+ }
705
+
706
+ if (ivp.metas.length >0)
707
+ ivp.metas.forEach((line) => res.push(`//${line}`));
708
+ else
709
+ res.push(defaultMetas);
710
+
711
+ return res;
712
+ } // getAnnot
713
+
714
+ /** Returns math functions arguments string */
715
+ function getMathArg(funcIdx: number): string {
716
+ return (funcIdx > POW_IDX) ? '(x)' : '(x, y)';
717
+ }
718
+
719
+ /** Return custom output lines: no expressions */
720
+ function getCustomOutputLinesNoExpressions(name: string,
721
+ outputs: Map<string, Output>, toAddUpdateCol: boolean): string[] {
722
+ const res = [''];
723
+
724
+ res.push(SCRIPT.CUSTOM_OUTPUT_COM);
725
+ res.push(SCRIPT.CUSTOM_COLUMNS);
726
+
727
+ outputs.forEach((val, key) => {
728
+ if (!val.formula)
729
+ res.push(`${SCRIPT.SPACE2}df.col('${key}'),`);
730
+ });
731
+
732
+ if (toAddUpdateCol)
733
+ res.push(`${SCRIPT.SPACE2}df.col('${STAGE_COL_NAME}'),`);
734
+
735
+ res.push('];');
736
+
737
+ outputs.forEach((val, key) => {
738
+ if (!val.formula)
739
+ res.push(`df.col('${key}').name = '${val.caption}';`);
740
+ });
741
+
742
+ res.push(`df = DG.DataFrame.fromColumns(_columns);`);
743
+ res.push(`df.name = '${name}';`);
744
+ return res;
745
+ } // getCustomOutputLinesNoExpressions
746
+
747
+ /** Return custom output lines: with expressions */
748
+ function getCustomOutputLinesWithExpressions(ivp: IVP): string[] {
749
+ const res = [''];
750
+
751
+ res.push(SCRIPT.CUSTOM_OUTPUT_COM);
752
+
753
+ // 1. Solution raw data
754
+ res.push(`const ${ivp.arg.name}RawData = df.col('${ivp.arg.name}').getRawData();`);
755
+ res.push(`let ${ivp.arg.name};`);
756
+ res.push(`const len = df.rowCount;\n`);
757
+
758
+ ivp.inits.forEach((val, key) => {
759
+ res.push(`const ${key}RawData = df.col('${key}').getRawData();`);
760
+ });
761
+
762
+ res.push('');
763
+
764
+ // 2. Expressions raw data & variables
765
+ ivp.exprs!.forEach((val, key) => {
766
+ if (ivp.outputs!.has(key))
767
+ res.push(`const ${key}RawData = new Float64Array(len);`);
768
+
769
+ res.push(`let ${key};`);
770
+ });
771
+
772
+ res.push('');
773
+
774
+ // 3. Computations
775
+ res.push('for (let i = 0; i < len; ++i) {');
776
+ res.push(`${SCRIPT.SPACE2}${ivp.arg.name} = ${ivp.arg.name}RawData[i];`);
777
+
778
+ ivp.inits.forEach((val, key) => {
779
+ res.push(`${SCRIPT.SPACE2}${key} = ${key}RawData[i];`);
780
+ });
781
+
782
+ ivp.exprs!.forEach((val, key) => {
783
+ res.push(`${SCRIPT.SPACE2}${key} = ${val};`);
784
+
785
+ if (ivp.outputs!.has(key))
786
+ res.push(`${SCRIPT.SPACE2}${key}RawData[i] = ${key};`);
787
+ });
788
+
789
+ res.push('}\n');
790
+
791
+ // 4. Form output
792
+ res.push(`df = DG.DataFrame.fromColumns([`);
793
+ ivp.outputs!.forEach((val, key) => {
794
+ if (!val.formula)
795
+ res.push(`${SCRIPT.SPACE2}DG.Column.fromFloat64Array('${val.caption}', ${key}RawData.slice(0, len)),`);
796
+ });
797
+
798
+ if (ivp.updates !== null)
799
+ res.push(`${SCRIPT.SPACE2}df.col('${STAGE_COL_NAME}'),`);
800
+
801
+ res.push(']);');
802
+ res.push(`df.name = '${ivp.name}';`);
803
+
804
+ return res;
805
+ } // getCustomOutputLinesWithExpressions
806
+
807
+ /** Return custom output lines */
808
+ function getCustomOutputLines(ivp: IVP): string[] {
809
+ if (ivp.exprs !== null) {
810
+ for (const key of ivp.exprs.keys()) {
811
+ if (ivp.outputs?.has(key))
812
+ return getCustomOutputLinesWithExpressions(ivp);
813
+ }
814
+ }
815
+
816
+ return getCustomOutputLinesNoExpressions(ivp.name, ivp.outputs!, (ivp.updates !== null));
817
+ } // getCustomOutputLinesWithExpressions
818
+
819
+ /** Return main body of JS-script: basic variant */
820
+ function getScriptMainBodyBasic(ivp: IVP): string[] {
821
+ const res = [] as string[];
822
+
823
+ // 1. Constants lines
824
+ if (ivp.consts !== null) {
825
+ res.push('');
826
+ res.push(SCRIPT.CONSTS);
827
+ ivp.consts.forEach((val, key) => res.push(`const ${key} = ${val.value};`));
828
+ }
829
+
830
+ // 2. The problem definition lines
831
+ res.push('');
832
+ res.push(SCRIPT.ODE_COM);
833
+ res.push(SCRIPT.ODE);
834
+ res.push(`${SCRIPT.SPACE4}name: '${ivp.name}',`);
835
+
836
+ // 2.1) argument
837
+ const t = ivp.arg.name;
838
+ const t0 = `_${t}0`;
839
+ const t1 = `_${t}1`;
840
+ const h = `_h`;
841
+ res.push(`${SCRIPT.SPACE4}arg: {name: '${t}', start: ${t0}, finish: ${t1}, step: ${h}},`);
842
+
843
+ const names = ivp.deqs.solutionNames;
844
+
845
+ // 2.2) initial values
846
+ res.push(`${SCRIPT.SPACE4}initial: [${names.join(', ')}],`);
847
+
848
+ // 2.3) the right-hand side of the problem
849
+ res.push(`${SCRIPT.SPACE4}func: (${t}, _y, _output) => {`);
850
+
851
+ res.push(`${SCRIPT.SPACE6}${SCRIPT.FUNC_VALS}`);
852
+ names.forEach((name, idx) => res.push(`${SCRIPT.SPACE6}const ${name} = _y[${idx}];`));
853
+
854
+ if (ivp.exprs !== null) {
855
+ res.push(`\n${SCRIPT.SPACE6}${SCRIPT.EVAL_EXPR}`);
856
+ ivp.exprs.forEach((val, key) => res.push(`${SCRIPT.SPACE6}const ${key} = ${val};`));
857
+ }
858
+
859
+ res.push(`\n${SCRIPT.SPACE6}${SCRIPT.COMP_OUT}`);
860
+ names.forEach((name, idx) => res.push(`${SCRIPT.SPACE6}_output[${idx}] = ${ivp.deqs.equations.get(name)};`));
861
+
862
+ res.push(`${SCRIPT.SPACE4}},`);
863
+
864
+ // 2.4) final lines of the problem specification
865
+ res.push(`${SCRIPT.SPACE4}tolerance: ${ivp.tolerance},`);
866
+ //res.push(`${SCRIPT.SPACE6}solverOptions: ${ivp.solverSettings.replaceAll(ANNOT_SEPAR, COMMA)},`);
867
+ res.push(`${SCRIPT.SPACE4}solutionColNames: [${names.map((key) => `'${key}'`).join(', ')}]`);
868
+ res.push('};');
869
+
870
+ // 2.5) solver options
871
+ res.push('');
872
+ res.push(`let opts = ${ivp.solverSettings.replaceAll(';', ',')};`);
873
+
874
+ // 3. Math functions
875
+ if (ivp.usedMathFuncs.length > 0) {
876
+ res.push('');
877
+ res.push(SCRIPT.MATH_FUNC_COM);
878
+ ivp.usedMathFuncs.forEach((i) =>
879
+ res.push(`const ${MATH_FUNCS[i]} = ${getMathArg(i)} => Math.${MATH_FUNCS[i]}${getMathArg(i)};`,
880
+ ));
881
+ }
882
+
883
+ // 4. Math constants
884
+ if (ivp.usedMathConsts.length > 0) {
885
+ res.push('');
886
+ res.push(SCRIPT.MATH_CONST_COM);
887
+ ivp.usedMathConsts.forEach((i) => res.push(`const ${MATH_CONSTS[i]} = Math.${MATH_CONSTS[i]};`));
888
+ }
889
+
890
+ // 5. The 'call solver' lines
891
+ res.push('');
892
+ res.push(SCRIPT.SOLVER_COM);
893
+ res.push(SCRIPT.SOLVER);
894
+ res.push(SCRIPT.PREPARE);
895
+ res.push(SCRIPT.CALL);
896
+ res.push(SCRIPT.OUTPUT);
897
+
898
+ return res;
899
+ } // getScriptMainBodyBasic
900
+
901
+ /** Return function for JS-script */
902
+ function getScriptFunc(ivp: IVP, funcParamsNames: string): string[] {
903
+ const res = [] as string[];
904
+
905
+ // 0. Function declaration
906
+ res.push(SCRIPT.ONE_STAGE_COM);
907
+ res.push(`${SCRIPT.ONE_STAGE_BEGIN}${funcParamsNames}${SCRIPT.ONE_STAGE_END}`);
908
+
909
+ // 1. Constants lines
910
+ if (ivp.consts !== null) {
911
+ res.push('');
912
+ res.push(SCRIPT.SPACE2 + SCRIPT.CONSTS);
913
+ ivp.consts.forEach((val, key) => res.push(`${SCRIPT.SPACE2}const ${key} = ${val.value};`));
914
+ }
915
+
916
+ // 2. The problem definition lines
917
+ res.push(SCRIPT.SPACE2 + SCRIPT.ODE_COM);
918
+ res.push(SCRIPT.SPACE2 + SCRIPT.ODE);
919
+ res.push(`${SCRIPT.SPACE6}name: '${ivp.name}',`);
920
+
921
+ // 2.1) argument
922
+ const t = ivp.arg.name;
923
+ const t0 = `_${t}0`;
924
+ const t1 = `_${t}1`;
925
+ const h = `_h`;
926
+ res.push(`${SCRIPT.SPACE6}arg: {name: '${t}', start: ${t0}, finish: ${t1}, step: ${h}},`);
927
+
928
+ const names = ivp.deqs.solutionNames;
929
+
930
+ // 2.2) initial values
931
+ res.push(`${SCRIPT.SPACE6}initial: [${names.join(', ')}],`);
932
+
933
+ // 2.3) the right-hand side of the problem
934
+ res.push(`${SCRIPT.SPACE6}func: (${t}, _y, _output) => {`);
935
+
936
+ res.push(`${SCRIPT.SPACE8}${SCRIPT.FUNC_VALS}`);
937
+ names.forEach((name, idx) => res.push(`${SCRIPT.SPACE8}const ${name} = _y[${idx}];`));
938
+
939
+ if (ivp.exprs !== null) {
940
+ res.push(`\n${SCRIPT.SPACE8}${SCRIPT.EVAL_EXPR}`);
941
+ ivp.exprs.forEach((val, key) => res.push(`${SCRIPT.SPACE8}const ${key} = ${val};`));
942
+ }
943
+
944
+ res.push(`\n${SCRIPT.SPACE8}${SCRIPT.COMP_OUT}`);
945
+ names.forEach((name, idx) => res.push(`${SCRIPT.SPACE8}_output[${idx}] = ${ivp.deqs.equations.get(name)};`));
946
+
947
+ res.push(`${SCRIPT.SPACE6}},`);
948
+
949
+ // 2.4) final lines of the problem specification
950
+ res.push(`${SCRIPT.SPACE6}tolerance: ${ivp.tolerance},`);
951
+ //res.push(`${SCRIPT.SPACE6}solverOptions: ${ivp.solverSettings.replaceAll(ANNOT_SEPAR, COMMA)},`);
952
+ res.push(`${SCRIPT.SPACE6}solutionColNames: [${names.map((key) => `'${key}'`).join(', ')}]`);
953
+ res.push(`${SCRIPT.SPACE2}};`);
954
+
955
+ // 2.5) solver options
956
+ res.push('');
957
+ res.push(`${SCRIPT.SPACE2}let opts = ${ivp.solverSettings.replaceAll(';', ',')};`);
958
+
959
+ // 3. Math functions
960
+ if (ivp.usedMathFuncs.length > 0) {
961
+ res.push('');
962
+ res.push(SCRIPT.SPACE2 + SCRIPT.MATH_FUNC_COM);
963
+ // eslint-disable-next-line max-len
964
+ ivp.usedMathFuncs.forEach((i) => res.push(`${SCRIPT.SPACE2}const ${MATH_FUNCS[i]} = ${getMathArg(i)} => Math.${MATH_FUNCS[i]}${getMathArg(i)};`));
965
+ }
966
+
967
+ // 4. Math constants
968
+ if (ivp.usedMathConsts.length > 0) {
969
+ res.push('');
970
+ res.push(SCRIPT.SPACE2 + SCRIPT.MATH_CONST_COM);
971
+ ivp.usedMathConsts.forEach((i) => res.push(`${SCRIPT.SPACE2}const ${MATH_CONSTS[i]} = Math.${MATH_CONSTS[i]};`));
972
+ }
973
+
974
+ // 5. The 'call solver' lines
975
+ res.push('');
976
+ res.push(SCRIPT.SPACE2 + SCRIPT.SOLVER_COM);
977
+ res.push(SCRIPT.SPACE2 + SCRIPT.SOLVER);
978
+ res.push(SCRIPT.SPACE2 + SCRIPT.PREPARE);
979
+ res.push(SCRIPT.SPACE2 + SCRIPT.CALL);
980
+ res.push(SCRIPT.SPACE2 + SCRIPT.RETURN_OUTPUT);
981
+
982
+ // 6. Close the function
983
+ res.push('};');
984
+
985
+ return res;
986
+ } // getScriptFunc
987
+
988
+ /** Return main body of JS-script: loop case */
989
+ function getScriptMainBodyLoopCase(ivp: IVP): string[] {
990
+ const funcParamsNames = getFuncParamsNames(ivp);
991
+ const res = getScriptFunc(ivp, funcParamsNames);
992
+ res.push('');
993
+ //res.push(`${SCRIPT.ASYNC_OUTPUT}${funcParamsNames});`);
994
+
995
+ res.push(SCRIPT.SOLUTION_DF_COM);
996
+ const dfNames = getSolutionDfColsNames(ivp);
997
+
998
+ res.push(`let df = DG.DataFrame.fromColumns([`);
999
+ dfNames.forEach((name) => res.push(`${SCRIPT.SPACE2}DG.Column.fromFloat64Array('${name}', []),`));
1000
+ res.push(`]);`);
1001
+ res.push(`df.name = '${ivp.name}';`);
1002
+ res.push('');
1003
+
1004
+ res.push(SCRIPT.LOOP_INTERVAL_COM);
1005
+ res.push(`const ${SCRIPT.LOOP_INTERVAL} = _${ivp.arg.name}1 - _${ivp.arg.name}0;`);
1006
+ res.push('');
1007
+ res.push(`let ${SCRIPT.LAST_IDX} = 0;\n`);
1008
+
1009
+ res.push(SCRIPT.SOLVER_COM);
1010
+ res.push(`for (let _idx = 0; _idx < ${LOOP.COUNT_NAME}; ++_idx) {`);
1011
+ ivp.loop!.updates.forEach((upd) => res.push(`${SCRIPT.SPACE2}${upd};`));
1012
+ res.push(`${SCRIPT.SPACE2}${SCRIPT.APPEND}${SCRIPT.ONE_STAGE}${funcParamsNames}), true);`);
1013
+ res.push(`${SCRIPT.SPACE2}_${ivp.arg.name}0 = _${ivp.arg.name}1;`);
1014
+ res.push(`${SCRIPT.SPACE2}_${ivp.arg.name}1 += ${SCRIPT.LOOP_INTERVAL};`);
1015
+ res.push(`${SCRIPT.SPACE2}${SCRIPT.LAST_IDX} = df.rowCount - 1;`);
1016
+
1017
+ dfNames.forEach((name, idx) => {
1018
+ if (idx !== 0)
1019
+ res.push(`${SCRIPT.SPACE2}${name} = df.get('${name}', ${SCRIPT.LAST_IDX});`);
1020
+ });
1021
+
1022
+ // eslint-disable-next-line max-len
1023
+ res.push(`${SCRIPT.SPACE2}df.set('${ivp.arg.name}', ${SCRIPT.LAST_IDX}, _${ivp.arg.name}0 - Math.min(_h * ${STEP_RATIO}, ${TINY}));`);
1024
+
1025
+ res.push('};');
1026
+
1027
+ return res;
1028
+ } // getScriptMainBodyLoopCase
1029
+
1030
+ /** Return main body of JS-script: update case */
1031
+ function getScriptMainBodyUpdateCase(ivp: IVP): string[] {
1032
+ const funcParamsNames = getFuncParamsNames(ivp);
1033
+ const res = getScriptFunc(ivp, funcParamsNames);
1034
+
1035
+ res.push('');
1036
+
1037
+ res.push(SCRIPT.SOLUTION_DF_COM);
1038
+ const dfNames = getSolutionDfColsNames(ivp);
1039
+
1040
+ res.push(`${SCRIPT.ASYNC_OUTPUT}${funcParamsNames});`);
1041
+
1042
+ res.push('');
1043
+
1044
+ res.push(SCRIPT.SEGMENT_COM);
1045
+ // eslint-disable-next-line max-len
1046
+ res.push(`df.columns.add(DG.Column.fromList('string', '${STAGE_COL_NAME}', new Array(df.rowCount).fill('${ivp.arg.updateName ?? INCEPTION}')));`);
1047
+
1048
+ res.push('');
1049
+ res.push(`let ${SCRIPT.LAST_IDX} = 0;`);
1050
+
1051
+ ivp.updates!.forEach((upd, idx) => {
1052
+ res.push('');
1053
+ res.push(`${SCRIPT.UPDATE_COM} ${idx + 1}`);
1054
+ res.push(`const ${UPDATE.DURATION}${idx + 1} = ${upd.durationFormula};`);
1055
+ res.push(`${SCRIPT.LAST_IDX} = df.rowCount - 1;`);
1056
+
1057
+ // eslint-disable-next-line max-len
1058
+ res.push(`df.set('${ivp.arg.name}', ${SCRIPT.LAST_IDX}, _${ivp.arg.name}1 - Math.min(_h * ${STEP_RATIO}, ${TINY}));`);
1059
+
1060
+ dfNames.forEach((name, idx) => {
1061
+ if (idx !== 0)
1062
+ res.push(`${name} = df.get('${name}', ${SCRIPT.LAST_IDX});`);
1063
+ });
1064
+
1065
+ upd.updates.forEach((upd) => res.push(`${upd};`));
1066
+
1067
+ res.push(`_${ivp.arg.name}0 = _${ivp.arg.name}1;`);
1068
+ res.push(`_${ivp.arg.name}1 += ${UPDATE.DURATION}${idx + 1};`);
1069
+
1070
+ res.push(`const _DF${idx + 1} = ${SCRIPT.ONE_STAGE}${funcParamsNames});`);
1071
+ // eslint-disable-next-line max-len
1072
+ res.push(`_DF${idx + 1}.columns.add(DG.Column.fromList('string', '${STAGE_COL_NAME}', new Array(_DF${idx + 1}.rowCount).fill('${upd.name}')));`);
1073
+ res.push(`${SCRIPT.APPEND}_DF${idx + 1}, true);`);
1074
+ });
1075
+
1076
+ return res;
1077
+ } // getScriptMainBodyUpdateCase
1078
+
1079
+ /** Return main body of JS-script */
1080
+ function getScriptMainBody(ivp: IVP): string[] {
1081
+ if (ivp.loop)
1082
+ return getScriptMainBodyLoopCase(ivp);
1083
+
1084
+ if (ivp.updates)
1085
+ return getScriptMainBodyUpdateCase(ivp);
1086
+
1087
+ return getScriptMainBodyBasic(ivp);
1088
+ }
1089
+
1090
+ /** Return JS-script lines */
1091
+ export function getScriptLines(ivp: IVP, toAddViewers = true, toAddEditor = false): string[] {
1092
+ const res = getAnnot(ivp, toAddViewers, toAddEditor).concat(getScriptMainBody(ivp));
1093
+
1094
+ if (ivp.outputs)
1095
+ return res.concat(getCustomOutputLines(ivp));
1096
+
1097
+ return res;
1098
+ }
1099
+
1100
+ /** Return parameters of JS-script */
1101
+ export function getScriptParams(ivp: IVP): Record<string, number> {
1102
+ const res = {} as Record<string, number>;
1103
+
1104
+ if (ivp.loop)
1105
+ res[LOOP.COUNT_NAME] = ivp.loop.count.value;
1106
+
1107
+ const arg = ivp.arg;
1108
+
1109
+ res[`_${arg.name}0`] = arg.initial.value;
1110
+ res[`_${arg.name}1`] = arg.final.value;
1111
+ res[`_h`] = arg.step.value;
1112
+
1113
+ ivp.inits.forEach((val, key) => res[key] = val.value);
1114
+
1115
+ if (ivp.params)
1116
+ ivp.params.forEach((val, key) => res[key] = val.value);
1117
+
1118
+ return res;
1119
+ }
1120
+
1121
+ /** Return func parameters names string */
1122
+ function getFuncParamsNames(ivp: IVP): string {
1123
+ const names = [] as string [];
1124
+
1125
+ const arg = ivp.arg.name;
1126
+
1127
+ names.push(`_${arg}0`);
1128
+ names.push(`_${arg}1`);
1129
+ names.push(`_h`);
1130
+
1131
+ ivp.inits.forEach((val, key) => names.push(key));
1132
+
1133
+ if (ivp.params)
1134
+ ivp.params.forEach((val, key) => names.push(key));
1135
+
1136
+ return names.join(', ');
1137
+ }
1138
+
1139
+ /** Return solution dataframe columns names*/
1140
+ function getSolutionDfColsNames(ivp: IVP): string[] {
1141
+ const res = [] as string[];
1142
+
1143
+ res.push(ivp.arg.name);
1144
+
1145
+ ivp.inits.forEach((val, key) => res.push(key));
1146
+
1147
+ return res;
1148
+ }
1149
+
1150
+ /** Check solver settings */
1151
+ function checkSolverSettings(line: string): void {
1152
+ const settings = new Map<string, string>();
1153
+
1154
+ const openBraceIdx = line.indexOf(BRACE_OPEN);
1155
+ const closeBraceIdx = line.indexOf(BRACE_CLOSE);
1156
+ let sepIdx: number;
1157
+
1158
+ if ((openBraceIdx < 0 )|| (closeBraceIdx < 0))
1159
+ throw new ModelError(`${ERROR_MSG.BRACES}. Correct the line **${line}**.`, ERROR_LINK.SOLVER_CONFIG, line);
1160
+
1161
+ for (const item of line.slice(openBraceIdx + 1, closeBraceIdx).split(ANNOT_SEPAR)) {
1162
+ sepIdx = item.indexOf(CONTROL_SEP);
1163
+
1164
+ if (sepIdx > 1)
1165
+ settings.set(item.slice(0, sepIdx).trim(), item.slice(sepIdx + 1).trim());
1166
+ }
1167
+
1168
+ SOLVER_OPTIONS_RANGES.forEach((range, opt) => {
1169
+ if (settings.has(opt)) {
1170
+ const val = Number(settings.get(opt));
1171
+
1172
+ if ((val < range.min) || (val > range.max)) {
1173
+ throw new ModelError(
1174
+ `${ERROR_MSG.SOLVER}: **${opt}** must be in the range **${range.min}..${range.max}**.`,
1175
+ ERROR_LINK.SOLVER_CONFIG,
1176
+ line,
1177
+ );
1178
+ }
1179
+ }
1180
+ });
1181
+ } // checkSolverSettings
1182
+
1183
+ /** Check IVP correctness */
1184
+ function checkCorrectness(ivp: IVP): void {
1185
+ // 0. Check basic elements
1186
+ if (ivp.name === undefined)
1187
+ throw new ModelError(ERROR_MSG.UNDEF_NAME, ERROR_LINK.CORE_BLOCKS);
1188
+
1189
+ if ((ivp.deqs === undefined) || (ivp.deqs.equations.size === 0))
1190
+ throw new ModelError(ERROR_MSG.UNDEF_DEQS, ERROR_LINK.CORE_BLOCKS);
1191
+
1192
+ if ((ivp.inits === undefined) || (ivp.inits.size === 0))
1193
+ throw new ModelError(ERROR_MSG.UNDEF_INITS, ERROR_LINK.CORE_BLOCKS);
1194
+
1195
+ if (ivp.arg === undefined)
1196
+ throw new ModelError(ERROR_MSG.UNDEF_ARG, ERROR_LINK.CORE_BLOCKS);
1197
+
1198
+ // 1. Check initial values
1199
+ ivp.deqs.equations.forEach((ignore, name) => {
1200
+ if (!ivp.inits.has(name)) {
1201
+ throw new ModelError(
1202
+ `Initial value for **${name}** is missing. ${ERROR_MSG.MISSING_INIT}`,
1203
+ ERROR_LINK.CORE_BLOCKS,
1204
+ CONTROL_EXPR.INITS,
1205
+ );
1206
+ }
1207
+ });
1208
+
1209
+ // 2. Check names of output columns
1210
+ const usedNames = [] as string[];
1211
+ let lowCase: string;
1212
+
1213
+ if (ivp.outputs !== null) {
1214
+ ivp.outputs.forEach((val) => {
1215
+ lowCase = val.caption.toLowerCase();
1216
+
1217
+ if (usedNames.includes(lowCase))
1218
+ throw new ModelError(`${ERROR_MSG.CASE_INSENS}**${val.caption}**.`, ERROR_LINK.UI_OPTS, CONTROL_EXPR.OUTPUT);
1219
+ else
1220
+ usedNames.push(lowCase);
1221
+ });
1222
+ } else {
1223
+ const usedNames = [ivp.arg.name];
1224
+
1225
+ ivp.deqs.solutionNames.forEach((name) => {
1226
+ lowCase = name.toLowerCase();
1227
+
1228
+ if (usedNames.includes(lowCase))
1229
+ throw new ModelError(`${ERROR_MSG.CASE_INSENS}**${name}**.`, ERROR_LINK.UI_OPTS);
1230
+ else
1231
+ usedNames.push(lowCase);
1232
+ });
1233
+ }
1234
+
1235
+ // 3. Check argument
1236
+ if (ivp.arg.initial.value >= ivp.arg.final.value) {
1237
+ throw new ModelError(
1238
+ `${ERROR_MSG.INTERVAL} **${ivp.arg.name}**. ${ERROR_MSG.CORRECT_ARG_LIM}`,
1239
+ ERROR_LINK.CORE_BLOCKS,
1240
+ CONTROL_EXPR.ARG,
1241
+ );
1242
+ }
1243
+
1244
+ if (ivp.arg.step.value <= 0) {
1245
+ throw new ModelError(
1246
+ `${ERROR_MSG.NEGATIVE_STEP}`,
1247
+ ERROR_LINK.CORE_BLOCKS);
1248
+ }
1249
+
1250
+ if (ivp.arg.step.value > ivp.arg.final.value - ivp.arg.initial.value)
1251
+ throw new ModelError(ERROR_MSG.INCOR_STEP, ERROR_LINK.CORE_BLOCKS);
1252
+
1253
+ // 4. Check script inputs, due to https://reddata.atlassian.net/browse/GROK-15152
1254
+ const scriptInputs = [] as string[];
1255
+ let current: string;
1256
+
1257
+ // 4.1) initial values
1258
+ ivp.inits.forEach((_, key) => {
1259
+ if (key[0] === SERVICE) {
1260
+ throw new ModelError(
1261
+ `${ERROR_MSG.SERVICE_START} Correct **"${key}"** in the **#inits** block.`,
1262
+ ERROR_LINK.CORE_BLOCKS,
1263
+ );
1264
+ }
1265
+
1266
+ current = key.toLocaleLowerCase();
1267
+
1268
+ if (scriptInputs.includes(current)) {
1269
+ throw new ModelError(
1270
+ `${ERROR_MSG.REUSE_NAME} **"${key}"** in the **#inits** block.`,
1271
+ ERROR_LINK.CORE_BLOCKS,
1272
+ );
1273
+ }
1274
+
1275
+ scriptInputs.push(current);
1276
+ });
1277
+
1278
+ // 4.2) parameters
1279
+ if (ivp.params !== null) {
1280
+ ivp.params.forEach((_, key) => {
1281
+ if (key[0] === SERVICE) {
1282
+ throw new ModelError(
1283
+ `${ERROR_MSG.SERVICE_START}: correct **"${key}"** in the **${CONTROL_EXPR.PARAMS}** block.`,
1284
+ ERROR_LINK.MODEL_PARAMS,
1285
+ );
1286
+ }
1287
+
1288
+ current = key.toLocaleLowerCase();
1289
+
1290
+ if (scriptInputs.includes(current)) {
1291
+ throw new ModelError(
1292
+ `${ERROR_MSG.REUSE_NAME} **"${key}"** in the **${CONTROL_EXPR.PARAMS}** block.`,
1293
+ ERROR_LINK.MODEL_PARAMS,
1294
+ );
1295
+ }
1296
+
1297
+ scriptInputs.push(current);
1298
+ });
1299
+ }
1300
+
1301
+ // 5. Check solver settings
1302
+ checkSolverSettings(ivp.solverSettings);
1303
+ } // checkCorrectness
1304
+
1305
+ /** */
1306
+ export function getJScode(ivp: IVP): string[] {
1307
+ const res = [] as string[];
1308
+
1309
+ // 1. Math functions
1310
+ if (ivp.usedMathFuncs.length > 0) {
1311
+ res.push(SCRIPT.MATH_FUNC_COM);
1312
+ ivp.usedMathFuncs.forEach((i) =>
1313
+ res.push(`const ${MATH_FUNCS[i]} = ${getMathArg(i)} => Math.${MATH_FUNCS[i]}${getMathArg(i)};`,
1314
+ ));
1315
+ }
1316
+
1317
+ // 2. Math constants
1318
+ if (ivp.usedMathConsts.length > 0) {
1319
+ res.push('');
1320
+ res.push(SCRIPT.MATH_CONST_COM);
1321
+ ivp.usedMathConsts.forEach((i) => res.push(`const ${MATH_CONSTS[i]} = Math.${MATH_CONSTS[i]};`));
1322
+ }
1323
+
1324
+ // 1. Constants lines
1325
+ if (ivp.consts !== null) {
1326
+ res.push('');
1327
+ res.push(SCRIPT.CONSTS);
1328
+ ivp.consts.forEach((val, key) => res.push(`const ${key} = ${val.value};`));
1329
+ }
1330
+
1331
+ // 2. The problem definition lines
1332
+ res.push('');
1333
+ res.push(SCRIPT.ODE_COM);
1334
+ res.push(`const odes = {`);
1335
+ res.push(`${SCRIPT.SPACE4}name: '${ivp.name}',`);
1336
+
1337
+ // 2.1) argument
1338
+ const t = ivp.arg.name;
1339
+ const t0 = `_${t}0`;
1340
+ const t1 = `_${t}1`;
1341
+ const h = `_h`;
1342
+ res.push(`${SCRIPT.SPACE4}arg: {name: '${t}', start: ${t0}, finish: ${t1}, step: ${h}},`);
1343
+
1344
+ const names = ivp.deqs.solutionNames;
1345
+
1346
+ // 2.2) initial values
1347
+ res.push(`${SCRIPT.SPACE4}initial: [${names.join(', ')}],`);
1348
+
1349
+ // 2.3) the right-hand side of the problem
1350
+ res.push(`${SCRIPT.SPACE4}func: (${t}, _y, _output) => {`);
1351
+
1352
+ res.push(`${SCRIPT.SPACE6}${SCRIPT.FUNC_VALS}`);
1353
+ names.forEach((name, idx) => res.push(`${SCRIPT.SPACE6}const ${name} = _y[${idx}];`));
1354
+
1355
+ if (ivp.exprs !== null) {
1356
+ res.push(`\n${SCRIPT.SPACE6}${SCRIPT.EVAL_EXPR}`);
1357
+ ivp.exprs.forEach((val, key) => res.push(`${SCRIPT.SPACE6}const ${key} = ${val};`));
1358
+ }
1359
+
1360
+ res.push(`\n${SCRIPT.SPACE6}${SCRIPT.COMP_OUT}`);
1361
+ names.forEach((name, idx) => res.push(`${SCRIPT.SPACE6}_output[${idx}] = ${ivp.deqs.equations.get(name)};`));
1362
+
1363
+ res.push(`${SCRIPT.SPACE4}},`);
1364
+
1365
+ // 2.4) final lines of the problem specification
1366
+ res.push(`${SCRIPT.SPACE4}tolerance: ${ivp.tolerance},`);
1367
+ //res.push(`${SCRIPT.SPACE6}solverOptions: ${ivp.solverSettings.replaceAll(ANNOT_SEPAR, COMMA)},`);
1368
+ res.push(`${SCRIPT.SPACE4}solutionColNames: [${names.map((key) => `'${key}'`).join(', ')}]`);
1369
+ res.push('};');
1370
+
1371
+ return res;
1372
+ } // getJScode
1373
+
1374
+ /** */
1375
+ export function getFunc4worker(ivp: IVP) {}