@sdeverywhere/cli 0.7.23 → 0.7.24
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/LICENSE +1 -1
- package/package.json +3 -3
- package/src/c/model.c +11 -25
- package/src/c/sde.h +2 -2
- package/src/c/vensim.c +26 -2
- package/src/c/vensim.h +4 -3
- package/src/sde-build.js +36 -2
- package/src/sde-causes.js +6 -2
- package/src/sde-exec.js +16 -1
- package/src/sde-flatten.js +1 -1
- package/src/sde-generate.js +17 -5
- package/src/sde-run.js +5 -0
- package/src/sde-test.js +5 -0
package/LICENSE
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
The MIT License (MIT)
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2016-
|
|
3
|
+
Copyright (c) 2016-2024 Todd Fincannon and Climate Interactive / New Venture Fund
|
|
4
4
|
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
6
|
of this software and associated documentation files (the "Software"), to deal
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdeverywhere/cli",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.24",
|
|
4
4
|
"description": "Contains the `sde` command line interface for the SDEverywhere tool suite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
"sde": "src/main.js"
|
|
12
12
|
},
|
|
13
13
|
"dependencies": {
|
|
14
|
-
"@sdeverywhere/build": "^0.3.
|
|
15
|
-
"@sdeverywhere/compile": "^0.7.
|
|
14
|
+
"@sdeverywhere/build": "^0.3.5",
|
|
15
|
+
"@sdeverywhere/compile": "^0.7.18",
|
|
16
16
|
"bufx": "^1.0.5",
|
|
17
17
|
"byline": "^5.0.0",
|
|
18
18
|
"ramda": "^0.27.0",
|
package/src/c/model.c
CHANGED
|
@@ -8,14 +8,6 @@
|
|
|
8
8
|
struct timespec startTime, finishTime;
|
|
9
9
|
#endif
|
|
10
10
|
|
|
11
|
-
// For each output variable specified in the indices buffer, there
|
|
12
|
-
// are 4 index values:
|
|
13
|
-
// varIndex
|
|
14
|
-
// subIndex0
|
|
15
|
-
// subIndex1
|
|
16
|
-
// subIndex2
|
|
17
|
-
#define INDICES_PER_OUTPUT 4
|
|
18
|
-
|
|
19
11
|
// The special _time variable is not included in .mdl files.
|
|
20
12
|
double _time;
|
|
21
13
|
|
|
@@ -75,13 +67,6 @@ double getSaveper() {
|
|
|
75
67
|
return _saveper;
|
|
76
68
|
}
|
|
77
69
|
|
|
78
|
-
/**
|
|
79
|
-
* Return the constant `maxOutputIndices` value.
|
|
80
|
-
*/
|
|
81
|
-
int getMaxOutputIndices() {
|
|
82
|
-
return maxOutputIndices;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
70
|
char* run_model(const char* inputs) {
|
|
86
71
|
// run_model does everything necessary to run the model with the given inputs.
|
|
87
72
|
// It may be called multiple times. Call finish() after all runs are complete.
|
|
@@ -157,18 +142,19 @@ void run() {
|
|
|
157
142
|
outputVarIndex = 0;
|
|
158
143
|
if (outputIndexBuffer != NULL) {
|
|
159
144
|
// Store the outputs as specified in the current output index buffer
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
145
|
+
size_t indexBufferOffset = 0;
|
|
146
|
+
size_t outputCount = (size_t)outputIndexBuffer[indexBufferOffset++];
|
|
147
|
+
for (size_t i = 0; i < outputCount; i++) {
|
|
148
|
+
size_t varIndex = (size_t)outputIndexBuffer[indexBufferOffset++];
|
|
149
|
+
size_t subCount = (size_t)outputIndexBuffer[indexBufferOffset++];
|
|
150
|
+
size_t* subIndices;
|
|
151
|
+
if (subCount > 0) {
|
|
152
|
+
subIndices = (size_t*)(outputIndexBuffer + indexBufferOffset);
|
|
168
153
|
} else {
|
|
169
|
-
|
|
170
|
-
break;
|
|
154
|
+
subIndices = NULL;
|
|
171
155
|
}
|
|
156
|
+
indexBufferOffset += subCount;
|
|
157
|
+
storeOutput(varIndex, subIndices);
|
|
172
158
|
}
|
|
173
159
|
} else {
|
|
174
160
|
// Store the normal outputs
|
package/src/c/sde.h
CHANGED
|
@@ -42,7 +42,6 @@ EXTERN double _epsilon;
|
|
|
42
42
|
|
|
43
43
|
// Internal variables
|
|
44
44
|
EXTERN const int numOutputs;
|
|
45
|
-
EXTERN const int maxOutputIndices;
|
|
46
45
|
|
|
47
46
|
// Standard simulation control parameters
|
|
48
47
|
EXTERN double _time;
|
|
@@ -67,10 +66,11 @@ void initConstants(void);
|
|
|
67
66
|
void initLevels(void);
|
|
68
67
|
void setInputs(const char* inputData);
|
|
69
68
|
void setInputsFromBuffer(double *inputData);
|
|
69
|
+
void setLookup(size_t varIndex, size_t* subIndices, double* points, size_t numPoints);
|
|
70
70
|
void evalAux(void);
|
|
71
71
|
void evalLevels(void);
|
|
72
72
|
void storeOutputData(void);
|
|
73
|
-
void storeOutput(size_t varIndex, size_t
|
|
73
|
+
void storeOutput(size_t varIndex, size_t* subIndices);
|
|
74
74
|
const char* getHeader(void);
|
|
75
75
|
|
|
76
76
|
#ifdef __cplusplus
|
package/src/c/vensim.c
CHANGED
|
@@ -94,7 +94,7 @@ double __lookup(Lookup* lookup, double input, bool use_inverted_data, LookupMode
|
|
|
94
94
|
// Interpolate the y value from an array of (x,y) pairs.
|
|
95
95
|
// NOTE: The x values are assumed to be monotonically increasing.
|
|
96
96
|
|
|
97
|
-
if (lookup == NULL) {
|
|
97
|
+
if (lookup == NULL || lookup->n == 0) {
|
|
98
98
|
return _NA_;
|
|
99
99
|
}
|
|
100
100
|
|
|
@@ -162,9 +162,16 @@ double __lookup(Lookup* lookup, double input, bool use_inverted_data, LookupMode
|
|
|
162
162
|
// This function is similar to `__lookup` in concept, but Vensim produces results for
|
|
163
163
|
// the GET DATA BETWEEN TIMES function that differ in unexpected ways from normal lookup
|
|
164
164
|
// behavior, so we implement it as a separate function here.
|
|
165
|
-
double __get_data_between_times(
|
|
165
|
+
double __get_data_between_times(Lookup* lookup, double input, LookupMode mode) {
|
|
166
166
|
// Interpolate the y value from an array of (x,y) pairs.
|
|
167
167
|
// NOTE: The x values are assumed to be monotonically increasing.
|
|
168
|
+
|
|
169
|
+
if (lookup == NULL || lookup->n == 0) {
|
|
170
|
+
return _NA_;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const double* data = lookup->data;
|
|
174
|
+
const size_t n = lookup->n;
|
|
168
175
|
const size_t max = n * 2;
|
|
169
176
|
|
|
170
177
|
switch (mode) {
|
|
@@ -253,6 +260,23 @@ double _LOOKUP_INVERT(Lookup* lookup, double y) {
|
|
|
253
260
|
return __lookup(lookup, y, true, Interpolate);
|
|
254
261
|
}
|
|
255
262
|
|
|
263
|
+
double _GAME(Lookup* lookup, double default_value) {
|
|
264
|
+
if (lookup == NULL || lookup->n <= 0) {
|
|
265
|
+
// The lookup is NULL or empty, so return the default value
|
|
266
|
+
return default_value;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
double x0 = lookup->data[0];
|
|
270
|
+
if (_time < x0) {
|
|
271
|
+
// The current time is earlier than the first data point, so return the
|
|
272
|
+
// default value
|
|
273
|
+
return default_value;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// For all other cases, we can use `__lookup` with `Backward` mode
|
|
277
|
+
return __lookup(lookup, _time, false, Backward);
|
|
278
|
+
}
|
|
279
|
+
|
|
256
280
|
typedef struct {
|
|
257
281
|
double x;
|
|
258
282
|
int ind;
|
package/src/c/vensim.h
CHANGED
|
@@ -21,7 +21,6 @@ extern "C" {
|
|
|
21
21
|
#define _ARCTAN(x) atan(x)
|
|
22
22
|
#define _COS(x) cos(x)
|
|
23
23
|
#define _EXP(x) exp(x)
|
|
24
|
-
#define _GAME(x) (x)
|
|
25
24
|
#define _GAMMA_LN(x) lgamma(x)
|
|
26
25
|
#define _IF_THEN_ELSE(c, t, f) (bool_cond(c) ? (t) : (f))
|
|
27
26
|
#define _INTEG(value, rate) ((value) + (rate) * _time_step)
|
|
@@ -74,9 +73,11 @@ double __lookup(Lookup *lookup, double input, bool use_inverted_data, LookupMode
|
|
|
74
73
|
#define _WITH_LOOKUP(x, lookup) __lookup(lookup, x, false, Interpolate)
|
|
75
74
|
double _LOOKUP_INVERT(Lookup* lookup, double y);
|
|
76
75
|
|
|
77
|
-
double __get_data_between_times(
|
|
76
|
+
double __get_data_between_times(Lookup* lookup, double input, LookupMode mode);
|
|
78
77
|
#define _GET_DATA_MODE_TO_LOOKUP_MODE(mode) ((mode) >= 1) ? Forward : (((mode) <= -1) ? Backward : Interpolate)
|
|
79
|
-
#define _GET_DATA_BETWEEN_TIMES(lookup, x, mode) __get_data_between_times(
|
|
78
|
+
#define _GET_DATA_BETWEEN_TIMES(lookup, x, mode) __get_data_between_times(lookup, x, _GET_DATA_MODE_TO_LOOKUP_MODE(mode))
|
|
79
|
+
|
|
80
|
+
double _GAME(Lookup* lookup, double default_value);
|
|
80
81
|
|
|
81
82
|
//
|
|
82
83
|
// DELAY FIXED
|
package/src/sde-build.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
|
+
import { writeFileSync } from 'fs'
|
|
2
|
+
import { resolve } from 'path'
|
|
3
|
+
|
|
1
4
|
import { generate } from './sde-generate.js'
|
|
2
5
|
import { compile } from './sde-compile.js'
|
|
3
6
|
|
|
7
|
+
import { buildDir, modelPathProps } from './utils.js'
|
|
8
|
+
|
|
4
9
|
export let command = 'build [options] <model>'
|
|
5
10
|
export let describe = 'generate model code and compile it'
|
|
6
11
|
export let builder = {
|
|
@@ -9,6 +14,11 @@ export let builder = {
|
|
|
9
14
|
type: 'string',
|
|
10
15
|
alias: 's'
|
|
11
16
|
},
|
|
17
|
+
genformat: {
|
|
18
|
+
describe: 'generated code format',
|
|
19
|
+
choices: ['js', 'c'],
|
|
20
|
+
default: 'js'
|
|
21
|
+
},
|
|
12
22
|
builddir: {
|
|
13
23
|
describe: 'build directory',
|
|
14
24
|
type: 'string',
|
|
@@ -20,9 +30,17 @@ export let handler = argv => {
|
|
|
20
30
|
}
|
|
21
31
|
export let build = async (model, opts) => {
|
|
22
32
|
try {
|
|
23
|
-
|
|
33
|
+
// Generate code in JS or C format
|
|
34
|
+
opts.outformat = opts.genformat || 'js'
|
|
24
35
|
await generate(model, opts)
|
|
25
|
-
|
|
36
|
+
if (opts.outformat === 'js') {
|
|
37
|
+
// Write a `main.js` file that can be used by `sde exec` to execute the model
|
|
38
|
+
// on the command line using Node.js
|
|
39
|
+
writeMainJs(model, opts)
|
|
40
|
+
} else if (opts.outformat === 'c') {
|
|
41
|
+
// Compile the generated C code to a native executable
|
|
42
|
+
compile(model, opts)
|
|
43
|
+
}
|
|
26
44
|
} catch (e) {
|
|
27
45
|
// Exit with a non-zero error code if any step failed
|
|
28
46
|
console.error(`ERROR: ${e.message}\n`)
|
|
@@ -37,3 +55,19 @@ export default {
|
|
|
37
55
|
handler,
|
|
38
56
|
build
|
|
39
57
|
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Write a minimal `main.js` file that can be used by `sde exec` to execute a
|
|
61
|
+
* generated JS model.
|
|
62
|
+
*/
|
|
63
|
+
let writeMainJs = (model, opts) => {
|
|
64
|
+
let { modelDirname, modelName } = modelPathProps(model)
|
|
65
|
+
let buildDirname = buildDir(opts.builddir, modelDirname)
|
|
66
|
+
const mainJsFile = resolve(buildDirname, 'main.js')
|
|
67
|
+
let mainJs = ''
|
|
68
|
+
mainJs += '#!/usr/bin/env node\n'
|
|
69
|
+
mainJs += `import { execJsModel } from '@sdeverywhere/runtime'\n`
|
|
70
|
+
mainJs += `import loadJsModel from './${modelName}.js'\n`
|
|
71
|
+
mainJs += 'execJsModel(await loadJsModel())\n'
|
|
72
|
+
writeFileSync(mainJsFile, mainJs, { mode: 0o755 })
|
|
73
|
+
}
|
package/src/sde-causes.js
CHANGED
|
@@ -21,11 +21,15 @@ let causes = (model, varname, opts) => {
|
|
|
21
21
|
let directData = new Map()
|
|
22
22
|
let spec = parseSpec(opts.spec)
|
|
23
23
|
// Preprocess model text into parser input.
|
|
24
|
+
// TODO: The legacy `parseModel` function previously required the `preprocessModel`
|
|
25
|
+
// step to be performed first, but the new `parseModel` runs the preprocessor
|
|
26
|
+
// implicitly, so we can remove this step (and can simplify this code to use
|
|
27
|
+
// `parseAndGenerate` instead)
|
|
24
28
|
let input = preprocessModel(modelPathname, spec)
|
|
25
29
|
// Parse the model to get variable and subscript information.
|
|
26
|
-
let
|
|
30
|
+
let parsedModel = parseModel(input, modelDirname)
|
|
27
31
|
let operations = ['printRefGraph']
|
|
28
|
-
generateCode(
|
|
32
|
+
generateCode(parsedModel, { spec, operations, extData, directData, modelDirname, varname })
|
|
29
33
|
}
|
|
30
34
|
export default {
|
|
31
35
|
command,
|
package/src/sde-exec.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { existsSync } from 'fs'
|
|
1
2
|
import path from 'path'
|
|
2
3
|
|
|
3
4
|
import { buildDir, execCmd, modelPathProps, outputDir } from './utils.js'
|
|
@@ -24,8 +25,22 @@ export let exec = (model, opts) => {
|
|
|
24
25
|
// Ensure the build and output directories exist.
|
|
25
26
|
let buildDirname = buildDir(opts.builddir, modelDirname)
|
|
26
27
|
let outputDirname = outputDir(opts.outfile, modelDirname)
|
|
28
|
+
// If `main.js` exists, it means that the generated code format
|
|
29
|
+
// is JS, so run that file, otherwise we assume the generated
|
|
30
|
+
// code format is C and that there is a native executable with
|
|
31
|
+
// the same name as the model.
|
|
32
|
+
let mainJs = path.join(buildDirname, 'main.js')
|
|
33
|
+
let mainC = path.join(buildDirname, modelName)
|
|
34
|
+
let modelCmd
|
|
35
|
+
if (existsSync(mainJs)) {
|
|
36
|
+
modelCmd = mainJs
|
|
37
|
+
} else if (existsSync(mainC)) {
|
|
38
|
+
modelCmd = mainC
|
|
39
|
+
} else {
|
|
40
|
+
console.error('ERROR: No executable model found in build directory')
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
27
43
|
// Run the model and capture output in the model directory.
|
|
28
|
-
let modelCmd = `${buildDirname}/${modelName}`
|
|
29
44
|
let outputPathname
|
|
30
45
|
if (opts.outfile) {
|
|
31
46
|
outputPathname = opts.outfile
|
package/src/sde-flatten.js
CHANGED
|
@@ -46,7 +46,7 @@ const flatten = async (outFile, inFiles, opts) => {
|
|
|
46
46
|
|
|
47
47
|
// Preprocess the mdl file and extract the equations
|
|
48
48
|
const decls = []
|
|
49
|
-
preprocessModel(inModelProps.modelPathname, undefined, '
|
|
49
|
+
preprocessModel(inModelProps.modelPathname, undefined, 'runnable', false, decls)
|
|
50
50
|
|
|
51
51
|
// Associate each declaration with the name of the model from which it came
|
|
52
52
|
for (const decl of decls) {
|
package/src/sde-generate.js
CHANGED
|
@@ -8,12 +8,18 @@ import { buildDir, modelPathProps, parseSpec } from './utils.js'
|
|
|
8
8
|
export let command = 'generate [options] <model>'
|
|
9
9
|
export let describe = 'generate model code'
|
|
10
10
|
export let builder = {
|
|
11
|
+
// TODO: The old `--genc` option is deprecated and replaced by `--outformat=c`
|
|
11
12
|
genc: {
|
|
12
13
|
describe: 'generate C code for the model',
|
|
13
|
-
type: 'boolean'
|
|
14
|
+
type: 'boolean',
|
|
15
|
+
hidden: true
|
|
16
|
+
},
|
|
17
|
+
outformat: {
|
|
18
|
+
describe: 'write generated code in the given format',
|
|
19
|
+
choices: ['js', 'c']
|
|
14
20
|
},
|
|
15
21
|
list: {
|
|
16
|
-
describe: '
|
|
22
|
+
describe: 'write a file that lists model variables',
|
|
17
23
|
type: 'boolean',
|
|
18
24
|
alias: 'l'
|
|
19
25
|
},
|
|
@@ -60,8 +66,8 @@ export let generate = async (model, opts) => {
|
|
|
60
66
|
let buildDirname = buildDir(opts.builddir, modelDirname)
|
|
61
67
|
// Preprocess model text into parser input. Stop now if that's all we're doing.
|
|
62
68
|
let spec = parseSpec(opts.spec)
|
|
63
|
-
// Produce a runnable model with the "
|
|
64
|
-
let profile = opts.analysis ? 'analysis' : '
|
|
69
|
+
// Produce a runnable model with the "runnable" and "preprocess" options.
|
|
70
|
+
let profile = opts.analysis ? 'analysis' : 'runnable'
|
|
65
71
|
// Write the preprocessed model and removals if the option is "analysis" or "preprocess".
|
|
66
72
|
let writeFiles = opts.analysis || opts.preprocess
|
|
67
73
|
let input = preprocessModel(modelPathname, spec, profile, writeFiles)
|
|
@@ -73,7 +79,13 @@ export let generate = async (model, opts) => {
|
|
|
73
79
|
// Parse the model and generate code. If no operation is specified, the code generator will
|
|
74
80
|
// read the model and do nothing else. This is required for the list operation.
|
|
75
81
|
let operations = []
|
|
76
|
-
if (opts.
|
|
82
|
+
if (opts.outformat === 'js') {
|
|
83
|
+
operations.push('generateJS')
|
|
84
|
+
}
|
|
85
|
+
if (opts.genc || opts.outformat === 'c') {
|
|
86
|
+
if (opts.genc) {
|
|
87
|
+
console.warn(`WARNING: --genc option is deprecated for the 'sde generate' command; use --outformat=c instead`)
|
|
88
|
+
}
|
|
77
89
|
operations.push('generateC')
|
|
78
90
|
}
|
|
79
91
|
if (opts.list) {
|
package/src/sde-run.js
CHANGED
package/src/sde-test.js
CHANGED