@sdeverywhere/cli 0.7.22 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2016-2022 Todd Fincannon and Climate Interactive / New Venture Fund
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/README.md CHANGED
@@ -188,8 +188,8 @@ Be sure to include `Time` first among the output variables.
188
188
 
189
189
  ```json
190
190
  {
191
- "inputVars": ["Reference predators", "Reference prey"],
192
- "outputVars": ["Time", "Predators Y", "Prey X"]
191
+ "inputVarNames": ["Reference predators", "Reference prey"],
192
+ "outputVarNames": ["Time", "Predators Y", "Prey X"]
193
193
  }
194
194
  ```
195
195
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/cli",
3
- "version": "0.7.22",
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.4",
15
- "@sdeverywhere/compile": "^0.7.16",
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
- for (size_t i = 0; i < maxOutputIndices; i++) {
161
- size_t indexBufferOffset = i * INDICES_PER_OUTPUT;
162
- size_t varIndex = (size_t)outputIndexBuffer[indexBufferOffset];
163
- if (varIndex > 0) {
164
- size_t subIndex0 = (size_t)outputIndexBuffer[indexBufferOffset + 1];
165
- size_t subIndex1 = (size_t)outputIndexBuffer[indexBufferOffset + 2];
166
- size_t subIndex2 = (size_t)outputIndexBuffer[indexBufferOffset + 3];
167
- storeOutput(varIndex, subIndex0, subIndex1, subIndex2);
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
- // Stop when we reach the first zero index
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 subIndex0, size_t subIndex1, size_t subIndex2);
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(double* data, size_t n, double input, LookupMode mode) {
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(double *data, size_t n, double input, LookupMode mode);
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((lookup)->data, (lookup)->n, x, _GET_DATA_MODE_TO_LOOKUP_MODE(mode))
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
- opts.genc = true
33
+ // Generate code in JS or C format
34
+ opts.outformat = opts.genformat || 'js'
24
35
  await generate(model, opts)
25
- compile(model, opts)
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 parseTree = parseModel(input)
30
+ let parsedModel = parseModel(input, modelDirname)
27
31
  let operations = ['printRefGraph']
28
- generateCode(parseTree, { spec, operations, extData, directData, modelDirname, varname })
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
@@ -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, 'genc', false, decls)
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) {
@@ -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: 'list model variables',
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 "genc" and "preprocess" options.
64
- let profile = opts.analysis ? 'analysis' : 'genc'
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.genc) {
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
@@ -9,6 +9,11 @@ export let builder = {
9
9
  type: 'string',
10
10
  alias: 's'
11
11
  },
12
+ genformat: {
13
+ describe: 'generated code format',
14
+ choices: ['js', 'c'],
15
+ default: 'js'
16
+ },
12
17
  builddir: {
13
18
  describe: 'build directory',
14
19
  type: 'string',
package/src/sde-test.js CHANGED
@@ -13,6 +13,11 @@ export let builder = {
13
13
  type: 'string',
14
14
  alias: 's'
15
15
  },
16
+ genformat: {
17
+ describe: 'generated code format',
18
+ choices: ['js', 'c'],
19
+ default: 'js'
20
+ },
16
21
  builddir: {
17
22
  describe: 'build directory',
18
23
  type: 'string',