@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 +20 -5
- package/dist/index.cjs +23 -30
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +35 -15
- package/dist/index.js +23 -33
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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 =
|
|
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="['
|
|
114
|
+
-s EXPORTED_FUNCTIONS="['_malloc','_getInitialTime','_getFinalTime','_getSaveper','_runModelWithBuffers']" \
|
|
106
115
|
-s EXPORTED_RUNTIME_METHODS="['cwrap']"
|
|
107
116
|
```
|
|
108
117
|
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|
77
|
+
function initWasmModelAndBuffers(wasmModule, numInputs, outputVarIds) {
|
|
84
78
|
const model = new WasmModel(wasmModule);
|
|
85
79
|
const inputsBuffer = new WasmBuffer(wasmModule, numInputs);
|
|
86
|
-
const
|
|
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
|
-
|
|
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) =>
|
|
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,
|
|
126
|
+
constructor(varIds, startTime, endTime, saveFreq = 1) {
|
|
137
127
|
this.varIds = varIds;
|
|
138
|
-
this.
|
|
139
|
-
this.
|
|
140
|
-
this.
|
|
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:
|
|
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 =
|
|
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"));
|
package/dist/index.cjs.map
CHANGED
|
@@ -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":"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","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":"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","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
|
|
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`
|
|
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[]
|
|
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
|
|
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
|
|
171
|
-
readonly
|
|
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
|
-
|
|
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
|
|
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
|
|
43
|
+
function initWasmModelAndBuffers(wasmModule, numInputs, outputVarIds) {
|
|
53
44
|
const model = new WasmModel(wasmModule);
|
|
54
45
|
const inputsBuffer = new WasmBuffer(wasmModule, numInputs);
|
|
55
|
-
const
|
|
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
|
-
|
|
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) =>
|
|
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,
|
|
92
|
+
constructor(varIds, startTime, endTime, saveFreq = 1) {
|
|
106
93
|
this.varIds = varIds;
|
|
107
|
-
this.
|
|
108
|
-
this.
|
|
109
|
-
this.
|
|
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:
|
|
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 =
|
|
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":[]}
|