datagrok-tools 6.2.5 → 6.3.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/CHANGELOG.md +8 -0
- package/GROK_S.md +11 -6
- package/bin/commands/server.js +5 -3
- package/bin/utils/node-dapi.js +7 -3
- package/download-demo-datasets.R +67 -0
- package/package.json +3 -1
- package/plugins/func-gen-plugin.js +151 -0
- package/plugins/ivp-parser.bundle.cjs +600 -0
- package/plugins/ivp-parser.entry.mjs +1 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Datagrok-tools changelog
|
|
2
2
|
|
|
3
|
+
## 6.3.0 (2026-06-12)
|
|
4
|
+
|
|
5
|
+
* `func-gen` webpack plugin — generates RichFunctionView model wrappers from Diff Studio `#meta.role: model` `.ivp` files. Inputs/output are derived from the parsed IVP, `#meta.icon` becomes the model icon, and `#meta.inputs` lookups are emitted as real `propagateChoice: all` inputs (rendered natively by the Rich Function View, unlike `meta.inputs`). Ships a prebuilt, tree-shaken CJS bundle of diff-grok's IVP parser (`plugins/ivp-parser.bundle.cjs`); regenerate with `npm run update:ivp-parser`.
|
|
6
|
+
|
|
7
|
+
## 6.2.6 (2026-05-26)
|
|
8
|
+
|
|
9
|
+
* `grok s tables upload` — accepts `.d42` binary blobs in addition to `.csv`. Content-Type is auto-detected from the file extension (`application/octet-stream` for d42, `text/csv` otherwise); server content-negotiates and persists either form against the same `/public/v1/tables/{name}` endpoint.
|
|
10
|
+
|
|
3
11
|
## 6.2.5 (2026-05-21)
|
|
4
12
|
|
|
5
13
|
* `grok report read` — renamed `--extract-actions` to `--extract-client-log`; sidecar is now `<stem>_client_log.json`. The old flag is no longer accepted.
|
package/GROK_S.md
CHANGED
|
@@ -27,7 +27,7 @@ browser or a logged-in session.
|
|
|
27
27
|
| Run a registered function | `grok s functions run 'Pkg:fn(arg1,arg2)'` |
|
|
28
28
|
| List functions with rich filters | `grok s functions list --type script --language python --package Chem` |
|
|
29
29
|
| List / browse files in a file share | `grok s files list "System:AppData" -r` |
|
|
30
|
-
| Upload / download a table (CSV)
|
|
30
|
+
| Upload / download a table (CSV or d42) | `grok s tables upload <name> file.csv\|file.d42` / `tables download <name> -O out.csv` |
|
|
31
31
|
| Check whether a package is deployed | `grok s packages list --filter "MyPlugin"` |
|
|
32
32
|
| Hit any undocumented endpoint | `grok s raw GET /api/users/current` |
|
|
33
33
|
| Check server + per-module health | `grok s healthcheck [--module <name>]` |
|
|
@@ -205,21 +205,26 @@ the `source` before sending).
|
|
|
205
205
|
|
|
206
206
|
## Tables
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
Table I/O — the shell counterpart to Python's `grok.tables.upload/download`.
|
|
209
209
|
Unlike `files put`, this registers a proper Datagrok table entity (returns `{ID, ...}`).
|
|
210
210
|
|
|
211
211
|
```bash
|
|
212
212
|
grok s tables upload MyTable ./data.csv # CSV → table
|
|
213
|
+
grok s tables upload MyTable ./data.d42 # d42 binary → table
|
|
213
214
|
grok s tables upload MyTable ./data.csv --output json # get ID and markup back
|
|
214
215
|
grok s tables download MyTable # CSV to stdout (pipe-friendly)
|
|
215
216
|
grok s tables download MyTable -O ./data.csv # CSV to a local file
|
|
216
217
|
grok s tables download <uuid> # UUID or namespace:name both work
|
|
217
218
|
```
|
|
218
219
|
|
|
219
|
-
Upload streams raw bytes
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
220
|
+
Upload streams raw bytes — `Content-Type: text/csv` for `.csv` and
|
|
221
|
+
`application/octet-stream` for `.d42` (auto-detected from the file extension). Both
|
|
222
|
+
formats handle large tables without loading the whole file into a JSON envelope.
|
|
223
|
+
`-O` / `--output-file` avoids colliding with the format flag (`--output
|
|
224
|
+
table|json|csv|quiet`), which still controls how the upload result is printed.
|
|
225
|
+
|
|
226
|
+
Download is CSV-only — the server reads the stored d42 blob and converts. If you
|
|
227
|
+
need the raw d42 bytes, hit `/tables/data/<id>` directly via `grok s raw`.
|
|
223
228
|
|
|
224
229
|
## Server health
|
|
225
230
|
|
package/bin/commands/server.js
CHANGED
|
@@ -8,6 +8,7 @@ exports.parseFuncCall = parseFuncCall;
|
|
|
8
8
|
exports.resolveManifestSources = resolveManifestSources;
|
|
9
9
|
exports.server = server;
|
|
10
10
|
var fs = _interopRequireWildcard(require("fs"));
|
|
11
|
+
var path = _interopRequireWildcard(require("path"));
|
|
11
12
|
var _nodeDapi = require("../utils/node-dapi");
|
|
12
13
|
var _serverClient = require("../utils/server-client");
|
|
13
14
|
var _serverOutput = require("../utils/server-output");
|
|
@@ -169,14 +170,15 @@ async function handleTablesDownload(dapi, rest, argv, output) {
|
|
|
169
170
|
async function handleTablesUpload(dapi, rest, output) {
|
|
170
171
|
const [name, localPath] = rest;
|
|
171
172
|
if (!name || !localPath) {
|
|
172
|
-
(0, _serverOutput.printError)(new Error('Usage: grok s tables upload <name> <file.csv>'));
|
|
173
|
+
(0, _serverOutput.printError)(new Error('Usage: grok s tables upload <name> <file.csv|file.d42>'));
|
|
173
174
|
return false;
|
|
174
175
|
}
|
|
175
176
|
if (!fs.existsSync(localPath)) {
|
|
176
177
|
(0, _serverOutput.printError)(new Error(`Local file not found: ${localPath}`));
|
|
177
178
|
return false;
|
|
178
179
|
}
|
|
179
|
-
const
|
|
180
|
+
const ct = path.extname(localPath).toLowerCase() === '.d42' ? 'application/octet-stream' : 'text/csv';
|
|
181
|
+
const result = await dapi.tables.upload(name, localPath, ct);
|
|
180
182
|
if (output === 'quiet') console.log(result?.ID ?? result?.id ?? '');else (0, _serverOutput.printOutput)(result, output);
|
|
181
183
|
return true;
|
|
182
184
|
}
|
|
@@ -611,7 +613,7 @@ Special commands:
|
|
|
611
613
|
grok s groups list-memberships <group> [--admin] List parent groups
|
|
612
614
|
grok s users block <id-or-login> Block a user from the platform
|
|
613
615
|
grok s users unblock <id-or-login> Unblock a previously blocked user
|
|
614
|
-
grok s tables upload <name> <file.csv>
|
|
616
|
+
grok s tables upload <name> <file.csv|file.d42> Upload a CSV or d42 binary as a Datagrok table
|
|
615
617
|
grok s tables download <name-or-id> [-O <file>] Download a table as CSV (stdout by default)
|
|
616
618
|
grok s batch <entity> <verb> arg1 [arg2 ...] Batch operation (one round-trip)
|
|
617
619
|
grok s batch <entity> <verb> --json params.json Batch from JSON array
|
package/bin/utils/node-dapi.js
CHANGED
|
@@ -502,12 +502,16 @@ class NodeTablesDataSource {
|
|
|
502
502
|
return result;
|
|
503
503
|
}
|
|
504
504
|
|
|
505
|
-
/**
|
|
506
|
-
|
|
505
|
+
/**
|
|
506
|
+
* POST /public/v1/tables/{name} with raw bytes. Returns `{ID, Grok name, Markup, URL}`.
|
|
507
|
+
* Defaults to `text/csv`; pass `application/octet-stream` to upload a `.d42`
|
|
508
|
+
* binary blob — the server content-negotiates on the header and persists either form.
|
|
509
|
+
*/
|
|
510
|
+
async upload(name, localPath, contentType = 'text/csv') {
|
|
507
511
|
const fs = require('fs');
|
|
508
512
|
const bytes = fs.readFileSync(localPath);
|
|
509
513
|
const seg = encodeURIComponent(name.replace(/:/g, '.'));
|
|
510
|
-
return this.client.putBytes(`/public/v1/tables/${seg}`, bytes,
|
|
514
|
+
return this.client.putBytes(`/public/v1/tables/${seg}`, bytes, contentType);
|
|
511
515
|
}
|
|
512
516
|
}
|
|
513
517
|
exports.NodeTablesDataSource = NodeTablesDataSource;
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# tools/download-demo-datasets.R
|
|
2
|
+
#
|
|
3
|
+
# Выгружает три демо-датасета для ANOVA UI:
|
|
4
|
+
# - InsectSprays (R datasets package) → CSV
|
|
5
|
+
# - ToothGrowth (R datasets package) → CSV
|
|
6
|
+
# - SiRstv (NIST StRD) → CSV
|
|
7
|
+
#
|
|
8
|
+
# Запуск из корня репозитория:
|
|
9
|
+
# Rscript tools/download-demo-datasets.R
|
|
10
|
+
#
|
|
11
|
+
# Результат сохраняется в packages/EDA/demo-data/.
|
|
12
|
+
|
|
13
|
+
OUT_DIR <- "packages/EDA/demo-data"
|
|
14
|
+
dir.create(OUT_DIR, showWarnings = FALSE, recursive = TRUE)
|
|
15
|
+
|
|
16
|
+
# ─── 1. InsectSprays ──────────────────────────────────────────────────
|
|
17
|
+
data(InsectSprays)
|
|
18
|
+
write.csv(InsectSprays,
|
|
19
|
+
file.path(OUT_DIR, "insect-sprays.csv"),
|
|
20
|
+
row.names = FALSE,
|
|
21
|
+
fileEncoding = "UTF-8")
|
|
22
|
+
cat("Wrote insect-sprays.csv (", nrow(InsectSprays), "rows )\n")
|
|
23
|
+
|
|
24
|
+
# ─── 2. ToothGrowth ───────────────────────────────────────────────────
|
|
25
|
+
data(ToothGrowth)
|
|
26
|
+
write.csv(ToothGrowth,
|
|
27
|
+
file.path(OUT_DIR, "tooth-growth.csv"),
|
|
28
|
+
row.names = FALSE,
|
|
29
|
+
fileEncoding = "UTF-8")
|
|
30
|
+
cat("Wrote tooth-growth.csv (", nrow(ToothGrowth), "rows )\n")
|
|
31
|
+
|
|
32
|
+
# ─── 3. SiRstv (NIST StRD) ────────────────────────────────────────────
|
|
33
|
+
# NIST .dat файл имеет большой текстовый header; данные идут после
|
|
34
|
+
# строки "Data: Instrument Resistance" (заголовок встроен в маркер).
|
|
35
|
+
nist_url <- "https://www.itl.nist.gov/div898/strd/anova/SiRstv.dat"
|
|
36
|
+
# На Windows libcurl падает на NIST SSL (CRYPT_E_NO_REVOCATION_CHECK).
|
|
37
|
+
# wininet использует системный HTTP-стек и работает; на других ОС — стандартный путь.
|
|
38
|
+
read_nist <- function(u) {
|
|
39
|
+
if (.Platform$OS.type == "windows") {
|
|
40
|
+
tmp <- tempfile()
|
|
41
|
+
suppressWarnings(download.file(u, tmp, quiet = TRUE, method = "wininet"))
|
|
42
|
+
readLines(tmp)
|
|
43
|
+
} else {
|
|
44
|
+
readLines(url(u))
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
lines <- read_nist(nist_url)
|
|
48
|
+
data_start <- grep("^\\s*Data:\\s+Instrument", lines)
|
|
49
|
+
if (length(data_start) != 1)
|
|
50
|
+
stop("Cannot locate 'Data: Instrument' marker in SiRstv.dat")
|
|
51
|
+
|
|
52
|
+
# Пропустить только строку "Data:..." — следующая уже содержит данные.
|
|
53
|
+
data_lines <- lines[(data_start + 1):length(lines)]
|
|
54
|
+
data_lines <- data_lines[nzchar(trimws(data_lines))]
|
|
55
|
+
|
|
56
|
+
# В каждой строке два числа через whitespace: Instrument Resistance
|
|
57
|
+
sirstv <- read.table(text = data_lines,
|
|
58
|
+
col.names = c("Instrument", "Resistance"))
|
|
59
|
+
sirstv$Instrument <- as.integer(sirstv$Instrument)
|
|
60
|
+
|
|
61
|
+
write.csv(sirstv,
|
|
62
|
+
file.path(OUT_DIR, "silicon-resistivity.csv"),
|
|
63
|
+
row.names = FALSE,
|
|
64
|
+
fileEncoding = "UTF-8")
|
|
65
|
+
cat("Wrote silicon-resistivity.csv (", nrow(sirstv), "rows )\n")
|
|
66
|
+
|
|
67
|
+
cat("\nAll three demo datasets written to", OUT_DIR, "\n")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "datagrok-tools",
|
|
3
|
-
"version": "6.
|
|
3
|
+
"version": "6.3.0",
|
|
4
4
|
"description": "Utility to upload and publish packages to Datagrok",
|
|
5
5
|
"homepage": "https://github.com/datagrok-ai/public/tree/master/tools#readme",
|
|
6
6
|
"dependencies": {
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"prepublishOnly": "babel bin --extensions .ts -d bin",
|
|
33
33
|
"babel": "babel bin --extensions .ts -d bin",
|
|
34
34
|
"build": "babel bin --extensions .ts -d bin",
|
|
35
|
+
"update:ivp-parser": "esbuild plugins/ivp-parser.entry.mjs --bundle --format=cjs --platform=node --alias:diff-grok=../libraries/compute-utils/node_modules/diff-grok --outfile=plugins/ivp-parser.bundle.cjs",
|
|
35
36
|
"debug-source-map": "babel bin --extensions .ts -d bin --source-maps true",
|
|
36
37
|
"test": "vitest run --project unit",
|
|
37
38
|
"test:watch": "vitest --project unit",
|
|
@@ -66,6 +67,7 @@
|
|
|
66
67
|
"@babel/cli": "^7.23.4",
|
|
67
68
|
"@babel/core": "^7.23.7",
|
|
68
69
|
"@babel/plugin-proposal-decorators": "^7.23.7",
|
|
70
|
+
"esbuild": "^0.27.7",
|
|
69
71
|
"@babel/plugin-transform-runtime": "^7.23.7",
|
|
70
72
|
"@babel/preset-env": "^7.23.8",
|
|
71
73
|
"@babel/preset-typescript": "7.15.0",
|
|
@@ -19,6 +19,88 @@ const {toCamelCase} = require('../bin/commands/migrate');
|
|
|
19
19
|
|
|
20
20
|
const {api} = require('../bin/commands/api');
|
|
21
21
|
|
|
22
|
+
// Prebuilt CJS bundle of diff-grok's IVP parser (`getIVP` + a couple constants), tree-shaken
|
|
23
|
+
// to exclude the script-code generator. Regenerate with `npm run update:ivp-parser`.
|
|
24
|
+
function loadIvpParser() {
|
|
25
|
+
try {
|
|
26
|
+
const parser = require('./ivp-parser.bundle.cjs');
|
|
27
|
+
if (typeof parser.getIVP !== 'function') {
|
|
28
|
+
console.warn('[func-gen] ivp-parser.bundle.cjs has no getIVP export — skipping ivp models');
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
return parser;
|
|
32
|
+
} catch (e) {
|
|
33
|
+
console.warn(`[func-gen] could not load ivp-parser.bundle.cjs (run "npm run update:ivp-parser"): ${e.message}`);
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Derive a function signature from a `.ivp` model using diff-grok's parser. Builds the
|
|
39
|
+
// `//input:` / `//output:` lines directly from the parsed object (input annotations are stored
|
|
40
|
+
// verbatim on it), matching the runnable script's names exactly — no codegen needed.
|
|
41
|
+
function preparseIvpModel(parser, text) {
|
|
42
|
+
const {getIVP, MAX_LINE_CHART, STAGE_COL_NAME} = parser;
|
|
43
|
+
let ivp;
|
|
44
|
+
try {
|
|
45
|
+
ivp = getIVP(text);
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
if (!ivp || !ivp.name) return null;
|
|
50
|
+
|
|
51
|
+
// User-facing input names carry no `_` prefix, so that `propagateChoice` lookups can map their
|
|
52
|
+
// dataframe columns to inputs by exact name. `scriptKey` is the name the generated DiffStudio
|
|
53
|
+
// script expects (arg bounds/step and the loop count are `_`-prefixed there) and is used only
|
|
54
|
+
// when forwarding values to `runDiffStudioModel`.
|
|
55
|
+
const mk = (type, name, scriptKey, input) =>
|
|
56
|
+
({tsType: 'number', name, scriptKey,
|
|
57
|
+
annotation: `//input: ${type} ${name} = ${input.value} ${input.annot ?? ''}`.trim()});
|
|
58
|
+
|
|
59
|
+
const inputs = [];
|
|
60
|
+
const a = ivp.arg.name;
|
|
61
|
+
if (ivp.loop) inputs.push(mk('int', 'count', '_count', ivp.loop.count));
|
|
62
|
+
inputs.push(mk('double', `${a}0`, `_${a}0`, ivp.arg.initial));
|
|
63
|
+
inputs.push(mk('double', `${a}1`, `_${a}1`, ivp.arg.final));
|
|
64
|
+
inputs.push(mk('double', 'h', '_h', ivp.arg.step));
|
|
65
|
+
for (const [k, v] of ivp.inits) inputs.push(mk('double', k, k, v));
|
|
66
|
+
if (ivp.params) for (const [k, v] of ivp.params) inputs.push(mk('double', k, k, v));
|
|
67
|
+
|
|
68
|
+
const cols = ivp.outputs ? ivp.outputs.size : ivp.inits.size;
|
|
69
|
+
const multiAxis = cols > MAX_LINE_CHART - 1 ? 'true' : 'false';
|
|
70
|
+
const segments = ivp.updates ? ` segmentColumnName: "${STAGE_COL_NAME}",` : '';
|
|
71
|
+
const viewer = `Grid(block: 100) | Line chart(block: 100, multiAxis: "${multiAxis}",${segments} ` +
|
|
72
|
+
`multiAxisLegendPosition: "RightCenter", autoLayout: "false", showAggrSelectors: "false")`;
|
|
73
|
+
const outputAnnotation = `//output: dataframe df {caption: ${ivp.name}; viewer: ${viewer}}`;
|
|
74
|
+
|
|
75
|
+
const metas = {runOnOpen: 'true', runOnInput: 'true', features: '{"sens-analysis": true, "fitting": true}'};
|
|
76
|
+
let isModel = false;
|
|
77
|
+
for (const meta of ivp.metas || []) {
|
|
78
|
+
const m = /^(?:\/\/)?meta\.([\w-]+):\s*(.*)$/.exec(String(meta).trim());
|
|
79
|
+
if (!m) continue;
|
|
80
|
+
if (m[1] === 'role') {
|
|
81
|
+
if (m[2].split(',').map((s) => s.trim()).includes('model')) isModel = true;
|
|
82
|
+
} else if (m[1] !== 'solver') metas[m[1]] = m[2];
|
|
83
|
+
}
|
|
84
|
+
// Convert a `#meta.inputs` lookup into a real `propagateChoice: all` string input. RFV renders
|
|
85
|
+
// this natively (it ignores `meta.inputs`); selecting a row fills the matching inputs by name.
|
|
86
|
+
// Name / brace parsing mirrors DiffStudio's getLookupsInfo (utils.ts): the name is the text
|
|
87
|
+
// before the first `{` with spaces stripped, and the options block ends at the first `}`.
|
|
88
|
+
if (ivp.inputsLookup) {
|
|
89
|
+
const raw = ivp.inputsLookup.trim();
|
|
90
|
+
const open = raw.indexOf('{');
|
|
91
|
+
const close = raw.indexOf('}');
|
|
92
|
+
if (open >= 0 && close >= 0) {
|
|
93
|
+
const name = raw.slice(0, open).replaceAll(' ', '');
|
|
94
|
+
inputs.unshift({
|
|
95
|
+
tsType: 'string', name, scriptKey: null,
|
|
96
|
+
annotation: `//input: string ${raw.slice(0, close)}; propagateChoice: all${raw.slice(close)}`.trim(),
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {name: ivp.name, description: ivp.descr || undefined, isModel, inputs, outputAnnotation, metas};
|
|
102
|
+
}
|
|
103
|
+
|
|
22
104
|
const baseImport = 'import * as DG from \'datagrok-api/dg\';\n';
|
|
23
105
|
|
|
24
106
|
function normEol(s) {
|
|
@@ -91,6 +173,7 @@ class FuncGeneratorPlugin {
|
|
|
91
173
|
this._insertImports([...imports]);
|
|
92
174
|
fs.appendFileSync(this.options.outputPath, normEol(functions.join('')), 'utf-8');
|
|
93
175
|
}
|
|
176
|
+
this._generateIvpModels(compiler.context);
|
|
94
177
|
this._checkPackageFileForDecoratorsExport(packageFilePath);
|
|
95
178
|
// Uncommment to add obvious import/export
|
|
96
179
|
// this._writeToPackageFile(packageFilePath, genImports, genExports);
|
|
@@ -515,6 +598,74 @@ class FuncGeneratorPlugin {
|
|
|
515
598
|
|
|
516
599
|
}
|
|
517
600
|
|
|
601
|
+
// Scan the package's `files/` tree for `.ivp` models. Each one with a `#meta.role: model`
|
|
602
|
+
// line is preparsed into a function signature and emitted as a Rich-Function-View model
|
|
603
|
+
// wrapper that links back to the deployed `.ivp` (multithreaded fitting reads that link).
|
|
604
|
+
// Parse failures are warned, never thrown, so a malformed `.ivp` cannot break the build.
|
|
605
|
+
_generateIvpModels(context) {
|
|
606
|
+
const filesDir = path.join(context, 'files');
|
|
607
|
+
if (!fs.existsSync(filesDir)) return;
|
|
608
|
+
|
|
609
|
+
const parser = loadIvpParser();
|
|
610
|
+
if (!parser) return;
|
|
611
|
+
|
|
612
|
+
const ivpFiles = fs.readdirSync(filesDir, {recursive: true})
|
|
613
|
+
.filter((f) => typeof f === 'string' && f.endsWith('.ivp'))
|
|
614
|
+
.map((f) => path.join(filesDir, f));
|
|
615
|
+
|
|
616
|
+
const namespace = path.basename(context);
|
|
617
|
+
const blocks = [];
|
|
618
|
+
for (const file of ivpFiles) {
|
|
619
|
+
const rel = path.relative(context, file).replace(/\\/g, '/');
|
|
620
|
+
let res;
|
|
621
|
+
try {
|
|
622
|
+
res = preparseIvpModel(parser, fs.readFileSync(file, 'utf-8'));
|
|
623
|
+
} catch (e) {
|
|
624
|
+
console.warn(`[func-gen] skipped ivp model ${rel}: ${e.message}`);
|
|
625
|
+
continue;
|
|
626
|
+
}
|
|
627
|
+
if (!res) {
|
|
628
|
+
console.warn(`[func-gen] skipped ivp model ${rel}: missing #name`);
|
|
629
|
+
continue;
|
|
630
|
+
}
|
|
631
|
+
if (!res.isModel) continue;
|
|
632
|
+
|
|
633
|
+
const modelPath = `System:AppData/${namespace}/${path.relative(filesDir, file).replace(/\\/g, '/')}`;
|
|
634
|
+
const fnName = 'ivpModel_' + res.name.replace(/[^A-Za-z0-9]/g, '_');
|
|
635
|
+
const argList = res.inputs.map((i) => `${i.name}: ${i.tsType}`).join(', ');
|
|
636
|
+
const argObj = res.inputs
|
|
637
|
+
.filter((i) => i.scriptKey)
|
|
638
|
+
.map((i) => i.scriptKey === i.name ? i.name : `${i.scriptKey}: ${i.name}`)
|
|
639
|
+
.join(', ');
|
|
640
|
+
// res.metas already merges the shared Diff Studio defaults with the model's own #meta.*.
|
|
641
|
+
const meta = {
|
|
642
|
+
...res.metas,
|
|
643
|
+
role: 'model',
|
|
644
|
+
diffStudioModel: modelPath,
|
|
645
|
+
};
|
|
646
|
+
// Emit name / description / editor / meta through the canonical annotation generator
|
|
647
|
+
// (handles meta key translation); keep the IVP-derived //input / //output lines verbatim
|
|
648
|
+
// so they match the runnable script exactly.
|
|
649
|
+
const header = getFuncAnnotation({
|
|
650
|
+
name: res.name,
|
|
651
|
+
description: res.description,
|
|
652
|
+
editor: 'Compute2:RichFunctionViewEditor',
|
|
653
|
+
meta,
|
|
654
|
+
inputs: [],
|
|
655
|
+
outputs: [],
|
|
656
|
+
}).trimEnd();
|
|
657
|
+
const annotations = [header, ...res.inputs.map((i) => i.annotation), res.outputAnnotation].join('\n');
|
|
658
|
+
blocks.push(`${annotations}\nexport async function ${fnName}(${argList}): Promise<DG.DataFrame> {\n` +
|
|
659
|
+
` return await runDiffStudioModel('${modelPath}', {${argObj}});\n}\n`);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
if (blocks.length === 0) return;
|
|
663
|
+
const importLine = `import {runDiffStudioModel} from './ivp-runtime';\n`;
|
|
664
|
+
let content = fs.readFileSync(this.options.outputPath, 'utf-8');
|
|
665
|
+
if (!content.includes(importLine)) content = content.replace(baseImport, baseImport + importLine);
|
|
666
|
+
content += '\n' + blocks.join('\n');
|
|
667
|
+
fs.writeFileSync(this.options.outputPath, normEol(content), 'utf-8');
|
|
668
|
+
}
|
|
518
669
|
_clearGeneratedFile() {
|
|
519
670
|
fs.writeFileSync(this.options.outputPath, normEol(baseImport));
|
|
520
671
|
}
|
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// plugins/ivp-parser.entry.mjs
|
|
21
|
+
var ivp_parser_entry_exports = {};
|
|
22
|
+
__export(ivp_parser_entry_exports, {
|
|
23
|
+
MAX_LINE_CHART: () => MAX_LINE_CHART,
|
|
24
|
+
STAGE_COL_NAME: () => STAGE_COL_NAME,
|
|
25
|
+
getIVP: () => getIVP
|
|
26
|
+
});
|
|
27
|
+
module.exports = __toCommonJS(ivp_parser_entry_exports);
|
|
28
|
+
|
|
29
|
+
// ../libraries/compute-utils/node_modules/diff-grok/dist/src/scripting-tools/constants.js
|
|
30
|
+
var CONTROL_TAG = "#";
|
|
31
|
+
var CONTROL_TAG_LEN = CONTROL_TAG.length;
|
|
32
|
+
var MAX_LINE_CHART = 4;
|
|
33
|
+
var CONTROL_EXPR;
|
|
34
|
+
(function(CONTROL_EXPR2) {
|
|
35
|
+
CONTROL_EXPR2["NAME"] = "#name";
|
|
36
|
+
CONTROL_EXPR2["TAGS"] = "#tags";
|
|
37
|
+
CONTROL_EXPR2["DESCR"] = "#description";
|
|
38
|
+
CONTROL_EXPR2["DIF_EQ"] = "#equations";
|
|
39
|
+
CONTROL_EXPR2["EXPR"] = "#expressions";
|
|
40
|
+
CONTROL_EXPR2["ARG"] = "#argument";
|
|
41
|
+
CONTROL_EXPR2["INITS"] = "#inits";
|
|
42
|
+
CONTROL_EXPR2["CONSTS"] = "#constants";
|
|
43
|
+
CONTROL_EXPR2["PARAMS"] = "#parameters";
|
|
44
|
+
CONTROL_EXPR2["TOL"] = "#tolerance";
|
|
45
|
+
CONTROL_EXPR2["LOOP"] = "#loop";
|
|
46
|
+
CONTROL_EXPR2["UPDATE"] = "#update";
|
|
47
|
+
CONTROL_EXPR2["RUN_ON_OPEN"] = "#meta.runOnOpen";
|
|
48
|
+
CONTROL_EXPR2["RUN_ON_INPUT"] = "#meta.runOnInput";
|
|
49
|
+
CONTROL_EXPR2["OUTPUT"] = "#output";
|
|
50
|
+
CONTROL_EXPR2["COMMENT"] = "#comment";
|
|
51
|
+
CONTROL_EXPR2["SOLVER"] = "#meta.solver";
|
|
52
|
+
CONTROL_EXPR2["INPUTS"] = "#meta.inputs";
|
|
53
|
+
})(CONTROL_EXPR || (CONTROL_EXPR = {}));
|
|
54
|
+
var LOOP;
|
|
55
|
+
(function(LOOP2) {
|
|
56
|
+
LOOP2[LOOP2["MIN_LINES_COUNT"] = 1] = "MIN_LINES_COUNT";
|
|
57
|
+
LOOP2[LOOP2["COUNT_IDX"] = 0] = "COUNT_IDX";
|
|
58
|
+
LOOP2["COUNT_NAME"] = "_count";
|
|
59
|
+
LOOP2[LOOP2["MIN_COUNT"] = 1] = "MIN_COUNT";
|
|
60
|
+
})(LOOP || (LOOP = {}));
|
|
61
|
+
var UPDATE;
|
|
62
|
+
(function(UPDATE2) {
|
|
63
|
+
UPDATE2[UPDATE2["MIN_LINES_COUNT"] = 1] = "MIN_LINES_COUNT";
|
|
64
|
+
UPDATE2[UPDATE2["DURATION_IDX"] = 0] = "DURATION_IDX";
|
|
65
|
+
UPDATE2["DURATION"] = "_duration";
|
|
66
|
+
})(UPDATE || (UPDATE = {}));
|
|
67
|
+
var SOLVER_OPTIONS_RANGES = /* @__PURE__ */ new Map([
|
|
68
|
+
["maxTime", { min: 1, max: 1e4 }],
|
|
69
|
+
["scale", { min: 0.5, max: 1 }]
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
// ../libraries/compute-utils/node_modules/diff-grok/dist/src/scripting-tools/model-error.js
|
|
73
|
+
var ModelError = class extends Error {
|
|
74
|
+
helpUrl;
|
|
75
|
+
toHighlight = void 0;
|
|
76
|
+
constructor(message, helpUrl, toHighlight) {
|
|
77
|
+
super(message);
|
|
78
|
+
this.helpUrl = helpUrl;
|
|
79
|
+
this.toHighlight = toHighlight;
|
|
80
|
+
}
|
|
81
|
+
getHelpUrl() {
|
|
82
|
+
return this.helpUrl;
|
|
83
|
+
}
|
|
84
|
+
getToHighlight() {
|
|
85
|
+
return this.toHighlight;
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// ../libraries/compute-utils/node_modules/diff-grok/dist/src/scripting-tools/scripting-tools.js
|
|
90
|
+
var CONTROL_SEP = ":";
|
|
91
|
+
var COMMA = ",";
|
|
92
|
+
var EQUAL_SIGN = "=";
|
|
93
|
+
var DIV_SIGN = "/";
|
|
94
|
+
var SERVICE = "_";
|
|
95
|
+
var BRACE_OPEN = "{";
|
|
96
|
+
var BRACE_CLOSE = "}";
|
|
97
|
+
var ANNOT_SEPAR = ";";
|
|
98
|
+
var DEFAULT_TOL = "0.00005";
|
|
99
|
+
var DEFAULT_SOLVER_SETTINGS = "{}";
|
|
100
|
+
var COMMENT_SEQ = "//";
|
|
101
|
+
var STAGE_COL_NAME = `_Stage`;
|
|
102
|
+
var MATH_FUNCS = ["pow", "sin", "cos", "tan", "asin", "acos", "atan", "sqrt", "exp", "log", "sinh", "cosh", "tanh"];
|
|
103
|
+
var POW_IDX = MATH_FUNCS.indexOf("pow");
|
|
104
|
+
var MATH_CONSTS = ["PI", "E"];
|
|
105
|
+
var SCRIPTING;
|
|
106
|
+
(function(SCRIPTING2) {
|
|
107
|
+
SCRIPTING2["ARG_NAME"] = "name";
|
|
108
|
+
SCRIPTING2["COUNT"] = "count";
|
|
109
|
+
SCRIPTING2["DURATION"] = "duration";
|
|
110
|
+
})(SCRIPTING || (SCRIPTING = {}));
|
|
111
|
+
var ERROR_LINK;
|
|
112
|
+
(function(ERROR_LINK2) {
|
|
113
|
+
ERROR_LINK2["MAIN_DOCS"] = "/help/compute/diff-studio";
|
|
114
|
+
ERROR_LINK2["CORE_BLOCKS"] = "/help/compute/diff-studio#core-blocks";
|
|
115
|
+
ERROR_LINK2["COMPS_SYNTAX"] = "/help/compute/diff-studio#model-components-and-syntax";
|
|
116
|
+
ERROR_LINK2["LOOP"] = "/help/compute/diff-studio#cyclic-processes";
|
|
117
|
+
ERROR_LINK2["UPDATE"] = "/help/compute/diff-studio#multistage-processes";
|
|
118
|
+
ERROR_LINK2["UI_OPTS"] = "/help/compute/diff-studio#user-interface-options";
|
|
119
|
+
ERROR_LINK2["LOOP_VS_UPDATE"] = "/help/compute/diff-studio#advanced-features";
|
|
120
|
+
ERROR_LINK2["SOLVER_CONFIG"] = "/help/compute/diff-studio#solver-configuration";
|
|
121
|
+
ERROR_LINK2["MODEL_PARAMS"] = "/help/compute/diff-studio#model-parameters";
|
|
122
|
+
})(ERROR_LINK || (ERROR_LINK = {}));
|
|
123
|
+
var ERROR_MSG;
|
|
124
|
+
(function(ERROR_MSG2) {
|
|
125
|
+
ERROR_MSG2["CTRL_EXPR"] = 'Unsupported control expression with the tag **"#"**';
|
|
126
|
+
ERROR_MSG2["ARG"] = "'The **#argument** block must consist of 3 lines specifying initial and final time, and solution grid step.";
|
|
127
|
+
ERROR_MSG2["LOOP"] = "The **#loop** block must contain at least one line.";
|
|
128
|
+
ERROR_MSG2["COUNT"] = "Incorrect loop count";
|
|
129
|
+
ERROR_MSG2["LOOP_VS_UPDATE"] = "The **#loop** and **'#update'** blocks cannot be used simultaneously.";
|
|
130
|
+
ERROR_MSG2["UPDATE_LINES_COUNT"] = "The **'#update'** block must contain at least one line.";
|
|
131
|
+
ERROR_MSG2["DURATION"] = "Incorrect update duration";
|
|
132
|
+
ERROR_MSG2["BRACES"] = " Missing one of the braces (**{**, **}**).";
|
|
133
|
+
ERROR_MSG2["COLON"] = 'Incorrect position of **":"**.';
|
|
134
|
+
ERROR_MSG2["CASE_INSENS"] = "Non-unique name (case-insensitive): use different caption for ";
|
|
135
|
+
ERROR_MSG2["MISSING_INIT"] = "Correct the **#inits** block.";
|
|
136
|
+
ERROR_MSG2["UNDEF_NAME"] = "Model name missing. Specify the model name in the **#name** block.";
|
|
137
|
+
ERROR_MSG2["UNDEF_DEQS"] = "Differential equation(s) are required for this model. Add equation(s) under the **#equations** block.";
|
|
138
|
+
ERROR_MSG2["UNDEF_INITS"] = "Initial conditions are required for this model. Add initial conditions under the **#inits** block.";
|
|
139
|
+
ERROR_MSG2["UNDEF_ARG"] = "Argument specification is required for this model. Specify an argument, its range, and a grid step in the **#argument** block.";
|
|
140
|
+
ERROR_MSG2["CORRECT_ARG_LIM"] = "Correct limits in the **#argument** block.";
|
|
141
|
+
ERROR_MSG2["INTERVAL"] = "Incorrect range for";
|
|
142
|
+
ERROR_MSG2["NEGATIVE_STEP"] = "Solution grid step must be positive. Correct the **#argument** block.";
|
|
143
|
+
ERROR_MSG2["INCOR_STEP"] = "Grid step must less than the length of solution interval. Correct the **#argument** block.";
|
|
144
|
+
ERROR_MSG2["MISS_COLON"] = 'Missing **":"**';
|
|
145
|
+
ERROR_MSG2["NAN"] = "is not a valid number. Correct the line";
|
|
146
|
+
ERROR_MSG2["SERVICE_START"] = 'Variable names must not begin with **"_"**.';
|
|
147
|
+
ERROR_MSG2["REUSE_NAME"] = "Variable reuse (case-insensitive): rename ";
|
|
148
|
+
ERROR_MSG2["SOLVER"] = "Incorrect solver options. Correct the **#meta.solver** line.";
|
|
149
|
+
})(ERROR_MSG || (ERROR_MSG = {}));
|
|
150
|
+
var ANNOT;
|
|
151
|
+
(function(ANNOT2) {
|
|
152
|
+
ANNOT2["NAME"] = "//name:";
|
|
153
|
+
ANNOT2["DESCR"] = "//description:";
|
|
154
|
+
ANNOT2["TAGS"] = "//tags:";
|
|
155
|
+
ANNOT2["LANG"] = "//language: javascript";
|
|
156
|
+
ANNOT2["DOUBLE_INPUT"] = "//input: double";
|
|
157
|
+
ANNOT2["INT_INPUT"] = "//input: int";
|
|
158
|
+
ANNOT2["OUTPUT"] = "//output: dataframe df";
|
|
159
|
+
ANNOT2["EDITOR"] = "//editor: Compute:RichFunctionViewEditor";
|
|
160
|
+
ANNOT2["SIDEBAR"] = "//sidebar: @compute";
|
|
161
|
+
ANNOT2["CAPTION"] = "caption:";
|
|
162
|
+
ANNOT2["ARG_INIT"] = "{caption: Initial; category: Argument}";
|
|
163
|
+
ANNOT2["ARG_FIN"] = "{caption: Final; category: Argument}";
|
|
164
|
+
ANNOT2["ARG_STEP"] = "{caption: Step; category: Argument}";
|
|
165
|
+
ANNOT2["INITS"] = "category: Initial values";
|
|
166
|
+
ANNOT2["PARAMS"] = "category: Parameters";
|
|
167
|
+
})(ANNOT || (ANNOT = {}));
|
|
168
|
+
var SCRIPT;
|
|
169
|
+
(function(SCRIPT2) {
|
|
170
|
+
SCRIPT2["CONSTS"] = "// constants";
|
|
171
|
+
SCRIPT2["ODE_COM"] = "// the problem definition";
|
|
172
|
+
SCRIPT2["ODE"] = "let odes = {";
|
|
173
|
+
SCRIPT2["SOLVER_COM"] = "// solve the problem";
|
|
174
|
+
SCRIPT2["SOLVER"] = "const solver = await grok.functions.eval('DiffStudio:solveEquations');";
|
|
175
|
+
SCRIPT2["PREPARE"] = "let call = solver.prepare({problem: odes, options: opts});";
|
|
176
|
+
SCRIPT2["CALL"] = "await call.call();";
|
|
177
|
+
SCRIPT2["OUTPUT"] = "let df = call.getParamValue('df');";
|
|
178
|
+
SCRIPT2["SPACE2"] = " ";
|
|
179
|
+
SCRIPT2["SPACE4"] = " ";
|
|
180
|
+
SCRIPT2["SPACE6"] = " ";
|
|
181
|
+
SCRIPT2["SPACE8"] = " ";
|
|
182
|
+
SCRIPT2["FUNC_VALS"] = "// extract function values";
|
|
183
|
+
SCRIPT2["EVAL_EXPR"] = "// evaluate expressions";
|
|
184
|
+
SCRIPT2["COMP_OUT"] = "// compute output";
|
|
185
|
+
SCRIPT2["MATH_FUNC_COM"] = "// used Math-functions";
|
|
186
|
+
SCRIPT2["MATH_CONST_COM"] = "// used Math-constants";
|
|
187
|
+
SCRIPT2["ONE_STAGE_COM"] = "\n// one stage solution";
|
|
188
|
+
SCRIPT2["ONE_STAGE_BEGIN"] = "let _oneStage = async (";
|
|
189
|
+
SCRIPT2["ONE_STAGE_END"] = ") => {";
|
|
190
|
+
SCRIPT2["ASYNC_OUTPUT"] = "let df = await _oneStage(";
|
|
191
|
+
SCRIPT2["RETURN_OUTPUT"] = "return call.getParamValue('df');";
|
|
192
|
+
SCRIPT2["EMPTY_OUTPUT"] = "let df = DG.DataFrame.create();";
|
|
193
|
+
SCRIPT2["APPEND"] = "df.append(";
|
|
194
|
+
SCRIPT2["SOLUTION_DF_COM"] = "// solution dataframe";
|
|
195
|
+
SCRIPT2["LOOP_INTERVAL_COM"] = "// loop interval";
|
|
196
|
+
SCRIPT2["LOOP_INTERVAL"] = "_interval";
|
|
197
|
+
SCRIPT2["LAST_IDX"] = "_lastIdx";
|
|
198
|
+
SCRIPT2["UPDATE_COM"] = "// update ";
|
|
199
|
+
SCRIPT2["CUSTOM_OUTPUT_COM"] = "// create custom output";
|
|
200
|
+
SCRIPT2["CUSTOM_COLUMNS"] = "let _columns = [";
|
|
201
|
+
SCRIPT2["ONE_STAGE"] = "await _oneStage(";
|
|
202
|
+
SCRIPT2["SEGMENT_COM"] = "// add segment category";
|
|
203
|
+
})(SCRIPT || (SCRIPT = {}));
|
|
204
|
+
function getStartOfProblemDef(lines) {
|
|
205
|
+
const linesCount = lines.length;
|
|
206
|
+
let idx = 0;
|
|
207
|
+
while (!lines[idx].startsWith(CONTROL_TAG)) {
|
|
208
|
+
++idx;
|
|
209
|
+
if (idx === linesCount)
|
|
210
|
+
throw new ModelError(ERROR_MSG.UNDEF_NAME, ERROR_LINK.CORE_BLOCKS);
|
|
211
|
+
}
|
|
212
|
+
return idx;
|
|
213
|
+
}
|
|
214
|
+
function getBlocks(lines, start) {
|
|
215
|
+
const linesCount = lines.length;
|
|
216
|
+
let beg = start;
|
|
217
|
+
let idx = start + 1;
|
|
218
|
+
const blocks = [];
|
|
219
|
+
while (true) {
|
|
220
|
+
if (idx === linesCount) {
|
|
221
|
+
blocks.push({ begin: beg, end: idx });
|
|
222
|
+
break;
|
|
223
|
+
}
|
|
224
|
+
if (lines[idx].startsWith(CONTROL_TAG)) {
|
|
225
|
+
blocks.push({ begin: beg, end: idx });
|
|
226
|
+
beg = idx;
|
|
227
|
+
}
|
|
228
|
+
++idx;
|
|
229
|
+
}
|
|
230
|
+
return blocks;
|
|
231
|
+
}
|
|
232
|
+
function concatMultilineFormulas(source) {
|
|
233
|
+
const res = [source[0]];
|
|
234
|
+
let idxRes = 0;
|
|
235
|
+
let idxSource = 1;
|
|
236
|
+
const size = source.length;
|
|
237
|
+
while (idxSource < size) {
|
|
238
|
+
const str = source[idxSource];
|
|
239
|
+
if (!str.includes(EQUAL_SIGN))
|
|
240
|
+
res[idxRes] += str;
|
|
241
|
+
else {
|
|
242
|
+
res.push(str);
|
|
243
|
+
++idxRes;
|
|
244
|
+
}
|
|
245
|
+
++idxSource;
|
|
246
|
+
}
|
|
247
|
+
return res;
|
|
248
|
+
}
|
|
249
|
+
function getUsedMathIds(text, mathIds) {
|
|
250
|
+
const res = [];
|
|
251
|
+
const size = mathIds.length;
|
|
252
|
+
for (let i = 0; i < size; ++i) {
|
|
253
|
+
if (text.includes(`${mathIds[i]}`))
|
|
254
|
+
res.push(i);
|
|
255
|
+
}
|
|
256
|
+
return res;
|
|
257
|
+
}
|
|
258
|
+
function getDifEquations(lines) {
|
|
259
|
+
const deqs = /* @__PURE__ */ new Map();
|
|
260
|
+
const names = [];
|
|
261
|
+
let divIdx = 0;
|
|
262
|
+
let eqIdx = 0;
|
|
263
|
+
for (const line of lines) {
|
|
264
|
+
if (line === void 0)
|
|
265
|
+
throw new ModelError(ERROR_MSG.UNDEF_DEQS, ERROR_LINK.CORE_BLOCKS, CONTROL_EXPR.DIF_EQ);
|
|
266
|
+
divIdx = line.indexOf(DIV_SIGN);
|
|
267
|
+
eqIdx = line.indexOf(EQUAL_SIGN);
|
|
268
|
+
const name = line.slice(line.indexOf("d") + 1, divIdx).replace(/[() ]/g, "");
|
|
269
|
+
names.push(name);
|
|
270
|
+
deqs.set(name, line.slice(eqIdx + 1).trim());
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
equations: deqs,
|
|
274
|
+
solutionNames: names
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function getExpressions(lines) {
|
|
278
|
+
const exprs = /* @__PURE__ */ new Map();
|
|
279
|
+
let eqIdx = 0;
|
|
280
|
+
for (const line of lines) {
|
|
281
|
+
if (line === void 0)
|
|
282
|
+
break;
|
|
283
|
+
eqIdx = line.indexOf(EQUAL_SIGN);
|
|
284
|
+
exprs.set(line.slice(0, eqIdx).replaceAll(" ", ""), line.slice(eqIdx + 1).trim());
|
|
285
|
+
}
|
|
286
|
+
return exprs;
|
|
287
|
+
}
|
|
288
|
+
function getInput(line) {
|
|
289
|
+
const str = line.slice(line.indexOf(EQUAL_SIGN) + 1).trim();
|
|
290
|
+
const braceIdx = str.indexOf(BRACE_OPEN);
|
|
291
|
+
let val;
|
|
292
|
+
if (braceIdx === -1) {
|
|
293
|
+
val = Number(str);
|
|
294
|
+
if (isNaN(val))
|
|
295
|
+
throw new ModelError(`**${str}** ${ERROR_MSG.NAN}
|
|
296
|
+
**${line}**.`, ERROR_LINK.COMPS_SYNTAX, line);
|
|
297
|
+
return {
|
|
298
|
+
value: val,
|
|
299
|
+
annot: null
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
val = Number(str.slice(0, braceIdx));
|
|
303
|
+
if (isNaN(val))
|
|
304
|
+
throw new ModelError(`**${str.slice(0, braceIdx)}** ${ERROR_MSG.NAN}: **${line}**`, ERROR_LINK.COMPS_SYNTAX, line);
|
|
305
|
+
return {
|
|
306
|
+
value: val,
|
|
307
|
+
annot: str.slice(braceIdx)
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
function getArg(lines) {
|
|
311
|
+
if (lines.length !== 4)
|
|
312
|
+
throw new ModelError(ERROR_MSG.ARG, ERROR_LINK.CORE_BLOCKS, CONTROL_EXPR.ARG);
|
|
313
|
+
const sepIdx = lines[0].indexOf(CONTROL_SEP);
|
|
314
|
+
if (sepIdx === -1)
|
|
315
|
+
throw new ModelError(`${ERROR_MSG.MISS_COLON}. Correct the line **${lines[0]}**`, ERROR_LINK.CORE_BLOCKS, lines[0]);
|
|
316
|
+
const commaIdx = lines[0].indexOf(COMMA);
|
|
317
|
+
return {
|
|
318
|
+
name: (commaIdx > -1 ? lines[0].slice(sepIdx + 1, commaIdx) : lines[0].slice(sepIdx + 1)).trim(),
|
|
319
|
+
initial: getInput(lines[1]),
|
|
320
|
+
final: getInput(lines[2]),
|
|
321
|
+
step: getInput(lines[3]),
|
|
322
|
+
updateName: commaIdx > -1 ? lines[0].slice(commaIdx + 1).trim() : void 0
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
function getEqualities(lines, begin, end) {
|
|
326
|
+
const source = concatMultilineFormulas(lines.slice(begin, end));
|
|
327
|
+
const eqs = /* @__PURE__ */ new Map();
|
|
328
|
+
let eqIdx = 0;
|
|
329
|
+
for (const line of source) {
|
|
330
|
+
if (line === void 0)
|
|
331
|
+
break;
|
|
332
|
+
eqIdx = line.indexOf(EQUAL_SIGN);
|
|
333
|
+
eqs.set(line.slice(0, eqIdx).replaceAll(" ", ""), getInput(line));
|
|
334
|
+
}
|
|
335
|
+
return eqs;
|
|
336
|
+
}
|
|
337
|
+
function getLoop(lines, begin, end) {
|
|
338
|
+
if (begin >= end)
|
|
339
|
+
throw new ModelError(ERROR_MSG.LOOP, ERROR_LINK.LOOP, CONTROL_EXPR.LOOP);
|
|
340
|
+
const source = concatMultilineFormulas(lines.slice(begin, end));
|
|
341
|
+
const size = source.length;
|
|
342
|
+
if (size < LOOP.MIN_LINES_COUNT)
|
|
343
|
+
throw new ModelError(ERROR_MSG.LOOP, ERROR_LINK.LOOP, CONTROL_EXPR.LOOP);
|
|
344
|
+
const count = getInput(source[LOOP.COUNT_IDX]);
|
|
345
|
+
if (count.value < LOOP.MIN_COUNT)
|
|
346
|
+
throw new ModelError(`${ERROR_MSG.COUNT}: **${count.value}**.`, ERROR_LINK.LOOP, source[LOOP.COUNT_IDX]);
|
|
347
|
+
return { count, updates: source.slice(LOOP.COUNT_IDX + 1) };
|
|
348
|
+
}
|
|
349
|
+
function getUpdate(lines, begin, end) {
|
|
350
|
+
const colonIdx = lines[begin].indexOf(CONTROL_SEP);
|
|
351
|
+
if (colonIdx === -1) {
|
|
352
|
+
throw new ModelError(`${ERROR_MSG.MISS_COLON}. Correct the line: **${lines[begin]}**`, ERROR_LINK.UPDATE, lines[begin]);
|
|
353
|
+
}
|
|
354
|
+
const source = concatMultilineFormulas(lines.slice(begin + 1, end));
|
|
355
|
+
const size = source.length;
|
|
356
|
+
if (size < UPDATE.MIN_LINES_COUNT)
|
|
357
|
+
throw new ModelError(ERROR_MSG.UPDATE_LINES_COUNT, ERROR_LINK.UPDATE, CONTROL_EXPR.UPDATE);
|
|
358
|
+
if (source[UPDATE.DURATION_IDX] == void 0)
|
|
359
|
+
throw new ModelError(ERROR_MSG.UPDATE_LINES_COUNT, ERROR_LINK.UPDATE, CONTROL_EXPR.UPDATE);
|
|
360
|
+
const eqIdx = source[UPDATE.DURATION_IDX].indexOf(EQUAL_SIGN);
|
|
361
|
+
if (eqIdx === -1) {
|
|
362
|
+
throw new ModelError(`Missing **${EQUAL_SIGN}**. Correct the line: **${source[UPDATE.DURATION_IDX]}**`, ERROR_LINK.UPDATE, source[UPDATE.DURATION_IDX]);
|
|
363
|
+
}
|
|
364
|
+
return {
|
|
365
|
+
name: lines[begin].slice(colonIdx + 1),
|
|
366
|
+
durationFormula: source[UPDATE.DURATION_IDX].slice(eqIdx + 1).trim(),
|
|
367
|
+
updates: source.slice(UPDATE.DURATION_IDX + 1)
|
|
368
|
+
};
|
|
369
|
+
}
|
|
370
|
+
function getOutput(lines, begin, end) {
|
|
371
|
+
const res = /* @__PURE__ */ new Map();
|
|
372
|
+
let line;
|
|
373
|
+
let token;
|
|
374
|
+
let eqIdx;
|
|
375
|
+
let openIdx;
|
|
376
|
+
let closeIdx;
|
|
377
|
+
let colonIdx;
|
|
378
|
+
for (let i = begin; i < end; ++i) {
|
|
379
|
+
line = lines[i].trim();
|
|
380
|
+
if (line.length < 1)
|
|
381
|
+
continue;
|
|
382
|
+
eqIdx = line.indexOf(EQUAL_SIGN);
|
|
383
|
+
openIdx = line.indexOf(BRACE_OPEN);
|
|
384
|
+
closeIdx = line.indexOf(BRACE_CLOSE);
|
|
385
|
+
colonIdx = line.indexOf(CONTROL_SEP);
|
|
386
|
+
if (openIdx * closeIdx <= 0) {
|
|
387
|
+
throw new ModelError(`${ERROR_MSG.BRACES} Correct the line **${line}** in the **${CONTROL_EXPR.OUTPUT}** block.`, ERROR_LINK.UI_OPTS, line);
|
|
388
|
+
}
|
|
389
|
+
if (openIdx * colonIdx <= 0) {
|
|
390
|
+
throw new ModelError(`${ERROR_MSG.COLON} Correct the line **${line}** in the **#output** block.`, ERROR_LINK.UI_OPTS, line);
|
|
391
|
+
}
|
|
392
|
+
if (eqIdx === -1) {
|
|
393
|
+
if (openIdx === -1) {
|
|
394
|
+
token = line.trim();
|
|
395
|
+
res.set(token, {
|
|
396
|
+
caption: token,
|
|
397
|
+
formula: null
|
|
398
|
+
});
|
|
399
|
+
} else {
|
|
400
|
+
token = line.slice(0, openIdx).trim();
|
|
401
|
+
res.set(token, {
|
|
402
|
+
caption: line.slice(colonIdx + 1, closeIdx).trim(),
|
|
403
|
+
formula: null
|
|
404
|
+
});
|
|
405
|
+
}
|
|
406
|
+
} else if (openIdx === -1) {
|
|
407
|
+
token = line.slice(0, eqIdx).trim();
|
|
408
|
+
res.set(token, {
|
|
409
|
+
caption: token,
|
|
410
|
+
formula: line.slice(eqIdx + 1)
|
|
411
|
+
});
|
|
412
|
+
} else {
|
|
413
|
+
token = line.slice(0, eqIdx).trim();
|
|
414
|
+
res.set(token, {
|
|
415
|
+
caption: line.slice(colonIdx + 1, closeIdx),
|
|
416
|
+
formula: line.slice(eqIdx + 1, openIdx)
|
|
417
|
+
});
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return res;
|
|
421
|
+
}
|
|
422
|
+
function getIVP(text) {
|
|
423
|
+
let name;
|
|
424
|
+
let tags = null;
|
|
425
|
+
let descr = null;
|
|
426
|
+
let deqs;
|
|
427
|
+
let exprs = null;
|
|
428
|
+
let arg;
|
|
429
|
+
let inits;
|
|
430
|
+
let consts = null;
|
|
431
|
+
let params = null;
|
|
432
|
+
let tolerance = DEFAULT_TOL;
|
|
433
|
+
let loop = null;
|
|
434
|
+
const updates = [];
|
|
435
|
+
const metas = [];
|
|
436
|
+
let outputs = null;
|
|
437
|
+
let solverSettings = DEFAULT_SOLVER_SETTINGS;
|
|
438
|
+
let inputsLookup = null;
|
|
439
|
+
const lines = text.replaceAll(" ", " ").split("\n").map((s) => {
|
|
440
|
+
const idx = s.indexOf(COMMENT_SEQ);
|
|
441
|
+
return s.slice(0, idx >= 0 ? idx : void 0).trimStart();
|
|
442
|
+
}).filter((s) => s !== "");
|
|
443
|
+
const start = getStartOfProblemDef(lines);
|
|
444
|
+
const blocks = getBlocks(lines, start);
|
|
445
|
+
for (const block of blocks) {
|
|
446
|
+
const firstLine = lines[block.begin];
|
|
447
|
+
if (firstLine.startsWith(CONTROL_EXPR.NAME)) {
|
|
448
|
+
name = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
|
|
449
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.TAGS)) {
|
|
450
|
+
tags = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
|
|
451
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.DESCR)) {
|
|
452
|
+
descr = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
|
|
453
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.DIF_EQ)) {
|
|
454
|
+
deqs = getDifEquations(concatMultilineFormulas(lines.slice(block.begin + 1, block.end)));
|
|
455
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.EXPR)) {
|
|
456
|
+
exprs = getExpressions(concatMultilineFormulas(lines.slice(block.begin + 1, block.end)));
|
|
457
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.ARG)) {
|
|
458
|
+
arg = getArg(concatMultilineFormulas(lines.slice(block.begin, block.end)));
|
|
459
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.INITS)) {
|
|
460
|
+
inits = getEqualities(lines, block.begin + 1, block.end);
|
|
461
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.CONSTS)) {
|
|
462
|
+
consts = getEqualities(lines, block.begin + 1, block.end);
|
|
463
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.PARAMS)) {
|
|
464
|
+
params = getEqualities(lines, block.begin + 1, block.end);
|
|
465
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.TOL)) {
|
|
466
|
+
tolerance = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
|
|
467
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.SOLVER)) {
|
|
468
|
+
solverSettings = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
|
|
469
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.LOOP)) {
|
|
470
|
+
loop = getLoop(lines, block.begin + 1, block.end);
|
|
471
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.UPDATE)) {
|
|
472
|
+
updates.push(getUpdate(lines, block.begin, block.end));
|
|
473
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.OUTPUT)) {
|
|
474
|
+
outputs = getOutput(lines, block.begin + 1, block.end);
|
|
475
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.INPUTS)) {
|
|
476
|
+
inputsLookup = firstLine.slice(firstLine.indexOf(CONTROL_SEP) + 1).trim();
|
|
477
|
+
} else if (firstLine.startsWith(CONTROL_EXPR.COMMENT)) {
|
|
478
|
+
} else
|
|
479
|
+
metas.push(firstLine.slice(CONTROL_TAG_LEN));
|
|
480
|
+
}
|
|
481
|
+
if (loop !== null && updates.length > 0)
|
|
482
|
+
throw new ModelError(ERROR_MSG.LOOP_VS_UPDATE, ERROR_LINK.LOOP_VS_UPDATE, CONTROL_EXPR.LOOP);
|
|
483
|
+
const ivp = {
|
|
484
|
+
name,
|
|
485
|
+
tags,
|
|
486
|
+
descr,
|
|
487
|
+
deqs,
|
|
488
|
+
exprs,
|
|
489
|
+
arg,
|
|
490
|
+
inits,
|
|
491
|
+
consts,
|
|
492
|
+
params,
|
|
493
|
+
tolerance,
|
|
494
|
+
usedMathFuncs: getUsedMathIds(text, MATH_FUNCS),
|
|
495
|
+
usedMathConsts: getUsedMathIds(text, MATH_CONSTS),
|
|
496
|
+
loop,
|
|
497
|
+
updates: updates.length === 0 ? null : updates,
|
|
498
|
+
metas,
|
|
499
|
+
outputs,
|
|
500
|
+
solverSettings,
|
|
501
|
+
inputsLookup
|
|
502
|
+
};
|
|
503
|
+
checkCorrectness(ivp);
|
|
504
|
+
return ivp;
|
|
505
|
+
}
|
|
506
|
+
function checkSolverSettings(line) {
|
|
507
|
+
const settings = /* @__PURE__ */ new Map();
|
|
508
|
+
const openBraceIdx = line.indexOf(BRACE_OPEN);
|
|
509
|
+
const closeBraceIdx = line.indexOf(BRACE_CLOSE);
|
|
510
|
+
let sepIdx;
|
|
511
|
+
if (openBraceIdx < 0 || closeBraceIdx < 0)
|
|
512
|
+
throw new ModelError(`${ERROR_MSG.BRACES}. Correct the line **${line}**.`, ERROR_LINK.SOLVER_CONFIG, line);
|
|
513
|
+
for (const item of line.slice(openBraceIdx + 1, closeBraceIdx).split(ANNOT_SEPAR)) {
|
|
514
|
+
sepIdx = item.indexOf(CONTROL_SEP);
|
|
515
|
+
if (sepIdx > 1)
|
|
516
|
+
settings.set(item.slice(0, sepIdx).trim(), item.slice(sepIdx + 1).trim());
|
|
517
|
+
}
|
|
518
|
+
SOLVER_OPTIONS_RANGES.forEach((range, opt) => {
|
|
519
|
+
if (settings.has(opt)) {
|
|
520
|
+
const val = Number(settings.get(opt));
|
|
521
|
+
if (val < range.min || val > range.max) {
|
|
522
|
+
throw new ModelError(`${ERROR_MSG.SOLVER}: **${opt}** must be in the range **${range.min}..${range.max}**.`, ERROR_LINK.SOLVER_CONFIG, line);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
function checkCorrectness(ivp) {
|
|
528
|
+
if (ivp.name === void 0)
|
|
529
|
+
throw new ModelError(ERROR_MSG.UNDEF_NAME, ERROR_LINK.CORE_BLOCKS);
|
|
530
|
+
if (ivp.deqs === void 0 || ivp.deqs.equations.size === 0)
|
|
531
|
+
throw new ModelError(ERROR_MSG.UNDEF_DEQS, ERROR_LINK.CORE_BLOCKS);
|
|
532
|
+
if (ivp.inits === void 0 || ivp.inits.size === 0)
|
|
533
|
+
throw new ModelError(ERROR_MSG.UNDEF_INITS, ERROR_LINK.CORE_BLOCKS);
|
|
534
|
+
if (ivp.arg === void 0)
|
|
535
|
+
throw new ModelError(ERROR_MSG.UNDEF_ARG, ERROR_LINK.CORE_BLOCKS);
|
|
536
|
+
ivp.deqs.equations.forEach((ignore, name) => {
|
|
537
|
+
if (!ivp.inits.has(name)) {
|
|
538
|
+
throw new ModelError(`Initial value for **${name}** is missing. ${ERROR_MSG.MISSING_INIT}`, ERROR_LINK.CORE_BLOCKS, CONTROL_EXPR.INITS);
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
const usedNames = [];
|
|
542
|
+
let lowCase;
|
|
543
|
+
if (ivp.outputs !== null) {
|
|
544
|
+
ivp.outputs.forEach((val) => {
|
|
545
|
+
lowCase = val.caption.toLowerCase();
|
|
546
|
+
if (usedNames.includes(lowCase))
|
|
547
|
+
throw new ModelError(`${ERROR_MSG.CASE_INSENS}**${val.caption}**.`, ERROR_LINK.UI_OPTS, CONTROL_EXPR.OUTPUT);
|
|
548
|
+
else
|
|
549
|
+
usedNames.push(lowCase);
|
|
550
|
+
});
|
|
551
|
+
} else {
|
|
552
|
+
const usedNames2 = [ivp.arg.name];
|
|
553
|
+
ivp.deqs.solutionNames.forEach((name) => {
|
|
554
|
+
lowCase = name.toLowerCase();
|
|
555
|
+
if (usedNames2.includes(lowCase))
|
|
556
|
+
throw new ModelError(`${ERROR_MSG.CASE_INSENS}**${name}**.`, ERROR_LINK.UI_OPTS);
|
|
557
|
+
else
|
|
558
|
+
usedNames2.push(lowCase);
|
|
559
|
+
});
|
|
560
|
+
}
|
|
561
|
+
if (ivp.arg.initial.value >= ivp.arg.final.value) {
|
|
562
|
+
throw new ModelError(`${ERROR_MSG.INTERVAL} **${ivp.arg.name}**. ${ERROR_MSG.CORRECT_ARG_LIM}`, ERROR_LINK.CORE_BLOCKS, CONTROL_EXPR.ARG);
|
|
563
|
+
}
|
|
564
|
+
if (ivp.arg.step.value <= 0) {
|
|
565
|
+
throw new ModelError(`${ERROR_MSG.NEGATIVE_STEP}`, ERROR_LINK.CORE_BLOCKS);
|
|
566
|
+
}
|
|
567
|
+
if (ivp.arg.step.value > ivp.arg.final.value - ivp.arg.initial.value)
|
|
568
|
+
throw new ModelError(ERROR_MSG.INCOR_STEP, ERROR_LINK.CORE_BLOCKS);
|
|
569
|
+
const scriptInputs = [];
|
|
570
|
+
let current;
|
|
571
|
+
ivp.inits.forEach((_, key) => {
|
|
572
|
+
if (key[0] === SERVICE) {
|
|
573
|
+
throw new ModelError(`${ERROR_MSG.SERVICE_START} Correct **"${key}"** in the **#inits** block.`, ERROR_LINK.CORE_BLOCKS);
|
|
574
|
+
}
|
|
575
|
+
current = key.toLocaleLowerCase();
|
|
576
|
+
if (scriptInputs.includes(current)) {
|
|
577
|
+
throw new ModelError(`${ERROR_MSG.REUSE_NAME} **"${key}"** in the **#inits** block.`, ERROR_LINK.CORE_BLOCKS);
|
|
578
|
+
}
|
|
579
|
+
scriptInputs.push(current);
|
|
580
|
+
});
|
|
581
|
+
if (ivp.params !== null) {
|
|
582
|
+
ivp.params.forEach((_, key) => {
|
|
583
|
+
if (key[0] === SERVICE) {
|
|
584
|
+
throw new ModelError(`${ERROR_MSG.SERVICE_START}: correct **"${key}"** in the **${CONTROL_EXPR.PARAMS}** block.`, ERROR_LINK.MODEL_PARAMS);
|
|
585
|
+
}
|
|
586
|
+
current = key.toLocaleLowerCase();
|
|
587
|
+
if (scriptInputs.includes(current)) {
|
|
588
|
+
throw new ModelError(`${ERROR_MSG.REUSE_NAME} **"${key}"** in the **${CONTROL_EXPR.PARAMS}** block.`, ERROR_LINK.MODEL_PARAMS);
|
|
589
|
+
}
|
|
590
|
+
scriptInputs.push(current);
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
checkSolverSettings(ivp.solverSettings);
|
|
594
|
+
}
|
|
595
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
596
|
+
0 && (module.exports = {
|
|
597
|
+
MAX_LINE_CHART,
|
|
598
|
+
STAGE_COL_NAME,
|
|
599
|
+
getIVP
|
|
600
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {getIVP, MAX_LINE_CHART, STAGE_COL_NAME} from 'diff-grok/dist/src/scripting-tools/index.js';
|