@sdeverywhere/runtime 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,6 +6,10 @@ and compiled to a WebAssembly (Wasm) module via [Emscripten](https://emscripten.
6
6
 
7
7
  ## Usage
8
8
 
9
+ NOTE: If you use the `@sdeverywhere/create` package, most of the initialization
10
+ steps listed below are already handled for you in the generated `core` package,
11
+ and you can work directly with a `ModelRunner` and/or `ModelScheduler` instance.
12
+
9
13
  ### 1. Initialize the `WasmModel`
10
14
 
11
15
  In your application, load the wasm module using the wrapper produced by
@@ -26,7 +30,7 @@ async function initWasmModel(): Promise<WasmModelInitResult> {
26
30
  const wasmModule = await loadWasm()
27
31
 
28
32
  // Initialize the wasm model and its associated buffers
29
- return initWasmModelAndBuffers(wasmModule, inputVarNames.length, outputVarNames, 2000, 2100)
33
+ return initWasmModelAndBuffers(wasmModule, inputVarNames.length, outputVarNames)
30
34
  }
31
35
  ```
32
36
 
@@ -52,7 +56,7 @@ async function main() {
52
56
  const inputs = [createInputValue('_input1', 2), createInputValue('_input2', 10)] // etc
53
57
 
54
58
  // Create an `Outputs` instance to hold the model outputs
55
- let outputs = new Outputs(wasmResult.outputVarIds, wasmResult.startTime, wasmResult.endTime)
59
+ let outputs = modelRunner.createOutputs()
56
60
 
57
61
  // Run the model with those inputs
58
62
  outputs = await modelRunner.runModel(inputs, outputs)
@@ -93,6 +97,11 @@ async function initModel() {
93
97
 
94
98
  ## Emscripten Notes
95
99
 
100
+ If you use the `@sdeverywhere/plugin-wasm` package to build a WebAssembly
101
+ version of your model, the following steps are already handled for you.
102
+ The notes below are only needed if you want more low-level control over
103
+ how the C model is compiled into a WebAssembly module.
104
+
96
105
  The `@sdeverywhere/runtime` package assumes you have created `<mymodel>.wasm`
97
106
  and `<mymodel>.js` files with Emscripten.
98
107
  The `emcc` command line options should be similar to the following:
@@ -102,12 +111,18 @@ $ emcc \
102
111
  build/<mymodel>.c build/macros.c build/model.c build/vensim.c \
103
112
  -Ibuild -o ./output/<mymodel>.js -Wall -Os \
104
113
  -s STRICT=1 -s MALLOC=emmalloc -s FILESYSTEM=0 -s MODULARIZE=1 \
105
- -s EXPORTED_FUNCTIONS="['_runModelWithBuffers', '_malloc']" \
114
+ -s EXPORTED_FUNCTIONS="['_malloc','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']" \
106
115
  -s EXPORTED_RUNTIME_METHODS="['cwrap']"
107
116
  ```
108
117
 
109
- (The generated module must export at minimum `_runModelWithBuffers`,
110
- `_malloc`, and `cwrap`.)
118
+ Note that the generated module must export the following functions at minimum:
119
+
120
+ - `_malloc`
121
+ - `_getInitialTime`
122
+ - `_getFinalTime`
123
+ - `_getSaveper`
124
+ - `_runModelWithBuffers`
125
+ - `cwrap`
111
126
 
112
127
  ## Documentation
113
128
 
package/dist/index.cjs CHANGED
@@ -1,21 +1,7 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
3
  var __getOwnPropNames = Object.getOwnPropertyNames;
4
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
4
  var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
- var __spreadValues = (a, b) => {
9
- for (var prop in b || (b = {}))
10
- if (__hasOwnProp.call(b, prop))
11
- __defNormalProp(a, prop, b[prop]);
12
- if (__getOwnPropSymbols)
13
- for (var prop of __getOwnPropSymbols(b)) {
14
- if (__propIsEnum.call(b, prop))
15
- __defNormalProp(a, prop, b[prop]);
16
- }
17
- return a;
18
- };
19
5
  var __export = (target, all) => {
20
6
  for (var name in all)
21
7
  __defProp(target, name, { get: all[name], enumerable: true });
@@ -74,24 +60,29 @@ var WasmBuffer = class {
74
60
  // src/wasm-model/wasm-model.ts
75
61
  var WasmModel = class {
76
62
  constructor(wasmModule) {
63
+ function getNumberValue(funcName) {
64
+ const wasmGetValue = wasmModule.cwrap(funcName, "number", []);
65
+ return wasmGetValue();
66
+ }
67
+ this.startTime = getNumberValue("getInitialTime");
68
+ this.endTime = getNumberValue("getFinalTime");
69
+ this.saveFreq = getNumberValue("getSaveper");
70
+ this.numSavePoints = Math.round((this.endTime - this.startTime) / this.saveFreq) + 1;
77
71
  this.wasmRunModel = wasmModule.cwrap("runModelWithBuffers", null, ["number", "number"]);
78
72
  }
79
73
  runModel(inputs, outputs) {
80
74
  this.wasmRunModel(inputs.getAddress(), outputs.getAddress());
81
75
  }
82
76
  };
83
- function initWasmModelAndBuffers(wasmModule, numInputs, outputVarIds, startTime, endTime) {
77
+ function initWasmModelAndBuffers(wasmModule, numInputs, outputVarIds) {
84
78
  const model = new WasmModel(wasmModule);
85
79
  const inputsBuffer = new WasmBuffer(wasmModule, numInputs);
86
- const seriesLength = endTime - startTime + 1;
87
- const outputsBuffer = new WasmBuffer(wasmModule, outputVarIds.length * seriesLength);
80
+ const outputsBuffer = new WasmBuffer(wasmModule, outputVarIds.length * model.numSavePoints);
88
81
  return {
89
82
  model,
90
83
  inputsBuffer,
91
84
  outputsBuffer,
92
- outputVarIds,
93
- startTime,
94
- endTime
85
+ outputVarIds
95
86
  };
96
87
  }
97
88
 
@@ -124,26 +115,25 @@ var Series = class {
124
115
  }
125
116
  getValueAtTime(time) {
126
117
  var _a;
127
- const startTime = this.points[0].x;
128
- return (_a = this.points[time - startTime]) == null ? void 0 : _a.y;
118
+ return (_a = this.points.find((p) => p.x === time)) == null ? void 0 : _a.y;
129
119
  }
130
120
  copy() {
131
- const pointsCopy = this.points.map((p) => __spreadValues({}, p));
121
+ const pointsCopy = this.points.map((p) => ({ ...p }));
132
122
  return new Series(this.varId, pointsCopy);
133
123
  }
134
124
  };
135
125
  var Outputs = class {
136
- constructor(varIds, timeStart, timeEnd) {
126
+ constructor(varIds, startTime, endTime, saveFreq = 1) {
137
127
  this.varIds = varIds;
138
- this.timeStart = timeStart;
139
- this.timeEnd = timeEnd;
140
- this.seriesLength = timeEnd - timeStart + 1;
128
+ this.startTime = startTime;
129
+ this.endTime = endTime;
130
+ this.saveFreq = saveFreq;
131
+ this.seriesLength = Math.round((endTime - startTime) / saveFreq) + 1;
141
132
  this.varSeries = new Array(varIds.length);
142
133
  for (let i = 0; i < varIds.length; i++) {
143
134
  const points = new Array(this.seriesLength);
144
- let time = timeStart;
145
135
  for (let j = 0; j < this.seriesLength; j++) {
146
- points[j] = { x: time++, y: 0 };
136
+ points[j] = { x: startTime + j * saveFreq, y: 0 };
147
137
  }
148
138
  const varId = varIds[i];
149
139
  this.varSeries[i] = new Series(varId, points);
@@ -219,7 +209,7 @@ function createWasmModelRunner(wasmResult) {
219
209
  const inputsArray = inputsBuffer.getArrayView();
220
210
  const outputsBuffer = wasmResult.outputsBuffer;
221
211
  const outputsArray = outputsBuffer.getArrayView();
222
- const rowLength = wasmResult.endTime - wasmResult.startTime + 1;
212
+ const rowLength = wasmModel.numSavePoints;
223
213
  let terminated = false;
224
214
  const runModelSync = (inputs, outputs) => {
225
215
  let i = 0;
@@ -233,6 +223,9 @@ function createWasmModelRunner(wasmResult) {
233
223
  return outputs;
234
224
  };
235
225
  return {
226
+ createOutputs: () => {
227
+ return new Outputs(wasmResult.outputVarIds, wasmModel.startTime, wasmModel.endTime, wasmModel.saveFreq);
228
+ },
236
229
  runModel: (inputs, outputs) => {
237
230
  if (terminated) {
238
231
  return Promise.reject(new Error("Model runner has already been terminated"));
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/wasm-model/wasm-buffer.ts","../src/wasm-model/wasm-model.ts","../src/model-runner/inputs.ts","../src/model-runner/outputs.ts","../src/model-runner/perf.ts","../src/model-runner/model-runner.ts","../src/model-scheduler/model-scheduler.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nexport type { InputVarId, OutputVarId } from './_shared'\nexport * from './wasm-model'\nexport * from './model-runner'\nexport * from './model-scheduler'\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { WasmModule } from './wasm-module'\n\n/**\n * Wraps a `WebAssembly.Memory` buffer allocated on the wasm heap.\n *\n * When this is used synchronously (in the browser's normal JavaScript thread),\n * the client can use `getArrayView` to write directly into the underlying memory.\n *\n * Note, however, that `WebAssembly.Memory` buffers cannot be transferred to/from\n * a Web Worker. When using this class in a worker thread, create a separate\n * `Float64Array` that can be transferred between the worker and the client running\n * in the browser's normal JS thread, and then use `getArrayView` to copy into and\n * out of the wasm buffer.\n */\nexport class WasmBuffer {\n private byteOffset: number\n private heapArray: Float64Array\n\n /**\n * @param wasmModule The `WasmModule` used to initialize the memory.\n * @param numElements The number of 64-bit `double` elements in the buffer.\n */\n constructor(private readonly wasmModule: WasmModule, numElements: number) {\n const sizeOfFloat64 = 8\n const lengthInBytes = numElements * sizeOfFloat64\n this.byteOffset = wasmModule._malloc(lengthInBytes)\n const float64Offset = this.byteOffset / sizeOfFloat64\n this.heapArray = wasmModule.HEAPF64.subarray(float64Offset, float64Offset + numElements)\n }\n\n /**\n * @return A new `Float64Array` view on the underlying heap buffer.\n */\n getArrayView(): Float64Array {\n return this.heapArray\n }\n\n /**\n * @return The raw address of the underlying heap buffer.\n * @hidden This is intended for use by `WasmModel` only.\n */\n getAddress(): number {\n return this.byteOffset\n }\n\n /**\n * Dispose the buffer by freeing the allocated heap memory.\n */\n dispose(): void {\n if (this.heapArray) {\n this.wasmModule._free(this.byteOffset)\n this.heapArray = undefined\n this.byteOffset = undefined\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { OutputVarId } from '../_shared'\nimport { WasmBuffer } from './wasm-buffer'\nimport type { WasmModule } from './wasm-module'\n\n/**\n * An interface to the En-ROADS model. Allows for running the model with\n * a given set of input values, producing a set of output values.\n */\nexport class WasmModel {\n private readonly wasmRunModel: (inputsAddress: number, outputsAddress: number) => void\n\n /**\n * @param wasmModule The `WasmModule` containing the `runModelWithBuffers` function.\n */\n constructor(wasmModule: WasmModule) {\n this.wasmRunModel = wasmModule.cwrap('runModelWithBuffers', null, ['number', 'number'])\n }\n\n /**\n * Run the model, using inputs from the `inputs` buffer, and writing outputs into\n * the `outputs` buffer.\n *\n * @param inputs The buffer containing inputs in the order expected by the model.\n * @param outputs The buffer into which the model will store output values.\n */\n runModel(inputs: WasmBuffer, outputs: WasmBuffer): void {\n this.wasmRunModel(inputs.getAddress(), outputs.getAddress())\n }\n}\n\n/**\n * The result of model initialization.\n */\nexport interface WasmModelInitResult {\n /** The wasm model. */\n model: WasmModel\n /** The buffer used to pass input values to the model. */\n inputsBuffer: WasmBuffer\n /** The buffer used to receive output values from the model. */\n outputsBuffer: WasmBuffer\n /** The output variable IDs. */\n outputVarIds: OutputVarId[]\n /** The start time (year) for the model. */\n startTime: number\n /** The end time (year) for the model. */\n endTime: number\n}\n\n/**\n * Initialize the wasm model and buffers.\n *\n * @param wasmModule The `WasmModule` that wraps the `wasm` binary.\n * @param numInputs The number of input variables, per the spec file passed to `sde`.\n * @param outputVarIds The output variable IDs, per the spec file passed to `sde`.\n * @param startTime The start time (year) for the model.\n * @param endTime The end time (year) for the model.\n */\nexport function initWasmModelAndBuffers(\n wasmModule: WasmModule,\n numInputs: number,\n outputVarIds: OutputVarId[],\n startTime: number,\n endTime: number\n): WasmModelInitResult {\n // Wrap the native C `runModelWithBuffers` function in a JS function that we can call\n const model = new WasmModel(wasmModule)\n\n // Allocate a buffer that is large enough to hold the input values\n const inputsBuffer = new WasmBuffer(wasmModule, numInputs)\n\n // Each series will include one data point per year, inclusive of the\n // start and end years\n // TODO: We should pull these from the C variables instead of having them passed in;\n // for now we assume `_saveper` is 1 but that should be pulled from the C variable too\n const seriesLength = endTime - startTime + 1\n\n // Allocate a buffer that is large enough to hold the series data for\n // each output variable\n const outputsBuffer = new WasmBuffer(wasmModule, outputVarIds.length * seriesLength)\n\n return {\n model,\n inputsBuffer,\n outputsBuffer,\n outputVarIds,\n startTime,\n endTime\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { InputVarId } from '../_shared'\n\n/** Callback functions that are called when the input value is changed. */\nexport interface InputCallbacks {\n /** Called after a new value is set. */\n onSet?: () => void\n}\n\n/**\n * Represents a writable model input.\n */\nexport interface InputValue {\n /** The ID of the associated input variable, as used in SDEverywhere. */\n varId: InputVarId\n /** Get the current value of the input. */\n get: () => number\n /** Set the input to the given value. */\n set: (value: number) => void\n /** Reset the input to its default value. */\n reset: () => void\n /** Callback functions that are called when the input value is changed. */\n callbacks: InputCallbacks\n}\n\n/**\n * Create a basic `InputValue` instance that notifies when a new value is set.\n *\n * @param varId The input variable ID, as used in SDEverywhere.\n * @param defaultValue The default value of the input.\n * @param initialValue The inital value of the input; if undefined, will use `defaultValue`.\n */\nexport function createInputValue(varId: InputVarId, defaultValue: number, initialValue?: number): InputValue {\n let currentValue = initialValue !== undefined ? initialValue : defaultValue\n\n // The `onSet` callback is initially undefined but will be installed by `ModelScheduler`\n const callbacks: InputCallbacks = {}\n\n const get = () => {\n return currentValue\n }\n\n const set = (newValue: number) => {\n if (newValue !== currentValue) {\n currentValue = newValue\n callbacks.onSet?.()\n }\n }\n\n const reset = () => {\n set(defaultValue)\n }\n\n return { varId, get, set, reset, callbacks }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { Result } from 'neverthrow'\nimport { ok, err } from 'neverthrow'\nimport type { OutputVarId } from '../_shared'\n\n/** Indicates the type of error encountered when parsing an outputs buffer. */\nexport type ParseError = 'invalid-point-count'\n\n/** A data point. */\nexport interface Point {\n /** The x value (typically a year). */\n x: number\n /** The y value. */\n y: number\n}\n\n/**\n * A time series of data points for an output variable.\n */\nexport class Series {\n /**\n * @param varId The ID for the output variable (as used by SDEverywhere).\n * @param points The data points for the variable, one point per time increment.\n */\n constructor(public readonly varId: OutputVarId, public readonly points: Point[]) {}\n\n /**\n * Return the Y value at the given time.\n *\n * @param time The x (time) value.\n */\n getValueAtTime(time: number): number | undefined {\n // TODO: This assumes one data point per year; we should take `_saveper` into account\n // and if it's not 1 point per year, search for a specific x value\n // TODO: Add option to allow interpolation if the given time value is in between points\n const startTime = this.points[0].x\n return this.points[time - startTime]?.y\n }\n\n /**\n * Create a new `Series` instance that is a copy of this one.\n */\n copy(): Series {\n // Create a deep copy\n const pointsCopy = this.points.map(p => ({ ...p }))\n return new Series(this.varId, pointsCopy)\n }\n}\n\n/** Represents the outputs from a model run. */\nexport class Outputs {\n /** The number of data points in each series. */\n public readonly seriesLength: number\n /** The array of series, one for each output variable. */\n public readonly varSeries: Series[]\n\n /**\n * The latest model run time, in milliseconds.\n * @hidden This is not yet part of the public API; it is exposed here for use\n * in performance testing tools.\n */\n public runTimeInMillis: number\n\n constructor(\n public readonly varIds: OutputVarId[],\n public readonly timeStart: number,\n public readonly timeEnd: number\n ) {\n // Each series will include one data point per year, inclusive of the start and end years\n this.seriesLength = timeEnd - timeStart + 1\n\n // Create an array of arrays, one for each output variable\n this.varSeries = new Array(varIds.length)\n\n // Populate the arrays, filling in the time for each point\n for (let i = 0; i < varIds.length; i++) {\n const points: Point[] = new Array(this.seriesLength)\n let time = timeStart\n for (let j = 0; j < this.seriesLength; j++) {\n points[j] = { x: time++, y: 0 }\n }\n const varId = varIds[i]\n this.varSeries[i] = new Series(varId, points)\n }\n }\n\n /**\n * Parse the given raw float buffer (produced by the model) and store the values\n * into this `Outputs` instance.\n *\n * Note that the length of `outputsBuffer` must be greater than or equal to\n * the capacity of this `Outputs` instance. The `Outputs` instance is allowed\n * to be smaller to support the case where you want to extract a subset of\n * the time range in the buffer produced by the model.\n *\n * @param outputsBuffer The raw outputs buffer produced by the model.\n * @param rowLength The number of elements per row (one element per year or save point).\n * @return An `ok` result if the buffer is valid, otherwise an `err` result.\n */\n updateFromBuffer(outputsBuffer: Float64Array, rowLength: number): Result<void, ParseError> {\n const result = parseOutputsBuffer(outputsBuffer, rowLength, this)\n if (result.isOk()) {\n return ok(undefined)\n } else {\n return err(result.error)\n }\n }\n\n /**\n * Return the series for the given output variable.\n *\n * @param varId The ID of the output variable (as used by SDEverywhere).\n */\n getSeriesForVar(varId: OutputVarId): Series | undefined {\n const seriesIndex = this.varIds.indexOf(varId)\n if (seriesIndex >= 0) {\n return this.varSeries[seriesIndex]\n } else {\n // TODO: Error\n return undefined\n }\n }\n}\n\n/**\n * Parse the raw buffer produced by the model and store the values in the\n * given (reused) `Outputs` object.\n *\n * @param outputsBuffer The raw outputs buffer produced by the model.\n * @param rowLength The number of elements per row (one element per year or save point).\n * @return An `ok` result if the buffer is valid, otherwise an `err` result.\n * @hidden\n */\nfunction parseOutputsBuffer(\n outputsBuffer: Float64Array,\n rowLength: number,\n outputs: Outputs\n): Result<Outputs, ParseError> {\n const varCount = outputs.varIds.length\n const seriesLength = outputs.seriesLength\n if (rowLength < seriesLength || outputsBuffer.length < varCount * seriesLength) {\n return err('invalid-point-count')\n }\n\n // The buffer populated by the C `runModelWithBuffers` function is already\n // transposed, so the first \"row\" contains the values for the first output\n // variable (from start time to end time), and so on.\n for (let outputVarIndex = 0; outputVarIndex < varCount; outputVarIndex++) {\n const series = outputs.varSeries[outputVarIndex]\n let sourceIndex = rowLength * outputVarIndex\n for (let valueIndex = 0; valueIndex < seriesLength; valueIndex++) {\n series.points[valueIndex].y = validateNumber(outputsBuffer[sourceIndex])\n sourceIndex++\n }\n }\n\n return ok(outputs)\n}\n\n/**\n * Return the given number if it is valid, or undefined if it is invalid.\n *\n * SDE converts Vensim's `:NA:` values to `-DBL_MAX`, so if we see a very large negative\n * value, convert it to `undefined`. This is preferable to including extreme values\n * because some charting libraries (e.g. Chart.js) appear to choke on these large values\n * in certain browsers (e.g. Safari), but `undefined` appears to be handled better and\n * does a better job of signaling that the data point is undefined.\n *\n * @hidden\n */\nfunction validateNumber(x: number): number | undefined {\n if (!isNaN(x) && x > -1e32) {\n return x\n } else {\n return undefined\n }\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nlet isWeb: boolean\n\n/**\n * Return a timestamp that can be passed to `perfElapsed` for calculating the elapsed\n * time of an operation.\n *\n * @hidden This is not part of the public API; exposed only for use in performance testing.\n */\nexport function perfNow(): unknown {\n // Note that `self` resolves to the window (in browser context) or the worker global scope\n // (in a Web Worker context)\n if (isWeb === undefined) {\n isWeb = typeof self !== 'undefined' && self?.performance !== undefined\n }\n if (isWeb) {\n return self.performance.now()\n } else {\n // XXX: We only use `process` in two places; we bypass type checking instead of\n // setting up type declarations\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n return process?.hrtime()\n }\n}\n\n/**\n * Return the elapsed time between the given timestamp (created by `perfNow`) and now.\n *\n * @hidden This is not part of the public API; exposed only for use in performance testing.\n */\nexport function perfElapsed(t0: unknown): number {\n if (isWeb) {\n const t1 = self.performance.now()\n return (t1 as number) - (t0 as number)\n } else {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const elapsed = process.hrtime(t0) as number[]\n // Convert from nanoseconds to milliseconds\n return (elapsed[0] * 1000000000 + elapsed[1]) / 1000000\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { WasmModelInitResult } from '../wasm-model'\nimport type { InputValue } from './inputs'\nimport type { Outputs } from './outputs'\nimport { perfElapsed, perfNow } from './perf'\n\n/**\n * Abstraction that allows for running the wasm model on the JS thread\n * or asynchronously (e.g. in a Web Worker), depending on the implementation.\n */\nexport interface ModelRunner {\n /**\n * Run the model.\n *\n * @param inputs The model input values (must be in the same order as in the spec file).\n * @param outputs The structure into which the model outputs will be stored.\n * @return A promise that resolves with the outputs when the model run is complete.\n */\n runModel(inputs: InputValue[], outputs: Outputs): Promise<Outputs>\n\n /**\n * Run the model synchronously.\n *\n * @param inputs The model input values (must be in the same order as in the spec file).\n * @param outputs The structure into which the model outputs will be stored.\n * @return The outputs of the run.\n *\n * @hidden This is only intended for internal use; some implementations may not support\n * running the model synchronously, in which case this will be undefined.\n */\n runModelSync?(inputs: InputValue[], outputs: Outputs): Outputs\n\n /**\n * Terminate the runner by releasing underlying resources (e.g., the worker thread or\n * Wasm module/buffers).\n */\n terminate(): Promise<void>\n}\n\n/**\n * Create a `ModelRunner` that runs the given wasm model on the JS thread.\n *\n * @param wasmResult The result of initializing the wasm model.\n */\nexport function createWasmModelRunner(wasmResult: WasmModelInitResult): ModelRunner {\n // Create views on the wasm buffers\n const wasmModel = wasmResult.model\n const inputsBuffer = wasmResult.inputsBuffer\n const inputsArray = inputsBuffer.getArrayView()\n const outputsBuffer = wasmResult.outputsBuffer\n const outputsArray = outputsBuffer.getArrayView()\n const rowLength = wasmResult.endTime - wasmResult.startTime + 1\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n const runModelSync = (inputs: InputValue[], outputs: Outputs) => {\n // Capture the current set of input values into the reusable buffer\n let i = 0\n for (const input of inputs) {\n inputsArray[i++] = input.get()\n }\n\n // Run the model\n const t0 = perfNow()\n wasmModel.runModel(inputsBuffer, outputsBuffer)\n outputs.runTimeInMillis = perfElapsed(t0)\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n outputs.updateFromBuffer(outputsArray, rowLength)\n\n return outputs\n }\n\n return {\n runModel: (inputs, outputs) => {\n if (terminated) {\n return Promise.reject(new Error('Model runner has already been terminated'))\n }\n return Promise.resolve(runModelSync(inputs, outputs))\n },\n runModelSync: (inputs, outputs) => {\n if (terminated) {\n throw new Error('Model runner has already been terminated')\n }\n return runModelSync(inputs, outputs)\n },\n terminate: () => {\n if (!terminated) {\n // TODO: Release wasm-related resources (module or buffers)\n terminated = true\n }\n return Promise.resolve()\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { InputVarId } from '../_shared'\nimport type { InputValue, ModelRunner, Outputs } from '../model-runner'\n\n/**\n * A high-level interface that schedules running of the underlying `WasmModel`.\n *\n * When one or more input values are changed, this class will schedule a model\n * run to be completed as soon as possible. When the model run has completed,\n * `onOutputsChanged` is called to notify that new output data is available.\n *\n * The `ModelRunner` is pluggable to allow for running the model synchronously\n * (on the main JavaScript thread) or asynchronously (in a Web Worker or Node.js\n * worker thread).\n */\nexport class ModelScheduler {\n /** The second array that holds a stable copy of the user inputs. */\n private readonly currentInputs: InputValue[]\n\n /** Whether a model run has been scheduled. */\n private runNeeded = false\n\n /** Whether a model run is in progress. */\n private runInProgress = false\n\n /** Called when `outputs` has been updated after a model run. */\n public onOutputsChanged?: (outputs: Outputs) => void\n\n /**\n * @param runner The model runner.\n * @param userInputs The input values, in the same order as in the spec file passed to `sde`.\n * @param outputs The structure into which the model outputs will be stored.\n */\n constructor(\n private readonly runner: ModelRunner,\n private readonly userInputs: InputValue[],\n private outputs: Outputs\n ) {\n // When any input has an updated value, schedule a model run on the next tick\n const afterSet = () => {\n this.runWasmModelIfNeeded()\n }\n for (const userInput of userInputs) {\n userInput.callbacks.onSet = afterSet\n }\n\n // Create a second array to hold a stable copy of the user inputs during model runs\n this.currentInputs = []\n for (const userInput of userInputs) {\n this.currentInputs.push(createSimpleInputValue(userInput.varId))\n }\n }\n\n /**\n * Schedule a wasm model run (if not already pending). When the run is\n * complete, save the outputs and call the `onOutputsChanged` callback.\n */\n private runWasmModelIfNeeded(): void {\n // Set a flag indicating that a new run is needed (even if one is already\n // in progress)\n this.runNeeded = true\n\n if (this.runInProgress) {\n // A run is already in progress; let it finish first\n return\n } else {\n // A run is not already in progress, so schedule it now. We use\n // `setTimeout` so that if a lot of inputs are all changing at once\n // (like after a reset), we wait for all those `set` or `reset`\n // calls to finish before gathering the input values into an array\n // and initiating the run on the next tick.\n this.runInProgress = true\n setTimeout(() => {\n // Kick off the (possibly asynchronous) model run\n this.runWasmModelNow()\n }, 0)\n }\n }\n\n /**\n * Run the wasm model asynchronously using the current set of input values.\n */\n private async runWasmModelNow(): Promise<void> {\n // Copy the current inputs into a separate array; this ensures that the\n // model run uses a stable set of inputs, even if the user continues to\n // change the inputs while the model is being run asynchronously\n for (let i = 0; i < this.userInputs.length; i++) {\n this.currentInputs[i].set(this.userInputs[i].get())\n }\n\n // Run the model with the current set of input values and save the outputs\n try {\n this.outputs = await this.runner.runModel(this.currentInputs, this.outputs)\n this.onOutputsChanged?.(this.outputs)\n } catch (e) {\n console.error(`ERROR: Failed to run model: ${e.message}`)\n }\n\n // See if another run is needed\n if (this.runNeeded) {\n // Keep `runInProgress` set, but clear the `runNeeded` flag\n this.runNeeded = false\n setTimeout(() => {\n this.runWasmModelNow()\n }, 0)\n } else {\n // No run needed, so clear both flags\n this.runNeeded = false\n this.runInProgress = false\n }\n }\n}\n\n/**\n * Create an `InputValue` that is only used to hold a copy of another input (no callbacks).\n * @hidden\n */\nfunction createSimpleInputValue(varId: InputVarId): InputValue {\n let currentValue = 0\n const get = () => {\n return currentValue\n }\n const set = (newValue: number) => {\n currentValue = newValue\n }\n const reset = () => {\n set(0)\n }\n return { varId, get, set, reset, callbacks: {} }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACgBO,IAAM,aAAN,MAAiB;AAAA,EAQtB,YAA6B,YAAwB,aAAqB;AAA7C;AAC3B,UAAM,gBAAgB;AACtB,UAAM,gBAAgB,cAAc;AACpC,SAAK,aAAa,WAAW,QAAQ,aAAa;AAClD,UAAM,gBAAgB,KAAK,aAAa;AACxC,SAAK,YAAY,WAAW,QAAQ,SAAS,eAAe,gBAAgB,WAAW;AAAA,EACzF;AAAA,EAKA,eAA6B;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAMA,aAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAKA,UAAgB;AACd,QAAI,KAAK,WAAW;AAClB,WAAK,WAAW,MAAM,KAAK,UAAU;AACrC,WAAK,YAAY;AACjB,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AACF;;;AC/CO,IAAM,YAAN,MAAgB;AAAA,EAMrB,YAAY,YAAwB;AAClC,SAAK,eAAe,WAAW,MAAM,uBAAuB,MAAM,CAAC,UAAU,QAAQ,CAAC;AAAA,EACxF;AAAA,EASA,SAAS,QAAoB,SAA2B;AACtD,SAAK,aAAa,OAAO,WAAW,GAAG,QAAQ,WAAW,CAAC;AAAA,EAC7D;AACF;AA6BO,iCACL,YACA,WACA,cACA,WACA,SACqB;AAErB,QAAM,QAAQ,IAAI,UAAU,UAAU;AAGtC,QAAM,eAAe,IAAI,WAAW,YAAY,SAAS;AAMzD,QAAM,eAAe,UAAU,YAAY;AAI3C,QAAM,gBAAgB,IAAI,WAAW,YAAY,aAAa,SAAS,YAAY;AAEnF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;ACzDO,0BAA0B,OAAmB,cAAsB,cAAmC;AAC3G,MAAI,eAAe,iBAAiB,SAAY,eAAe;AAG/D,QAAM,YAA4B,CAAC;AAEnC,QAAM,MAAM,MAAM;AAChB,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,CAAC,aAAqB;AA3CpC;AA4CI,QAAI,aAAa,cAAc;AAC7B,qBAAe;AACf,sBAAU,UAAV;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM;AAClB,QAAI,YAAY;AAAA,EAClB;AAEA,SAAO,EAAE,OAAO,KAAK,KAAK,OAAO,UAAU;AAC7C;;;ACpDA,wBAAwB;AAiBjB,IAAM,SAAN,MAAa;AAAA,EAKlB,YAA4B,OAAoC,QAAiB;AAArD;AAAoC;AAAA,EAAkB;AAAA,EAOlF,eAAe,MAAkC;AAhCnD;AAoCI,UAAM,YAAY,KAAK,OAAO,GAAG;AACjC,WAAO,WAAK,OAAO,OAAO,eAAnB,mBAA+B;AAAA,EACxC;AAAA,EAKA,OAAe;AAEb,UAAM,aAAa,KAAK,OAAO,IAAI,OAAM,mBAAK,EAAI;AAClD,WAAO,IAAI,OAAO,KAAK,OAAO,UAAU;AAAA,EAC1C;AACF;AAGO,IAAM,UAAN,MAAc;AAAA,EAanB,YACkB,QACA,WACA,SAChB;AAHgB;AACA;AACA;AAGhB,SAAK,eAAe,UAAU,YAAY;AAG1C,SAAK,YAAY,IAAI,MAAM,OAAO,MAAM;AAGxC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,SAAkB,IAAI,MAAM,KAAK,YAAY;AACnD,UAAI,OAAO;AACX,eAAS,IAAI,GAAG,IAAI,KAAK,cAAc,KAAK;AAC1C,eAAO,KAAK,EAAE,GAAG,QAAQ,GAAG,EAAE;AAAA,MAChC;AACA,YAAM,QAAQ,OAAO;AACrB,WAAK,UAAU,KAAK,IAAI,OAAO,OAAO,MAAM;AAAA,IAC9C;AAAA,EACF;AAAA,EAeA,iBAAiB,eAA6B,WAA6C;AACzF,UAAM,SAAS,mBAAmB,eAAe,WAAW,IAAI;AAChE,QAAI,OAAO,KAAK,GAAG;AACjB,aAAO,0BAAG,MAAS;AAAA,IACrB,OAAO;AACL,aAAO,2BAAI,OAAO,KAAK;AAAA,IACzB;AAAA,EACF;AAAA,EAOA,gBAAgB,OAAwC;AACtD,UAAM,cAAc,KAAK,OAAO,QAAQ,KAAK;AAC7C,QAAI,eAAe,GAAG;AACpB,aAAO,KAAK,UAAU;AAAA,IACxB,OAAO;AAEL,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAWA,4BACE,eACA,WACA,SAC6B;AAC7B,QAAM,WAAW,QAAQ,OAAO;AAChC,QAAM,eAAe,QAAQ;AAC7B,MAAI,YAAY,gBAAgB,cAAc,SAAS,WAAW,cAAc;AAC9E,WAAO,2BAAI,qBAAqB;AAAA,EAClC;AAKA,WAAS,iBAAiB,GAAG,iBAAiB,UAAU,kBAAkB;AACxE,UAAM,SAAS,QAAQ,UAAU;AACjC,QAAI,cAAc,YAAY;AAC9B,aAAS,aAAa,GAAG,aAAa,cAAc,cAAc;AAChE,aAAO,OAAO,YAAY,IAAI,eAAe,cAAc,YAAY;AACvE;AAAA,IACF;AAAA,EACF;AAEA,SAAO,0BAAG,OAAO;AACnB;AAaA,wBAAwB,GAA+B;AACrD,MAAI,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO;AAC1B,WAAO;AAAA,EACT,OAAO;AACL,WAAO;AAAA,EACT;AACF;;;AC/KA,IAAI;AAQG,mBAA4B;AAGjC,MAAI,UAAU,QAAW;AACvB,YAAQ,OAAO,SAAS,eAAe,8BAAM,iBAAgB;AAAA,EAC/D;AACA,MAAI,OAAO;AACT,WAAO,KAAK,YAAY,IAAI;AAAA,EAC9B,OAAO;AAKL,WAAO,mCAAS;AAAA,EAClB;AACF;AAOO,qBAAqB,IAAqB;AAC/C,MAAI,OAAO;AACT,UAAM,KAAK,KAAK,YAAY,IAAI;AAChC,WAAQ,KAAiB;AAAA,EAC3B,OAAO;AAGL,UAAM,UAAU,QAAQ,OAAO,EAAE;AAEjC,WAAQ,SAAQ,KAAK,MAAa,QAAQ,MAAM;AAAA,EAClD;AACF;;;ACEO,+BAA+B,YAA8C;AAElF,QAAM,YAAY,WAAW;AAC7B,QAAM,eAAe,WAAW;AAChC,QAAM,cAAc,aAAa,aAAa;AAC9C,QAAM,gBAAgB,WAAW;AACjC,QAAM,eAAe,cAAc,aAAa;AAChD,QAAM,YAAY,WAAW,UAAU,WAAW,YAAY;AAG9D,MAAI,aAAa;AAEjB,QAAM,eAAe,CAAC,QAAsB,YAAqB;AAE/D,QAAI,IAAI;AACR,eAAW,SAAS,QAAQ;AAC1B,kBAAY,OAAO,MAAM,IAAI;AAAA,IAC/B;AAGA,UAAM,KAAK,QAAQ;AACnB,cAAU,SAAS,cAAc,aAAa;AAC9C,YAAQ,kBAAkB,YAAY,EAAE;AAIxC,YAAQ,iBAAiB,cAAc,SAAS;AAEhD,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,UAAU,CAAC,QAAQ,YAAY;AAC7B,UAAI,YAAY;AACd,eAAO,QAAQ,OAAO,IAAI,MAAM,0CAA0C,CAAC;AAAA,MAC7E;AACA,aAAO,QAAQ,QAAQ,aAAa,QAAQ,OAAO,CAAC;AAAA,IACtD;AAAA,IACA,cAAc,CAAC,QAAQ,YAAY;AACjC,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,0CAA0C;AAAA,MAC5D;AACA,aAAO,aAAa,QAAQ,OAAO;AAAA,IACrC;AAAA,IACA,WAAW,MAAM;AACf,UAAI,CAAC,YAAY;AAEf,qBAAa;AAAA,MACf;AACA,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACF;AACF;;;ACjFO,IAAM,iBAAN,MAAqB;AAAA,EAkB1B,YACmB,QACA,YACT,SACR;AAHiB;AACA;AACT;AAhBV,SAAQ,YAAY;AAGpB,SAAQ,gBAAgB;AAgBtB,UAAM,WAAW,MAAM;AACrB,WAAK,qBAAqB;AAAA,IAC5B;AACA,eAAW,aAAa,YAAY;AAClC,gBAAU,UAAU,QAAQ;AAAA,IAC9B;AAGA,SAAK,gBAAgB,CAAC;AACtB,eAAW,aAAa,YAAY;AAClC,WAAK,cAAc,KAAK,uBAAuB,UAAU,KAAK,CAAC;AAAA,IACjE;AAAA,EACF;AAAA,EAMA,AAAQ,uBAA6B;AAGnC,SAAK,YAAY;AAEjB,QAAI,KAAK,eAAe;AAEtB;AAAA,IACF,OAAO;AAML,WAAK,gBAAgB;AACrB,iBAAW,MAAM;AAEf,aAAK,gBAAgB;AAAA,MACvB,GAAG,CAAC;AAAA,IACN;AAAA,EACF;AAAA,EAKA,MAAc,kBAAiC;AAnFjD;AAuFI,aAAS,IAAI,GAAG,IAAI,KAAK,WAAW,QAAQ,KAAK;AAC/C,WAAK,cAAc,GAAG,IAAI,KAAK,WAAW,GAAG,IAAI,CAAC;AAAA,IACpD;AAGA,QAAI;AACF,WAAK,UAAU,MAAM,KAAK,OAAO,SAAS,KAAK,eAAe,KAAK,OAAO;AAC1E,iBAAK,qBAAL,8BAAwB,KAAK;AAAA,IAC/B,SAAS,GAAP;AACA,cAAQ,MAAM,+BAA+B,EAAE,SAAS;AAAA,IAC1D;AAGA,QAAI,KAAK,WAAW;AAElB,WAAK,YAAY;AACjB,iBAAW,MAAM;AACf,aAAK,gBAAgB;AAAA,MACvB,GAAG,CAAC;AAAA,IACN,OAAO;AAEL,WAAK,YAAY;AACjB,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AACF;AAMA,gCAAgC,OAA+B;AAC7D,MAAI,eAAe;AACnB,QAAM,MAAM,MAAM;AAChB,WAAO;AAAA,EACT;AACA,QAAM,MAAM,CAAC,aAAqB;AAChC,mBAAe;AAAA,EACjB;AACA,QAAM,QAAQ,MAAM;AAClB,QAAI,CAAC;AAAA,EACP;AACA,SAAO,EAAE,OAAO,KAAK,KAAK,OAAO,WAAW,CAAC,EAAE;AACjD;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/wasm-model/wasm-buffer.ts","../src/wasm-model/wasm-model.ts","../src/model-runner/inputs.ts","../src/model-runner/outputs.ts","../src/model-runner/perf.ts","../src/model-runner/model-runner.ts","../src/model-scheduler/model-scheduler.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nexport type { InputVarId, OutputVarId } from './_shared'\nexport * from './wasm-model'\nexport * from './model-runner'\nexport * from './model-scheduler'\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { WasmModule } from './wasm-module'\n\n/**\n * Wraps a `WebAssembly.Memory` buffer allocated on the wasm heap.\n *\n * When this is used synchronously (in the browser's normal JavaScript thread),\n * the client can use `getArrayView` to write directly into the underlying memory.\n *\n * Note, however, that `WebAssembly.Memory` buffers cannot be transferred to/from\n * a Web Worker. When using this class in a worker thread, create a separate\n * `Float64Array` that can be transferred between the worker and the client running\n * in the browser's normal JS thread, and then use `getArrayView` to copy into and\n * out of the wasm buffer.\n */\nexport class WasmBuffer {\n private byteOffset: number\n private heapArray: Float64Array\n\n /**\n * @param wasmModule The `WasmModule` used to initialize the memory.\n * @param numElements The number of 64-bit `double` elements in the buffer.\n */\n constructor(private readonly wasmModule: WasmModule, numElements: number) {\n const sizeOfFloat64 = 8\n const lengthInBytes = numElements * sizeOfFloat64\n this.byteOffset = wasmModule._malloc(lengthInBytes)\n const float64Offset = this.byteOffset / sizeOfFloat64\n this.heapArray = wasmModule.HEAPF64.subarray(float64Offset, float64Offset + numElements)\n }\n\n /**\n * @return A new `Float64Array` view on the underlying heap buffer.\n */\n getArrayView(): Float64Array {\n return this.heapArray\n }\n\n /**\n * @return The raw address of the underlying heap buffer.\n * @hidden This is intended for use by `WasmModel` only.\n */\n getAddress(): number {\n return this.byteOffset\n }\n\n /**\n * Dispose the buffer by freeing the allocated heap memory.\n */\n dispose(): void {\n if (this.heapArray) {\n this.wasmModule._free(this.byteOffset)\n this.heapArray = undefined\n this.byteOffset = undefined\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { OutputVarId } from '../_shared'\nimport { WasmBuffer } from './wasm-buffer'\nimport type { WasmModule } from './wasm-module'\n\n/**\n * An interface to the generated WebAssembly model. Allows for running the model with\n * a given set of input values, producing a set of output values.\n */\nexport class WasmModel {\n /** The start time for the model (aka `INITIAL TIME`). */\n public readonly startTime: number\n /** The end time for the model (aka `FINAL TIME`). */\n public readonly endTime: number\n /** The frequency with which output values are saved (aka `SAVEPER`). */\n public readonly saveFreq: number\n /** The number of save points for each output. */\n public readonly numSavePoints: number\n\n private readonly wasmRunModel: (inputsAddress: number, outputsAddress: number) => void\n\n /**\n * @param wasmModule The `WasmModule` that provides access to the native functions.\n */\n constructor(wasmModule: WasmModule) {\n function getNumberValue(funcName: string): number {\n const wasmGetValue: () => number = wasmModule.cwrap(funcName, 'number', [])\n return wasmGetValue()\n }\n this.startTime = getNumberValue('getInitialTime')\n this.endTime = getNumberValue('getFinalTime')\n this.saveFreq = getNumberValue('getSaveper')\n\n // Each series will include one data point per \"save\", inclusive of the\n // start and end times\n this.numSavePoints = Math.round((this.endTime - this.startTime) / this.saveFreq) + 1\n\n this.wasmRunModel = wasmModule.cwrap('runModelWithBuffers', null, ['number', 'number'])\n }\n\n /**\n * Run the model, using inputs from the `inputs` buffer, and writing outputs into\n * the `outputs` buffer.\n *\n * @param inputs The buffer containing inputs in the order expected by the model.\n * @param outputs The buffer into which the model will store output values.\n */\n runModel(inputs: WasmBuffer, outputs: WasmBuffer): void {\n this.wasmRunModel(inputs.getAddress(), outputs.getAddress())\n }\n}\n\n/**\n * The result of model initialization.\n */\nexport interface WasmModelInitResult {\n /** The wasm model. */\n model: WasmModel\n /** The buffer used to pass input values to the model. */\n inputsBuffer: WasmBuffer\n /** The buffer used to receive output values from the model. */\n outputsBuffer: WasmBuffer\n /** The output variable IDs. */\n outputVarIds: OutputVarId[]\n}\n\n/**\n * Initialize the wasm model and buffers.\n *\n * @param wasmModule The `WasmModule` that wraps the `wasm` binary.\n * @param numInputs The number of input variables, per the spec file passed to `sde`.\n * @param outputVarIds The output variable IDs, per the spec file passed to `sde`.\n */\nexport function initWasmModelAndBuffers(\n wasmModule: WasmModule,\n numInputs: number,\n outputVarIds: OutputVarId[]\n): WasmModelInitResult {\n // Wrap the native C `runModelWithBuffers` function in a JS function that we can call\n const model = new WasmModel(wasmModule)\n\n // Allocate a buffer that is large enough to hold the input values\n const inputsBuffer = new WasmBuffer(wasmModule, numInputs)\n\n // Allocate a buffer that is large enough to hold the series data for\n // each output variable\n const outputsBuffer = new WasmBuffer(wasmModule, outputVarIds.length * model.numSavePoints)\n\n return {\n model,\n inputsBuffer,\n outputsBuffer,\n outputVarIds\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { InputVarId } from '../_shared'\n\n/** Callback functions that are called when the input value is changed. */\nexport interface InputCallbacks {\n /** Called after a new value is set. */\n onSet?: () => void\n}\n\n/**\n * Represents a writable model input.\n */\nexport interface InputValue {\n /** The ID of the associated input variable, as used in SDEverywhere. */\n varId: InputVarId\n /** Get the current value of the input. */\n get: () => number\n /** Set the input to the given value. */\n set: (value: number) => void\n /** Reset the input to its default value. */\n reset: () => void\n /** Callback functions that are called when the input value is changed. */\n callbacks: InputCallbacks\n}\n\n/**\n * Create a basic `InputValue` instance that notifies when a new value is set.\n *\n * @param varId The input variable ID, as used in SDEverywhere.\n * @param defaultValue The default value of the input.\n * @param initialValue The inital value of the input; if undefined, will use `defaultValue`.\n */\nexport function createInputValue(varId: InputVarId, defaultValue: number, initialValue?: number): InputValue {\n let currentValue = initialValue !== undefined ? initialValue : defaultValue\n\n // The `onSet` callback is initially undefined but will be installed by `ModelScheduler`\n const callbacks: InputCallbacks = {}\n\n const get = () => {\n return currentValue\n }\n\n const set = (newValue: number) => {\n if (newValue !== currentValue) {\n currentValue = newValue\n callbacks.onSet?.()\n }\n }\n\n const reset = () => {\n set(defaultValue)\n }\n\n return { varId, get, set, reset, callbacks }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { Result } from 'neverthrow'\nimport { ok, err } from 'neverthrow'\nimport type { OutputVarId } from '../_shared'\n\n/** Indicates the type of error encountered when parsing an outputs buffer. */\nexport type ParseError = 'invalid-point-count'\n\n/** A data point. */\nexport interface Point {\n /** The x value (typically a time value). */\n x: number\n /** The y value. */\n y: number\n}\n\n/**\n * A time series of data points for an output variable.\n */\nexport class Series {\n /**\n * @param varId The ID for the output variable (as used by SDEverywhere).\n * @param points The data points for the variable, one point per time increment.\n */\n constructor(public readonly varId: OutputVarId, public readonly points: Point[]) {}\n\n /**\n * Return the Y value at the given time. Note that this does not attempt to interpolate\n * if there is no data point defined for the given time and will return undefined in\n * that case.\n *\n * @param time The x (time) value.\n * @return The y value for the given time, or undefined if there is no data point defined\n * for the given time.\n */\n getValueAtTime(time: number): number | undefined {\n // TODO: Add option to allow interpolation if the given time value is in between points\n // TODO: Use binary search to make lookups faster\n return this.points.find(p => p.x === time)?.y\n }\n\n /**\n * Create a new `Series` instance that is a copy of this one.\n */\n copy(): Series {\n // Create a deep copy\n const pointsCopy = this.points.map(p => ({ ...p }))\n return new Series(this.varId, pointsCopy)\n }\n}\n\n/** Represents the outputs from a model run. */\nexport class Outputs {\n /** The number of data points in each series. */\n public readonly seriesLength: number\n /** The array of series, one for each output variable. */\n public readonly varSeries: Series[]\n\n /**\n * The latest model run time, in milliseconds.\n * @hidden This is not yet part of the public API; it is exposed here for use\n * in performance testing tools.\n */\n public runTimeInMillis: number\n\n /**\n * @param varIds The output variable identifiers.\n * @param startTime The start time for the model.\n * @param endTime The end time for the model.\n * @param saveFreq The frequency with which output values are saved (aka `SAVEPER`).\n */\n constructor(\n public readonly varIds: OutputVarId[],\n public readonly startTime: number,\n public readonly endTime: number,\n public readonly saveFreq = 1\n ) {\n // Each series will include one data point per \"save\", inclusive of the\n // start and end times\n this.seriesLength = Math.round((endTime - startTime) / saveFreq) + 1\n\n // Create an array of arrays, one for each output variable\n this.varSeries = new Array(varIds.length)\n\n // Populate the arrays, filling in the time for each point\n for (let i = 0; i < varIds.length; i++) {\n const points: Point[] = new Array(this.seriesLength)\n for (let j = 0; j < this.seriesLength; j++) {\n points[j] = { x: startTime + j * saveFreq, y: 0 }\n }\n const varId = varIds[i]\n this.varSeries[i] = new Series(varId, points)\n }\n }\n\n /**\n * Parse the given raw float buffer (produced by the model) and store the values\n * into this `Outputs` instance.\n *\n * Note that the length of `outputsBuffer` must be greater than or equal to\n * the capacity of this `Outputs` instance. The `Outputs` instance is allowed\n * to be smaller to support the case where you want to extract a subset of\n * the time range in the buffer produced by the model.\n *\n * @param outputsBuffer The raw outputs buffer produced by the model.\n * @param rowLength The number of elements per row (one element per save point).\n * @return An `ok` result if the buffer is valid, otherwise an `err` result.\n */\n updateFromBuffer(outputsBuffer: Float64Array, rowLength: number): Result<void, ParseError> {\n const result = parseOutputsBuffer(outputsBuffer, rowLength, this)\n if (result.isOk()) {\n return ok(undefined)\n } else {\n return err(result.error)\n }\n }\n\n /**\n * Return the series for the given output variable.\n *\n * @param varId The ID of the output variable (as used by SDEverywhere).\n */\n getSeriesForVar(varId: OutputVarId): Series | undefined {\n const seriesIndex = this.varIds.indexOf(varId)\n if (seriesIndex >= 0) {\n return this.varSeries[seriesIndex]\n } else {\n // TODO: Error\n return undefined\n }\n }\n}\n\n/**\n * Parse the raw buffer produced by the model and store the values in the\n * given (reused) `Outputs` object.\n *\n * @param outputsBuffer The raw outputs buffer produced by the model.\n * @param rowLength The number of elements per row (one element per year or save point).\n * @return An `ok` result if the buffer is valid, otherwise an `err` result.\n * @hidden\n */\nfunction parseOutputsBuffer(\n outputsBuffer: Float64Array,\n rowLength: number,\n outputs: Outputs\n): Result<Outputs, ParseError> {\n const varCount = outputs.varIds.length\n const seriesLength = outputs.seriesLength\n if (rowLength < seriesLength || outputsBuffer.length < varCount * seriesLength) {\n return err('invalid-point-count')\n }\n\n // The buffer populated by the C `runModelWithBuffers` function is already\n // transposed, so the first \"row\" contains the values for the first output\n // variable (from start time to end time), and so on.\n for (let outputVarIndex = 0; outputVarIndex < varCount; outputVarIndex++) {\n const series = outputs.varSeries[outputVarIndex]\n let sourceIndex = rowLength * outputVarIndex\n for (let valueIndex = 0; valueIndex < seriesLength; valueIndex++) {\n series.points[valueIndex].y = validateNumber(outputsBuffer[sourceIndex])\n sourceIndex++\n }\n }\n\n return ok(outputs)\n}\n\n/**\n * Return the given number if it is valid, or undefined if it is invalid.\n *\n * SDE converts Vensim's `:NA:` values to `-DBL_MAX`, so if we see a very large negative\n * value, convert it to `undefined`. This is preferable to including extreme values\n * because some charting libraries (e.g. Chart.js) appear to choke on these large values\n * in certain browsers (e.g. Safari), but `undefined` appears to be handled better and\n * does a better job of signaling that the data point is undefined.\n *\n * @hidden\n */\nfunction validateNumber(x: number): number | undefined {\n if (!isNaN(x) && x > -1e32) {\n return x\n } else {\n return undefined\n }\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nlet isWeb: boolean\n\n/**\n * Return a timestamp that can be passed to `perfElapsed` for calculating the elapsed\n * time of an operation.\n *\n * @hidden This is not part of the public API; exposed only for use in performance testing.\n */\nexport function perfNow(): unknown {\n // Note that `self` resolves to the window (in browser context) or the worker global scope\n // (in a Web Worker context)\n if (isWeb === undefined) {\n isWeb = typeof self !== 'undefined' && self?.performance !== undefined\n }\n if (isWeb) {\n return self.performance.now()\n } else {\n // XXX: We only use `process` in two places; we bypass type checking instead of\n // setting up type declarations\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n return process?.hrtime()\n }\n}\n\n/**\n * Return the elapsed time between the given timestamp (created by `perfNow`) and now.\n *\n * @hidden This is not part of the public API; exposed only for use in performance testing.\n */\nexport function perfElapsed(t0: unknown): number {\n if (isWeb) {\n const t1 = self.performance.now()\n return (t1 as number) - (t0 as number)\n } else {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const elapsed = process.hrtime(t0) as number[]\n // Convert from nanoseconds to milliseconds\n return (elapsed[0] * 1000000000 + elapsed[1]) / 1000000\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { WasmModelInitResult } from '../wasm-model'\nimport type { InputValue } from './inputs'\nimport { Outputs } from './outputs'\nimport { perfElapsed, perfNow } from './perf'\n\n/**\n * Abstraction that allows for running the wasm model on the JS thread\n * or asynchronously (e.g. in a Web Worker), depending on the implementation.\n */\nexport interface ModelRunner {\n /**\n * Create an `Outputs` instance that is sized to accommodate the output variable\n * data stored by the model.\n *\n * @return A new `Outputs` instance.\n */\n createOutputs(): Outputs\n\n /**\n * Run the model.\n *\n * @param inputs The model input values (must be in the same order as in the spec file).\n * @param outputs The structure into which the model outputs will be stored.\n * @return A promise that resolves with the outputs when the model run is complete.\n */\n runModel(inputs: InputValue[], outputs: Outputs): Promise<Outputs>\n\n /**\n * Run the model synchronously.\n *\n * @param inputs The model input values (must be in the same order as in the spec file).\n * @param outputs The structure into which the model outputs will be stored.\n * @return The outputs of the run.\n *\n * @hidden This is only intended for internal use; some implementations may not support\n * running the model synchronously, in which case this will be undefined.\n */\n runModelSync?(inputs: InputValue[], outputs: Outputs): Outputs\n\n /**\n * Terminate the runner by releasing underlying resources (e.g., the worker thread or\n * Wasm module/buffers).\n */\n terminate(): Promise<void>\n}\n\n/**\n * Create a `ModelRunner` that runs the given wasm model on the JS thread.\n *\n * @param wasmResult The result of initializing the wasm model.\n */\nexport function createWasmModelRunner(wasmResult: WasmModelInitResult): ModelRunner {\n // Create views on the wasm buffers\n const wasmModel = wasmResult.model\n const inputsBuffer = wasmResult.inputsBuffer\n const inputsArray = inputsBuffer.getArrayView()\n const outputsBuffer = wasmResult.outputsBuffer\n const outputsArray = outputsBuffer.getArrayView()\n const rowLength = wasmModel.numSavePoints\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n const runModelSync = (inputs: InputValue[], outputs: Outputs) => {\n // Capture the current set of input values into the reusable buffer\n let i = 0\n for (const input of inputs) {\n inputsArray[i++] = input.get()\n }\n\n // Run the model\n const t0 = perfNow()\n wasmModel.runModel(inputsBuffer, outputsBuffer)\n outputs.runTimeInMillis = perfElapsed(t0)\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n outputs.updateFromBuffer(outputsArray, rowLength)\n\n return outputs\n }\n\n return {\n createOutputs: () => {\n return new Outputs(wasmResult.outputVarIds, wasmModel.startTime, wasmModel.endTime, wasmModel.saveFreq)\n },\n\n runModel: (inputs, outputs) => {\n if (terminated) {\n return Promise.reject(new Error('Model runner has already been terminated'))\n }\n return Promise.resolve(runModelSync(inputs, outputs))\n },\n\n runModelSync: (inputs, outputs) => {\n if (terminated) {\n throw new Error('Model runner has already been terminated')\n }\n return runModelSync(inputs, outputs)\n },\n\n terminate: () => {\n if (!terminated) {\n // TODO: Release wasm-related resources (module or buffers)\n terminated = true\n }\n return Promise.resolve()\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { InputVarId } from '../_shared'\nimport type { InputValue, ModelRunner, Outputs } from '../model-runner'\n\n/**\n * A high-level interface that schedules running of the underlying `WasmModel`.\n *\n * When one or more input values are changed, this class will schedule a model\n * run to be completed as soon as possible. When the model run has completed,\n * `onOutputsChanged` is called to notify that new output data is available.\n *\n * The `ModelRunner` is pluggable to allow for running the model synchronously\n * (on the main JavaScript thread) or asynchronously (in a Web Worker or Node.js\n * worker thread).\n */\nexport class ModelScheduler {\n /** The second array that holds a stable copy of the user inputs. */\n private readonly currentInputs: InputValue[]\n\n /** Whether a model run has been scheduled. */\n private runNeeded = false\n\n /** Whether a model run is in progress. */\n private runInProgress = false\n\n /** Called when `outputs` has been updated after a model run. */\n public onOutputsChanged?: (outputs: Outputs) => void\n\n /**\n * @param runner The model runner.\n * @param userInputs The input values, in the same order as in the spec file passed to `sde`.\n * @param outputs The structure into which the model outputs will be stored.\n */\n constructor(\n private readonly runner: ModelRunner,\n private readonly userInputs: InputValue[],\n private outputs: Outputs\n ) {\n // When any input has an updated value, schedule a model run on the next tick\n const afterSet = () => {\n this.runWasmModelIfNeeded()\n }\n for (const userInput of userInputs) {\n userInput.callbacks.onSet = afterSet\n }\n\n // Create a second array to hold a stable copy of the user inputs during model runs\n this.currentInputs = []\n for (const userInput of userInputs) {\n this.currentInputs.push(createSimpleInputValue(userInput.varId))\n }\n }\n\n /**\n * Schedule a wasm model run (if not already pending). When the run is\n * complete, save the outputs and call the `onOutputsChanged` callback.\n */\n private runWasmModelIfNeeded(): void {\n // Set a flag indicating that a new run is needed (even if one is already\n // in progress)\n this.runNeeded = true\n\n if (this.runInProgress) {\n // A run is already in progress; let it finish first\n return\n } else {\n // A run is not already in progress, so schedule it now. We use\n // `setTimeout` so that if a lot of inputs are all changing at once\n // (like after a reset), we wait for all those `set` or `reset`\n // calls to finish before gathering the input values into an array\n // and initiating the run on the next tick.\n this.runInProgress = true\n setTimeout(() => {\n // Kick off the (possibly asynchronous) model run\n this.runWasmModelNow()\n }, 0)\n }\n }\n\n /**\n * Run the wasm model asynchronously using the current set of input values.\n */\n private async runWasmModelNow(): Promise<void> {\n // Copy the current inputs into a separate array; this ensures that the\n // model run uses a stable set of inputs, even if the user continues to\n // change the inputs while the model is being run asynchronously\n for (let i = 0; i < this.userInputs.length; i++) {\n this.currentInputs[i].set(this.userInputs[i].get())\n }\n\n // Run the model with the current set of input values and save the outputs\n try {\n this.outputs = await this.runner.runModel(this.currentInputs, this.outputs)\n this.onOutputsChanged?.(this.outputs)\n } catch (e) {\n console.error(`ERROR: Failed to run model: ${e.message}`)\n }\n\n // See if another run is needed\n if (this.runNeeded) {\n // Keep `runInProgress` set, but clear the `runNeeded` flag\n this.runNeeded = false\n setTimeout(() => {\n this.runWasmModelNow()\n }, 0)\n } else {\n // No run needed, so clear both flags\n this.runNeeded = false\n this.runInProgress = false\n }\n }\n}\n\n/**\n * Create an `InputValue` that is only used to hold a copy of another input (no callbacks).\n * @hidden\n */\nfunction createSimpleInputValue(varId: InputVarId): InputValue {\n let currentValue = 0\n const get = () => {\n return currentValue\n }\n const set = (newValue: number) => {\n currentValue = newValue\n }\n const reset = () => {\n set(0)\n }\n return { varId, get, set, reset, callbacks: {} }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACgBO,IAAM,aAAN,MAAiB;AAAA,EAQtB,YAA6B,YAAwB,aAAqB;AAA7C;AAC3B,UAAM,gBAAgB;AACtB,UAAM,gBAAgB,cAAc;AACpC,SAAK,aAAa,WAAW,QAAQ,aAAa;AAClD,UAAM,gBAAgB,KAAK,aAAa;AACxC,SAAK,YAAY,WAAW,QAAQ,SAAS,eAAe,gBAAgB,WAAW;AAAA,EACzF;AAAA,EAKA,eAA6B;AAC3B,WAAO,KAAK;AAAA,EACd;AAAA,EAMA,aAAqB;AACnB,WAAO,KAAK;AAAA,EACd;AAAA,EAKA,UAAgB;AACd,QAAI,KAAK,WAAW;AAClB,WAAK,WAAW,MAAM,KAAK,UAAU;AACrC,WAAK,YAAY;AACjB,WAAK,aAAa;AAAA,IACpB;AAAA,EACF;AACF;;;AC/CO,IAAM,YAAN,MAAgB;AAAA,EAerB,YAAY,YAAwB;AAClC,aAAS,eAAe,UAA0B;AAChD,YAAM,eAA6B,WAAW,MAAM,UAAU,UAAU,CAAC,CAAC;AAC1E,aAAO,aAAa;AAAA,IACtB;AACA,SAAK,YAAY,eAAe,gBAAgB;AAChD,SAAK,UAAU,eAAe,cAAc;AAC5C,SAAK,WAAW,eAAe,YAAY;AAI3C,SAAK,gBAAgB,KAAK,OAAO,KAAK,UAAU,KAAK,aAAa,KAAK,QAAQ,IAAI;AAEnF,SAAK,eAAe,WAAW,MAAM,uBAAuB,MAAM,CAAC,UAAU,QAAQ,CAAC;AAAA,EACxF;AAAA,EASA,SAAS,QAAoB,SAA2B;AACtD,SAAK,aAAa,OAAO,WAAW,GAAG,QAAQ,WAAW,CAAC;AAAA,EAC7D;AACF;AAuBO,SAAS,wBACd,YACA,WACA,cACqB;AAErB,QAAM,QAAQ,IAAI,UAAU,UAAU;AAGtC,QAAM,eAAe,IAAI,WAAW,YAAY,SAAS;AAIzD,QAAM,gBAAgB,IAAI,WAAW,YAAY,aAAa,SAAS,MAAM,aAAa;AAE1F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;;;AC9DO,SAAS,iBAAiB,OAAmB,cAAsB,cAAmC;AAC3G,MAAI,eAAe,iBAAiB,SAAY,eAAe;AAG/D,QAAM,YAA4B,CAAC;AAEnC,QAAM,MAAM,MAAM;AAChB,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,CAAC,aAAqB;AA3CpC;AA4CI,QAAI,aAAa,cAAc;AAC7B,qBAAe;AACf,sBAAU,UAAV;AAAA,IACF;AAAA,EACF;AAEA,QAAM,QAAQ,MAAM;AAClB,QAAI,YAAY;AAAA,EAClB;AAEA,SAAO,EAAE,OAAO,KAAK,KAAK,OAAO,UAAU;AAC7C;;;ACpDA,wBAAwB;AAiBjB,IAAM,SAAN,MAAa;AAAA,EAKlB,YAA4B,OAAoC,QAAiB;AAArD;AAAoC;AAAA,EAAkB;AAAA,EAWlF,eAAe,MAAkC;AApCnD;AAuCI,YAAO,UAAK,OAAO,KAAK,OAAK,EAAE,MAAM,IAAI,MAAlC,mBAAqC;AAAA,EAC9C;AAAA,EAKA,OAAe;AAEb,UAAM,aAAa,KAAK,OAAO,IAAI,QAAM,EAAE,GAAG,EAAE,EAAE;AAClD,WAAO,IAAI,OAAO,KAAK,OAAO,UAAU;AAAA,EAC1C;AACF;AAGO,IAAM,UAAN,MAAc;AAAA,EAmBnB,YACkB,QACA,WACA,SACA,WAAW,GAC3B;AAJgB;AACA;AACA;AACA;AAIhB,SAAK,eAAe,KAAK,OAAO,UAAU,aAAa,QAAQ,IAAI;AAGnE,SAAK,YAAY,IAAI,MAAM,OAAO,MAAM;AAGxC,aAAS,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;AACtC,YAAM,SAAkB,IAAI,MAAM,KAAK,YAAY;AACnD,eAAS,IAAI,GAAG,IAAI,KAAK,cAAc,KAAK;AAC1C,eAAO,KAAK,EAAE,GAAG,YAAY,IAAI,UAAU,GAAG,EAAE;AAAA,MAClD;AACA,YAAM,QAAQ,OAAO;AACrB,WAAK,UAAU,KAAK,IAAI,OAAO,OAAO,MAAM;AAAA,IAC9C;AAAA,EACF;AAAA,EAeA,iBAAiB,eAA6B,WAA6C;AACzF,UAAM,SAAS,mBAAmB,eAAe,WAAW,IAAI;AAChE,QAAI,OAAO,KAAK,GAAG;AACjB,iBAAO,sBAAG,MAAS;AAAA,IACrB,OAAO;AACL,iBAAO,uBAAI,OAAO,KAAK;AAAA,IACzB;AAAA,EACF;AAAA,EAOA,gBAAgB,OAAwC;AACtD,UAAM,cAAc,KAAK,OAAO,QAAQ,KAAK;AAC7C,QAAI,eAAe,GAAG;AACpB,aAAO,KAAK,UAAU;AAAA,IACxB,OAAO;AAEL,aAAO;AAAA,IACT;AAAA,EACF;AACF;AAWA,SAAS,mBACP,eACA,WACA,SAC6B;AAC7B,QAAM,WAAW,QAAQ,OAAO;AAChC,QAAM,eAAe,QAAQ;AAC7B,MAAI,YAAY,gBAAgB,cAAc,SAAS,WAAW,cAAc;AAC9E,eAAO,uBAAI,qBAAqB;AAAA,EAClC;AAKA,WAAS,iBAAiB,GAAG,iBAAiB,UAAU,kBAAkB;AACxE,UAAM,SAAS,QAAQ,UAAU;AACjC,QAAI,cAAc,YAAY;AAC9B,aAAS,aAAa,GAAG,aAAa,cAAc,cAAc;AAChE,aAAO,OAAO,YAAY,IAAI,eAAe,cAAc,YAAY;AACvE;AAAA,IACF;AAAA,EACF;AAEA,aAAO,sBAAG,OAAO;AACnB;AAaA,SAAS,eAAe,GAA+B;AACrD,MAAI,CAAC,MAAM,CAAC,KAAK,IAAI,OAAO;AAC1B,WAAO;AAAA,EACT,OAAO;AACL,WAAO;AAAA,EACT;AACF;;;ACxLA,IAAI;AAQG,SAAS,UAAmB;AAGjC,MAAI,UAAU,QAAW;AACvB,YAAQ,OAAO,SAAS,gBAAe,6BAAM,iBAAgB;AAAA,EAC/D;AACA,MAAI,OAAO;AACT,WAAO,KAAK,YAAY,IAAI;AAAA,EAC9B,OAAO;AAKL,WAAO,mCAAS;AAAA,EAClB;AACF;AAOO,SAAS,YAAY,IAAqB;AAC/C,MAAI,OAAO;AACT,UAAM,KAAK,KAAK,YAAY,IAAI;AAChC,WAAQ,KAAiB;AAAA,EAC3B,OAAO;AAGL,UAAM,UAAU,QAAQ,OAAO,EAAE;AAEjC,YAAQ,QAAQ,KAAK,MAAa,QAAQ,MAAM;AAAA,EAClD;AACF;;;ACUO,SAAS,sBAAsB,YAA8C;AAElF,QAAM,YAAY,WAAW;AAC7B,QAAM,eAAe,WAAW;AAChC,QAAM,cAAc,aAAa,aAAa;AAC9C,QAAM,gBAAgB,WAAW;AACjC,QAAM,eAAe,cAAc,aAAa;AAChD,QAAM,YAAY,UAAU;AAG5B,MAAI,aAAa;AAEjB,QAAM,eAAe,CAAC,QAAsB,YAAqB;AAE/D,QAAI,IAAI;AACR,eAAW,SAAS,QAAQ;AAC1B,kBAAY,OAAO,MAAM,IAAI;AAAA,IAC/B;AAGA,UAAM,KAAK,QAAQ;AACnB,cAAU,SAAS,cAAc,aAAa;AAC9C,YAAQ,kBAAkB,YAAY,EAAE;AAIxC,YAAQ,iBAAiB,cAAc,SAAS;AAEhD,WAAO;AAAA,EACT;AAEA,SAAO;AAAA,IACL,eAAe,MAAM;AACnB,aAAO,IAAI,QAAQ,WAAW,cAAc,UAAU,WAAW,UAAU,SAAS,UAAU,QAAQ;AAAA,IACxG;AAAA,IAEA,UAAU,CAAC,QAAQ,YAAY;AAC7B,UAAI,YAAY;AACd,eAAO,QAAQ,OAAO,IAAI,MAAM,0CAA0C,CAAC;AAAA,MAC7E;AACA,aAAO,QAAQ,QAAQ,aAAa,QAAQ,OAAO,CAAC;AAAA,IACtD;AAAA,IAEA,cAAc,CAAC,QAAQ,YAAY;AACjC,UAAI,YAAY;AACd,cAAM,IAAI,MAAM,0CAA0C;AAAA,MAC5D;AACA,aAAO,aAAa,QAAQ,OAAO;AAAA,IACrC;AAAA,IAEA,WAAW,MAAM;AACf,UAAI,CAAC,YAAY;AAEf,qBAAa;AAAA,MACf;AACA,aAAO,QAAQ,QAAQ;AAAA,IACzB;AAAA,EACF;AACF;;;AC/FO,IAAM,iBAAN,MAAqB;AAAA,EAkB1B,YACmB,QACA,YACT,SACR;AAHiB;AACA;AACT;AAhBV,SAAQ,YAAY;AAGpB,SAAQ,gBAAgB;AAgBtB,UAAM,WAAW,MAAM;AACrB,WAAK,qBAAqB;AAAA,IAC5B;AACA,eAAW,aAAa,YAAY;AAClC,gBAAU,UAAU,QAAQ;AAAA,IAC9B;AAGA,SAAK,gBAAgB,CAAC;AACtB,eAAW,aAAa,YAAY;AAClC,WAAK,cAAc,KAAK,uBAAuB,UAAU,KAAK,CAAC;AAAA,IACjE;AAAA,EACF;AAAA,EAMQ,uBAA6B;AAGnC,SAAK,YAAY;AAEjB,QAAI,KAAK,eAAe;AAEtB;AAAA,IACF,OAAO;AAML,WAAK,gBAAgB;AACrB,iBAAW,MAAM;AAEf,aAAK,gBAAgB;AAAA,MACvB,GAAG,CAAC;AAAA,IACN;AAAA,EACF;AAAA,EAKA,MAAc,kBAAiC;AAnFjD;AAuFI,aAAS,IAAI,GAAG,IAAI,KAAK,WAAW,QAAQ,KAAK;AAC/C,WAAK,cAAc,GAAG,IAAI,KAAK,WAAW,GAAG,IAAI,CAAC;AAAA,IACpD;AAGA,QAAI;AACF,WAAK,UAAU,MAAM,KAAK,OAAO,SAAS,KAAK,eAAe,KAAK,OAAO;AAC1E,iBAAK,qBAAL,8BAAwB,KAAK;AAAA,IAC/B,SAAS,GAAP;AACA,cAAQ,MAAM,+BAA+B,EAAE,SAAS;AAAA,IAC1D;AAGA,QAAI,KAAK,WAAW;AAElB,WAAK,YAAY;AACjB,iBAAW,MAAM;AACf,aAAK,gBAAgB;AAAA,MACvB,GAAG,CAAC;AAAA,IACN,OAAO;AAEL,WAAK,YAAY;AACjB,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AACF;AAMA,SAAS,uBAAuB,OAA+B;AAC7D,MAAI,eAAe;AACnB,QAAM,MAAM,MAAM;AAChB,WAAO;AAAA,EACT;AACA,QAAM,MAAM,CAAC,aAAqB;AAChC,mBAAe;AAAA,EACjB;AACA,QAAM,QAAQ,MAAM;AAClB,QAAI,CAAC;AAAA,EACP;AACA,SAAO,EAAE,OAAO,KAAK,KAAK,OAAO,WAAW,CAAC,EAAE;AACjD;","names":[]}
package/dist/index.d.ts CHANGED
@@ -58,13 +58,21 @@ declare class WasmBuffer {
58
58
  }
59
59
 
60
60
  /**
61
- * An interface to the En-ROADS model. Allows for running the model with
61
+ * An interface to the generated WebAssembly model. Allows for running the model with
62
62
  * a given set of input values, producing a set of output values.
63
63
  */
64
64
  declare class WasmModel {
65
+ /** The start time for the model (aka `INITIAL TIME`). */
66
+ readonly startTime: number;
67
+ /** The end time for the model (aka `FINAL TIME`). */
68
+ readonly endTime: number;
69
+ /** The frequency with which output values are saved (aka `SAVEPER`). */
70
+ readonly saveFreq: number;
71
+ /** The number of save points for each output. */
72
+ readonly numSavePoints: number;
65
73
  private readonly wasmRunModel;
66
74
  /**
67
- * @param wasmModule The `WasmModule` containing the `runModelWithBuffers` function.
75
+ * @param wasmModule The `WasmModule` that provides access to the native functions.
68
76
  */
69
77
  constructor(wasmModule: WasmModule);
70
78
  /**
@@ -88,10 +96,6 @@ interface WasmModelInitResult {
88
96
  outputsBuffer: WasmBuffer;
89
97
  /** The output variable IDs. */
90
98
  outputVarIds: OutputVarId[];
91
- /** The start time (year) for the model. */
92
- startTime: number;
93
- /** The end time (year) for the model. */
94
- endTime: number;
95
99
  }
96
100
  /**
97
101
  * Initialize the wasm model and buffers.
@@ -99,10 +103,8 @@ interface WasmModelInitResult {
99
103
  * @param wasmModule The `WasmModule` that wraps the `wasm` binary.
100
104
  * @param numInputs The number of input variables, per the spec file passed to `sde`.
101
105
  * @param outputVarIds The output variable IDs, per the spec file passed to `sde`.
102
- * @param startTime The start time (year) for the model.
103
- * @param endTime The end time (year) for the model.
104
106
  */
105
- declare function initWasmModelAndBuffers(wasmModule: WasmModule, numInputs: number, outputVarIds: OutputVarId[], startTime: number, endTime: number): WasmModelInitResult;
107
+ declare function initWasmModelAndBuffers(wasmModule: WasmModule, numInputs: number, outputVarIds: OutputVarId[]): WasmModelInitResult;
106
108
 
107
109
  /** Callback functions that are called when the input value is changed. */
108
110
  interface InputCallbacks {
@@ -137,7 +139,7 @@ declare function createInputValue(varId: InputVarId, defaultValue: number, initi
137
139
  declare type ParseError = 'invalid-point-count';
138
140
  /** A data point. */
139
141
  interface Point {
140
- /** The x value (typically a year). */
142
+ /** The x value (typically a time value). */
141
143
  x: number;
142
144
  /** The y value. */
143
145
  y: number;
@@ -154,9 +156,13 @@ declare class Series {
154
156
  */
155
157
  constructor(varId: OutputVarId, points: Point[]);
156
158
  /**
157
- * Return the Y value at the given time.
159
+ * Return the Y value at the given time. Note that this does not attempt to interpolate
160
+ * if there is no data point defined for the given time and will return undefined in
161
+ * that case.
158
162
  *
159
163
  * @param time The x (time) value.
164
+ * @return The y value for the given time, or undefined if there is no data point defined
165
+ * for the given time.
160
166
  */
161
167
  getValueAtTime(time: number): number | undefined;
162
168
  /**
@@ -167,8 +173,9 @@ declare class Series {
167
173
  /** Represents the outputs from a model run. */
168
174
  declare class Outputs {
169
175
  readonly varIds: OutputVarId[];
170
- readonly timeStart: number;
171
- readonly timeEnd: number;
176
+ readonly startTime: number;
177
+ readonly endTime: number;
178
+ readonly saveFreq: number;
172
179
  /** The number of data points in each series. */
173
180
  readonly seriesLength: number;
174
181
  /** The array of series, one for each output variable. */
@@ -179,7 +186,13 @@ declare class Outputs {
179
186
  * in performance testing tools.
180
187
  */
181
188
  runTimeInMillis: number;
182
- constructor(varIds: OutputVarId[], timeStart: number, timeEnd: number);
189
+ /**
190
+ * @param varIds The output variable identifiers.
191
+ * @param startTime The start time for the model.
192
+ * @param endTime The end time for the model.
193
+ * @param saveFreq The frequency with which output values are saved (aka `SAVEPER`).
194
+ */
195
+ constructor(varIds: OutputVarId[], startTime: number, endTime: number, saveFreq?: number);
183
196
  /**
184
197
  * Parse the given raw float buffer (produced by the model) and store the values
185
198
  * into this `Outputs` instance.
@@ -190,7 +203,7 @@ declare class Outputs {
190
203
  * the time range in the buffer produced by the model.
191
204
  *
192
205
  * @param outputsBuffer The raw outputs buffer produced by the model.
193
- * @param rowLength The number of elements per row (one element per year or save point).
206
+ * @param rowLength The number of elements per row (one element per save point).
194
207
  * @return An `ok` result if the buffer is valid, otherwise an `err` result.
195
208
  */
196
209
  updateFromBuffer(outputsBuffer: Float64Array, rowLength: number): Result<void, ParseError>;
@@ -207,6 +220,13 @@ declare class Outputs {
207
220
  * or asynchronously (e.g. in a Web Worker), depending on the implementation.
208
221
  */
209
222
  interface ModelRunner {
223
+ /**
224
+ * Create an `Outputs` instance that is sized to accommodate the output variable
225
+ * data stored by the model.
226
+ *
227
+ * @return A new `Outputs` instance.
228
+ */
229
+ createOutputs(): Outputs;
210
230
  /**
211
231
  * Run the model.
212
232
  *
package/dist/index.js CHANGED
@@ -1,20 +1,3 @@
1
- var __defProp = Object.defineProperty;
2
- var __getOwnPropSymbols = Object.getOwnPropertySymbols;
3
- var __hasOwnProp = Object.prototype.hasOwnProperty;
4
- var __propIsEnum = Object.prototype.propertyIsEnumerable;
5
- var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
6
- var __spreadValues = (a, b) => {
7
- for (var prop in b || (b = {}))
8
- if (__hasOwnProp.call(b, prop))
9
- __defNormalProp(a, prop, b[prop]);
10
- if (__getOwnPropSymbols)
11
- for (var prop of __getOwnPropSymbols(b)) {
12
- if (__propIsEnum.call(b, prop))
13
- __defNormalProp(a, prop, b[prop]);
14
- }
15
- return a;
16
- };
17
-
18
1
  // src/wasm-model/wasm-buffer.ts
19
2
  var WasmBuffer = class {
20
3
  constructor(wasmModule, numElements) {
@@ -43,24 +26,29 @@ var WasmBuffer = class {
43
26
  // src/wasm-model/wasm-model.ts
44
27
  var WasmModel = class {
45
28
  constructor(wasmModule) {
29
+ function getNumberValue(funcName) {
30
+ const wasmGetValue = wasmModule.cwrap(funcName, "number", []);
31
+ return wasmGetValue();
32
+ }
33
+ this.startTime = getNumberValue("getInitialTime");
34
+ this.endTime = getNumberValue("getFinalTime");
35
+ this.saveFreq = getNumberValue("getSaveper");
36
+ this.numSavePoints = Math.round((this.endTime - this.startTime) / this.saveFreq) + 1;
46
37
  this.wasmRunModel = wasmModule.cwrap("runModelWithBuffers", null, ["number", "number"]);
47
38
  }
48
39
  runModel(inputs, outputs) {
49
40
  this.wasmRunModel(inputs.getAddress(), outputs.getAddress());
50
41
  }
51
42
  };
52
- function initWasmModelAndBuffers(wasmModule, numInputs, outputVarIds, startTime, endTime) {
43
+ function initWasmModelAndBuffers(wasmModule, numInputs, outputVarIds) {
53
44
  const model = new WasmModel(wasmModule);
54
45
  const inputsBuffer = new WasmBuffer(wasmModule, numInputs);
55
- const seriesLength = endTime - startTime + 1;
56
- const outputsBuffer = new WasmBuffer(wasmModule, outputVarIds.length * seriesLength);
46
+ const outputsBuffer = new WasmBuffer(wasmModule, outputVarIds.length * model.numSavePoints);
57
47
  return {
58
48
  model,
59
49
  inputsBuffer,
60
50
  outputsBuffer,
61
- outputVarIds,
62
- startTime,
63
- endTime
51
+ outputVarIds
64
52
  };
65
53
  }
66
54
 
@@ -93,26 +81,25 @@ var Series = class {
93
81
  }
94
82
  getValueAtTime(time) {
95
83
  var _a;
96
- const startTime = this.points[0].x;
97
- return (_a = this.points[time - startTime]) == null ? void 0 : _a.y;
84
+ return (_a = this.points.find((p) => p.x === time)) == null ? void 0 : _a.y;
98
85
  }
99
86
  copy() {
100
- const pointsCopy = this.points.map((p) => __spreadValues({}, p));
87
+ const pointsCopy = this.points.map((p) => ({ ...p }));
101
88
  return new Series(this.varId, pointsCopy);
102
89
  }
103
90
  };
104
91
  var Outputs = class {
105
- constructor(varIds, timeStart, timeEnd) {
92
+ constructor(varIds, startTime, endTime, saveFreq = 1) {
106
93
  this.varIds = varIds;
107
- this.timeStart = timeStart;
108
- this.timeEnd = timeEnd;
109
- this.seriesLength = timeEnd - timeStart + 1;
94
+ this.startTime = startTime;
95
+ this.endTime = endTime;
96
+ this.saveFreq = saveFreq;
97
+ this.seriesLength = Math.round((endTime - startTime) / saveFreq) + 1;
110
98
  this.varSeries = new Array(varIds.length);
111
99
  for (let i = 0; i < varIds.length; i++) {
112
100
  const points = new Array(this.seriesLength);
113
- let time = timeStart;
114
101
  for (let j = 0; j < this.seriesLength; j++) {
115
- points[j] = { x: time++, y: 0 };
102
+ points[j] = { x: startTime + j * saveFreq, y: 0 };
116
103
  }
117
104
  const varId = varIds[i];
118
105
  this.varSeries[i] = new Series(varId, points);
@@ -188,7 +175,7 @@ function createWasmModelRunner(wasmResult) {
188
175
  const inputsArray = inputsBuffer.getArrayView();
189
176
  const outputsBuffer = wasmResult.outputsBuffer;
190
177
  const outputsArray = outputsBuffer.getArrayView();
191
- const rowLength = wasmResult.endTime - wasmResult.startTime + 1;
178
+ const rowLength = wasmModel.numSavePoints;
192
179
  let terminated = false;
193
180
  const runModelSync = (inputs, outputs) => {
194
181
  let i = 0;
@@ -202,6 +189,9 @@ function createWasmModelRunner(wasmResult) {
202
189
  return outputs;
203
190
  };
204
191
  return {
192
+ createOutputs: () => {
193
+ return new Outputs(wasmResult.outputVarIds, wasmModel.startTime, wasmModel.endTime, wasmModel.saveFreq);
194
+ },
205
195
  runModel: (inputs, outputs) => {
206
196
  if (terminated) {
207
197
  return Promise.reject(new Error("Model runner has already been terminated"));
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/wasm-model/wasm-buffer.ts","../src/wasm-model/wasm-model.ts","../src/model-runner/inputs.ts","../src/model-runner/outputs.ts","../src/model-runner/perf.ts","../src/model-runner/model-runner.ts","../src/model-scheduler/model-scheduler.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { WasmModule } from './wasm-module'\n\n/**\n * Wraps a `WebAssembly.Memory` buffer allocated on the wasm heap.\n *\n * When this is used synchronously (in the browser's normal JavaScript thread),\n * the client can use `getArrayView` to write directly into the underlying memory.\n *\n * Note, however, that `WebAssembly.Memory` buffers cannot be transferred to/from\n * a Web Worker. When using this class in a worker thread, create a separate\n * `Float64Array` that can be transferred between the worker and the client running\n * in the browser's normal JS thread, and then use `getArrayView` to copy into and\n * out of the wasm buffer.\n */\nexport class WasmBuffer {\n private byteOffset: number\n private heapArray: Float64Array\n\n /**\n * @param wasmModule The `WasmModule` used to initialize the memory.\n * @param numElements The number of 64-bit `double` elements in the buffer.\n */\n constructor(private readonly wasmModule: WasmModule, numElements: number) {\n const sizeOfFloat64 = 8\n const lengthInBytes = numElements * sizeOfFloat64\n this.byteOffset = wasmModule._malloc(lengthInBytes)\n const float64Offset = this.byteOffset / sizeOfFloat64\n this.heapArray = wasmModule.HEAPF64.subarray(float64Offset, float64Offset + numElements)\n }\n\n /**\n * @return A new `Float64Array` view on the underlying heap buffer.\n */\n getArrayView(): Float64Array {\n return this.heapArray\n }\n\n /**\n * @return The raw address of the underlying heap buffer.\n * @hidden This is intended for use by `WasmModel` only.\n */\n getAddress(): number {\n return this.byteOffset\n }\n\n /**\n * Dispose the buffer by freeing the allocated heap memory.\n */\n dispose(): void {\n if (this.heapArray) {\n this.wasmModule._free(this.byteOffset)\n this.heapArray = undefined\n this.byteOffset = undefined\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { OutputVarId } from '../_shared'\nimport { WasmBuffer } from './wasm-buffer'\nimport type { WasmModule } from './wasm-module'\n\n/**\n * An interface to the En-ROADS model. Allows for running the model with\n * a given set of input values, producing a set of output values.\n */\nexport class WasmModel {\n private readonly wasmRunModel: (inputsAddress: number, outputsAddress: number) => void\n\n /**\n * @param wasmModule The `WasmModule` containing the `runModelWithBuffers` function.\n */\n constructor(wasmModule: WasmModule) {\n this.wasmRunModel = wasmModule.cwrap('runModelWithBuffers', null, ['number', 'number'])\n }\n\n /**\n * Run the model, using inputs from the `inputs` buffer, and writing outputs into\n * the `outputs` buffer.\n *\n * @param inputs The buffer containing inputs in the order expected by the model.\n * @param outputs The buffer into which the model will store output values.\n */\n runModel(inputs: WasmBuffer, outputs: WasmBuffer): void {\n this.wasmRunModel(inputs.getAddress(), outputs.getAddress())\n }\n}\n\n/**\n * The result of model initialization.\n */\nexport interface WasmModelInitResult {\n /** The wasm model. */\n model: WasmModel\n /** The buffer used to pass input values to the model. */\n inputsBuffer: WasmBuffer\n /** The buffer used to receive output values from the model. */\n outputsBuffer: WasmBuffer\n /** The output variable IDs. */\n outputVarIds: OutputVarId[]\n /** The start time (year) for the model. */\n startTime: number\n /** The end time (year) for the model. */\n endTime: number\n}\n\n/**\n * Initialize the wasm model and buffers.\n *\n * @param wasmModule The `WasmModule` that wraps the `wasm` binary.\n * @param numInputs The number of input variables, per the spec file passed to `sde`.\n * @param outputVarIds The output variable IDs, per the spec file passed to `sde`.\n * @param startTime The start time (year) for the model.\n * @param endTime The end time (year) for the model.\n */\nexport function initWasmModelAndBuffers(\n wasmModule: WasmModule,\n numInputs: number,\n outputVarIds: OutputVarId[],\n startTime: number,\n endTime: number\n): WasmModelInitResult {\n // Wrap the native C `runModelWithBuffers` function in a JS function that we can call\n const model = new WasmModel(wasmModule)\n\n // Allocate a buffer that is large enough to hold the input values\n const inputsBuffer = new WasmBuffer(wasmModule, numInputs)\n\n // Each series will include one data point per year, inclusive of the\n // start and end years\n // TODO: We should pull these from the C variables instead of having them passed in;\n // for now we assume `_saveper` is 1 but that should be pulled from the C variable too\n const seriesLength = endTime - startTime + 1\n\n // Allocate a buffer that is large enough to hold the series data for\n // each output variable\n const outputsBuffer = new WasmBuffer(wasmModule, outputVarIds.length * seriesLength)\n\n return {\n model,\n inputsBuffer,\n outputsBuffer,\n outputVarIds,\n startTime,\n endTime\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { InputVarId } from '../_shared'\n\n/** Callback functions that are called when the input value is changed. */\nexport interface InputCallbacks {\n /** Called after a new value is set. */\n onSet?: () => void\n}\n\n/**\n * Represents a writable model input.\n */\nexport interface InputValue {\n /** The ID of the associated input variable, as used in SDEverywhere. */\n varId: InputVarId\n /** Get the current value of the input. */\n get: () => number\n /** Set the input to the given value. */\n set: (value: number) => void\n /** Reset the input to its default value. */\n reset: () => void\n /** Callback functions that are called when the input value is changed. */\n callbacks: InputCallbacks\n}\n\n/**\n * Create a basic `InputValue` instance that notifies when a new value is set.\n *\n * @param varId The input variable ID, as used in SDEverywhere.\n * @param defaultValue The default value of the input.\n * @param initialValue The inital value of the input; if undefined, will use `defaultValue`.\n */\nexport function createInputValue(varId: InputVarId, defaultValue: number, initialValue?: number): InputValue {\n let currentValue = initialValue !== undefined ? initialValue : defaultValue\n\n // The `onSet` callback is initially undefined but will be installed by `ModelScheduler`\n const callbacks: InputCallbacks = {}\n\n const get = () => {\n return currentValue\n }\n\n const set = (newValue: number) => {\n if (newValue !== currentValue) {\n currentValue = newValue\n callbacks.onSet?.()\n }\n }\n\n const reset = () => {\n set(defaultValue)\n }\n\n return { varId, get, set, reset, callbacks }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { Result } from 'neverthrow'\nimport { ok, err } from 'neverthrow'\nimport type { OutputVarId } from '../_shared'\n\n/** Indicates the type of error encountered when parsing an outputs buffer. */\nexport type ParseError = 'invalid-point-count'\n\n/** A data point. */\nexport interface Point {\n /** The x value (typically a year). */\n x: number\n /** The y value. */\n y: number\n}\n\n/**\n * A time series of data points for an output variable.\n */\nexport class Series {\n /**\n * @param varId The ID for the output variable (as used by SDEverywhere).\n * @param points The data points for the variable, one point per time increment.\n */\n constructor(public readonly varId: OutputVarId, public readonly points: Point[]) {}\n\n /**\n * Return the Y value at the given time.\n *\n * @param time The x (time) value.\n */\n getValueAtTime(time: number): number | undefined {\n // TODO: This assumes one data point per year; we should take `_saveper` into account\n // and if it's not 1 point per year, search for a specific x value\n // TODO: Add option to allow interpolation if the given time value is in between points\n const startTime = this.points[0].x\n return this.points[time - startTime]?.y\n }\n\n /**\n * Create a new `Series` instance that is a copy of this one.\n */\n copy(): Series {\n // Create a deep copy\n const pointsCopy = this.points.map(p => ({ ...p }))\n return new Series(this.varId, pointsCopy)\n }\n}\n\n/** Represents the outputs from a model run. */\nexport class Outputs {\n /** The number of data points in each series. */\n public readonly seriesLength: number\n /** The array of series, one for each output variable. */\n public readonly varSeries: Series[]\n\n /**\n * The latest model run time, in milliseconds.\n * @hidden This is not yet part of the public API; it is exposed here for use\n * in performance testing tools.\n */\n public runTimeInMillis: number\n\n constructor(\n public readonly varIds: OutputVarId[],\n public readonly timeStart: number,\n public readonly timeEnd: number\n ) {\n // Each series will include one data point per year, inclusive of the start and end years\n this.seriesLength = timeEnd - timeStart + 1\n\n // Create an array of arrays, one for each output variable\n this.varSeries = new Array(varIds.length)\n\n // Populate the arrays, filling in the time for each point\n for (let i = 0; i < varIds.length; i++) {\n const points: Point[] = new Array(this.seriesLength)\n let time = timeStart\n for (let j = 0; j < this.seriesLength; j++) {\n points[j] = { x: time++, y: 0 }\n }\n const varId = varIds[i]\n this.varSeries[i] = new Series(varId, points)\n }\n }\n\n /**\n * Parse the given raw float buffer (produced by the model) and store the values\n * into this `Outputs` instance.\n *\n * Note that the length of `outputsBuffer` must be greater than or equal to\n * the capacity of this `Outputs` instance. The `Outputs` instance is allowed\n * to be smaller to support the case where you want to extract a subset of\n * the time range in the buffer produced by the model.\n *\n * @param outputsBuffer The raw outputs buffer produced by the model.\n * @param rowLength The number of elements per row (one element per year or save point).\n * @return An `ok` result if the buffer is valid, otherwise an `err` result.\n */\n updateFromBuffer(outputsBuffer: Float64Array, rowLength: number): Result<void, ParseError> {\n const result = parseOutputsBuffer(outputsBuffer, rowLength, this)\n if (result.isOk()) {\n return ok(undefined)\n } else {\n return err(result.error)\n }\n }\n\n /**\n * Return the series for the given output variable.\n *\n * @param varId The ID of the output variable (as used by SDEverywhere).\n */\n getSeriesForVar(varId: OutputVarId): Series | undefined {\n const seriesIndex = this.varIds.indexOf(varId)\n if (seriesIndex >= 0) {\n return this.varSeries[seriesIndex]\n } else {\n // TODO: Error\n return undefined\n }\n }\n}\n\n/**\n * Parse the raw buffer produced by the model and store the values in the\n * given (reused) `Outputs` object.\n *\n * @param outputsBuffer The raw outputs buffer produced by the model.\n * @param rowLength The number of elements per row (one element per year or save point).\n * @return An `ok` result if the buffer is valid, otherwise an `err` result.\n * @hidden\n */\nfunction parseOutputsBuffer(\n outputsBuffer: Float64Array,\n rowLength: number,\n outputs: Outputs\n): Result<Outputs, ParseError> {\n const varCount = outputs.varIds.length\n const seriesLength = outputs.seriesLength\n if (rowLength < seriesLength || outputsBuffer.length < varCount * seriesLength) {\n return err('invalid-point-count')\n }\n\n // The buffer populated by the C `runModelWithBuffers` function is already\n // transposed, so the first \"row\" contains the values for the first output\n // variable (from start time to end time), and so on.\n for (let outputVarIndex = 0; outputVarIndex < varCount; outputVarIndex++) {\n const series = outputs.varSeries[outputVarIndex]\n let sourceIndex = rowLength * outputVarIndex\n for (let valueIndex = 0; valueIndex < seriesLength; valueIndex++) {\n series.points[valueIndex].y = validateNumber(outputsBuffer[sourceIndex])\n sourceIndex++\n }\n }\n\n return ok(outputs)\n}\n\n/**\n * Return the given number if it is valid, or undefined if it is invalid.\n *\n * SDE converts Vensim's `:NA:` values to `-DBL_MAX`, so if we see a very large negative\n * value, convert it to `undefined`. This is preferable to including extreme values\n * because some charting libraries (e.g. Chart.js) appear to choke on these large values\n * in certain browsers (e.g. Safari), but `undefined` appears to be handled better and\n * does a better job of signaling that the data point is undefined.\n *\n * @hidden\n */\nfunction validateNumber(x: number): number | undefined {\n if (!isNaN(x) && x > -1e32) {\n return x\n } else {\n return undefined\n }\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nlet isWeb: boolean\n\n/**\n * Return a timestamp that can be passed to `perfElapsed` for calculating the elapsed\n * time of an operation.\n *\n * @hidden This is not part of the public API; exposed only for use in performance testing.\n */\nexport function perfNow(): unknown {\n // Note that `self` resolves to the window (in browser context) or the worker global scope\n // (in a Web Worker context)\n if (isWeb === undefined) {\n isWeb = typeof self !== 'undefined' && self?.performance !== undefined\n }\n if (isWeb) {\n return self.performance.now()\n } else {\n // XXX: We only use `process` in two places; we bypass type checking instead of\n // setting up type declarations\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n return process?.hrtime()\n }\n}\n\n/**\n * Return the elapsed time between the given timestamp (created by `perfNow`) and now.\n *\n * @hidden This is not part of the public API; exposed only for use in performance testing.\n */\nexport function perfElapsed(t0: unknown): number {\n if (isWeb) {\n const t1 = self.performance.now()\n return (t1 as number) - (t0 as number)\n } else {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const elapsed = process.hrtime(t0) as number[]\n // Convert from nanoseconds to milliseconds\n return (elapsed[0] * 1000000000 + elapsed[1]) / 1000000\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { WasmModelInitResult } from '../wasm-model'\nimport type { InputValue } from './inputs'\nimport type { Outputs } from './outputs'\nimport { perfElapsed, perfNow } from './perf'\n\n/**\n * Abstraction that allows for running the wasm model on the JS thread\n * or asynchronously (e.g. in a Web Worker), depending on the implementation.\n */\nexport interface ModelRunner {\n /**\n * Run the model.\n *\n * @param inputs The model input values (must be in the same order as in the spec file).\n * @param outputs The structure into which the model outputs will be stored.\n * @return A promise that resolves with the outputs when the model run is complete.\n */\n runModel(inputs: InputValue[], outputs: Outputs): Promise<Outputs>\n\n /**\n * Run the model synchronously.\n *\n * @param inputs The model input values (must be in the same order as in the spec file).\n * @param outputs The structure into which the model outputs will be stored.\n * @return The outputs of the run.\n *\n * @hidden This is only intended for internal use; some implementations may not support\n * running the model synchronously, in which case this will be undefined.\n */\n runModelSync?(inputs: InputValue[], outputs: Outputs): Outputs\n\n /**\n * Terminate the runner by releasing underlying resources (e.g., the worker thread or\n * Wasm module/buffers).\n */\n terminate(): Promise<void>\n}\n\n/**\n * Create a `ModelRunner` that runs the given wasm model on the JS thread.\n *\n * @param wasmResult The result of initializing the wasm model.\n */\nexport function createWasmModelRunner(wasmResult: WasmModelInitResult): ModelRunner {\n // Create views on the wasm buffers\n const wasmModel = wasmResult.model\n const inputsBuffer = wasmResult.inputsBuffer\n const inputsArray = inputsBuffer.getArrayView()\n const outputsBuffer = wasmResult.outputsBuffer\n const outputsArray = outputsBuffer.getArrayView()\n const rowLength = wasmResult.endTime - wasmResult.startTime + 1\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n const runModelSync = (inputs: InputValue[], outputs: Outputs) => {\n // Capture the current set of input values into the reusable buffer\n let i = 0\n for (const input of inputs) {\n inputsArray[i++] = input.get()\n }\n\n // Run the model\n const t0 = perfNow()\n wasmModel.runModel(inputsBuffer, outputsBuffer)\n outputs.runTimeInMillis = perfElapsed(t0)\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n outputs.updateFromBuffer(outputsArray, rowLength)\n\n return outputs\n }\n\n return {\n runModel: (inputs, outputs) => {\n if (terminated) {\n return Promise.reject(new Error('Model runner has already been terminated'))\n }\n return Promise.resolve(runModelSync(inputs, outputs))\n },\n runModelSync: (inputs, outputs) => {\n if (terminated) {\n throw new Error('Model runner has already been terminated')\n }\n return runModelSync(inputs, outputs)\n },\n terminate: () => {\n if (!terminated) {\n // TODO: Release wasm-related resources (module or buffers)\n terminated = true\n }\n return Promise.resolve()\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { InputVarId } from '../_shared'\nimport type { InputValue, ModelRunner, Outputs } from '../model-runner'\n\n/**\n * A high-level interface that schedules running of the underlying `WasmModel`.\n *\n * When one or more input values are changed, this class will schedule a model\n * run to be completed as soon as possible. When the model run has completed,\n * `onOutputsChanged` is called to notify that new output data is available.\n *\n * The `ModelRunner` is pluggable to allow for running the model synchronously\n * (on the main JavaScript thread) or asynchronously (in a Web Worker or Node.js\n * worker thread).\n */\nexport class ModelScheduler {\n /** The second array that holds a stable copy of the user inputs. */\n private readonly currentInputs: InputValue[]\n\n /** Whether a model run has been scheduled. */\n private runNeeded = false\n\n /** Whether a model run is in progress. */\n private runInProgress = false\n\n /** Called when `outputs` has been updated after a model run. */\n public onOutputsChanged?: (outputs: Outputs) => void\n\n /**\n * @param runner The model runner.\n * @param userInputs The input values, in the same order as in the spec file passed to `sde`.\n * @param outputs The structure into which the model outputs will be stored.\n */\n constructor(\n private readonly runner: ModelRunner,\n private readonly userInputs: InputValue[],\n private outputs: Outputs\n ) {\n // When any input has an updated value, schedule a model run on the next tick\n const afterSet = () => {\n this.runWasmModelIfNeeded()\n }\n for (const userInput of userInputs) {\n userInput.callbacks.onSet = afterSet\n }\n\n // Create a second array to hold a stable copy of the user inputs during model runs\n this.currentInputs = []\n for (const userInput of userInputs) {\n this.currentInputs.push(createSimpleInputValue(userInput.varId))\n }\n }\n\n /**\n * Schedule a wasm model run (if not already pending). When the run is\n * complete, save the outputs and call the `onOutputsChanged` callback.\n */\n private runWasmModelIfNeeded(): void {\n // Set a flag indicating that a new run is needed (even if one is already\n // in progress)\n this.runNeeded = true\n\n if (this.runInProgress) {\n // A run is already in progress; let it finish first\n return\n } else {\n // A run is not already in progress, so schedule it now. We use\n // `setTimeout` so that if a lot of inputs are all changing at once\n // (like after a reset), we wait for all those `set` or `reset`\n // calls to finish before gathering the input values into an array\n // and initiating the run on the next tick.\n this.runInProgress = true\n setTimeout(() => {\n // Kick off the (possibly asynchronous) model run\n this.runWasmModelNow()\n }, 0)\n }\n }\n\n /**\n * Run the wasm model asynchronously using the current set of input values.\n */\n private async runWasmModelNow(): Promise<void> {\n // Copy the current inputs into a separate array; this ensures that the\n // model run uses a stable set of inputs, even if the user continues to\n // change the inputs while the model is being run asynchronously\n for (let i = 0; i < this.userInputs.length; i++) {\n this.currentInputs[i].set(this.userInputs[i].get())\n }\n\n // Run the model with the current set of input values and save the outputs\n try {\n this.outputs = await this.runner.runModel(this.currentInputs, this.outputs)\n this.onOutputsChanged?.(this.outputs)\n } catch (e) {\n console.error(`ERROR: Failed to run model: ${e.message}`)\n }\n\n // See if another run is needed\n if (this.runNeeded) {\n // Keep `runInProgress` set, but clear the `runNeeded` flag\n this.runNeeded = false\n setTimeout(() => {\n this.runWasmModelNow()\n }, 0)\n } else {\n // No run needed, so clear both flags\n this.runNeeded = false\n this.runInProgress = false\n }\n }\n}\n\n/**\n * Create an `InputValue` that is only used to hold a copy of another input (no callbacks).\n * @hidden\n */\nfunction createSimpleInputValue(varId: InputVarId): InputValue {\n let currentValue = 0\n const get = () => {\n return currentValue\n }\n const set = (newValue: number) => {\n currentValue = newValue\n }\n const reset = () => {\n set(0)\n }\n return { varId, get, set, reset, callbacks: {} }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/wasm-model/wasm-buffer.ts","../src/wasm-model/wasm-model.ts","../src/model-runner/inputs.ts","../src/model-runner/outputs.ts","../src/model-runner/perf.ts","../src/model-runner/model-runner.ts","../src/model-scheduler/model-scheduler.ts"],"sourcesContent":["// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { WasmModule } from './wasm-module'\n\n/**\n * Wraps a `WebAssembly.Memory` buffer allocated on the wasm heap.\n *\n * When this is used synchronously (in the browser's normal JavaScript thread),\n * the client can use `getArrayView` to write directly into the underlying memory.\n *\n * Note, however, that `WebAssembly.Memory` buffers cannot be transferred to/from\n * a Web Worker. When using this class in a worker thread, create a separate\n * `Float64Array` that can be transferred between the worker and the client running\n * in the browser's normal JS thread, and then use `getArrayView` to copy into and\n * out of the wasm buffer.\n */\nexport class WasmBuffer {\n private byteOffset: number\n private heapArray: Float64Array\n\n /**\n * @param wasmModule The `WasmModule` used to initialize the memory.\n * @param numElements The number of 64-bit `double` elements in the buffer.\n */\n constructor(private readonly wasmModule: WasmModule, numElements: number) {\n const sizeOfFloat64 = 8\n const lengthInBytes = numElements * sizeOfFloat64\n this.byteOffset = wasmModule._malloc(lengthInBytes)\n const float64Offset = this.byteOffset / sizeOfFloat64\n this.heapArray = wasmModule.HEAPF64.subarray(float64Offset, float64Offset + numElements)\n }\n\n /**\n * @return A new `Float64Array` view on the underlying heap buffer.\n */\n getArrayView(): Float64Array {\n return this.heapArray\n }\n\n /**\n * @return The raw address of the underlying heap buffer.\n * @hidden This is intended for use by `WasmModel` only.\n */\n getAddress(): number {\n return this.byteOffset\n }\n\n /**\n * Dispose the buffer by freeing the allocated heap memory.\n */\n dispose(): void {\n if (this.heapArray) {\n this.wasmModule._free(this.byteOffset)\n this.heapArray = undefined\n this.byteOffset = undefined\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { OutputVarId } from '../_shared'\nimport { WasmBuffer } from './wasm-buffer'\nimport type { WasmModule } from './wasm-module'\n\n/**\n * An interface to the generated WebAssembly model. Allows for running the model with\n * a given set of input values, producing a set of output values.\n */\nexport class WasmModel {\n /** The start time for the model (aka `INITIAL TIME`). */\n public readonly startTime: number\n /** The end time for the model (aka `FINAL TIME`). */\n public readonly endTime: number\n /** The frequency with which output values are saved (aka `SAVEPER`). */\n public readonly saveFreq: number\n /** The number of save points for each output. */\n public readonly numSavePoints: number\n\n private readonly wasmRunModel: (inputsAddress: number, outputsAddress: number) => void\n\n /**\n * @param wasmModule The `WasmModule` that provides access to the native functions.\n */\n constructor(wasmModule: WasmModule) {\n function getNumberValue(funcName: string): number {\n const wasmGetValue: () => number = wasmModule.cwrap(funcName, 'number', [])\n return wasmGetValue()\n }\n this.startTime = getNumberValue('getInitialTime')\n this.endTime = getNumberValue('getFinalTime')\n this.saveFreq = getNumberValue('getSaveper')\n\n // Each series will include one data point per \"save\", inclusive of the\n // start and end times\n this.numSavePoints = Math.round((this.endTime - this.startTime) / this.saveFreq) + 1\n\n this.wasmRunModel = wasmModule.cwrap('runModelWithBuffers', null, ['number', 'number'])\n }\n\n /**\n * Run the model, using inputs from the `inputs` buffer, and writing outputs into\n * the `outputs` buffer.\n *\n * @param inputs The buffer containing inputs in the order expected by the model.\n * @param outputs The buffer into which the model will store output values.\n */\n runModel(inputs: WasmBuffer, outputs: WasmBuffer): void {\n this.wasmRunModel(inputs.getAddress(), outputs.getAddress())\n }\n}\n\n/**\n * The result of model initialization.\n */\nexport interface WasmModelInitResult {\n /** The wasm model. */\n model: WasmModel\n /** The buffer used to pass input values to the model. */\n inputsBuffer: WasmBuffer\n /** The buffer used to receive output values from the model. */\n outputsBuffer: WasmBuffer\n /** The output variable IDs. */\n outputVarIds: OutputVarId[]\n}\n\n/**\n * Initialize the wasm model and buffers.\n *\n * @param wasmModule The `WasmModule` that wraps the `wasm` binary.\n * @param numInputs The number of input variables, per the spec file passed to `sde`.\n * @param outputVarIds The output variable IDs, per the spec file passed to `sde`.\n */\nexport function initWasmModelAndBuffers(\n wasmModule: WasmModule,\n numInputs: number,\n outputVarIds: OutputVarId[]\n): WasmModelInitResult {\n // Wrap the native C `runModelWithBuffers` function in a JS function that we can call\n const model = new WasmModel(wasmModule)\n\n // Allocate a buffer that is large enough to hold the input values\n const inputsBuffer = new WasmBuffer(wasmModule, numInputs)\n\n // Allocate a buffer that is large enough to hold the series data for\n // each output variable\n const outputsBuffer = new WasmBuffer(wasmModule, outputVarIds.length * model.numSavePoints)\n\n return {\n model,\n inputsBuffer,\n outputsBuffer,\n outputVarIds\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { InputVarId } from '../_shared'\n\n/** Callback functions that are called when the input value is changed. */\nexport interface InputCallbacks {\n /** Called after a new value is set. */\n onSet?: () => void\n}\n\n/**\n * Represents a writable model input.\n */\nexport interface InputValue {\n /** The ID of the associated input variable, as used in SDEverywhere. */\n varId: InputVarId\n /** Get the current value of the input. */\n get: () => number\n /** Set the input to the given value. */\n set: (value: number) => void\n /** Reset the input to its default value. */\n reset: () => void\n /** Callback functions that are called when the input value is changed. */\n callbacks: InputCallbacks\n}\n\n/**\n * Create a basic `InputValue` instance that notifies when a new value is set.\n *\n * @param varId The input variable ID, as used in SDEverywhere.\n * @param defaultValue The default value of the input.\n * @param initialValue The inital value of the input; if undefined, will use `defaultValue`.\n */\nexport function createInputValue(varId: InputVarId, defaultValue: number, initialValue?: number): InputValue {\n let currentValue = initialValue !== undefined ? initialValue : defaultValue\n\n // The `onSet` callback is initially undefined but will be installed by `ModelScheduler`\n const callbacks: InputCallbacks = {}\n\n const get = () => {\n return currentValue\n }\n\n const set = (newValue: number) => {\n if (newValue !== currentValue) {\n currentValue = newValue\n callbacks.onSet?.()\n }\n }\n\n const reset = () => {\n set(defaultValue)\n }\n\n return { varId, get, set, reset, callbacks }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { Result } from 'neverthrow'\nimport { ok, err } from 'neverthrow'\nimport type { OutputVarId } from '../_shared'\n\n/** Indicates the type of error encountered when parsing an outputs buffer. */\nexport type ParseError = 'invalid-point-count'\n\n/** A data point. */\nexport interface Point {\n /** The x value (typically a time value). */\n x: number\n /** The y value. */\n y: number\n}\n\n/**\n * A time series of data points for an output variable.\n */\nexport class Series {\n /**\n * @param varId The ID for the output variable (as used by SDEverywhere).\n * @param points The data points for the variable, one point per time increment.\n */\n constructor(public readonly varId: OutputVarId, public readonly points: Point[]) {}\n\n /**\n * Return the Y value at the given time. Note that this does not attempt to interpolate\n * if there is no data point defined for the given time and will return undefined in\n * that case.\n *\n * @param time The x (time) value.\n * @return The y value for the given time, or undefined if there is no data point defined\n * for the given time.\n */\n getValueAtTime(time: number): number | undefined {\n // TODO: Add option to allow interpolation if the given time value is in between points\n // TODO: Use binary search to make lookups faster\n return this.points.find(p => p.x === time)?.y\n }\n\n /**\n * Create a new `Series` instance that is a copy of this one.\n */\n copy(): Series {\n // Create a deep copy\n const pointsCopy = this.points.map(p => ({ ...p }))\n return new Series(this.varId, pointsCopy)\n }\n}\n\n/** Represents the outputs from a model run. */\nexport class Outputs {\n /** The number of data points in each series. */\n public readonly seriesLength: number\n /** The array of series, one for each output variable. */\n public readonly varSeries: Series[]\n\n /**\n * The latest model run time, in milliseconds.\n * @hidden This is not yet part of the public API; it is exposed here for use\n * in performance testing tools.\n */\n public runTimeInMillis: number\n\n /**\n * @param varIds The output variable identifiers.\n * @param startTime The start time for the model.\n * @param endTime The end time for the model.\n * @param saveFreq The frequency with which output values are saved (aka `SAVEPER`).\n */\n constructor(\n public readonly varIds: OutputVarId[],\n public readonly startTime: number,\n public readonly endTime: number,\n public readonly saveFreq = 1\n ) {\n // Each series will include one data point per \"save\", inclusive of the\n // start and end times\n this.seriesLength = Math.round((endTime - startTime) / saveFreq) + 1\n\n // Create an array of arrays, one for each output variable\n this.varSeries = new Array(varIds.length)\n\n // Populate the arrays, filling in the time for each point\n for (let i = 0; i < varIds.length; i++) {\n const points: Point[] = new Array(this.seriesLength)\n for (let j = 0; j < this.seriesLength; j++) {\n points[j] = { x: startTime + j * saveFreq, y: 0 }\n }\n const varId = varIds[i]\n this.varSeries[i] = new Series(varId, points)\n }\n }\n\n /**\n * Parse the given raw float buffer (produced by the model) and store the values\n * into this `Outputs` instance.\n *\n * Note that the length of `outputsBuffer` must be greater than or equal to\n * the capacity of this `Outputs` instance. The `Outputs` instance is allowed\n * to be smaller to support the case where you want to extract a subset of\n * the time range in the buffer produced by the model.\n *\n * @param outputsBuffer The raw outputs buffer produced by the model.\n * @param rowLength The number of elements per row (one element per save point).\n * @return An `ok` result if the buffer is valid, otherwise an `err` result.\n */\n updateFromBuffer(outputsBuffer: Float64Array, rowLength: number): Result<void, ParseError> {\n const result = parseOutputsBuffer(outputsBuffer, rowLength, this)\n if (result.isOk()) {\n return ok(undefined)\n } else {\n return err(result.error)\n }\n }\n\n /**\n * Return the series for the given output variable.\n *\n * @param varId The ID of the output variable (as used by SDEverywhere).\n */\n getSeriesForVar(varId: OutputVarId): Series | undefined {\n const seriesIndex = this.varIds.indexOf(varId)\n if (seriesIndex >= 0) {\n return this.varSeries[seriesIndex]\n } else {\n // TODO: Error\n return undefined\n }\n }\n}\n\n/**\n * Parse the raw buffer produced by the model and store the values in the\n * given (reused) `Outputs` object.\n *\n * @param outputsBuffer The raw outputs buffer produced by the model.\n * @param rowLength The number of elements per row (one element per year or save point).\n * @return An `ok` result if the buffer is valid, otherwise an `err` result.\n * @hidden\n */\nfunction parseOutputsBuffer(\n outputsBuffer: Float64Array,\n rowLength: number,\n outputs: Outputs\n): Result<Outputs, ParseError> {\n const varCount = outputs.varIds.length\n const seriesLength = outputs.seriesLength\n if (rowLength < seriesLength || outputsBuffer.length < varCount * seriesLength) {\n return err('invalid-point-count')\n }\n\n // The buffer populated by the C `runModelWithBuffers` function is already\n // transposed, so the first \"row\" contains the values for the first output\n // variable (from start time to end time), and so on.\n for (let outputVarIndex = 0; outputVarIndex < varCount; outputVarIndex++) {\n const series = outputs.varSeries[outputVarIndex]\n let sourceIndex = rowLength * outputVarIndex\n for (let valueIndex = 0; valueIndex < seriesLength; valueIndex++) {\n series.points[valueIndex].y = validateNumber(outputsBuffer[sourceIndex])\n sourceIndex++\n }\n }\n\n return ok(outputs)\n}\n\n/**\n * Return the given number if it is valid, or undefined if it is invalid.\n *\n * SDE converts Vensim's `:NA:` values to `-DBL_MAX`, so if we see a very large negative\n * value, convert it to `undefined`. This is preferable to including extreme values\n * because some charting libraries (e.g. Chart.js) appear to choke on these large values\n * in certain browsers (e.g. Safari), but `undefined` appears to be handled better and\n * does a better job of signaling that the data point is undefined.\n *\n * @hidden\n */\nfunction validateNumber(x: number): number | undefined {\n if (!isNaN(x) && x > -1e32) {\n return x\n } else {\n return undefined\n }\n}\n","// Copyright (c) 2021-2022 Climate Interactive / New Venture Fund\n\nlet isWeb: boolean\n\n/**\n * Return a timestamp that can be passed to `perfElapsed` for calculating the elapsed\n * time of an operation.\n *\n * @hidden This is not part of the public API; exposed only for use in performance testing.\n */\nexport function perfNow(): unknown {\n // Note that `self` resolves to the window (in browser context) or the worker global scope\n // (in a Web Worker context)\n if (isWeb === undefined) {\n isWeb = typeof self !== 'undefined' && self?.performance !== undefined\n }\n if (isWeb) {\n return self.performance.now()\n } else {\n // XXX: We only use `process` in two places; we bypass type checking instead of\n // setting up type declarations\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n return process?.hrtime()\n }\n}\n\n/**\n * Return the elapsed time between the given timestamp (created by `perfNow`) and now.\n *\n * @hidden This is not part of the public API; exposed only for use in performance testing.\n */\nexport function perfElapsed(t0: unknown): number {\n if (isWeb) {\n const t1 = self.performance.now()\n return (t1 as number) - (t0 as number)\n } else {\n // eslint-disable-next-line @typescript-eslint/ban-ts-comment\n // @ts-ignore\n const elapsed = process.hrtime(t0) as number[]\n // Convert from nanoseconds to milliseconds\n return (elapsed[0] * 1000000000 + elapsed[1]) / 1000000\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { WasmModelInitResult } from '../wasm-model'\nimport type { InputValue } from './inputs'\nimport { Outputs } from './outputs'\nimport { perfElapsed, perfNow } from './perf'\n\n/**\n * Abstraction that allows for running the wasm model on the JS thread\n * or asynchronously (e.g. in a Web Worker), depending on the implementation.\n */\nexport interface ModelRunner {\n /**\n * Create an `Outputs` instance that is sized to accommodate the output variable\n * data stored by the model.\n *\n * @return A new `Outputs` instance.\n */\n createOutputs(): Outputs\n\n /**\n * Run the model.\n *\n * @param inputs The model input values (must be in the same order as in the spec file).\n * @param outputs The structure into which the model outputs will be stored.\n * @return A promise that resolves with the outputs when the model run is complete.\n */\n runModel(inputs: InputValue[], outputs: Outputs): Promise<Outputs>\n\n /**\n * Run the model synchronously.\n *\n * @param inputs The model input values (must be in the same order as in the spec file).\n * @param outputs The structure into which the model outputs will be stored.\n * @return The outputs of the run.\n *\n * @hidden This is only intended for internal use; some implementations may not support\n * running the model synchronously, in which case this will be undefined.\n */\n runModelSync?(inputs: InputValue[], outputs: Outputs): Outputs\n\n /**\n * Terminate the runner by releasing underlying resources (e.g., the worker thread or\n * Wasm module/buffers).\n */\n terminate(): Promise<void>\n}\n\n/**\n * Create a `ModelRunner` that runs the given wasm model on the JS thread.\n *\n * @param wasmResult The result of initializing the wasm model.\n */\nexport function createWasmModelRunner(wasmResult: WasmModelInitResult): ModelRunner {\n // Create views on the wasm buffers\n const wasmModel = wasmResult.model\n const inputsBuffer = wasmResult.inputsBuffer\n const inputsArray = inputsBuffer.getArrayView()\n const outputsBuffer = wasmResult.outputsBuffer\n const outputsArray = outputsBuffer.getArrayView()\n const rowLength = wasmModel.numSavePoints\n\n // Disallow `runModel` after the runner has been terminated\n let terminated = false\n\n const runModelSync = (inputs: InputValue[], outputs: Outputs) => {\n // Capture the current set of input values into the reusable buffer\n let i = 0\n for (const input of inputs) {\n inputsArray[i++] = input.get()\n }\n\n // Run the model\n const t0 = perfNow()\n wasmModel.runModel(inputsBuffer, outputsBuffer)\n outputs.runTimeInMillis = perfElapsed(t0)\n\n // Capture the outputs array by copying the data into the given `Outputs`\n // data structure\n outputs.updateFromBuffer(outputsArray, rowLength)\n\n return outputs\n }\n\n return {\n createOutputs: () => {\n return new Outputs(wasmResult.outputVarIds, wasmModel.startTime, wasmModel.endTime, wasmModel.saveFreq)\n },\n\n runModel: (inputs, outputs) => {\n if (terminated) {\n return Promise.reject(new Error('Model runner has already been terminated'))\n }\n return Promise.resolve(runModelSync(inputs, outputs))\n },\n\n runModelSync: (inputs, outputs) => {\n if (terminated) {\n throw new Error('Model runner has already been terminated')\n }\n return runModelSync(inputs, outputs)\n },\n\n terminate: () => {\n if (!terminated) {\n // TODO: Release wasm-related resources (module or buffers)\n terminated = true\n }\n return Promise.resolve()\n }\n }\n}\n","// Copyright (c) 2020-2022 Climate Interactive / New Venture Fund\n\nimport type { InputVarId } from '../_shared'\nimport type { InputValue, ModelRunner, Outputs } from '../model-runner'\n\n/**\n * A high-level interface that schedules running of the underlying `WasmModel`.\n *\n * When one or more input values are changed, this class will schedule a model\n * run to be completed as soon as possible. When the model run has completed,\n * `onOutputsChanged` is called to notify that new output data is available.\n *\n * The `ModelRunner` is pluggable to allow for running the model synchronously\n * (on the main JavaScript thread) or asynchronously (in a Web Worker or Node.js\n * worker thread).\n */\nexport class ModelScheduler {\n /** The second array that holds a stable copy of the user inputs. */\n private readonly currentInputs: InputValue[]\n\n /** Whether a model run has been scheduled. */\n private runNeeded = false\n\n /** Whether a model run is in progress. */\n private runInProgress = false\n\n /** Called when `outputs` has been updated after a model run. */\n public onOutputsChanged?: (outputs: Outputs) => void\n\n /**\n * @param runner The model runner.\n * @param userInputs The input values, in the same order as in the spec file passed to `sde`.\n * @param outputs The structure into which the model outputs will be stored.\n */\n constructor(\n private readonly runner: ModelRunner,\n private readonly userInputs: InputValue[],\n private outputs: Outputs\n ) {\n // When any input has an updated value, schedule a model run on the next tick\n const afterSet = () => {\n this.runWasmModelIfNeeded()\n }\n for (const userInput of userInputs) {\n userInput.callbacks.onSet = afterSet\n }\n\n // Create a second array to hold a stable copy of the user inputs during model runs\n this.currentInputs = []\n for (const userInput of userInputs) {\n this.currentInputs.push(createSimpleInputValue(userInput.varId))\n }\n }\n\n /**\n * Schedule a wasm model run (if not already pending). When the run is\n * complete, save the outputs and call the `onOutputsChanged` callback.\n */\n private runWasmModelIfNeeded(): void {\n // Set a flag indicating that a new run is needed (even if one is already\n // in progress)\n this.runNeeded = true\n\n if (this.runInProgress) {\n // A run is already in progress; let it finish first\n return\n } else {\n // A run is not already in progress, so schedule it now. We use\n // `setTimeout` so that if a lot of inputs are all changing at once\n // (like after a reset), we wait for all those `set` or `reset`\n // calls to finish before gathering the input values into an array\n // and initiating the run on the next tick.\n this.runInProgress = true\n setTimeout(() => {\n // Kick off the (possibly asynchronous) model run\n this.runWasmModelNow()\n }, 0)\n }\n }\n\n /**\n * Run the wasm model asynchronously using the current set of input values.\n */\n private async runWasmModelNow(): Promise<void> {\n // Copy the current inputs into a separate array; this ensures that the\n // model run uses a stable set of inputs, even if the user continues to\n // change the inputs while the model is being run asynchronously\n for (let i = 0; i < this.userInputs.length; i++) {\n this.currentInputs[i].set(this.userInputs[i].get())\n }\n\n // Run the model with the current set of input values and save the outputs\n try {\n this.outputs = await this.runner.runModel(this.currentInputs, this.outputs)\n this.onOutputsChanged?.(this.outputs)\n } catch (e) {\n console.error(`ERROR: Failed to run model: ${e.message}`)\n }\n\n // See if another run is needed\n if (this.runNeeded) {\n // Keep `runInProgress` set, but clear the `runNeeded` flag\n this.runNeeded = false\n setTimeout(() => {\n this.runWasmModelNow()\n }, 0)\n } else {\n // No run needed, so clear both flags\n this.runNeeded = false\n this.runInProgress = false\n }\n }\n}\n\n/**\n * Create an `InputValue` that is only used to hold a copy of another input (no callbacks).\n * @hidden\n */\nfunction createSimpleInputValue(varId: InputVarId): InputValue {\n let currentValue = 0\n const get = () => {\n return currentValue\n }\n const set = (newValue: number) => {\n currentValue = newValue\n }\n const reset = () => {\n set(0)\n }\n return { varId, get, set, reset, callbacks: {} }\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/runtime",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "files": [
5
5
  "dist/**"
6
6
  ],