@sdeverywhere/runtime 0.2.9 → 0.2.11
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 +3 -3
- package/dist/index.d.ts +888 -864
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2152 -2344
- package/dist/index.js.map +1 -1
- package/package.json +3 -5
- package/dist/index.cjs +0 -2490
- package/dist/index.cjs.map +0 -1
- package/dist/index.d.cts +0 -1224
package/dist/index.js
CHANGED
|
@@ -1,2440 +1,2248 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
var __async = (__this, __arguments, generator) => {
|
|
18
|
-
return new Promise((resolve, reject) => {
|
|
19
|
-
var fulfilled = (value) => {
|
|
20
|
-
try {
|
|
21
|
-
step(generator.next(value));
|
|
22
|
-
} catch (e) {
|
|
23
|
-
reject(e);
|
|
24
|
-
}
|
|
25
|
-
};
|
|
26
|
-
var rejected = (value) => {
|
|
27
|
-
try {
|
|
28
|
-
step(generator.throw(value));
|
|
29
|
-
} catch (e) {
|
|
30
|
-
reject(e);
|
|
31
|
-
}
|
|
32
|
-
};
|
|
33
|
-
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
34
|
-
step((generator = generator.apply(__this, __arguments)).next());
|
|
35
|
-
});
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// src/_shared/inputs.ts
|
|
1
|
+
import { err, ok } from "neverthrow";
|
|
2
|
+
//#region src/_shared/inputs.ts
|
|
3
|
+
/**
|
|
4
|
+
* Create a basic `InputValue` instance that notifies when a new value is set.
|
|
5
|
+
*
|
|
6
|
+
* @param varId The input variable ID, as used in SDEverywhere.
|
|
7
|
+
* @param defaultValue The default value of the input.
|
|
8
|
+
* @param initialValue The inital value of the input; if undefined, will use `defaultValue`.
|
|
9
|
+
*/
|
|
39
10
|
function createInputValue(varId, defaultValue, initialValue) {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
11
|
+
let currentValue = initialValue !== void 0 ? initialValue : defaultValue;
|
|
12
|
+
const callbacks = {};
|
|
13
|
+
const get = () => {
|
|
14
|
+
return currentValue;
|
|
15
|
+
};
|
|
16
|
+
const set = (newValue) => {
|
|
17
|
+
if (newValue !== currentValue) {
|
|
18
|
+
currentValue = newValue;
|
|
19
|
+
callbacks.onSet?.();
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
const reset = () => {
|
|
23
|
+
set(defaultValue);
|
|
24
|
+
};
|
|
25
|
+
return {
|
|
26
|
+
varId,
|
|
27
|
+
get,
|
|
28
|
+
set,
|
|
29
|
+
reset,
|
|
30
|
+
callbacks
|
|
31
|
+
};
|
|
56
32
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
33
|
+
//#endregion
|
|
34
|
+
//#region src/_shared/outputs.ts
|
|
35
|
+
/**
|
|
36
|
+
* A time series of data points for an output variable.
|
|
37
|
+
*/
|
|
38
|
+
var Series = class Series {
|
|
39
|
+
/**
|
|
40
|
+
* @param varId The ID for the output variable (as used by SDEverywhere).
|
|
41
|
+
* @param points The data points for the variable, one point per time increment.
|
|
42
|
+
*/
|
|
43
|
+
constructor(varId, points) {
|
|
44
|
+
this.varId = varId;
|
|
45
|
+
this.points = points;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Return the Y value at the given time. Note that this does not attempt to interpolate
|
|
49
|
+
* if there is no data point defined for the given time and will return undefined in
|
|
50
|
+
* that case.
|
|
51
|
+
*
|
|
52
|
+
* @param time The x (time) value.
|
|
53
|
+
* @return The y value for the given time, or undefined if there is no data point defined
|
|
54
|
+
* for the given time.
|
|
55
|
+
*/
|
|
56
|
+
getValueAtTime(time) {
|
|
57
|
+
return this.points.find((p) => p.x === time)?.y;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Create a new `Series` instance that is a copy of this one.
|
|
61
|
+
*/
|
|
62
|
+
copy() {
|
|
63
|
+
const pointsCopy = this.points.map((p) => ({ ...p }));
|
|
64
|
+
return new Series(this.varId, pointsCopy);
|
|
65
|
+
}
|
|
89
66
|
};
|
|
67
|
+
/** Represents the outputs from a model run. */
|
|
90
68
|
var Outputs = class {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
const seriesIndex = this.varIds.indexOf(varId);
|
|
154
|
-
if (seriesIndex >= 0) {
|
|
155
|
-
return this.varSeries[seriesIndex];
|
|
156
|
-
} else {
|
|
157
|
-
return void 0;
|
|
158
|
-
}
|
|
159
|
-
}
|
|
69
|
+
/**
|
|
70
|
+
* @param varIds The output variable identifiers.
|
|
71
|
+
* @param startTime The start time for the model.
|
|
72
|
+
* @param endTime The end time for the model.
|
|
73
|
+
* @param saveFreq The frequency with which output values are saved (aka `SAVEPER`).
|
|
74
|
+
*/
|
|
75
|
+
constructor(varIds, startTime, endTime, saveFreq = 1) {
|
|
76
|
+
this.varIds = varIds;
|
|
77
|
+
this.startTime = startTime;
|
|
78
|
+
this.endTime = endTime;
|
|
79
|
+
this.saveFreq = saveFreq;
|
|
80
|
+
this.seriesLength = Math.round((endTime - startTime) / saveFreq) + 1;
|
|
81
|
+
this.varSeries = new Array(varIds.length);
|
|
82
|
+
for (let i = 0; i < varIds.length; i++) {
|
|
83
|
+
const points = new Array(this.seriesLength);
|
|
84
|
+
for (let j = 0; j < this.seriesLength; j++) points[j] = {
|
|
85
|
+
x: startTime + j * saveFreq,
|
|
86
|
+
y: 0
|
|
87
|
+
};
|
|
88
|
+
const varId = varIds[i];
|
|
89
|
+
this.varSeries[i] = new Series(varId, points);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* The optional set of specs that dictate which variables from the model will be
|
|
94
|
+
* stored in this `Outputs` instance. If undefined, the default set of outputs
|
|
95
|
+
* will be stored (as configured in `varIds`).
|
|
96
|
+
* @hidden This is not yet part of the public API; it is exposed here for use
|
|
97
|
+
* in experimental testing tools.
|
|
98
|
+
*/
|
|
99
|
+
setVarSpecs(varSpecs) {
|
|
100
|
+
if (varSpecs.length !== this.varIds.length) throw new Error("Length of output varSpecs must match that of varIds");
|
|
101
|
+
this.varSpecs = varSpecs;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Parse the given raw float buffer (produced by the model) and store the values
|
|
105
|
+
* into this `Outputs` instance.
|
|
106
|
+
*
|
|
107
|
+
* Note that the length of `outputsBuffer` must be greater than or equal to
|
|
108
|
+
* the capacity of this `Outputs` instance. The `Outputs` instance is allowed
|
|
109
|
+
* to be smaller to support the case where you want to extract a subset of
|
|
110
|
+
* the time range in the buffer produced by the model.
|
|
111
|
+
*
|
|
112
|
+
* @param outputsBuffer The raw outputs buffer produced by the model.
|
|
113
|
+
* @param rowLength The number of elements per row (one element per save point).
|
|
114
|
+
* @return An `ok` result if the buffer is valid, otherwise an `err` result.
|
|
115
|
+
*/
|
|
116
|
+
updateFromBuffer(outputsBuffer, rowLength) {
|
|
117
|
+
const result = parseOutputsBuffer(outputsBuffer, rowLength, this);
|
|
118
|
+
if (result.isOk()) return ok(void 0);
|
|
119
|
+
else return err(result.error);
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Return the series for the given output variable.
|
|
123
|
+
*
|
|
124
|
+
* @param varId The ID of the output variable (as used by SDEverywhere).
|
|
125
|
+
*/
|
|
126
|
+
getSeriesForVar(varId) {
|
|
127
|
+
const seriesIndex = this.varIds.indexOf(varId);
|
|
128
|
+
if (seriesIndex >= 0) return this.varSeries[seriesIndex];
|
|
129
|
+
else return;
|
|
130
|
+
}
|
|
160
131
|
};
|
|
132
|
+
/**
|
|
133
|
+
* Parse the raw buffer produced by the model and store the values in the
|
|
134
|
+
* given (reused) `Outputs` object.
|
|
135
|
+
*
|
|
136
|
+
* @param outputsBuffer The raw outputs buffer produced by the model.
|
|
137
|
+
* @param rowLength The number of elements per row (one element per year or save point).
|
|
138
|
+
* @return An `ok` result if the buffer is valid, otherwise an `err` result.
|
|
139
|
+
* @hidden
|
|
140
|
+
*/
|
|
161
141
|
function parseOutputsBuffer(outputsBuffer, rowLength, outputs) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
}
|
|
175
|
-
return ok(outputs);
|
|
142
|
+
const varCount = outputs.varIds.length;
|
|
143
|
+
const seriesLength = outputs.seriesLength;
|
|
144
|
+
if (rowLength < seriesLength || outputsBuffer.length < varCount * seriesLength) return err("invalid-point-count");
|
|
145
|
+
for (let outputVarIndex = 0; outputVarIndex < varCount; outputVarIndex++) {
|
|
146
|
+
const series = outputs.varSeries[outputVarIndex];
|
|
147
|
+
let sourceIndex = rowLength * outputVarIndex;
|
|
148
|
+
for (let valueIndex = 0; valueIndex < seriesLength; valueIndex++) {
|
|
149
|
+
series.points[valueIndex].y = validateNumber(outputsBuffer[sourceIndex]);
|
|
150
|
+
sourceIndex++;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
return ok(outputs);
|
|
176
154
|
}
|
|
155
|
+
/**
|
|
156
|
+
* Return the given number if it is valid, or undefined if it is invalid.
|
|
157
|
+
*
|
|
158
|
+
* SDE converts Vensim's `:NA:` values to `-DBL_MAX`, so if we see a very large negative
|
|
159
|
+
* value, convert it to `undefined`. This is preferable to including extreme values
|
|
160
|
+
* because some charting libraries (e.g. Chart.js) appear to choke on these large values
|
|
161
|
+
* in certain browsers (e.g. Safari), but `undefined` appears to be handled better and
|
|
162
|
+
* does a better job of signaling that the data point is undefined.
|
|
163
|
+
*
|
|
164
|
+
* @hidden
|
|
165
|
+
*/
|
|
177
166
|
function validateNumber(x) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
} else {
|
|
181
|
-
return void 0;
|
|
182
|
-
}
|
|
167
|
+
if (!isNaN(x) && x > -1e32) return x;
|
|
168
|
+
else return;
|
|
183
169
|
}
|
|
184
|
-
|
|
185
|
-
|
|
170
|
+
//#endregion
|
|
171
|
+
//#region src/_shared/var-indices.ts
|
|
172
|
+
/**
|
|
173
|
+
* Return the length of the array that is required to store the variable
|
|
174
|
+
* indices for the given `VarSpec` instances.
|
|
175
|
+
*
|
|
176
|
+
* @hidden This is not part of the public API; it is exposed here for use by
|
|
177
|
+
* the synchronous and asynchronous model runner implementations.
|
|
178
|
+
*
|
|
179
|
+
* @param varSpecs The `VarSpec` instances to encode.
|
|
180
|
+
*/
|
|
186
181
|
function getEncodedVarIndicesLength(varSpecs) {
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
return length;
|
|
182
|
+
let length = 1;
|
|
183
|
+
for (const varSpec of varSpecs) {
|
|
184
|
+
length += 2;
|
|
185
|
+
const subCount = varSpec.subscriptIndices?.length || 0;
|
|
186
|
+
length += subCount;
|
|
187
|
+
}
|
|
188
|
+
return length;
|
|
195
189
|
}
|
|
190
|
+
/**
|
|
191
|
+
* Encode variable indices to the given array.
|
|
192
|
+
*
|
|
193
|
+
* @hidden This is not part of the public API; it is exposed here for use by
|
|
194
|
+
* the synchronous and asynchronous model runner implementations.
|
|
195
|
+
*
|
|
196
|
+
* @param varSpecs The `VarSpec` instances to encode.
|
|
197
|
+
*/
|
|
196
198
|
function encodeVarIndices(varSpecs, indicesArray) {
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
}
|
|
207
|
-
}
|
|
199
|
+
let offset = 0;
|
|
200
|
+
indicesArray[offset++] = varSpecs.length;
|
|
201
|
+
for (const varSpec of varSpecs) {
|
|
202
|
+
indicesArray[offset++] = varSpec.varIndex;
|
|
203
|
+
const subs = varSpec.subscriptIndices;
|
|
204
|
+
const subCount = subs?.length || 0;
|
|
205
|
+
indicesArray[offset++] = subCount;
|
|
206
|
+
for (let i = 0; i < subCount; i++) indicesArray[offset++] = subs[i];
|
|
207
|
+
}
|
|
208
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* Return the lengths of the arrays that are required to store the constant values
|
|
211
|
+
* and indices for the given `ConstantDef` instances.
|
|
212
|
+
*
|
|
213
|
+
* @hidden This is not part of the public API; it is exposed here for use by
|
|
214
|
+
* the synchronous and asynchronous model runner implementations.
|
|
215
|
+
*
|
|
216
|
+
* @param constantDefs The `ConstantDef` instances to encode.
|
|
217
|
+
*/
|
|
209
218
|
function getEncodedConstantBufferLengths(constantDefs) {
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
constantIndicesLength,
|
|
225
|
-
constantsLength
|
|
226
|
-
};
|
|
219
|
+
let constantIndicesLength = 1;
|
|
220
|
+
let constantsLength = 0;
|
|
221
|
+
for (const constantDef of constantDefs) {
|
|
222
|
+
const varSpec = constantDef.varRef.varSpec;
|
|
223
|
+
if (varSpec === void 0) throw new Error("Cannot compute constant buffer lengths until all constant var specs are defined");
|
|
224
|
+
constantIndicesLength += 2;
|
|
225
|
+
const subCount = varSpec.subscriptIndices?.length || 0;
|
|
226
|
+
constantIndicesLength += subCount;
|
|
227
|
+
constantsLength += 1;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
constantIndicesLength,
|
|
231
|
+
constantsLength
|
|
232
|
+
};
|
|
227
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* Encode constant values and indices to the given arrays.
|
|
236
|
+
*
|
|
237
|
+
* @hidden This is not part of the public API; it is exposed here for use by
|
|
238
|
+
* the synchronous and asynchronous model runner implementations.
|
|
239
|
+
*
|
|
240
|
+
* @param constantDefs The `ConstantDef` instances to encode.
|
|
241
|
+
* @param constantIndicesArray The view on the constant indices buffer.
|
|
242
|
+
* @param constantsArray The view on the constant values buffer.
|
|
243
|
+
*/
|
|
228
244
|
function encodeConstants(constantDefs, constantIndicesArray, constantsArray) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
constantsArray[constantDataOffset++] = constantDef.value;
|
|
242
|
-
}
|
|
245
|
+
let ci = 0;
|
|
246
|
+
constantIndicesArray[ci++] = constantDefs.length;
|
|
247
|
+
let constantDataOffset = 0;
|
|
248
|
+
for (const constantDef of constantDefs) {
|
|
249
|
+
const varSpec = constantDef.varRef.varSpec;
|
|
250
|
+
constantIndicesArray[ci++] = varSpec.varIndex;
|
|
251
|
+
const subs = varSpec.subscriptIndices;
|
|
252
|
+
const subCount = subs?.length || 0;
|
|
253
|
+
constantIndicesArray[ci++] = subCount;
|
|
254
|
+
for (let i = 0; i < subCount; i++) constantIndicesArray[ci++] = subs[i];
|
|
255
|
+
constantsArray[constantDataOffset++] = constantDef.value;
|
|
256
|
+
}
|
|
243
257
|
}
|
|
258
|
+
/**
|
|
259
|
+
* Decode constant values and indices from the given buffer views and return the
|
|
260
|
+
* reconstructed `ConstantDef` instances.
|
|
261
|
+
*
|
|
262
|
+
* @hidden This is not part of the public API; it is exposed here for use by
|
|
263
|
+
* the synchronous and asynchronous model runner implementations.
|
|
264
|
+
*
|
|
265
|
+
* @param constantIndicesArray The view on the constant indices buffer.
|
|
266
|
+
* @param constantsArray The view on the constant values buffer.
|
|
267
|
+
*/
|
|
244
268
|
function decodeConstants(constantIndicesArray, constantsArray) {
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
value
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
return constantDefs;
|
|
269
|
+
const constantDefs = [];
|
|
270
|
+
let ci = 0;
|
|
271
|
+
const constantCount = constantIndicesArray[ci++];
|
|
272
|
+
for (let i = 0; i < constantCount; i++) {
|
|
273
|
+
const varIndex = constantIndicesArray[ci++];
|
|
274
|
+
const subCount = constantIndicesArray[ci++];
|
|
275
|
+
const subscriptIndices = subCount > 0 ? Array(subCount) : void 0;
|
|
276
|
+
for (let subIndex = 0; subIndex < subCount; subIndex++) subscriptIndices[subIndex] = constantIndicesArray[ci++];
|
|
277
|
+
const varSpec = {
|
|
278
|
+
varIndex,
|
|
279
|
+
subscriptIndices
|
|
280
|
+
};
|
|
281
|
+
const value = constantsArray[i];
|
|
282
|
+
constantDefs.push({
|
|
283
|
+
varRef: { varSpec },
|
|
284
|
+
value
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
return constantDefs;
|
|
268
288
|
}
|
|
289
|
+
/**
|
|
290
|
+
* Return the lengths of the arrays that are required to store the lookup data
|
|
291
|
+
* and indices for the given `LookupDef` instances.
|
|
292
|
+
*
|
|
293
|
+
* @hidden This is not part of the public API; it is exposed here for use by
|
|
294
|
+
* the synchronous and asynchronous model runner implementations.
|
|
295
|
+
*
|
|
296
|
+
* @param lookupDefs The `LookupDef` instances to encode.
|
|
297
|
+
*/
|
|
269
298
|
function getEncodedLookupBufferLengths(lookupDefs) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
lookupIndicesLength,
|
|
286
|
-
lookupsLength
|
|
287
|
-
};
|
|
299
|
+
let lookupIndicesLength = 1;
|
|
300
|
+
let lookupsLength = 0;
|
|
301
|
+
for (const lookupDef of lookupDefs) {
|
|
302
|
+
const varSpec = lookupDef.varRef.varSpec;
|
|
303
|
+
if (varSpec === void 0) throw new Error("Cannot compute lookup buffer lengths until all lookup var specs are defined");
|
|
304
|
+
lookupIndicesLength += 2;
|
|
305
|
+
const subCount = varSpec.subscriptIndices?.length || 0;
|
|
306
|
+
lookupIndicesLength += subCount;
|
|
307
|
+
lookupIndicesLength += 2;
|
|
308
|
+
lookupsLength += lookupDef.points?.length || 0;
|
|
309
|
+
}
|
|
310
|
+
return {
|
|
311
|
+
lookupIndicesLength,
|
|
312
|
+
lookupsLength
|
|
313
|
+
};
|
|
288
314
|
}
|
|
315
|
+
/**
|
|
316
|
+
* Encode lookup data and indices to the given arrays.
|
|
317
|
+
*
|
|
318
|
+
* @hidden This is not part of the public API; it is exposed here for use by
|
|
319
|
+
* the synchronous and asynchronous model runner implementations.
|
|
320
|
+
*
|
|
321
|
+
* @param lookupDefs The `LookupDef` instances to encode.
|
|
322
|
+
* @param lookupIndicesArray The view on the lookup indices buffer.
|
|
323
|
+
* @param lookupsArray The view on the lookup data buffer. This can be undefined in
|
|
324
|
+
* the case where the data for the lookup(s) is empty.
|
|
325
|
+
*/
|
|
289
326
|
function encodeLookups(lookupDefs, lookupIndicesArray, lookupsArray) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
}
|
|
311
|
-
}
|
|
327
|
+
let li = 0;
|
|
328
|
+
lookupIndicesArray[li++] = lookupDefs.length;
|
|
329
|
+
let lookupDataOffset = 0;
|
|
330
|
+
for (const lookupDef of lookupDefs) {
|
|
331
|
+
const varSpec = lookupDef.varRef.varSpec;
|
|
332
|
+
lookupIndicesArray[li++] = varSpec.varIndex;
|
|
333
|
+
const subs = varSpec.subscriptIndices;
|
|
334
|
+
const subCount = subs?.length || 0;
|
|
335
|
+
lookupIndicesArray[li++] = subCount;
|
|
336
|
+
for (let i = 0; i < subCount; i++) lookupIndicesArray[li++] = subs[i];
|
|
337
|
+
if (lookupDef.points !== void 0) {
|
|
338
|
+
lookupIndicesArray[li++] = lookupDataOffset;
|
|
339
|
+
lookupIndicesArray[li++] = lookupDef.points.length;
|
|
340
|
+
lookupsArray?.set(lookupDef.points, lookupDataOffset);
|
|
341
|
+
lookupDataOffset += lookupDef.points.length;
|
|
342
|
+
} else {
|
|
343
|
+
lookupIndicesArray[li++] = -1;
|
|
344
|
+
lookupIndicesArray[li++] = 0;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
312
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* Decode lookup data and indices from the given buffer views and return the
|
|
350
|
+
* reconstructed `LookupDef` instances.
|
|
351
|
+
*
|
|
352
|
+
* @hidden This is not part of the public API; it is exposed here for use by
|
|
353
|
+
* the synchronous and asynchronous model runner implementations.
|
|
354
|
+
*
|
|
355
|
+
* @param lookupIndicesArray The view on the lookup indices buffer.
|
|
356
|
+
* @param lookupsArray The view on the lookup data buffer. This can be undefined in
|
|
357
|
+
* the case where the data for the lookup(s) is empty.
|
|
358
|
+
*/
|
|
313
359
|
function decodeLookups(lookupIndicesArray, lookupsArray) {
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
lookupDefs.push({
|
|
341
|
-
varRef: {
|
|
342
|
-
varSpec
|
|
343
|
-
},
|
|
344
|
-
points
|
|
345
|
-
});
|
|
346
|
-
}
|
|
347
|
-
return lookupDefs;
|
|
360
|
+
const lookupDefs = [];
|
|
361
|
+
let li = 0;
|
|
362
|
+
const lookupCount = lookupIndicesArray[li++];
|
|
363
|
+
for (let i = 0; i < lookupCount; i++) {
|
|
364
|
+
const varIndex = lookupIndicesArray[li++];
|
|
365
|
+
const subCount = lookupIndicesArray[li++];
|
|
366
|
+
const subscriptIndices = subCount > 0 ? Array(subCount) : void 0;
|
|
367
|
+
for (let subIndex = 0; subIndex < subCount; subIndex++) subscriptIndices[subIndex] = lookupIndicesArray[li++];
|
|
368
|
+
const lookupDataOffset = lookupIndicesArray[li++];
|
|
369
|
+
const lookupDataLength = lookupIndicesArray[li++];
|
|
370
|
+
const varSpec = {
|
|
371
|
+
varIndex,
|
|
372
|
+
subscriptIndices
|
|
373
|
+
};
|
|
374
|
+
let points;
|
|
375
|
+
if (lookupDataOffset >= 0) {
|
|
376
|
+
if (lookupsArray) points = lookupsArray.slice(lookupDataOffset, lookupDataOffset + lookupDataLength);
|
|
377
|
+
else points = /* @__PURE__ */ new Float64Array(0);
|
|
378
|
+
} else points = void 0;
|
|
379
|
+
lookupDefs.push({
|
|
380
|
+
varRef: { varSpec },
|
|
381
|
+
points
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
return lookupDefs;
|
|
348
385
|
}
|
|
349
|
-
|
|
350
|
-
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/_shared/constant-def.ts
|
|
388
|
+
/**
|
|
389
|
+
* Create a `ConstantDef` instance.
|
|
390
|
+
*
|
|
391
|
+
* @param varRef The reference to the constant variable to be modified.
|
|
392
|
+
* @param value The new constant value.
|
|
393
|
+
*/
|
|
351
394
|
function createConstantDef(varRef, value) {
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
395
|
+
return {
|
|
396
|
+
varRef,
|
|
397
|
+
value
|
|
398
|
+
};
|
|
356
399
|
}
|
|
357
|
-
|
|
358
|
-
|
|
400
|
+
//#endregion
|
|
401
|
+
//#region src/_shared/lookup-def.ts
|
|
402
|
+
/**
|
|
403
|
+
* Create a `LookupDef` instance from the given array of `Point` objects.
|
|
404
|
+
*
|
|
405
|
+
* @param varRef The reference to the lookup or data variable to be modified.
|
|
406
|
+
* @param points The lookup data as an array of `Point` objects. This can be
|
|
407
|
+
* undefined, in which case the lookup data will be reset to the original data.
|
|
408
|
+
*/
|
|
359
409
|
function createLookupDef(varRef, points) {
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
410
|
+
let flatPoints;
|
|
411
|
+
if (points) {
|
|
412
|
+
flatPoints = new Float64Array(points.length * 2);
|
|
413
|
+
let i = 0;
|
|
414
|
+
for (const p of points) {
|
|
415
|
+
flatPoints[i++] = p.x;
|
|
416
|
+
flatPoints[i++] = p.y;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return {
|
|
420
|
+
varRef,
|
|
421
|
+
points: flatPoints
|
|
422
|
+
};
|
|
373
423
|
}
|
|
374
|
-
|
|
375
|
-
|
|
424
|
+
//#endregion
|
|
425
|
+
//#region src/model-listing/model-listing.ts
|
|
426
|
+
/**
|
|
427
|
+
* @hidden This is not yet part of the public API; it is exposed here for use
|
|
428
|
+
* in experimental testing tools.
|
|
429
|
+
*/
|
|
376
430
|
var ModelListing = class {
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
deriveOutputs(normalOutputs, varIds) {
|
|
454
|
-
const varSpecs = [];
|
|
455
|
-
for (const varId of varIds) {
|
|
456
|
-
const varSpec = this.varSpecs.get(varId);
|
|
457
|
-
if (varSpec !== void 0) {
|
|
458
|
-
varSpecs.push(varSpec);
|
|
459
|
-
} else {
|
|
460
|
-
console.warn(`WARNING: No output var spec found for id=${varId}`);
|
|
461
|
-
}
|
|
462
|
-
}
|
|
463
|
-
const newOutputs = new Outputs(varIds, normalOutputs.startTime, normalOutputs.endTime, normalOutputs.saveFreq);
|
|
464
|
-
newOutputs.varSpecs = varSpecs;
|
|
465
|
-
return newOutputs;
|
|
466
|
-
}
|
|
431
|
+
constructor(listingObj) {
|
|
432
|
+
this.varSpecs = /* @__PURE__ */ new Map();
|
|
433
|
+
const dimensions = /* @__PURE__ */ new Map();
|
|
434
|
+
for (const dimInfo of listingObj.dimensions) {
|
|
435
|
+
const dimId = dimInfo.id;
|
|
436
|
+
const subscripts = [];
|
|
437
|
+
for (let i = 0; i < dimInfo.subIds.length; i++) subscripts.push({
|
|
438
|
+
id: dimInfo.subIds[i],
|
|
439
|
+
index: i
|
|
440
|
+
});
|
|
441
|
+
dimensions.set(dimId, {
|
|
442
|
+
id: dimId,
|
|
443
|
+
subscripts
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function dimensionForId(dimId) {
|
|
447
|
+
const dim = dimensions.get(dimId);
|
|
448
|
+
if (dim === void 0) throw new Error(`No dimension info found for id=${dimId}`);
|
|
449
|
+
return dim;
|
|
450
|
+
}
|
|
451
|
+
const baseVarIds = /* @__PURE__ */ new Set();
|
|
452
|
+
for (const v of listingObj.variables) {
|
|
453
|
+
const baseVarId = varIdWithoutSubscripts(v.id);
|
|
454
|
+
if (!baseVarIds.has(baseVarId)) {
|
|
455
|
+
const dimensions = (v.dimIds || []).map(dimensionForId);
|
|
456
|
+
if (dimensions.length > 0) {
|
|
457
|
+
const dimSubs = [];
|
|
458
|
+
for (const dim of dimensions) dimSubs.push(dim.subscripts);
|
|
459
|
+
const combos = cartesianProductOf(dimSubs);
|
|
460
|
+
for (const combo of combos) {
|
|
461
|
+
const subs = combo.map((sub) => sub.id).join(",");
|
|
462
|
+
const subIndices = combo.map((sub) => sub.index);
|
|
463
|
+
const fullVarId = `${baseVarId}[${subs}]`;
|
|
464
|
+
this.varSpecs.set(fullVarId, {
|
|
465
|
+
varIndex: v.index,
|
|
466
|
+
subscriptIndices: subIndices
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
} else this.varSpecs.set(baseVarId, { varIndex: v.index });
|
|
470
|
+
baseVarIds.add(baseVarId);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Return the `VarSpec` for the given variable ID, or undefined if there is no spec defined
|
|
476
|
+
* in the listing for that variable.
|
|
477
|
+
*/
|
|
478
|
+
getSpecForVarId(varId) {
|
|
479
|
+
return this.varSpecs.get(varId);
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Return the `VarSpec` for the given variable name, or undefined if there is no spec defined
|
|
483
|
+
* in the listing for that variable.
|
|
484
|
+
*/
|
|
485
|
+
getSpecForVarName(varName) {
|
|
486
|
+
const varId = sdeVarIdForVensimVarName(varName);
|
|
487
|
+
return this.varSpecs.get(varId);
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Create a new `Outputs` instance that uses the same start/end years as the given "normal"
|
|
491
|
+
* `Outputs` instance but is prepared for reading the specified internal variables from the model.
|
|
492
|
+
*
|
|
493
|
+
* @param normalOutputs The `Outputs` that is used to access normal output variables from the model.
|
|
494
|
+
* @param varIds The variable IDs to include with the new `Outputs` instance.
|
|
495
|
+
*/
|
|
496
|
+
deriveOutputs(normalOutputs, varIds) {
|
|
497
|
+
const varSpecs = [];
|
|
498
|
+
for (const varId of varIds) {
|
|
499
|
+
const varSpec = this.varSpecs.get(varId);
|
|
500
|
+
if (varSpec !== void 0) varSpecs.push(varSpec);
|
|
501
|
+
else console.warn(`WARNING: No output var spec found for id=${varId}`);
|
|
502
|
+
}
|
|
503
|
+
const newOutputs = new Outputs(varIds, normalOutputs.startTime, normalOutputs.endTime, normalOutputs.saveFreq);
|
|
504
|
+
newOutputs.varSpecs = varSpecs;
|
|
505
|
+
return newOutputs;
|
|
506
|
+
}
|
|
467
507
|
};
|
|
508
|
+
/**
|
|
509
|
+
* Helper function that returns the base name of a variable without the subscripts.
|
|
510
|
+
*/
|
|
468
511
|
function varIdWithoutSubscripts(fullVarId) {
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
} else {
|
|
473
|
-
return fullVarId;
|
|
474
|
-
}
|
|
512
|
+
const bracketIndex = fullVarId.indexOf("[");
|
|
513
|
+
if (bracketIndex >= 0) return fullVarId.substring(0, bracketIndex);
|
|
514
|
+
else return fullVarId;
|
|
475
515
|
}
|
|
516
|
+
/**
|
|
517
|
+
* Return the cartesian product of the given array of arrays.
|
|
518
|
+
*
|
|
519
|
+
* For example, if we have an array that lists out two dimensions:
|
|
520
|
+
* [ ['a1','a2'], ['b1','b2','b3'] ]
|
|
521
|
+
* this function will return all the combinations, e.g.:
|
|
522
|
+
* [ ['a1', 'b1'], ['a1', 'b2'], ['a1', 'b3'], ['a2', 'b1'], ... ]
|
|
523
|
+
*
|
|
524
|
+
* This can be used in place of nested for loops and has the benefit of working
|
|
525
|
+
* for multi-dimensional inputs.
|
|
526
|
+
*/
|
|
476
527
|
function cartesianProductOf(arr) {
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
},
|
|
481
|
-
[[]]
|
|
482
|
-
);
|
|
528
|
+
return arr.reduce((a, b) => {
|
|
529
|
+
return a.map((x) => b.map((y) => x.concat([y]))).reduce((v, w) => v.concat(w), []);
|
|
530
|
+
}, [[]]);
|
|
483
531
|
}
|
|
532
|
+
/**
|
|
533
|
+
* Helper function that converts a Vensim variable or subscript name
|
|
534
|
+
* into a valid C identifier as used by SDE.
|
|
535
|
+
* TODO: Import helper function from `compile` package instead
|
|
536
|
+
*/
|
|
484
537
|
function sdeVarIdForVensimName(name) {
|
|
485
|
-
|
|
538
|
+
return "_" + name.trim().replace(/"/g, "_").replace(/\s+!$/g, "!").replace(/\s/g, "_").replace(/,/g, "_").replace(/-/g, "_").replace(/\./g, "_").replace(/\$/g, "_").replace(/'/g, "_").replace(/&/g, "_").replace(/%/g, "_").replace(/\//g, "_").replace(/\|/g, "_").toLowerCase();
|
|
486
539
|
}
|
|
540
|
+
/**
|
|
541
|
+
* Helper function that converts a Vensim variable name (possibly containing
|
|
542
|
+
* subscripts) into a valid C identifier as used by SDE.
|
|
543
|
+
* TODO: Import helper function from `compile` package instead
|
|
544
|
+
*/
|
|
487
545
|
function sdeVarIdForVensimVarName(varName) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
}
|
|
497
|
-
return id;
|
|
546
|
+
const m = varName.match(/([^[]+)(?:\[([^\]]+)\])?/);
|
|
547
|
+
if (!m) throw new Error(`Invalid Vensim name: ${varName}`);
|
|
548
|
+
let id = sdeVarIdForVensimName(m[1]);
|
|
549
|
+
if (m[2]) {
|
|
550
|
+
const subscripts = m[2].split(",").map((x) => sdeVarIdForVensimName(x));
|
|
551
|
+
id += `[${subscripts.join(",")}]`;
|
|
552
|
+
}
|
|
553
|
+
return id;
|
|
498
554
|
}
|
|
499
|
-
|
|
500
|
-
|
|
555
|
+
//#endregion
|
|
556
|
+
//#region src/runnable-model/resolve-var-ref.ts
|
|
557
|
+
/**
|
|
558
|
+
* Resolve the provided variable reference using the `ModelListing` and variable name
|
|
559
|
+
* or identifier.
|
|
560
|
+
*
|
|
561
|
+
* - If `varRef` has a defined `varSpec`, it is assumed to be valid.
|
|
562
|
+
* - Otherwise, if `varRef` has a defined `varId`, it will be used to locate the
|
|
563
|
+
* corresponding `varSpec` in the provided listing.
|
|
564
|
+
* - Otherwise, if `varRef` has a defined `varName`, it will be used to locate the
|
|
565
|
+
* corresponding `varSpec` in the provided listing.
|
|
566
|
+
*
|
|
567
|
+
* If the `varSpec` is found, this will set the `varSpec` property on the provided `varRef`.
|
|
568
|
+
* Otherwise, this will throw an error.
|
|
569
|
+
*
|
|
570
|
+
* @hidden This is not part of the public API; it is exposed here for internal
|
|
571
|
+
* use only.
|
|
572
|
+
*
|
|
573
|
+
* @param listing The model listing.
|
|
574
|
+
* @param varRef The variable reference.
|
|
575
|
+
* @param varKind The kind of variable (e.g., "lookup"), used to build an error message.
|
|
576
|
+
*/
|
|
501
577
|
function resolveVarRef(listing, varRef, varKind) {
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
varRef.varSpec = varSpec;
|
|
514
|
-
} else {
|
|
515
|
-
throw new Error(`Failed to resolve ${varKind} variable reference for varId=${varRef.varId}`);
|
|
516
|
-
}
|
|
517
|
-
} else {
|
|
518
|
-
const varSpec = listing == null ? void 0 : listing.getSpecForVarName(varRef.varName);
|
|
519
|
-
if (varSpec) {
|
|
520
|
-
varRef.varSpec = varSpec;
|
|
521
|
-
} else {
|
|
522
|
-
throw new Error(`Failed to resolve ${varKind} variable reference for varName='${varRef.varId}'`);
|
|
523
|
-
}
|
|
524
|
-
}
|
|
578
|
+
if (varRef.varSpec) return;
|
|
579
|
+
if (listing === void 0) throw new Error(`Unable to resolve ${varKind} variable references by name or identifier when model listing is unavailable`);
|
|
580
|
+
if (varRef.varId) {
|
|
581
|
+
const varSpec = listing?.getSpecForVarId(varRef.varId);
|
|
582
|
+
if (varSpec) varRef.varSpec = varSpec;
|
|
583
|
+
else throw new Error(`Failed to resolve ${varKind} variable reference for varId=${varRef.varId}`);
|
|
584
|
+
} else {
|
|
585
|
+
const varSpec = listing?.getSpecForVarName(varRef.varName);
|
|
586
|
+
if (varSpec) varRef.varSpec = varSpec;
|
|
587
|
+
else throw new Error(`Failed to resolve ${varKind} variable reference for varName='${varRef.varId}'`);
|
|
588
|
+
}
|
|
525
589
|
}
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/runnable-model/buffered-run-model-params.ts
|
|
592
|
+
const headerLengthInElements = 20;
|
|
593
|
+
const extrasLengthInElements = 1;
|
|
530
594
|
var Int32Section = class {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
595
|
+
constructor() {
|
|
596
|
+
this.offsetInBytes = 0;
|
|
597
|
+
this.lengthInElements = 0;
|
|
598
|
+
}
|
|
599
|
+
update(encoded, offsetInBytes, lengthInElements) {
|
|
600
|
+
this.view = lengthInElements > 0 ? new Int32Array(encoded, offsetInBytes, lengthInElements) : void 0;
|
|
601
|
+
this.offsetInBytes = offsetInBytes;
|
|
602
|
+
this.lengthInElements = lengthInElements;
|
|
603
|
+
}
|
|
540
604
|
};
|
|
541
605
|
var Float64Section = class {
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
606
|
+
constructor() {
|
|
607
|
+
this.offsetInBytes = 0;
|
|
608
|
+
this.lengthInElements = 0;
|
|
609
|
+
}
|
|
610
|
+
update(encoded, offsetInBytes, lengthInElements) {
|
|
611
|
+
this.view = lengthInElements > 0 ? new Float64Array(encoded, offsetInBytes, lengthInElements) : void 0;
|
|
612
|
+
this.offsetInBytes = offsetInBytes;
|
|
613
|
+
this.lengthInElements = lengthInElements;
|
|
614
|
+
}
|
|
551
615
|
};
|
|
616
|
+
/**
|
|
617
|
+
* An implementation of `RunModelParams` that copies the input and output arrays into a single,
|
|
618
|
+
* combined buffer. This implementation is designed to work with an asynchronous `ModelRunner`
|
|
619
|
+
* implementation because the buffer can be transferred to/from a Web Worker or Node.js worker
|
|
620
|
+
* thread without copying (if it is marked `Transferable`).
|
|
621
|
+
*
|
|
622
|
+
* @hidden This is not yet exposed in the public API; it is currently only used by
|
|
623
|
+
* the implementations of the `RunnableModel` interface.
|
|
624
|
+
*/
|
|
552
625
|
var BufferedRunModelParams = class {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
} else {
|
|
777
|
-
inputsView[i] = input.get();
|
|
778
|
-
}
|
|
779
|
-
}
|
|
780
|
-
if (this.outputIndices.view) {
|
|
781
|
-
encodeVarIndices(outputVarSpecs, this.outputIndices.view);
|
|
782
|
-
}
|
|
783
|
-
if (constantIndicesLengthInElements > 0) {
|
|
784
|
-
encodeConstants(options.constants, this.constantIndices.view, this.constants.view);
|
|
785
|
-
}
|
|
786
|
-
if (lookupIndicesLengthInElements > 0) {
|
|
787
|
-
encodeLookups(options.lookups, this.lookupIndices.view, this.lookups.view);
|
|
788
|
-
}
|
|
789
|
-
}
|
|
790
|
-
/**
|
|
791
|
-
* Update this instance using the values contained in the encoded buffer from another
|
|
792
|
-
* `BufferedRunModelParams` instance.
|
|
793
|
-
*
|
|
794
|
-
* @param buffer An encoded buffer returned by `getEncodedBuffer`.
|
|
795
|
-
*/
|
|
796
|
-
updateFromEncodedBuffer(buffer) {
|
|
797
|
-
const headerLengthInBytes = headerLengthInElements * Int32Array.BYTES_PER_ELEMENT;
|
|
798
|
-
if (buffer.byteLength < headerLengthInBytes) {
|
|
799
|
-
throw new Error("Buffer must be long enough to contain header section");
|
|
800
|
-
}
|
|
801
|
-
this.encoded = buffer;
|
|
802
|
-
const headerOffsetInBytes = 0;
|
|
803
|
-
this.header.update(this.encoded, headerOffsetInBytes, headerLengthInElements);
|
|
804
|
-
const headerView = this.header.view;
|
|
805
|
-
let headerIndex = 0;
|
|
806
|
-
const extrasOffsetInBytes = headerView[headerIndex++];
|
|
807
|
-
const extrasLengthInElements2 = headerView[headerIndex++];
|
|
808
|
-
const inputsOffsetInBytes = headerView[headerIndex++];
|
|
809
|
-
const inputsLengthInElements = headerView[headerIndex++];
|
|
810
|
-
const outputsOffsetInBytes = headerView[headerIndex++];
|
|
811
|
-
const outputsLengthInElements = headerView[headerIndex++];
|
|
812
|
-
const outputIndicesOffsetInBytes = headerView[headerIndex++];
|
|
813
|
-
const outputIndicesLengthInElements = headerView[headerIndex++];
|
|
814
|
-
const constantsOffsetInBytes = headerView[headerIndex++];
|
|
815
|
-
const constantsLengthInElements = headerView[headerIndex++];
|
|
816
|
-
const constantIndicesOffsetInBytes = headerView[headerIndex++];
|
|
817
|
-
const constantIndicesLengthInElements = headerView[headerIndex++];
|
|
818
|
-
const lookupsOffsetInBytes = headerView[headerIndex++];
|
|
819
|
-
const lookupsLengthInElements = headerView[headerIndex++];
|
|
820
|
-
const lookupIndicesOffsetInBytes = headerView[headerIndex++];
|
|
821
|
-
const lookupIndicesLengthInElements = headerView[headerIndex++];
|
|
822
|
-
const extrasLengthInBytes = extrasLengthInElements2 * Float64Array.BYTES_PER_ELEMENT;
|
|
823
|
-
const inputsLengthInBytes = inputsLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
824
|
-
const outputsLengthInBytes = outputsLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
825
|
-
const outputIndicesLengthInBytes = outputIndicesLengthInElements * Int32Array.BYTES_PER_ELEMENT;
|
|
826
|
-
const constantsLengthInBytes = constantsLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
827
|
-
const constantIndicesLengthInBytes = constantIndicesLengthInElements * Int32Array.BYTES_PER_ELEMENT;
|
|
828
|
-
const lookupsLengthInBytes = lookupsLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
829
|
-
const lookupIndicesLengthInBytes = lookupIndicesLengthInElements * Int32Array.BYTES_PER_ELEMENT;
|
|
830
|
-
const requiredLengthInBytes = headerLengthInBytes + extrasLengthInBytes + inputsLengthInBytes + outputsLengthInBytes + outputIndicesLengthInBytes + constantsLengthInBytes + constantIndicesLengthInBytes + lookupsLengthInBytes + lookupIndicesLengthInBytes;
|
|
831
|
-
if (buffer.byteLength < requiredLengthInBytes) {
|
|
832
|
-
throw new Error("Buffer must be long enough to contain sections declared in header");
|
|
833
|
-
}
|
|
834
|
-
this.extras.update(this.encoded, extrasOffsetInBytes, extrasLengthInElements2);
|
|
835
|
-
this.inputs.update(this.encoded, inputsOffsetInBytes, inputsLengthInElements);
|
|
836
|
-
this.outputs.update(this.encoded, outputsOffsetInBytes, outputsLengthInElements);
|
|
837
|
-
this.outputIndices.update(this.encoded, outputIndicesOffsetInBytes, outputIndicesLengthInElements);
|
|
838
|
-
this.constants.update(this.encoded, constantsOffsetInBytes, constantsLengthInElements);
|
|
839
|
-
this.constantIndices.update(this.encoded, constantIndicesOffsetInBytes, constantIndicesLengthInElements);
|
|
840
|
-
this.lookups.update(this.encoded, lookupsOffsetInBytes, lookupsLengthInElements);
|
|
841
|
-
this.lookupIndices.update(this.encoded, lookupIndicesOffsetInBytes, lookupIndicesLengthInElements);
|
|
842
|
-
}
|
|
626
|
+
/**
|
|
627
|
+
* @param listing The model listing that is used to locate a variable that is referenced by
|
|
628
|
+
* name or identifier. If undefined, variables cannot be referenced by name or identifier,
|
|
629
|
+
* and can only be referenced using a valid `VarSpec`.
|
|
630
|
+
*/
|
|
631
|
+
constructor(listing) {
|
|
632
|
+
this.listing = listing;
|
|
633
|
+
this.header = new Int32Section();
|
|
634
|
+
this.extras = new Float64Section();
|
|
635
|
+
this.inputs = new Float64Section();
|
|
636
|
+
this.outputs = new Float64Section();
|
|
637
|
+
this.outputIndices = new Int32Section();
|
|
638
|
+
this.constants = new Float64Section();
|
|
639
|
+
this.constantIndices = new Int32Section();
|
|
640
|
+
this.lookups = new Float64Section();
|
|
641
|
+
this.lookupIndices = new Int32Section();
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Return the encoded buffer from this instance, which can be passed to `updateFromEncodedBuffer`.
|
|
645
|
+
*/
|
|
646
|
+
getEncodedBuffer() {
|
|
647
|
+
return this.encoded;
|
|
648
|
+
}
|
|
649
|
+
getInputs() {
|
|
650
|
+
return this.inputs.view;
|
|
651
|
+
}
|
|
652
|
+
copyInputs(array, create) {
|
|
653
|
+
if (this.inputs.lengthInElements === 0) return;
|
|
654
|
+
if (array === void 0 || array.length < this.inputs.lengthInElements) array = create(this.inputs.lengthInElements);
|
|
655
|
+
array.set(this.inputs.view);
|
|
656
|
+
}
|
|
657
|
+
getOutputIndicesLength() {
|
|
658
|
+
return this.outputIndices.lengthInElements;
|
|
659
|
+
}
|
|
660
|
+
getOutputIndices() {
|
|
661
|
+
return this.outputIndices.view;
|
|
662
|
+
}
|
|
663
|
+
copyOutputIndices(array, create) {
|
|
664
|
+
if (this.outputIndices.lengthInElements === 0) return;
|
|
665
|
+
if (array === void 0 || array.length < this.outputIndices.lengthInElements) array = create(this.outputIndices.lengthInElements);
|
|
666
|
+
array.set(this.outputIndices.view);
|
|
667
|
+
}
|
|
668
|
+
getOutputsLength() {
|
|
669
|
+
return this.outputs.lengthInElements;
|
|
670
|
+
}
|
|
671
|
+
getOutputs() {
|
|
672
|
+
return this.outputs.view;
|
|
673
|
+
}
|
|
674
|
+
getOutputsObject() {}
|
|
675
|
+
storeOutputs(array) {
|
|
676
|
+
if (this.outputs.view === void 0) return;
|
|
677
|
+
if (array.length > this.outputs.view.length) this.outputs.view.set(array.subarray(0, this.outputs.view.length));
|
|
678
|
+
else this.outputs.view.set(array);
|
|
679
|
+
}
|
|
680
|
+
getConstants() {
|
|
681
|
+
if (this.constantIndices.lengthInElements === 0) return;
|
|
682
|
+
return decodeConstants(this.constantIndices.view, this.constants.view);
|
|
683
|
+
}
|
|
684
|
+
getLookups() {
|
|
685
|
+
if (this.lookupIndices.lengthInElements === 0) return;
|
|
686
|
+
return decodeLookups(this.lookupIndices.view, this.lookups.view);
|
|
687
|
+
}
|
|
688
|
+
getElapsedTime() {
|
|
689
|
+
return this.extras.view[0];
|
|
690
|
+
}
|
|
691
|
+
storeElapsedTime(elapsed) {
|
|
692
|
+
this.extras.view[0] = elapsed;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Copy the outputs buffer to the given `Outputs` instance. This should be called
|
|
696
|
+
* after the `runModel` call has completed so that the output values are copied from
|
|
697
|
+
* the internal buffer to the `Outputs` instance that was passed to `runModel`.
|
|
698
|
+
*
|
|
699
|
+
* @param outputs The `Outputs` instance into which the output values will be copied.
|
|
700
|
+
*/
|
|
701
|
+
finalizeOutputs(outputs) {
|
|
702
|
+
if (this.outputs.view) outputs.updateFromBuffer(this.outputs.view, outputs.seriesLength);
|
|
703
|
+
outputs.runTimeInMillis = this.getElapsedTime();
|
|
704
|
+
}
|
|
705
|
+
/**
|
|
706
|
+
* Update this instance using the parameters that are passed to a `runModel` call.
|
|
707
|
+
*
|
|
708
|
+
* @param inputs The model input values (must be in the same order as in the spec file).
|
|
709
|
+
* @param outputs The structure into which the model outputs will be stored.
|
|
710
|
+
* @param options Additional options that influence the model run.
|
|
711
|
+
*/
|
|
712
|
+
updateFromParams(inputs, outputs, options) {
|
|
713
|
+
const inputsLengthInElements = inputs.length;
|
|
714
|
+
const outputsLengthInElements = outputs.varIds.length * outputs.seriesLength;
|
|
715
|
+
let outputIndicesLengthInElements;
|
|
716
|
+
const outputVarSpecs = outputs.varSpecs;
|
|
717
|
+
if (outputVarSpecs !== void 0 && outputVarSpecs.length > 0) outputIndicesLengthInElements = getEncodedVarIndicesLength(outputVarSpecs);
|
|
718
|
+
else outputIndicesLengthInElements = 0;
|
|
719
|
+
let constantsLengthInElements;
|
|
720
|
+
let constantIndicesLengthInElements;
|
|
721
|
+
if (options?.constants !== void 0 && options.constants.length > 0) {
|
|
722
|
+
for (const constantDef of options.constants) resolveVarRef(this.listing, constantDef.varRef, "constant");
|
|
723
|
+
const encodedLengths = getEncodedConstantBufferLengths(options.constants);
|
|
724
|
+
constantsLengthInElements = encodedLengths.constantsLength;
|
|
725
|
+
constantIndicesLengthInElements = encodedLengths.constantIndicesLength;
|
|
726
|
+
} else {
|
|
727
|
+
constantsLengthInElements = 0;
|
|
728
|
+
constantIndicesLengthInElements = 0;
|
|
729
|
+
}
|
|
730
|
+
let lookupsLengthInElements;
|
|
731
|
+
let lookupIndicesLengthInElements;
|
|
732
|
+
if (options?.lookups !== void 0 && options.lookups.length > 0) {
|
|
733
|
+
for (const lookupDef of options.lookups) resolveVarRef(this.listing, lookupDef.varRef, "lookup");
|
|
734
|
+
const encodedLengths = getEncodedLookupBufferLengths(options.lookups);
|
|
735
|
+
lookupsLengthInElements = encodedLengths.lookupsLength;
|
|
736
|
+
lookupIndicesLengthInElements = encodedLengths.lookupIndicesLength;
|
|
737
|
+
} else {
|
|
738
|
+
lookupsLengthInElements = 0;
|
|
739
|
+
lookupIndicesLengthInElements = 0;
|
|
740
|
+
}
|
|
741
|
+
let byteOffset = 0;
|
|
742
|
+
function section(kind, lengthInElements) {
|
|
743
|
+
const sectionOffsetInBytes = byteOffset;
|
|
744
|
+
const bytesPerElement = kind === "float64" ? Float64Array.BYTES_PER_ELEMENT : Int32Array.BYTES_PER_ELEMENT;
|
|
745
|
+
const requiredSectionLengthInBytes = Math.round(lengthInElements * bytesPerElement);
|
|
746
|
+
const alignedSectionLengthInBytes = Math.ceil(requiredSectionLengthInBytes / 8) * 8;
|
|
747
|
+
byteOffset += alignedSectionLengthInBytes;
|
|
748
|
+
return sectionOffsetInBytes;
|
|
749
|
+
}
|
|
750
|
+
const headerOffsetInBytes = section("int32", headerLengthInElements);
|
|
751
|
+
const extrasOffsetInBytes = section("float64", extrasLengthInElements);
|
|
752
|
+
const inputsOffsetInBytes = section("float64", inputsLengthInElements);
|
|
753
|
+
const outputsOffsetInBytes = section("float64", outputsLengthInElements);
|
|
754
|
+
const outputIndicesOffsetInBytes = section("int32", outputIndicesLengthInElements);
|
|
755
|
+
const constantsOffsetInBytes = section("float64", constantsLengthInElements);
|
|
756
|
+
const constantIndicesOffsetInBytes = section("int32", constantIndicesLengthInElements);
|
|
757
|
+
const lookupsOffsetInBytes = section("float64", lookupsLengthInElements);
|
|
758
|
+
const lookupIndicesOffsetInBytes = section("int32", lookupIndicesLengthInElements);
|
|
759
|
+
const requiredLengthInBytes = byteOffset;
|
|
760
|
+
if (this.encoded === void 0 || this.encoded.byteLength < requiredLengthInBytes) {
|
|
761
|
+
const totalLengthInBytes = Math.ceil(requiredLengthInBytes * 1.2);
|
|
762
|
+
this.encoded = new ArrayBuffer(totalLengthInBytes);
|
|
763
|
+
this.header.update(this.encoded, headerOffsetInBytes, headerLengthInElements);
|
|
764
|
+
}
|
|
765
|
+
const headerView = this.header.view;
|
|
766
|
+
let headerIndex = 0;
|
|
767
|
+
headerView[headerIndex++] = extrasOffsetInBytes;
|
|
768
|
+
headerView[headerIndex++] = extrasLengthInElements;
|
|
769
|
+
headerView[headerIndex++] = inputsOffsetInBytes;
|
|
770
|
+
headerView[headerIndex++] = inputsLengthInElements;
|
|
771
|
+
headerView[headerIndex++] = outputsOffsetInBytes;
|
|
772
|
+
headerView[headerIndex++] = outputsLengthInElements;
|
|
773
|
+
headerView[headerIndex++] = outputIndicesOffsetInBytes;
|
|
774
|
+
headerView[headerIndex++] = outputIndicesLengthInElements;
|
|
775
|
+
headerView[headerIndex++] = constantsOffsetInBytes;
|
|
776
|
+
headerView[headerIndex++] = constantsLengthInElements;
|
|
777
|
+
headerView[headerIndex++] = constantIndicesOffsetInBytes;
|
|
778
|
+
headerView[headerIndex++] = constantIndicesLengthInElements;
|
|
779
|
+
headerView[headerIndex++] = lookupsOffsetInBytes;
|
|
780
|
+
headerView[headerIndex++] = lookupsLengthInElements;
|
|
781
|
+
headerView[headerIndex++] = lookupIndicesOffsetInBytes;
|
|
782
|
+
headerView[headerIndex++] = lookupIndicesLengthInElements;
|
|
783
|
+
this.inputs.update(this.encoded, inputsOffsetInBytes, inputsLengthInElements);
|
|
784
|
+
this.extras.update(this.encoded, extrasOffsetInBytes, extrasLengthInElements);
|
|
785
|
+
this.outputs.update(this.encoded, outputsOffsetInBytes, outputsLengthInElements);
|
|
786
|
+
this.outputIndices.update(this.encoded, outputIndicesOffsetInBytes, outputIndicesLengthInElements);
|
|
787
|
+
this.constants.update(this.encoded, constantsOffsetInBytes, constantsLengthInElements);
|
|
788
|
+
this.constantIndices.update(this.encoded, constantIndicesOffsetInBytes, constantIndicesLengthInElements);
|
|
789
|
+
this.lookups.update(this.encoded, lookupsOffsetInBytes, lookupsLengthInElements);
|
|
790
|
+
this.lookupIndices.update(this.encoded, lookupIndicesOffsetInBytes, lookupIndicesLengthInElements);
|
|
791
|
+
const inputsView = this.inputs.view;
|
|
792
|
+
for (let i = 0; i < inputs.length; i++) {
|
|
793
|
+
const input = inputs[i];
|
|
794
|
+
if (typeof input === "number") inputsView[i] = input;
|
|
795
|
+
else inputsView[i] = input.get();
|
|
796
|
+
}
|
|
797
|
+
if (this.outputIndices.view) encodeVarIndices(outputVarSpecs, this.outputIndices.view);
|
|
798
|
+
if (constantIndicesLengthInElements > 0) encodeConstants(options.constants, this.constantIndices.view, this.constants.view);
|
|
799
|
+
if (lookupIndicesLengthInElements > 0) encodeLookups(options.lookups, this.lookupIndices.view, this.lookups.view);
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* Update this instance using the values contained in the encoded buffer from another
|
|
803
|
+
* `BufferedRunModelParams` instance.
|
|
804
|
+
*
|
|
805
|
+
* @param buffer An encoded buffer returned by `getEncodedBuffer`.
|
|
806
|
+
*/
|
|
807
|
+
updateFromEncodedBuffer(buffer) {
|
|
808
|
+
const headerLengthInBytes = headerLengthInElements * Int32Array.BYTES_PER_ELEMENT;
|
|
809
|
+
if (buffer.byteLength < headerLengthInBytes) throw new Error("Buffer must be long enough to contain header section");
|
|
810
|
+
this.encoded = buffer;
|
|
811
|
+
this.header.update(this.encoded, 0, headerLengthInElements);
|
|
812
|
+
const headerView = this.header.view;
|
|
813
|
+
let headerIndex = 0;
|
|
814
|
+
const extrasOffsetInBytes = headerView[headerIndex++];
|
|
815
|
+
const extrasLengthInElements = headerView[headerIndex++];
|
|
816
|
+
const inputsOffsetInBytes = headerView[headerIndex++];
|
|
817
|
+
const inputsLengthInElements = headerView[headerIndex++];
|
|
818
|
+
const outputsOffsetInBytes = headerView[headerIndex++];
|
|
819
|
+
const outputsLengthInElements = headerView[headerIndex++];
|
|
820
|
+
const outputIndicesOffsetInBytes = headerView[headerIndex++];
|
|
821
|
+
const outputIndicesLengthInElements = headerView[headerIndex++];
|
|
822
|
+
const constantsOffsetInBytes = headerView[headerIndex++];
|
|
823
|
+
const constantsLengthInElements = headerView[headerIndex++];
|
|
824
|
+
const constantIndicesOffsetInBytes = headerView[headerIndex++];
|
|
825
|
+
const constantIndicesLengthInElements = headerView[headerIndex++];
|
|
826
|
+
const lookupsOffsetInBytes = headerView[headerIndex++];
|
|
827
|
+
const lookupsLengthInElements = headerView[headerIndex++];
|
|
828
|
+
const lookupIndicesOffsetInBytes = headerView[headerIndex++];
|
|
829
|
+
const lookupIndicesLengthInElements = headerView[headerIndex++];
|
|
830
|
+
const extrasLengthInBytes = extrasLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
831
|
+
const inputsLengthInBytes = inputsLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
832
|
+
const outputsLengthInBytes = outputsLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
833
|
+
const outputIndicesLengthInBytes = outputIndicesLengthInElements * Int32Array.BYTES_PER_ELEMENT;
|
|
834
|
+
const constantsLengthInBytes = constantsLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
835
|
+
const constantIndicesLengthInBytes = constantIndicesLengthInElements * Int32Array.BYTES_PER_ELEMENT;
|
|
836
|
+
const lookupsLengthInBytes = lookupsLengthInElements * Float64Array.BYTES_PER_ELEMENT;
|
|
837
|
+
const lookupIndicesLengthInBytes = lookupIndicesLengthInElements * Int32Array.BYTES_PER_ELEMENT;
|
|
838
|
+
const requiredLengthInBytes = headerLengthInBytes + extrasLengthInBytes + inputsLengthInBytes + outputsLengthInBytes + outputIndicesLengthInBytes + constantsLengthInBytes + constantIndicesLengthInBytes + lookupsLengthInBytes + lookupIndicesLengthInBytes;
|
|
839
|
+
if (buffer.byteLength < requiredLengthInBytes) throw new Error("Buffer must be long enough to contain sections declared in header");
|
|
840
|
+
this.extras.update(this.encoded, extrasOffsetInBytes, extrasLengthInElements);
|
|
841
|
+
this.inputs.update(this.encoded, inputsOffsetInBytes, inputsLengthInElements);
|
|
842
|
+
this.outputs.update(this.encoded, outputsOffsetInBytes, outputsLengthInElements);
|
|
843
|
+
this.outputIndices.update(this.encoded, outputIndicesOffsetInBytes, outputIndicesLengthInElements);
|
|
844
|
+
this.constants.update(this.encoded, constantsOffsetInBytes, constantsLengthInElements);
|
|
845
|
+
this.constantIndices.update(this.encoded, constantIndicesOffsetInBytes, constantIndicesLengthInElements);
|
|
846
|
+
this.lookups.update(this.encoded, lookupsOffsetInBytes, lookupsLengthInElements);
|
|
847
|
+
this.lookupIndices.update(this.encoded, lookupIndicesOffsetInBytes, lookupIndicesLengthInElements);
|
|
848
|
+
}
|
|
843
849
|
};
|
|
844
|
-
|
|
845
|
-
|
|
850
|
+
//#endregion
|
|
851
|
+
//#region src/runnable-model/referenced-run-model-params.ts
|
|
852
|
+
/**
|
|
853
|
+
* An implementation of `RunModelParams` that keeps references to the `inputs` and
|
|
854
|
+
* `outputs` parameters that are passed to the `runModel` function. This implementation
|
|
855
|
+
* is best used with a synchronous `ModelRunner`.
|
|
856
|
+
*
|
|
857
|
+
* @hidden This is not yet exposed in the public API; it is currently only used by
|
|
858
|
+
* the implementations of the `RunnableModel` interface.
|
|
859
|
+
*/
|
|
846
860
|
var ReferencedRunModelParams = class {
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
}
|
|
923
|
-
// from RunModelParams interface
|
|
924
|
-
getLookups() {
|
|
925
|
-
if (this.lookups !== void 0 && this.lookups.length > 0) {
|
|
926
|
-
return this.lookups;
|
|
927
|
-
} else {
|
|
928
|
-
return void 0;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
// from RunModelParams interface
|
|
932
|
-
getElapsedTime() {
|
|
933
|
-
var _a;
|
|
934
|
-
return (_a = this.outputs) == null ? void 0 : _a.runTimeInMillis;
|
|
935
|
-
}
|
|
936
|
-
// from RunModelParams interface
|
|
937
|
-
storeElapsedTime(elapsed) {
|
|
938
|
-
if (this.outputs) {
|
|
939
|
-
this.outputs.runTimeInMillis = elapsed;
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
/**
|
|
943
|
-
* Update this instance using the parameters that are passed to a `runModel` call.
|
|
944
|
-
*
|
|
945
|
-
* @param inputs The model input values (must be in the same order as in the spec file).
|
|
946
|
-
* @param outputs The structure into which the model outputs will be stored.
|
|
947
|
-
* @param options Additional options that influence the model run.
|
|
948
|
-
*/
|
|
949
|
-
updateFromParams(inputs, outputs, options) {
|
|
950
|
-
this.inputs = inputs;
|
|
951
|
-
this.outputs = outputs;
|
|
952
|
-
this.outputsLengthInElements = outputs.varIds.length * outputs.seriesLength;
|
|
953
|
-
this.constants = options == null ? void 0 : options.constants;
|
|
954
|
-
this.lookups = options == null ? void 0 : options.lookups;
|
|
955
|
-
if (this.constants) {
|
|
956
|
-
for (const constantDef of this.constants) {
|
|
957
|
-
resolveVarRef(this.listing, constantDef.varRef, "constant");
|
|
958
|
-
}
|
|
959
|
-
}
|
|
960
|
-
if (this.lookups) {
|
|
961
|
-
for (const lookupDef of this.lookups) {
|
|
962
|
-
resolveVarRef(this.listing, lookupDef.varRef, "lookup");
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
const outputVarSpecs = outputs.varSpecs;
|
|
966
|
-
if (outputVarSpecs !== void 0 && outputVarSpecs.length > 0) {
|
|
967
|
-
this.outputIndicesLengthInElements = getEncodedVarIndicesLength(outputVarSpecs);
|
|
968
|
-
} else {
|
|
969
|
-
this.outputIndicesLengthInElements = 0;
|
|
970
|
-
}
|
|
971
|
-
}
|
|
861
|
+
/**
|
|
862
|
+
* @param listing The model listing that is used to locate a variable that is referenced by
|
|
863
|
+
* name or identifier. If undefined, variables cannot be referenced by name or identifier,
|
|
864
|
+
* and can only be referenced using a valid `VarSpec`.
|
|
865
|
+
*/
|
|
866
|
+
constructor(listing) {
|
|
867
|
+
this.listing = listing;
|
|
868
|
+
this.outputsLengthInElements = 0;
|
|
869
|
+
this.outputIndicesLengthInElements = 0;
|
|
870
|
+
}
|
|
871
|
+
getInputs() {}
|
|
872
|
+
copyInputs(array, create) {
|
|
873
|
+
const inputsLengthInElements = this.inputs.length;
|
|
874
|
+
if (array === void 0 || array.length < inputsLengthInElements) array = create(inputsLengthInElements);
|
|
875
|
+
for (let i = 0; i < this.inputs.length; i++) {
|
|
876
|
+
const input = this.inputs[i];
|
|
877
|
+
if (typeof input === "number") array[i] = input;
|
|
878
|
+
else array[i] = input.get();
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
getOutputIndicesLength() {
|
|
882
|
+
return this.outputIndicesLengthInElements;
|
|
883
|
+
}
|
|
884
|
+
getOutputIndices() {}
|
|
885
|
+
copyOutputIndices(array, create) {
|
|
886
|
+
if (this.outputIndicesLengthInElements === 0) return;
|
|
887
|
+
if (array === void 0 || array.length < this.outputIndicesLengthInElements) array = create(this.outputIndicesLengthInElements);
|
|
888
|
+
encodeVarIndices(this.outputs.varSpecs, array);
|
|
889
|
+
}
|
|
890
|
+
getOutputsLength() {
|
|
891
|
+
return this.outputsLengthInElements;
|
|
892
|
+
}
|
|
893
|
+
getOutputs() {}
|
|
894
|
+
getOutputsObject() {
|
|
895
|
+
return this.outputs;
|
|
896
|
+
}
|
|
897
|
+
storeOutputs(array) {
|
|
898
|
+
if (this.outputs) {
|
|
899
|
+
const result = this.outputs.updateFromBuffer(array, this.outputs.seriesLength);
|
|
900
|
+
if (result.isErr()) throw new Error(`Failed to store outputs: ${result.error}`);
|
|
901
|
+
}
|
|
902
|
+
}
|
|
903
|
+
getConstants() {
|
|
904
|
+
if (this.constants !== void 0 && this.constants.length > 0) return this.constants;
|
|
905
|
+
else return;
|
|
906
|
+
}
|
|
907
|
+
getLookups() {
|
|
908
|
+
if (this.lookups !== void 0 && this.lookups.length > 0) return this.lookups;
|
|
909
|
+
else return;
|
|
910
|
+
}
|
|
911
|
+
getElapsedTime() {
|
|
912
|
+
return this.outputs?.runTimeInMillis;
|
|
913
|
+
}
|
|
914
|
+
storeElapsedTime(elapsed) {
|
|
915
|
+
if (this.outputs) this.outputs.runTimeInMillis = elapsed;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Update this instance using the parameters that are passed to a `runModel` call.
|
|
919
|
+
*
|
|
920
|
+
* @param inputs The model input values (must be in the same order as in the spec file).
|
|
921
|
+
* @param outputs The structure into which the model outputs will be stored.
|
|
922
|
+
* @param options Additional options that influence the model run.
|
|
923
|
+
*/
|
|
924
|
+
updateFromParams(inputs, outputs, options) {
|
|
925
|
+
this.inputs = inputs;
|
|
926
|
+
this.outputs = outputs;
|
|
927
|
+
this.outputsLengthInElements = outputs.varIds.length * outputs.seriesLength;
|
|
928
|
+
this.constants = options?.constants;
|
|
929
|
+
this.lookups = options?.lookups;
|
|
930
|
+
if (this.constants) for (const constantDef of this.constants) resolveVarRef(this.listing, constantDef.varRef, "constant");
|
|
931
|
+
if (this.lookups) for (const lookupDef of this.lookups) resolveVarRef(this.listing, lookupDef.varRef, "lookup");
|
|
932
|
+
const outputVarSpecs = outputs.varSpecs;
|
|
933
|
+
if (outputVarSpecs !== void 0 && outputVarSpecs.length > 0) this.outputIndicesLengthInElements = getEncodedVarIndicesLength(outputVarSpecs);
|
|
934
|
+
else this.outputIndicesLengthInElements = 0;
|
|
935
|
+
}
|
|
972
936
|
};
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
937
|
+
//#endregion
|
|
938
|
+
//#region src/js-model/js-model-constants.ts
|
|
939
|
+
const _NA_ = -Number.MAX_VALUE;
|
|
940
|
+
//#endregion
|
|
941
|
+
//#region src/js-model/js-model-lookup.ts
|
|
942
|
+
/**
|
|
943
|
+
* @hidden This is not yet part of the public API; for internal use only.
|
|
944
|
+
*/
|
|
978
945
|
var JsModelLookup = class {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
return data[max - 1];
|
|
1153
|
-
}
|
|
1154
|
-
case "backward": {
|
|
1155
|
-
input = Math.floor(input);
|
|
1156
|
-
for (let xi = 2; xi < max; xi += 2) {
|
|
1157
|
-
const x = data[xi];
|
|
1158
|
-
if (x >= input) {
|
|
1159
|
-
return data[xi - 1];
|
|
1160
|
-
}
|
|
1161
|
-
}
|
|
1162
|
-
if (max >= 4) {
|
|
1163
|
-
return data[max - 3];
|
|
1164
|
-
} else {
|
|
1165
|
-
return data[1];
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
case "interpolate":
|
|
1169
|
-
default: {
|
|
1170
|
-
if (input - Math.floor(input) > 0) {
|
|
1171
|
-
let msg = `GET DATA BETWEEN TIMES was called with an input value (${input}) that has a fractional part. `;
|
|
1172
|
-
msg += "When mode is 0 (interpolate) and the input value is not a whole number, Vensim produces unexpected ";
|
|
1173
|
-
msg += "results that may differ from those produced by SDEverywhere.";
|
|
1174
|
-
throw new Error(msg);
|
|
1175
|
-
}
|
|
1176
|
-
for (let xi = 2; xi < max; xi += 2) {
|
|
1177
|
-
const x = data[xi];
|
|
1178
|
-
if (x >= input) {
|
|
1179
|
-
const last_x = data[xi - 2];
|
|
1180
|
-
const last_y = data[xi - 1];
|
|
1181
|
-
const y = data[xi + 1];
|
|
1182
|
-
const dx = x - last_x;
|
|
1183
|
-
const dy = y - last_y;
|
|
1184
|
-
return last_y + dy / dx * (input - last_x);
|
|
1185
|
-
}
|
|
1186
|
-
}
|
|
1187
|
-
return data[max - 1];
|
|
1188
|
-
}
|
|
1189
|
-
}
|
|
1190
|
-
}
|
|
946
|
+
/**
|
|
947
|
+
* @param size The number of (x,y) pairs in the lookup.
|
|
948
|
+
* @param data The lookup data, as (x,y) pairs. The length of the array must be
|
|
949
|
+
* >= 2*n. Note that the data will be stored by reference, so if there is a chance
|
|
950
|
+
* that the array will be reused or modified by other code, be sure to pass in a
|
|
951
|
+
* copy of the array.
|
|
952
|
+
*/
|
|
953
|
+
constructor(size, data) {
|
|
954
|
+
if (data && data.length < size * 2) throw new Error(`Lookup data array length must be >= 2*size (length=${data.length} size=${size}`);
|
|
955
|
+
this.originalData = data;
|
|
956
|
+
this.originalSize = size;
|
|
957
|
+
this.dynamicData = void 0;
|
|
958
|
+
this.dynamicSize = 0;
|
|
959
|
+
this.activeData = this.originalData;
|
|
960
|
+
this.activeSize = this.originalSize;
|
|
961
|
+
this.lastInput = Number.MAX_VALUE;
|
|
962
|
+
this.lastHitIndex = 0;
|
|
963
|
+
}
|
|
964
|
+
/**
|
|
965
|
+
* Set new data for this lookup instance, or restore the original data.
|
|
966
|
+
*
|
|
967
|
+
* If `data` is undefined, the original data that was supplied to the constructor will
|
|
968
|
+
* be restored as the "active" data. Otherwise, `data` will be copied to an internal
|
|
969
|
+
* data buffer, which will be the "active" data. If `size` is greater than the size
|
|
970
|
+
* passed to previous calls, the internal data buffer will be grown as needed.
|
|
971
|
+
*
|
|
972
|
+
* @param size The number of (x,y) pairs in the lookup.
|
|
973
|
+
* @param data The lookup data, as (x,y) pairs. The length of the array must be
|
|
974
|
+
* >= 2*n. Note that the data will be copied into an internal data buffer, so it
|
|
975
|
+
* is not necessary to defensively copy data before calling this method.
|
|
976
|
+
*/
|
|
977
|
+
setData(size, data) {
|
|
978
|
+
if (data) {
|
|
979
|
+
if (data.length < size * 2) throw new Error(`Lookup data array length must be >= 2*size (length=${data.length} size=${size}`);
|
|
980
|
+
const dataLengthInElems = size * 2;
|
|
981
|
+
if (this.dynamicData === void 0 || dataLengthInElems > this.dynamicData.length) this.dynamicData = new Float64Array(dataLengthInElems);
|
|
982
|
+
this.dynamicSize = size;
|
|
983
|
+
if (size > 0) {
|
|
984
|
+
const subarray = data.subarray(0, dataLengthInElems);
|
|
985
|
+
this.dynamicData.set(subarray);
|
|
986
|
+
}
|
|
987
|
+
this.activeData = this.dynamicData;
|
|
988
|
+
this.activeSize = this.dynamicSize;
|
|
989
|
+
} else {
|
|
990
|
+
this.activeData = this.originalData;
|
|
991
|
+
this.activeSize = this.originalSize;
|
|
992
|
+
}
|
|
993
|
+
this.invertedData = void 0;
|
|
994
|
+
this.lastInput = Number.MAX_VALUE;
|
|
995
|
+
this.lastHitIndex = 0;
|
|
996
|
+
}
|
|
997
|
+
getValueForX(x, mode) {
|
|
998
|
+
return this.getValue(x, false, mode);
|
|
999
|
+
}
|
|
1000
|
+
getValueForY(y) {
|
|
1001
|
+
if (this.invertedData === void 0) {
|
|
1002
|
+
const numValues = this.activeSize * 2;
|
|
1003
|
+
const normalData = this.activeData;
|
|
1004
|
+
const invertedData = Array(numValues);
|
|
1005
|
+
for (let i = 0; i < numValues; i += 2) {
|
|
1006
|
+
invertedData[i] = normalData[i + 1];
|
|
1007
|
+
invertedData[i + 1] = normalData[i];
|
|
1008
|
+
}
|
|
1009
|
+
this.invertedData = invertedData;
|
|
1010
|
+
}
|
|
1011
|
+
return this.getValue(y, true, "interpolate");
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Interpolate the y value from the array of (x,y) pairs.
|
|
1015
|
+
* NOTE: The x values are assumed to be monotonically increasing.
|
|
1016
|
+
*/
|
|
1017
|
+
getValue(input, useInvertedData, mode) {
|
|
1018
|
+
if (this.activeSize === 0) return _NA_;
|
|
1019
|
+
const data = useInvertedData ? this.invertedData : this.activeData;
|
|
1020
|
+
const max = this.activeSize * 2;
|
|
1021
|
+
const useCachedValues = !useInvertedData;
|
|
1022
|
+
let startIndex;
|
|
1023
|
+
if (useCachedValues && input >= this.lastInput) startIndex = this.lastHitIndex;
|
|
1024
|
+
else startIndex = 0;
|
|
1025
|
+
for (let xi = startIndex; xi < max; xi += 2) {
|
|
1026
|
+
const x = data[xi];
|
|
1027
|
+
if (x >= input) {
|
|
1028
|
+
if (useCachedValues) {
|
|
1029
|
+
this.lastInput = input;
|
|
1030
|
+
this.lastHitIndex = xi;
|
|
1031
|
+
}
|
|
1032
|
+
if (xi === 0 || x === input) return data[xi + 1];
|
|
1033
|
+
switch (mode) {
|
|
1034
|
+
default:
|
|
1035
|
+
case "interpolate": {
|
|
1036
|
+
const last_x = data[xi - 2];
|
|
1037
|
+
const last_y = data[xi - 1];
|
|
1038
|
+
const y = data[xi + 1];
|
|
1039
|
+
const dx = x - last_x;
|
|
1040
|
+
return last_y + (y - last_y) / dx * (input - last_x);
|
|
1041
|
+
}
|
|
1042
|
+
case "forward": return data[xi + 1];
|
|
1043
|
+
case "backward": return data[xi - 1];
|
|
1044
|
+
}
|
|
1045
|
+
}
|
|
1046
|
+
}
|
|
1047
|
+
if (useCachedValues) {
|
|
1048
|
+
this.lastInput = input;
|
|
1049
|
+
this.lastHitIndex = max;
|
|
1050
|
+
}
|
|
1051
|
+
return data[max - 1];
|
|
1052
|
+
}
|
|
1053
|
+
/**
|
|
1054
|
+
* Return the most appropriate y value from the array of (x,y) pairs when
|
|
1055
|
+
* this instance is used to provide inputs for the `GAME` function.
|
|
1056
|
+
*
|
|
1057
|
+
* NOTE: The x values are assumed to be monotonically increasing.
|
|
1058
|
+
*
|
|
1059
|
+
* This method is similar to `getValueForX` in concept, except that this one
|
|
1060
|
+
* returns the provided `defaultValue` if the `time` parameter is earlier than
|
|
1061
|
+
* the first data point in the lookup. Also, this method always uses the
|
|
1062
|
+
* `backward` interpolation mode, meaning that it holds the "current" value
|
|
1063
|
+
* constant instead of interpolating.
|
|
1064
|
+
*
|
|
1065
|
+
* @param time The time that is used to select the data point that has an
|
|
1066
|
+
* `x` value less than or equal to the provided time.
|
|
1067
|
+
* @param defaultValue The value that is returned if this lookup is empty (has
|
|
1068
|
+
* no points) or if the provided time is earlier than the first data point.
|
|
1069
|
+
*/
|
|
1070
|
+
getValueForGameTime(time, defaultValue) {
|
|
1071
|
+
if (this.activeSize <= 0) return defaultValue;
|
|
1072
|
+
if (time < this.activeData[0]) return defaultValue;
|
|
1073
|
+
return this.getValue(time, false, "backward");
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Interpolate the y value from the array of (x,y) pairs.
|
|
1077
|
+
* NOTE: The x values are assumed to be monotonically increasing.
|
|
1078
|
+
*
|
|
1079
|
+
* This method is similar to `getValue` in concept, but Vensim produces results for
|
|
1080
|
+
* the `GET DATA BETWEEN TIMES` function that differ in unexpected ways from normal
|
|
1081
|
+
* lookup behavior, so we implement it as a separate method here.
|
|
1082
|
+
*/
|
|
1083
|
+
getValueBetweenTimes(input, mode) {
|
|
1084
|
+
if (this.activeSize === 0) return _NA_;
|
|
1085
|
+
const data = this.activeData;
|
|
1086
|
+
const max = this.activeSize * 2;
|
|
1087
|
+
switch (mode) {
|
|
1088
|
+
case "forward":
|
|
1089
|
+
input = Math.floor(input);
|
|
1090
|
+
for (let xi = 0; xi < max; xi += 2) if (data[xi] >= input) return data[xi + 1];
|
|
1091
|
+
return data[max - 1];
|
|
1092
|
+
case "backward":
|
|
1093
|
+
input = Math.floor(input);
|
|
1094
|
+
for (let xi = 2; xi < max; xi += 2) if (data[xi] >= input) return data[xi - 1];
|
|
1095
|
+
if (max >= 4) return data[max - 3];
|
|
1096
|
+
else return data[1];
|
|
1097
|
+
default:
|
|
1098
|
+
if (input - Math.floor(input) > 0) {
|
|
1099
|
+
let msg = `GET DATA BETWEEN TIMES was called with an input value (${input}) that has a fractional part. `;
|
|
1100
|
+
msg += "When mode is 0 (interpolate) and the input value is not a whole number, Vensim produces unexpected ";
|
|
1101
|
+
msg += "results that may differ from those produced by SDEverywhere.";
|
|
1102
|
+
throw new Error(msg);
|
|
1103
|
+
}
|
|
1104
|
+
for (let xi = 2; xi < max; xi += 2) {
|
|
1105
|
+
const x = data[xi];
|
|
1106
|
+
if (x >= input) {
|
|
1107
|
+
const last_x = data[xi - 2];
|
|
1108
|
+
const last_y = data[xi - 1];
|
|
1109
|
+
const y = data[xi + 1];
|
|
1110
|
+
const dx = x - last_x;
|
|
1111
|
+
return last_y + (y - last_y) / dx * (input - last_x);
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
return data[max - 1];
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1191
1117
|
};
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1118
|
+
//#endregion
|
|
1119
|
+
//#region src/js-model/js-model-functions.ts
|
|
1120
|
+
const EPSILON = 1e-6;
|
|
1121
|
+
/**
|
|
1122
|
+
* Returns a default implementation of the `JsModelFunctions` interface. If needed,
|
|
1123
|
+
* you can provide a custom implementation of any exposed function by overriding
|
|
1124
|
+
* (setting) a new function implementation on the returned instance.
|
|
1125
|
+
*
|
|
1126
|
+
* @hidden This is not yet part of the public API; for internal use by generated
|
|
1127
|
+
* `JsModel` implementations.
|
|
1128
|
+
*/
|
|
1195
1129
|
function getJsModelFunctions() {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
}
|
|
1390
|
-
return result * sortOrder;
|
|
1391
|
-
});
|
|
1392
|
-
for (let i = 0; i < size; i++) {
|
|
1393
|
-
outArray[i] = sortVector[i].ind;
|
|
1394
|
-
}
|
|
1395
|
-
return outArray;
|
|
1396
|
-
},
|
|
1397
|
-
XIDZ(a, b, x) {
|
|
1398
|
-
return Math.abs(b) < EPSILON ? x : a / b;
|
|
1399
|
-
},
|
|
1400
|
-
ZIDZ(a, b) {
|
|
1401
|
-
if (Math.abs(b) < EPSILON) {
|
|
1402
|
-
return 0;
|
|
1403
|
-
} else {
|
|
1404
|
-
return a / b;
|
|
1405
|
-
}
|
|
1406
|
-
},
|
|
1407
|
-
//
|
|
1408
|
-
// Lookup functions
|
|
1409
|
-
//
|
|
1410
|
-
createLookup(size, data) {
|
|
1411
|
-
return new JsModelLookup(size, data);
|
|
1412
|
-
},
|
|
1413
|
-
LOOKUP(lookup, x) {
|
|
1414
|
-
return lookup ? lookup.getValueForX(x, "interpolate") : _NA_;
|
|
1415
|
-
},
|
|
1416
|
-
LOOKUP_FORWARD(lookup, x) {
|
|
1417
|
-
return lookup ? lookup.getValueForX(x, "forward") : _NA_;
|
|
1418
|
-
},
|
|
1419
|
-
LOOKUP_BACKWARD(lookup, x) {
|
|
1420
|
-
return lookup ? lookup.getValueForX(x, "backward") : _NA_;
|
|
1421
|
-
},
|
|
1422
|
-
LOOKUP_INVERT(lookup, y) {
|
|
1423
|
-
return lookup ? lookup.getValueForY(y) : _NA_;
|
|
1424
|
-
},
|
|
1425
|
-
WITH_LOOKUP(x, lookup) {
|
|
1426
|
-
return lookup ? lookup.getValueForX(x, "interpolate") : _NA_;
|
|
1427
|
-
},
|
|
1428
|
-
GET_DATA_BETWEEN_TIMES(lookup, x, mode) {
|
|
1429
|
-
let lookupMode;
|
|
1430
|
-
if (mode >= 1) {
|
|
1431
|
-
lookupMode = "forward";
|
|
1432
|
-
} else if (mode <= -1) {
|
|
1433
|
-
lookupMode = "backward";
|
|
1434
|
-
} else {
|
|
1435
|
-
lookupMode = "interpolate";
|
|
1436
|
-
}
|
|
1437
|
-
return lookup ? lookup.getValueBetweenTimes(x, lookupMode) : _NA_;
|
|
1438
|
-
}
|
|
1439
|
-
};
|
|
1130
|
+
let ctx;
|
|
1131
|
+
const cachedVectors = /* @__PURE__ */ new Map();
|
|
1132
|
+
const cachedSortVectors = /* @__PURE__ */ new Map();
|
|
1133
|
+
const cachedInvertWorkMatrices = /* @__PURE__ */ new Map();
|
|
1134
|
+
const cachedInvertResultMatrices = /* @__PURE__ */ new Map();
|
|
1135
|
+
return {
|
|
1136
|
+
setContext(context) {
|
|
1137
|
+
ctx = context;
|
|
1138
|
+
},
|
|
1139
|
+
ABS(x) {
|
|
1140
|
+
return Math.abs(x);
|
|
1141
|
+
},
|
|
1142
|
+
ARCCOS(x) {
|
|
1143
|
+
return Math.acos(x);
|
|
1144
|
+
},
|
|
1145
|
+
ARCSIN(x) {
|
|
1146
|
+
return Math.asin(x);
|
|
1147
|
+
},
|
|
1148
|
+
ARCTAN(x) {
|
|
1149
|
+
return Math.atan(x);
|
|
1150
|
+
},
|
|
1151
|
+
COS(x) {
|
|
1152
|
+
return Math.cos(x);
|
|
1153
|
+
},
|
|
1154
|
+
EXP(x) {
|
|
1155
|
+
return Math.exp(x);
|
|
1156
|
+
},
|
|
1157
|
+
GAME(inputs, x) {
|
|
1158
|
+
return inputs ? inputs.getValueForGameTime(ctx.currentTime, x) : x;
|
|
1159
|
+
},
|
|
1160
|
+
INTEG(value, rate) {
|
|
1161
|
+
return value + rate * ctx.timeStep;
|
|
1162
|
+
},
|
|
1163
|
+
INTEGER(x) {
|
|
1164
|
+
return Math.trunc(x);
|
|
1165
|
+
},
|
|
1166
|
+
INVERT_MATRIX(matrix, n) {
|
|
1167
|
+
let work = cachedInvertWorkMatrices.get(n);
|
|
1168
|
+
if (work === void 0) {
|
|
1169
|
+
work = Array(n);
|
|
1170
|
+
for (let i = 0; i < n; i++) work[i] = Array(2 * n);
|
|
1171
|
+
cachedInvertWorkMatrices.set(n, work);
|
|
1172
|
+
}
|
|
1173
|
+
let result = cachedInvertResultMatrices.get(n);
|
|
1174
|
+
if (result === void 0) {
|
|
1175
|
+
result = Array(n);
|
|
1176
|
+
for (let i = 0; i < n; i++) result[i] = Array(n);
|
|
1177
|
+
cachedInvertResultMatrices.set(n, result);
|
|
1178
|
+
}
|
|
1179
|
+
for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) {
|
|
1180
|
+
work[i][j] = matrix[i][j];
|
|
1181
|
+
work[i][n + j] = i === j ? 1 : 0;
|
|
1182
|
+
}
|
|
1183
|
+
for (let k = 0; k < n; k++) {
|
|
1184
|
+
let pivot = k;
|
|
1185
|
+
let max = Math.abs(work[k][k]);
|
|
1186
|
+
for (let i = k + 1; i < n; i++) {
|
|
1187
|
+
const v = Math.abs(work[i][k]);
|
|
1188
|
+
if (v > max) {
|
|
1189
|
+
max = v;
|
|
1190
|
+
pivot = i;
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
if (max === 0) {
|
|
1194
|
+
for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) result[i][j] = _NA_;
|
|
1195
|
+
return result;
|
|
1196
|
+
}
|
|
1197
|
+
if (pivot !== k) {
|
|
1198
|
+
const tmpRow = work[k];
|
|
1199
|
+
work[k] = work[pivot];
|
|
1200
|
+
work[pivot] = tmpRow;
|
|
1201
|
+
}
|
|
1202
|
+
const p = work[k][k];
|
|
1203
|
+
for (let j = 0; j < 2 * n; j++) work[k][j] /= p;
|
|
1204
|
+
for (let i = 0; i < n; i++) if (i !== k) {
|
|
1205
|
+
const f = work[i][k];
|
|
1206
|
+
if (f !== 0) for (let j = 0; j < 2 * n; j++) work[i][j] -= f * work[k][j];
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
for (let i = 0; i < n; i++) for (let j = 0; j < n; j++) result[i][j] = work[i][n + j];
|
|
1210
|
+
return result;
|
|
1211
|
+
},
|
|
1212
|
+
LN(x) {
|
|
1213
|
+
return Math.log(x);
|
|
1214
|
+
},
|
|
1215
|
+
MAX(x, y) {
|
|
1216
|
+
return Math.max(x, y);
|
|
1217
|
+
},
|
|
1218
|
+
MIN(x, y) {
|
|
1219
|
+
return Math.min(x, y);
|
|
1220
|
+
},
|
|
1221
|
+
MODULO(x, y) {
|
|
1222
|
+
return x % y;
|
|
1223
|
+
},
|
|
1224
|
+
POW(x, y) {
|
|
1225
|
+
return Math.pow(x, y);
|
|
1226
|
+
},
|
|
1227
|
+
POWER(x, y) {
|
|
1228
|
+
return Math.pow(x, y);
|
|
1229
|
+
},
|
|
1230
|
+
PULSE(start, width) {
|
|
1231
|
+
return pulse(ctx, start, width);
|
|
1232
|
+
},
|
|
1233
|
+
PULSE_TRAIN(start, width, interval, end) {
|
|
1234
|
+
const n = Math.floor((end - start) / interval);
|
|
1235
|
+
for (let k = 0; k <= n; k++) if (ctx.currentTime <= end && pulse(ctx, start + k * interval, width)) return 1;
|
|
1236
|
+
return 0;
|
|
1237
|
+
},
|
|
1238
|
+
QUANTUM(x, y) {
|
|
1239
|
+
return y <= 0 ? x : y * Math.trunc(x / y);
|
|
1240
|
+
},
|
|
1241
|
+
RAMP(slope, startTime, endTime) {
|
|
1242
|
+
if (ctx.currentTime > startTime) {
|
|
1243
|
+
if (ctx.currentTime < endTime || startTime > endTime) return slope * (ctx.currentTime - startTime);
|
|
1244
|
+
else return slope * (endTime - startTime);
|
|
1245
|
+
} else return 0;
|
|
1246
|
+
},
|
|
1247
|
+
SIN(x) {
|
|
1248
|
+
return Math.sin(x);
|
|
1249
|
+
},
|
|
1250
|
+
SQRT(x) {
|
|
1251
|
+
return Math.sqrt(x);
|
|
1252
|
+
},
|
|
1253
|
+
STEP(height, stepTime) {
|
|
1254
|
+
return ctx.currentTime + ctx.timeStep / 2 > stepTime ? height : 0;
|
|
1255
|
+
},
|
|
1256
|
+
TAN(x) {
|
|
1257
|
+
return Math.tan(x);
|
|
1258
|
+
},
|
|
1259
|
+
VECTOR_SORT_ORDER(vector, size, direction) {
|
|
1260
|
+
if (size > vector.length) throw new Error(`VECTOR SORT ORDER input vector length (${vector.length}) must be >= size (${size})`);
|
|
1261
|
+
let sortVector = cachedSortVectors.get(size);
|
|
1262
|
+
if (sortVector === void 0) {
|
|
1263
|
+
sortVector = Array(size);
|
|
1264
|
+
for (let i = 0; i < size; i++) sortVector[i] = {
|
|
1265
|
+
x: 0,
|
|
1266
|
+
ind: 0
|
|
1267
|
+
};
|
|
1268
|
+
cachedSortVectors.set(size, sortVector);
|
|
1269
|
+
}
|
|
1270
|
+
let outArray = cachedVectors.get(size);
|
|
1271
|
+
if (outArray === void 0) {
|
|
1272
|
+
outArray = Array(size);
|
|
1273
|
+
cachedVectors.set(size, outArray);
|
|
1274
|
+
}
|
|
1275
|
+
for (let i = 0; i < size; i++) {
|
|
1276
|
+
sortVector[i].x = vector[i];
|
|
1277
|
+
sortVector[i].ind = i;
|
|
1278
|
+
}
|
|
1279
|
+
const sortOrder = direction > 0 ? 1 : -1;
|
|
1280
|
+
sortVector.sort((a, b) => {
|
|
1281
|
+
let result;
|
|
1282
|
+
if (a.x < b.x) result = -1;
|
|
1283
|
+
else if (a.x > b.x) result = 1;
|
|
1284
|
+
else result = 0;
|
|
1285
|
+
return result * sortOrder;
|
|
1286
|
+
});
|
|
1287
|
+
for (let i = 0; i < size; i++) outArray[i] = sortVector[i].ind;
|
|
1288
|
+
return outArray;
|
|
1289
|
+
},
|
|
1290
|
+
XIDZ(a, b, x) {
|
|
1291
|
+
return Math.abs(b) < EPSILON ? x : a / b;
|
|
1292
|
+
},
|
|
1293
|
+
ZIDZ(a, b) {
|
|
1294
|
+
if (Math.abs(b) < EPSILON) return 0;
|
|
1295
|
+
else return a / b;
|
|
1296
|
+
},
|
|
1297
|
+
createLookup(size, data) {
|
|
1298
|
+
return new JsModelLookup(size, data);
|
|
1299
|
+
},
|
|
1300
|
+
LOOKUP(lookup, x) {
|
|
1301
|
+
return lookup ? lookup.getValueForX(x, "interpolate") : _NA_;
|
|
1302
|
+
},
|
|
1303
|
+
LOOKUP_FORWARD(lookup, x) {
|
|
1304
|
+
return lookup ? lookup.getValueForX(x, "forward") : _NA_;
|
|
1305
|
+
},
|
|
1306
|
+
LOOKUP_BACKWARD(lookup, x) {
|
|
1307
|
+
return lookup ? lookup.getValueForX(x, "backward") : _NA_;
|
|
1308
|
+
},
|
|
1309
|
+
LOOKUP_INVERT(lookup, y) {
|
|
1310
|
+
return lookup ? lookup.getValueForY(y) : _NA_;
|
|
1311
|
+
},
|
|
1312
|
+
WITH_LOOKUP(x, lookup) {
|
|
1313
|
+
return lookup ? lookup.getValueForX(x, "interpolate") : _NA_;
|
|
1314
|
+
},
|
|
1315
|
+
GET_DATA_BETWEEN_TIMES(lookup, x, mode) {
|
|
1316
|
+
let lookupMode;
|
|
1317
|
+
if (mode >= 1) lookupMode = "forward";
|
|
1318
|
+
else if (mode <= -1) lookupMode = "backward";
|
|
1319
|
+
else lookupMode = "interpolate";
|
|
1320
|
+
return lookup ? lookup.getValueBetweenTimes(x, lookupMode) : _NA_;
|
|
1321
|
+
}
|
|
1322
|
+
};
|
|
1440
1323
|
}
|
|
1441
1324
|
function pulse(ctx, start, width) {
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
}
|
|
1446
|
-
return timePlus > start && timePlus < start + width ? 1 : 0;
|
|
1325
|
+
const timePlus = ctx.currentTime + ctx.timeStep / 2;
|
|
1326
|
+
if (width === 0) width = ctx.timeStep;
|
|
1327
|
+
return timePlus > start && timePlus < start + width ? 1 : 0;
|
|
1447
1328
|
}
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1329
|
+
//#endregion
|
|
1330
|
+
//#region src/perf/perf.ts
|
|
1331
|
+
let isWeb;
|
|
1332
|
+
/**
|
|
1333
|
+
* Return a timestamp that can be passed to `perfElapsed` for calculating the elapsed
|
|
1334
|
+
* time of an operation.
|
|
1335
|
+
*
|
|
1336
|
+
* @hidden This is not part of the public API; exposed only for use in performance testing.
|
|
1337
|
+
*/
|
|
1451
1338
|
function perfNow() {
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
if (isWeb) {
|
|
1456
|
-
return self.performance.now();
|
|
1457
|
-
} else {
|
|
1458
|
-
return process == null ? void 0 : process.hrtime();
|
|
1459
|
-
}
|
|
1339
|
+
if (isWeb === void 0) isWeb = typeof self !== "undefined" && self?.performance !== void 0;
|
|
1340
|
+
if (isWeb) return self.performance.now();
|
|
1341
|
+
else return process?.hrtime();
|
|
1460
1342
|
}
|
|
1343
|
+
/**
|
|
1344
|
+
* Return the elapsed time between the given timestamp (created by `perfNow`) and now.
|
|
1345
|
+
*
|
|
1346
|
+
* @hidden This is not part of the public API; exposed only for use in performance testing.
|
|
1347
|
+
*/
|
|
1461
1348
|
function perfElapsed(t0) {
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
return (elapsed[0] * 1e9 + elapsed[1]) / 1e6;
|
|
1468
|
-
}
|
|
1349
|
+
if (isWeb) return self.performance.now() - t0;
|
|
1350
|
+
else {
|
|
1351
|
+
const elapsed = process.hrtime(t0);
|
|
1352
|
+
return (elapsed[0] * 1e9 + elapsed[1]) / 1e6;
|
|
1353
|
+
}
|
|
1469
1354
|
}
|
|
1470
|
-
|
|
1471
|
-
|
|
1355
|
+
//#endregion
|
|
1356
|
+
//#region src/runnable-model/base-runnable-model.ts
|
|
1357
|
+
/**
|
|
1358
|
+
* A base implementation of the `RunnableModel` interface that takes care
|
|
1359
|
+
* of managing internal buffers and copying values to/from the `RunModelParams`.
|
|
1360
|
+
*
|
|
1361
|
+
* The `onRunModel` function allows an implementation of the core model run loop
|
|
1362
|
+
* to deal with typed arrays only.
|
|
1363
|
+
*
|
|
1364
|
+
* @hidden This is not part of the public API; for internal use only.
|
|
1365
|
+
*/
|
|
1472
1366
|
var BaseRunnableModel = class {
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
params.storeOutputs(outputsArray);
|
|
1514
|
-
params.storeElapsedTime(elapsed);
|
|
1515
|
-
}
|
|
1516
|
-
// from RunnableModel interface
|
|
1517
|
-
terminate() {
|
|
1518
|
-
}
|
|
1367
|
+
constructor(options) {
|
|
1368
|
+
this.startTime = options.startTime;
|
|
1369
|
+
this.endTime = options.endTime;
|
|
1370
|
+
this.saveFreq = options.saveFreq;
|
|
1371
|
+
this.numSavePoints = options.numSavePoints;
|
|
1372
|
+
this.outputVarIds = options.outputVarIds;
|
|
1373
|
+
this.modelListing = options.modelListing;
|
|
1374
|
+
this.onRunModel = options.onRunModel;
|
|
1375
|
+
}
|
|
1376
|
+
runModel(params) {
|
|
1377
|
+
let inputsArray = params.getInputs();
|
|
1378
|
+
if (inputsArray === void 0) {
|
|
1379
|
+
params.copyInputs(this.inputs, (numElements) => {
|
|
1380
|
+
this.inputs = new Float64Array(numElements);
|
|
1381
|
+
return this.inputs;
|
|
1382
|
+
});
|
|
1383
|
+
inputsArray = this.inputs;
|
|
1384
|
+
}
|
|
1385
|
+
let outputIndicesArray = params.getOutputIndices();
|
|
1386
|
+
if (outputIndicesArray === void 0 && params.getOutputIndicesLength() > 0) {
|
|
1387
|
+
params.copyOutputIndices(this.outputIndices, (numElements) => {
|
|
1388
|
+
this.outputIndices = new Int32Array(numElements);
|
|
1389
|
+
return this.outputIndices;
|
|
1390
|
+
});
|
|
1391
|
+
outputIndicesArray = this.outputIndices;
|
|
1392
|
+
}
|
|
1393
|
+
const outputsLengthInElements = params.getOutputsLength();
|
|
1394
|
+
if (this.outputs === void 0 || this.outputs.length < outputsLengthInElements) this.outputs = new Float64Array(outputsLengthInElements);
|
|
1395
|
+
const outputsArray = this.outputs;
|
|
1396
|
+
const t0 = perfNow();
|
|
1397
|
+
this.onRunModel?.(inputsArray, outputsArray, {
|
|
1398
|
+
outputIndices: outputIndicesArray,
|
|
1399
|
+
constants: params.getConstants(),
|
|
1400
|
+
lookups: params.getLookups()
|
|
1401
|
+
});
|
|
1402
|
+
const elapsed = perfElapsed(t0);
|
|
1403
|
+
params.storeOutputs(outputsArray);
|
|
1404
|
+
params.storeElapsedTime(elapsed);
|
|
1405
|
+
}
|
|
1406
|
+
terminate() {}
|
|
1519
1407
|
};
|
|
1520
|
-
|
|
1521
|
-
|
|
1408
|
+
//#endregion
|
|
1409
|
+
//#region src/js-model/js-model.ts
|
|
1410
|
+
/**
|
|
1411
|
+
* Create a `RunnableModel` from a given `JsModel` that was generated by the
|
|
1412
|
+
* SDEverywhere transpiler.
|
|
1413
|
+
*
|
|
1414
|
+
* @hidden This is not part of the public API; only the top-level `createRunnableModel`
|
|
1415
|
+
* function is exposed in the public API.
|
|
1416
|
+
*/
|
|
1522
1417
|
function initJsModel(model) {
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
finalTime,
|
|
1545
|
-
timeStep,
|
|
1546
|
-
saveFreq,
|
|
1547
|
-
numSavePoints,
|
|
1548
|
-
inputs,
|
|
1549
|
-
outputs,
|
|
1550
|
-
options == null ? void 0 : options.outputIndices,
|
|
1551
|
-
options == null ? void 0 : options.constants,
|
|
1552
|
-
options == null ? void 0 : options.lookups,
|
|
1553
|
-
void 0
|
|
1554
|
-
);
|
|
1555
|
-
}
|
|
1556
|
-
});
|
|
1418
|
+
let fns = model.getModelFunctions();
|
|
1419
|
+
if (fns === void 0) {
|
|
1420
|
+
fns = getJsModelFunctions();
|
|
1421
|
+
model.setModelFunctions(fns);
|
|
1422
|
+
}
|
|
1423
|
+
const initialTime = model.getInitialTime();
|
|
1424
|
+
const finalTime = model.getFinalTime();
|
|
1425
|
+
const timeStep = model.getTimeStep();
|
|
1426
|
+
const saveFreq = model.getSaveFreq();
|
|
1427
|
+
const numSavePoints = Math.round((finalTime - initialTime) / saveFreq) + 1;
|
|
1428
|
+
return new BaseRunnableModel({
|
|
1429
|
+
startTime: initialTime,
|
|
1430
|
+
endTime: finalTime,
|
|
1431
|
+
saveFreq,
|
|
1432
|
+
numSavePoints,
|
|
1433
|
+
outputVarIds: model.outputVarIds,
|
|
1434
|
+
modelListing: model.modelListing,
|
|
1435
|
+
onRunModel: (inputs, outputs, options) => {
|
|
1436
|
+
runJsModel(model, initialTime, finalTime, timeStep, saveFreq, numSavePoints, inputs, outputs, options?.outputIndices, options?.constants, options?.lookups, void 0);
|
|
1437
|
+
}
|
|
1438
|
+
});
|
|
1557
1439
|
}
|
|
1558
1440
|
function runJsModel(model, initialTime, finalTime, timeStep, saveFreq, numSavePoints, inputs, outputs, outputIndices, constants, lookups, stopAfterTime) {
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
} else {
|
|
1613
|
-
model.storeOutputs(storeValue);
|
|
1614
|
-
}
|
|
1615
|
-
savePointIndex++;
|
|
1616
|
-
}
|
|
1617
|
-
if (step === lastStep) {
|
|
1618
|
-
break;
|
|
1619
|
-
}
|
|
1620
|
-
model.evalLevels();
|
|
1621
|
-
time += timeStep;
|
|
1622
|
-
model.setTime(time);
|
|
1623
|
-
fnContext.currentTime = time;
|
|
1624
|
-
step++;
|
|
1625
|
-
}
|
|
1441
|
+
let time = initialTime;
|
|
1442
|
+
model.setTime(time);
|
|
1443
|
+
const fnContext = {
|
|
1444
|
+
timeStep,
|
|
1445
|
+
currentTime: time
|
|
1446
|
+
};
|
|
1447
|
+
model.getModelFunctions().setContext(fnContext);
|
|
1448
|
+
model.initConstants();
|
|
1449
|
+
if (constants !== void 0) for (const constantDef of constants) model.setConstant(constantDef.varRef.varSpec, constantDef.value);
|
|
1450
|
+
if (lookups !== void 0) for (const lookupDef of lookups) model.setLookup(lookupDef.varRef.varSpec, lookupDef.points);
|
|
1451
|
+
if (inputs?.length > 0) model.setInputs((index) => inputs[index]);
|
|
1452
|
+
model.initLevels();
|
|
1453
|
+
const lastStep = Math.round((finalTime - initialTime) / timeStep);
|
|
1454
|
+
const stopTime = stopAfterTime !== void 0 ? stopAfterTime : finalTime;
|
|
1455
|
+
let step = 0;
|
|
1456
|
+
let savePointIndex = 0;
|
|
1457
|
+
let outputVarIndex = 0;
|
|
1458
|
+
while (step <= lastStep) {
|
|
1459
|
+
model.evalAux();
|
|
1460
|
+
if (time % saveFreq < 1e-6) {
|
|
1461
|
+
outputVarIndex = 0;
|
|
1462
|
+
const storeValue = (value) => {
|
|
1463
|
+
const outputBufferIndex = outputVarIndex * numSavePoints + savePointIndex;
|
|
1464
|
+
outputs[outputBufferIndex] = time <= stopTime ? value : void 0;
|
|
1465
|
+
outputVarIndex++;
|
|
1466
|
+
};
|
|
1467
|
+
if (outputIndices !== void 0) {
|
|
1468
|
+
let indexBufferOffset = 0;
|
|
1469
|
+
const outputCount = outputIndices[indexBufferOffset++];
|
|
1470
|
+
for (let i = 0; i < outputCount; i++) {
|
|
1471
|
+
const varIndex = outputIndices[indexBufferOffset++];
|
|
1472
|
+
const subCount = outputIndices[indexBufferOffset++];
|
|
1473
|
+
let subscriptIndices;
|
|
1474
|
+
if (subCount > 0) {
|
|
1475
|
+
subscriptIndices = outputIndices.subarray(indexBufferOffset, indexBufferOffset + subCount);
|
|
1476
|
+
indexBufferOffset += subCount;
|
|
1477
|
+
}
|
|
1478
|
+
const varSpec = {
|
|
1479
|
+
varIndex,
|
|
1480
|
+
subscriptIndices
|
|
1481
|
+
};
|
|
1482
|
+
model.storeOutput(varSpec, storeValue);
|
|
1483
|
+
}
|
|
1484
|
+
} else model.storeOutputs(storeValue);
|
|
1485
|
+
savePointIndex++;
|
|
1486
|
+
}
|
|
1487
|
+
if (step === lastStep) break;
|
|
1488
|
+
model.evalLevels();
|
|
1489
|
+
time += timeStep;
|
|
1490
|
+
model.setTime(time);
|
|
1491
|
+
fnContext.currentTime = time;
|
|
1492
|
+
step++;
|
|
1493
|
+
}
|
|
1626
1494
|
}
|
|
1627
|
-
|
|
1628
|
-
|
|
1495
|
+
//#endregion
|
|
1496
|
+
//#region src/js-model/exec-js-model.ts
|
|
1497
|
+
/**
|
|
1498
|
+
* Run the given model synchronously and log the output values to the console in
|
|
1499
|
+
* TSV (tab-separated values) format.
|
|
1500
|
+
*
|
|
1501
|
+
* @hidden This is mainly intended for use in implementing the `sde exec` command,
|
|
1502
|
+
* so isn't exposed in the public API at this time.
|
|
1503
|
+
*
|
|
1504
|
+
* @param jsModel A `JsModel` instance.
|
|
1505
|
+
*/
|
|
1629
1506
|
function execJsModel(jsModel) {
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
const rowValues = [];
|
|
1645
|
-
for (const series of outputs.varSeries) {
|
|
1646
|
-
rowValues.push(series.points[i].y);
|
|
1647
|
-
}
|
|
1648
|
-
console.log(rowValues.join(" "));
|
|
1649
|
-
}
|
|
1507
|
+
const runnableModel = initJsModel(jsModel);
|
|
1508
|
+
const inputs = [];
|
|
1509
|
+
const outputVarIds = jsModel.outputVarIds;
|
|
1510
|
+
const outputs = new Outputs(outputVarIds, jsModel.getInitialTime(), jsModel.getFinalTime(), jsModel.getSaveFreq());
|
|
1511
|
+
const params = new ReferencedRunModelParams();
|
|
1512
|
+
params.updateFromParams(inputs, outputs);
|
|
1513
|
+
runnableModel.runModel(params);
|
|
1514
|
+
const header = jsModel.outputVarNames.map((name) => name.replace(/"/g, "\\\"")).join(" ");
|
|
1515
|
+
console.log(header);
|
|
1516
|
+
for (let i = 0; i < outputs.seriesLength; i++) {
|
|
1517
|
+
const rowValues = [];
|
|
1518
|
+
for (const series of outputs.varSeries) rowValues.push(series.points[i].y);
|
|
1519
|
+
console.log(rowValues.join(" "));
|
|
1520
|
+
}
|
|
1650
1521
|
}
|
|
1651
|
-
|
|
1652
|
-
|
|
1522
|
+
//#endregion
|
|
1523
|
+
//#region src/js-model/_mocks/mock-js-model.ts
|
|
1524
|
+
/**
|
|
1525
|
+
* @hidden This type is not part of the public API; it is exposed only for use in
|
|
1526
|
+
* tests in the runtime-async package.
|
|
1527
|
+
*/
|
|
1653
1528
|
var MockJsModel = class {
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
throw new Error(`No lookup variable found for spec ${varSpec}`);
|
|
1723
|
-
}
|
|
1724
|
-
const numPoints = points ? points.length / 2 : 0;
|
|
1725
|
-
this.lookups.set(varId, new JsModelLookup(numPoints, points));
|
|
1726
|
-
}
|
|
1727
|
-
// from JsModel interface
|
|
1728
|
-
storeOutputs(storeValue) {
|
|
1729
|
-
for (const varId of this.outputVarIds) {
|
|
1730
|
-
storeValue(this.vars.get(varId));
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
// from JsModel interface
|
|
1734
|
-
storeOutput(varSpec, storeValue) {
|
|
1735
|
-
const varId = this.varIdForSpec(varSpec);
|
|
1736
|
-
if (varId === void 0) {
|
|
1737
|
-
throw new Error(`No output variable found for spec ${varSpec}`);
|
|
1738
|
-
}
|
|
1739
|
-
storeValue(this.vars.get(varId));
|
|
1740
|
-
}
|
|
1741
|
-
// from JsModel interface
|
|
1742
|
-
initConstants() {
|
|
1743
|
-
this.constants.clear();
|
|
1744
|
-
}
|
|
1745
|
-
// from JsModel interface
|
|
1746
|
-
initLevels() {
|
|
1747
|
-
}
|
|
1748
|
-
// from JsModel interface
|
|
1749
|
-
evalAux() {
|
|
1750
|
-
var _a;
|
|
1751
|
-
(_a = this.onEvalAux) == null ? void 0 : _a.call(this, this.vars, this.constants.size > 0 ? this.constants : void 0, this.lookups);
|
|
1752
|
-
}
|
|
1753
|
-
// from JsModel interface
|
|
1754
|
-
evalLevels() {
|
|
1755
|
-
}
|
|
1529
|
+
constructor(options) {
|
|
1530
|
+
this.kind = "js";
|
|
1531
|
+
this.vars = /* @__PURE__ */ new Map();
|
|
1532
|
+
this.constants = /* @__PURE__ */ new Map();
|
|
1533
|
+
this.lookups = /* @__PURE__ */ new Map();
|
|
1534
|
+
this.outputVarIds = options.outputVarIds;
|
|
1535
|
+
this.outputVarNames = options.outputVarIds;
|
|
1536
|
+
this.initialTime = options.initialTime;
|
|
1537
|
+
this.finalTime = options.finalTime;
|
|
1538
|
+
this.outputVarIds = options.outputVarIds;
|
|
1539
|
+
if (options.listingJson) {
|
|
1540
|
+
this.modelListing = JSON.parse(options.listingJson);
|
|
1541
|
+
this.internalListing = new ModelListing(this.modelListing);
|
|
1542
|
+
}
|
|
1543
|
+
this.onEvalAux = options.onEvalAux;
|
|
1544
|
+
}
|
|
1545
|
+
varIdForSpec(varSpec) {
|
|
1546
|
+
for (const [listingVarId, listingSpec] of this.internalListing.varSpecs) if (listingSpec.varIndex === varSpec.varIndex) return listingVarId;
|
|
1547
|
+
}
|
|
1548
|
+
getInitialTime() {
|
|
1549
|
+
return this.initialTime;
|
|
1550
|
+
}
|
|
1551
|
+
getFinalTime() {
|
|
1552
|
+
return this.finalTime;
|
|
1553
|
+
}
|
|
1554
|
+
getTimeStep() {
|
|
1555
|
+
return 1;
|
|
1556
|
+
}
|
|
1557
|
+
getSaveFreq() {
|
|
1558
|
+
return 1;
|
|
1559
|
+
}
|
|
1560
|
+
getModelFunctions() {
|
|
1561
|
+
return this.fns;
|
|
1562
|
+
}
|
|
1563
|
+
setModelFunctions(fns) {
|
|
1564
|
+
this.fns = fns;
|
|
1565
|
+
}
|
|
1566
|
+
setTime(time) {
|
|
1567
|
+
this.vars.set("_time", time);
|
|
1568
|
+
}
|
|
1569
|
+
setInputs() {}
|
|
1570
|
+
setConstant(varSpec, value) {
|
|
1571
|
+
const varId = this.varIdForSpec(varSpec);
|
|
1572
|
+
if (varId === void 0) throw new Error(`No constant variable found for spec ${varSpec}`);
|
|
1573
|
+
this.constants.set(varId, value);
|
|
1574
|
+
}
|
|
1575
|
+
setLookup(varSpec, points) {
|
|
1576
|
+
const varId = this.varIdForSpec(varSpec);
|
|
1577
|
+
if (varId === void 0) throw new Error(`No lookup variable found for spec ${varSpec}`);
|
|
1578
|
+
const numPoints = points ? points.length / 2 : 0;
|
|
1579
|
+
this.lookups.set(varId, new JsModelLookup(numPoints, points));
|
|
1580
|
+
}
|
|
1581
|
+
storeOutputs(storeValue) {
|
|
1582
|
+
for (const varId of this.outputVarIds) storeValue(this.vars.get(varId));
|
|
1583
|
+
}
|
|
1584
|
+
storeOutput(varSpec, storeValue) {
|
|
1585
|
+
const varId = this.varIdForSpec(varSpec);
|
|
1586
|
+
if (varId === void 0) throw new Error(`No output variable found for spec ${varSpec}`);
|
|
1587
|
+
storeValue(this.vars.get(varId));
|
|
1588
|
+
}
|
|
1589
|
+
initConstants() {
|
|
1590
|
+
this.constants.clear();
|
|
1591
|
+
}
|
|
1592
|
+
initLevels() {}
|
|
1593
|
+
evalAux() {
|
|
1594
|
+
this.onEvalAux?.(this.vars, this.constants.size > 0 ? this.constants : void 0, this.lookups);
|
|
1595
|
+
}
|
|
1596
|
+
evalLevels() {}
|
|
1756
1597
|
};
|
|
1757
|
-
|
|
1758
|
-
|
|
1598
|
+
//#endregion
|
|
1599
|
+
//#region src/wasm-model/wasm-buffer.ts
|
|
1600
|
+
/**
|
|
1601
|
+
* Wraps a `WebAssembly.Memory` buffer allocated on the wasm heap.
|
|
1602
|
+
*
|
|
1603
|
+
* When this is used synchronously (in the browser's normal JavaScript thread),
|
|
1604
|
+
* the client can use `getArrayView` to write directly into the underlying memory.
|
|
1605
|
+
*
|
|
1606
|
+
* Note, however, that `WebAssembly.Memory` buffers cannot be transferred to/from
|
|
1607
|
+
* a Web Worker. When using this class in a worker thread, create a separate
|
|
1608
|
+
* `Float64Array` that can be transferred between the worker and the client running
|
|
1609
|
+
* in the browser's normal JS thread, and then use `getArrayView` to copy into and
|
|
1610
|
+
* out of the wasm buffer.
|
|
1611
|
+
*
|
|
1612
|
+
* @hidden For internal use only.
|
|
1613
|
+
*/
|
|
1759
1614
|
var WasmBuffer = class {
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
}
|
|
1615
|
+
/**
|
|
1616
|
+
* @param wasmModule The `WasmModule` used to initialize the memory.
|
|
1617
|
+
* @param numElements The number of elements in the buffer.
|
|
1618
|
+
* @param byteOffset The byte offset within the wasm heap.
|
|
1619
|
+
* @param heapArray The array view on the underlying heap buffer.
|
|
1620
|
+
*/
|
|
1621
|
+
constructor(wasmModule, numElements, byteOffset, heapArray) {
|
|
1622
|
+
this.wasmModule = wasmModule;
|
|
1623
|
+
this.numElements = numElements;
|
|
1624
|
+
this.byteOffset = byteOffset;
|
|
1625
|
+
this.heapArray = heapArray;
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* @return An `ArrType` view on the underlying heap buffer.
|
|
1629
|
+
*/
|
|
1630
|
+
getArrayView() {
|
|
1631
|
+
return this.heapArray;
|
|
1632
|
+
}
|
|
1633
|
+
/**
|
|
1634
|
+
* @return The raw address of the underlying heap buffer.
|
|
1635
|
+
* @hidden This is intended for use by `WasmModel` only.
|
|
1636
|
+
*/
|
|
1637
|
+
getAddress() {
|
|
1638
|
+
return this.byteOffset;
|
|
1639
|
+
}
|
|
1640
|
+
/**
|
|
1641
|
+
* Dispose the buffer by freeing the allocated heap memory.
|
|
1642
|
+
*/
|
|
1643
|
+
dispose() {
|
|
1644
|
+
if (this.heapArray) {
|
|
1645
|
+
this.wasmModule._free?.(this.byteOffset);
|
|
1646
|
+
this.numElements = void 0;
|
|
1647
|
+
this.heapArray = void 0;
|
|
1648
|
+
this.byteOffset = void 0;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1797
1651
|
};
|
|
1652
|
+
/**
|
|
1653
|
+
* Return a `WasmBuffer` that holds int32 elements.
|
|
1654
|
+
*
|
|
1655
|
+
* @hidden For internal use only.
|
|
1656
|
+
*
|
|
1657
|
+
* @param wasmModule The `WasmModule` used to initialize the memory.
|
|
1658
|
+
* @param numElements The number of elements in the buffer.
|
|
1659
|
+
*/
|
|
1798
1660
|
function createInt32WasmBuffer(wasmModule, numElements) {
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
return new WasmBuffer(wasmModule, numElements, byteOffset, heapArray);
|
|
1661
|
+
const elemSizeInBytes = 4;
|
|
1662
|
+
const lengthInBytes = numElements * elemSizeInBytes;
|
|
1663
|
+
const byteOffset = wasmModule._malloc(lengthInBytes);
|
|
1664
|
+
const elemOffset = byteOffset / elemSizeInBytes;
|
|
1665
|
+
return new WasmBuffer(wasmModule, numElements, byteOffset, wasmModule.HEAP32.subarray(elemOffset, elemOffset + numElements));
|
|
1805
1666
|
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Return a `WasmBuffer` that holds float64 elements.
|
|
1669
|
+
*
|
|
1670
|
+
* @hidden For internal use only.
|
|
1671
|
+
*
|
|
1672
|
+
* @param wasmModule The `WasmModule` used to initialize the memory.
|
|
1673
|
+
* @param numElements The number of elements in the buffer.
|
|
1674
|
+
*/
|
|
1806
1675
|
function createFloat64WasmBuffer(wasmModule, numElements) {
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
return new WasmBuffer(wasmModule, numElements, byteOffset, heapArray);
|
|
1676
|
+
const elemSizeInBytes = 8;
|
|
1677
|
+
const lengthInBytes = numElements * elemSizeInBytes;
|
|
1678
|
+
const byteOffset = wasmModule._malloc(lengthInBytes);
|
|
1679
|
+
const elemOffset = byteOffset / elemSizeInBytes;
|
|
1680
|
+
return new WasmBuffer(wasmModule, numElements, byteOffset, wasmModule.HEAPF64.subarray(elemOffset, elemOffset + numElements));
|
|
1813
1681
|
}
|
|
1814
|
-
|
|
1815
|
-
|
|
1682
|
+
//#endregion
|
|
1683
|
+
//#region src/wasm-model/wasm-model.ts
|
|
1684
|
+
/**
|
|
1685
|
+
* A wrapper around a WebAssembly module generated by the SDEverywhere transpiler,
|
|
1686
|
+
* which allows for running the model with a given set of input values, producing
|
|
1687
|
+
* a set of output values.
|
|
1688
|
+
*/
|
|
1816
1689
|
var WasmModel = class {
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
(constantValuesBuffer == null ? void 0 : constantValuesBuffer.getAddress()) || 0,
|
|
1954
|
-
(constantIndicesBuffer == null ? void 0 : constantIndicesBuffer.getAddress()) || 0
|
|
1955
|
-
);
|
|
1956
|
-
const elapsed = perfElapsed(t0);
|
|
1957
|
-
params.storeOutputs(this.outputsBuffer.getArrayView());
|
|
1958
|
-
params.storeElapsedTime(elapsed);
|
|
1959
|
-
}
|
|
1960
|
-
// from RunnableModel interface
|
|
1961
|
-
terminate() {
|
|
1962
|
-
var _a, _b, _c, _d, _e;
|
|
1963
|
-
(_a = this.inputsBuffer) == null ? void 0 : _a.dispose();
|
|
1964
|
-
this.inputsBuffer = void 0;
|
|
1965
|
-
(_b = this.outputsBuffer) == null ? void 0 : _b.dispose();
|
|
1966
|
-
this.outputsBuffer = void 0;
|
|
1967
|
-
(_c = this.outputIndicesBuffer) == null ? void 0 : _c.dispose();
|
|
1968
|
-
this.outputIndicesBuffer = void 0;
|
|
1969
|
-
(_d = this.constantValuesBuffer) == null ? void 0 : _d.dispose();
|
|
1970
|
-
this.constantValuesBuffer = void 0;
|
|
1971
|
-
(_e = this.constantIndicesBuffer) == null ? void 0 : _e.dispose();
|
|
1972
|
-
this.constantIndicesBuffer = void 0;
|
|
1973
|
-
}
|
|
1690
|
+
/**
|
|
1691
|
+
* @param wasmModule The `WasmModule` that provides access to the native functions.
|
|
1692
|
+
* @param outputVarIds The output variable IDs for this model.
|
|
1693
|
+
*/
|
|
1694
|
+
constructor(wasmModule) {
|
|
1695
|
+
this.wasmModule = wasmModule;
|
|
1696
|
+
function getNumberValue(funcName) {
|
|
1697
|
+
return wasmModule.cwrap(funcName, "number", [])();
|
|
1698
|
+
}
|
|
1699
|
+
this.startTime = getNumberValue("getInitialTime");
|
|
1700
|
+
this.endTime = getNumberValue("getFinalTime");
|
|
1701
|
+
this.saveFreq = getNumberValue("getSaveper");
|
|
1702
|
+
this.numSavePoints = Math.round((this.endTime - this.startTime) / this.saveFreq) + 1;
|
|
1703
|
+
this.outputVarIds = wasmModule.outputVarIds;
|
|
1704
|
+
this.modelListing = wasmModule.modelListing;
|
|
1705
|
+
this.wasmSetLookup = wasmModule.cwrap("setLookup", null, [
|
|
1706
|
+
"number",
|
|
1707
|
+
"number",
|
|
1708
|
+
"number",
|
|
1709
|
+
"number"
|
|
1710
|
+
]);
|
|
1711
|
+
this.wasmRunModel = wasmModule.cwrap("runModelWithBuffers", null, [
|
|
1712
|
+
"number",
|
|
1713
|
+
"number",
|
|
1714
|
+
"number",
|
|
1715
|
+
"number",
|
|
1716
|
+
"number",
|
|
1717
|
+
"number"
|
|
1718
|
+
]);
|
|
1719
|
+
}
|
|
1720
|
+
runModel(params) {
|
|
1721
|
+
const lookups = params.getLookups();
|
|
1722
|
+
if (lookups !== void 0) for (const lookupDef of lookups) {
|
|
1723
|
+
const varSpec = lookupDef.varRef.varSpec;
|
|
1724
|
+
const numSubElements = varSpec.subscriptIndices?.length || 0;
|
|
1725
|
+
let subIndicesAddress;
|
|
1726
|
+
if (numSubElements > 0) {
|
|
1727
|
+
if (this.lookupSubIndicesBuffer === void 0 || this.lookupSubIndicesBuffer.numElements < numSubElements) {
|
|
1728
|
+
this.lookupSubIndicesBuffer?.dispose();
|
|
1729
|
+
this.lookupSubIndicesBuffer = createInt32WasmBuffer(this.wasmModule, numSubElements);
|
|
1730
|
+
}
|
|
1731
|
+
this.lookupSubIndicesBuffer.getArrayView().set(varSpec.subscriptIndices);
|
|
1732
|
+
subIndicesAddress = this.lookupSubIndicesBuffer.getAddress();
|
|
1733
|
+
} else subIndicesAddress = 0;
|
|
1734
|
+
let pointsAddress;
|
|
1735
|
+
let numPoints;
|
|
1736
|
+
if (lookupDef.points) {
|
|
1737
|
+
const numLookupElements = lookupDef.points.length;
|
|
1738
|
+
if (this.lookupDataBuffer === void 0 || this.lookupDataBuffer.numElements < numLookupElements) {
|
|
1739
|
+
this.lookupDataBuffer?.dispose();
|
|
1740
|
+
this.lookupDataBuffer = createFloat64WasmBuffer(this.wasmModule, numLookupElements);
|
|
1741
|
+
}
|
|
1742
|
+
this.lookupDataBuffer.getArrayView().set(lookupDef.points);
|
|
1743
|
+
pointsAddress = this.lookupDataBuffer.getAddress();
|
|
1744
|
+
numPoints = numLookupElements / 2;
|
|
1745
|
+
} else {
|
|
1746
|
+
pointsAddress = 0;
|
|
1747
|
+
numPoints = 0;
|
|
1748
|
+
}
|
|
1749
|
+
const varIndex = varSpec.varIndex;
|
|
1750
|
+
this.wasmSetLookup(varIndex, subIndicesAddress, pointsAddress, numPoints);
|
|
1751
|
+
}
|
|
1752
|
+
let constantIndicesBuffer;
|
|
1753
|
+
let constantValuesBuffer;
|
|
1754
|
+
const constants = params.getConstants();
|
|
1755
|
+
if (constants !== void 0 && constants.length > 0) {
|
|
1756
|
+
let totalIndicesSize = 1;
|
|
1757
|
+
for (const constantDef of constants) {
|
|
1758
|
+
const numSubElements = constantDef.varRef.varSpec.subscriptIndices?.length || 0;
|
|
1759
|
+
totalIndicesSize += 2 + numSubElements;
|
|
1760
|
+
}
|
|
1761
|
+
if (this.constantIndicesBuffer === void 0 || this.constantIndicesBuffer.numElements < totalIndicesSize) {
|
|
1762
|
+
this.constantIndicesBuffer?.dispose();
|
|
1763
|
+
this.constantIndicesBuffer = createInt32WasmBuffer(this.wasmModule, totalIndicesSize);
|
|
1764
|
+
}
|
|
1765
|
+
const numConstants = constants.length;
|
|
1766
|
+
if (this.constantValuesBuffer === void 0 || this.constantValuesBuffer.numElements < numConstants) {
|
|
1767
|
+
this.constantValuesBuffer?.dispose();
|
|
1768
|
+
this.constantValuesBuffer = createFloat64WasmBuffer(this.wasmModule, numConstants);
|
|
1769
|
+
}
|
|
1770
|
+
const indicesView = this.constantIndicesBuffer.getArrayView();
|
|
1771
|
+
const valuesView = this.constantValuesBuffer.getArrayView();
|
|
1772
|
+
let indicesOffset = 0;
|
|
1773
|
+
let valuesOffset = 0;
|
|
1774
|
+
indicesView[indicesOffset++] = numConstants;
|
|
1775
|
+
for (const constantDef of constants) {
|
|
1776
|
+
const varSpec = constantDef.varRef.varSpec;
|
|
1777
|
+
const numSubElements = varSpec.subscriptIndices?.length || 0;
|
|
1778
|
+
indicesView[indicesOffset++] = varSpec.varIndex;
|
|
1779
|
+
indicesView[indicesOffset++] = numSubElements;
|
|
1780
|
+
if (numSubElements > 0) for (let i = 0; i < numSubElements; i++) indicesView[indicesOffset++] = varSpec.subscriptIndices[i];
|
|
1781
|
+
valuesView[valuesOffset++] = constantDef.value;
|
|
1782
|
+
}
|
|
1783
|
+
constantIndicesBuffer = this.constantIndicesBuffer;
|
|
1784
|
+
constantValuesBuffer = this.constantValuesBuffer;
|
|
1785
|
+
} else {
|
|
1786
|
+
constantIndicesBuffer = void 0;
|
|
1787
|
+
constantValuesBuffer = void 0;
|
|
1788
|
+
}
|
|
1789
|
+
params.copyInputs(this.inputsBuffer?.getArrayView(), (numElements) => {
|
|
1790
|
+
this.inputsBuffer?.dispose();
|
|
1791
|
+
this.inputsBuffer = createFloat64WasmBuffer(this.wasmModule, numElements);
|
|
1792
|
+
return this.inputsBuffer.getArrayView();
|
|
1793
|
+
});
|
|
1794
|
+
let outputIndicesBuffer;
|
|
1795
|
+
if (params.getOutputIndicesLength() > 0) {
|
|
1796
|
+
params.copyOutputIndices(this.outputIndicesBuffer?.getArrayView(), (numElements) => {
|
|
1797
|
+
this.outputIndicesBuffer?.dispose();
|
|
1798
|
+
this.outputIndicesBuffer = createInt32WasmBuffer(this.wasmModule, numElements);
|
|
1799
|
+
return this.outputIndicesBuffer.getArrayView();
|
|
1800
|
+
});
|
|
1801
|
+
outputIndicesBuffer = this.outputIndicesBuffer;
|
|
1802
|
+
} else outputIndicesBuffer = void 0;
|
|
1803
|
+
const outputsLengthInElements = params.getOutputsLength();
|
|
1804
|
+
if (this.outputsBuffer === void 0 || this.outputsBuffer.numElements < outputsLengthInElements) {
|
|
1805
|
+
this.outputsBuffer?.dispose();
|
|
1806
|
+
this.outputsBuffer = createFloat64WasmBuffer(this.wasmModule, outputsLengthInElements);
|
|
1807
|
+
}
|
|
1808
|
+
const t0 = perfNow();
|
|
1809
|
+
this.wasmRunModel(this.inputsBuffer?.getAddress() || 0, 0, this.outputsBuffer.getAddress(), outputIndicesBuffer?.getAddress() || 0, constantValuesBuffer?.getAddress() || 0, constantIndicesBuffer?.getAddress() || 0);
|
|
1810
|
+
const elapsed = perfElapsed(t0);
|
|
1811
|
+
params.storeOutputs(this.outputsBuffer.getArrayView());
|
|
1812
|
+
params.storeElapsedTime(elapsed);
|
|
1813
|
+
}
|
|
1814
|
+
terminate() {
|
|
1815
|
+
this.inputsBuffer?.dispose();
|
|
1816
|
+
this.inputsBuffer = void 0;
|
|
1817
|
+
this.outputsBuffer?.dispose();
|
|
1818
|
+
this.outputsBuffer = void 0;
|
|
1819
|
+
this.outputIndicesBuffer?.dispose();
|
|
1820
|
+
this.outputIndicesBuffer = void 0;
|
|
1821
|
+
this.constantValuesBuffer?.dispose();
|
|
1822
|
+
this.constantValuesBuffer = void 0;
|
|
1823
|
+
this.constantIndicesBuffer?.dispose();
|
|
1824
|
+
this.constantIndicesBuffer = void 0;
|
|
1825
|
+
}
|
|
1974
1826
|
};
|
|
1827
|
+
/**
|
|
1828
|
+
* Initialize the wasm model.
|
|
1829
|
+
*
|
|
1830
|
+
* @hidden This is not part of the public API; only the top-level `createRunnableModel`
|
|
1831
|
+
* function is exposed in the public API.
|
|
1832
|
+
*
|
|
1833
|
+
* @param wasmModule The `WasmModule` that wraps the `wasm` binary.
|
|
1834
|
+
* @return The initialized `WasmModel` instance.
|
|
1835
|
+
*/
|
|
1975
1836
|
function initWasmModel(wasmModule) {
|
|
1976
|
-
|
|
1837
|
+
return new WasmModel(wasmModule);
|
|
1977
1838
|
}
|
|
1978
|
-
|
|
1979
|
-
|
|
1839
|
+
//#endregion
|
|
1840
|
+
//#region src/wasm-model/_mocks/mock-wasm-module.ts
|
|
1841
|
+
/**
|
|
1842
|
+
* @hidden This type is not part of the public API; it is exposed only for use in
|
|
1843
|
+
* tests in the runtime-async package.
|
|
1844
|
+
*/
|
|
1980
1845
|
var MockWasmModule = class {
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
1997
|
-
|
|
1998
|
-
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
|
|
2010
|
-
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2024
|
-
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
default:
|
|
2059
|
-
throw new Error(`Unhandled call to cwrap with function name '${fname}'`);
|
|
2060
|
-
}
|
|
2061
|
-
}
|
|
2062
|
-
// from WasmModule interface
|
|
2063
|
-
_malloc(lengthInBytes) {
|
|
2064
|
-
const currentOffset = this.mallocOffset;
|
|
2065
|
-
this.allocs.set(currentOffset, lengthInBytes);
|
|
2066
|
-
if (lengthInBytes > 0) {
|
|
2067
|
-
this.mallocOffset += lengthInBytes;
|
|
2068
|
-
} else {
|
|
2069
|
-
this.mallocOffset += 8;
|
|
2070
|
-
}
|
|
2071
|
-
return currentOffset;
|
|
2072
|
-
}
|
|
2073
|
-
// from WasmModule interface
|
|
2074
|
-
_free() {
|
|
2075
|
-
}
|
|
2076
|
-
getHeapView(kind, address) {
|
|
2077
|
-
if (address === 0) {
|
|
2078
|
-
return void 0;
|
|
2079
|
-
}
|
|
2080
|
-
const lengthInBytes = this.allocs.get(address);
|
|
2081
|
-
if (lengthInBytes === void 0) {
|
|
2082
|
-
throw new Error("Failed to locate heap allocation");
|
|
2083
|
-
}
|
|
2084
|
-
if (kind === "float64") {
|
|
2085
|
-
const offset = address / 8;
|
|
2086
|
-
return this.HEAPF64.subarray(offset, offset + lengthInBytes / 8);
|
|
2087
|
-
} else {
|
|
2088
|
-
const offset = address / 4;
|
|
2089
|
-
return this.HEAP32.subarray(offset, offset + lengthInBytes / 4);
|
|
2090
|
-
}
|
|
2091
|
-
}
|
|
1846
|
+
constructor(options) {
|
|
1847
|
+
this.kind = "wasm";
|
|
1848
|
+
this.mallocOffset = 8;
|
|
1849
|
+
this.allocs = /* @__PURE__ */ new Map();
|
|
1850
|
+
this.lookups = /* @__PURE__ */ new Map();
|
|
1851
|
+
this.constants = /* @__PURE__ */ new Map();
|
|
1852
|
+
this.initialTime = options.initialTime;
|
|
1853
|
+
this.finalTime = options.finalTime;
|
|
1854
|
+
this.outputVarIds = options.outputVarIds;
|
|
1855
|
+
if (options.listingJson) {
|
|
1856
|
+
this.modelListing = JSON.parse(options.listingJson);
|
|
1857
|
+
this.internalListing = new ModelListing(this.modelListing);
|
|
1858
|
+
}
|
|
1859
|
+
this.onRunModel = options.onRunModel;
|
|
1860
|
+
this.heap = /* @__PURE__ */ new ArrayBuffer(8192);
|
|
1861
|
+
this.HEAP32 = new Int32Array(this.heap);
|
|
1862
|
+
this.HEAPF64 = new Float64Array(this.heap);
|
|
1863
|
+
}
|
|
1864
|
+
varIdForSpec(varSpec) {
|
|
1865
|
+
for (const [listingVarId, listingSpec] of this.internalListing.varSpecs) if (listingSpec.varIndex === varSpec.varIndex) return listingVarId;
|
|
1866
|
+
}
|
|
1867
|
+
cwrap(fname) {
|
|
1868
|
+
switch (fname) {
|
|
1869
|
+
case "getInitialTime": return () => this.initialTime;
|
|
1870
|
+
case "getFinalTime": return () => this.finalTime;
|
|
1871
|
+
case "getSaveper": return () => 1;
|
|
1872
|
+
case "setLookup": return (varIndex, _subIndicesAddress, pointsAddress, numPoints) => {
|
|
1873
|
+
const varId = this.varIdForSpec({ varIndex });
|
|
1874
|
+
if (varId === void 0) throw new Error(`No lookup variable found for var index ${varIndex}`);
|
|
1875
|
+
const points = new Float64Array(this.getHeapView("float64", pointsAddress));
|
|
1876
|
+
this.lookups.set(varId, new JsModelLookup(numPoints, points));
|
|
1877
|
+
};
|
|
1878
|
+
case "runModelWithBuffers": return (inputsAddress, _inputIndicesAddress, outputsAddress, outputIndicesAddress, constantValuesAddress, constantIndicesAddress) => {
|
|
1879
|
+
const inputs = this.getHeapView("float64", inputsAddress);
|
|
1880
|
+
const outputs = this.getHeapView("float64", outputsAddress);
|
|
1881
|
+
const outputIndices = this.getHeapView("int32", outputIndicesAddress);
|
|
1882
|
+
this.constants.clear();
|
|
1883
|
+
if (constantValuesAddress !== 0 && constantIndicesAddress !== 0) {
|
|
1884
|
+
const constantValues = this.getHeapView("float64", constantValuesAddress);
|
|
1885
|
+
const constantIndices = this.getHeapView("int32", constantIndicesAddress);
|
|
1886
|
+
const numConstants = constantIndices[0];
|
|
1887
|
+
let indicesOffset = 1;
|
|
1888
|
+
let valuesOffset = 0;
|
|
1889
|
+
for (let i = 0; i < numConstants; i++) {
|
|
1890
|
+
const varIndex = constantIndices[indicesOffset++];
|
|
1891
|
+
const subCount = constantIndices[indicesOffset++];
|
|
1892
|
+
indicesOffset += subCount;
|
|
1893
|
+
const varId = this.varIdForSpec({ varIndex });
|
|
1894
|
+
if (varId) this.constants.set(varId, constantValues[valuesOffset++]);
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
this.onRunModel(inputs, outputs, this.constants.size > 0 ? this.constants : void 0, this.lookups, outputIndices);
|
|
1898
|
+
this.constants.clear();
|
|
1899
|
+
};
|
|
1900
|
+
default: throw new Error(`Unhandled call to cwrap with function name '${fname}'`);
|
|
1901
|
+
}
|
|
1902
|
+
}
|
|
1903
|
+
_malloc(lengthInBytes) {
|
|
1904
|
+
const currentOffset = this.mallocOffset;
|
|
1905
|
+
this.allocs.set(currentOffset, lengthInBytes);
|
|
1906
|
+
if (lengthInBytes > 0) this.mallocOffset += lengthInBytes;
|
|
1907
|
+
else this.mallocOffset += 8;
|
|
1908
|
+
return currentOffset;
|
|
1909
|
+
}
|
|
1910
|
+
_free() {}
|
|
1911
|
+
getHeapView(kind, address) {
|
|
1912
|
+
if (address === 0) return;
|
|
1913
|
+
const lengthInBytes = this.allocs.get(address);
|
|
1914
|
+
if (lengthInBytes === void 0) throw new Error("Failed to locate heap allocation");
|
|
1915
|
+
if (kind === "float64") {
|
|
1916
|
+
const offset = address / 8;
|
|
1917
|
+
return this.HEAPF64.subarray(offset, offset + lengthInBytes / 8);
|
|
1918
|
+
} else {
|
|
1919
|
+
const offset = address / 4;
|
|
1920
|
+
return this.HEAP32.subarray(offset, offset + lengthInBytes / 4);
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
2092
1923
|
};
|
|
2093
|
-
|
|
2094
|
-
|
|
1924
|
+
//#endregion
|
|
1925
|
+
//#region src/model-runner/synchronous-model-runner.ts
|
|
1926
|
+
/**
|
|
1927
|
+
* Create a `RunnableModel` from a given `JsModel` or `WasmModule` that was generated by the
|
|
1928
|
+
* SDEverywhere transpiler/builder.
|
|
1929
|
+
*
|
|
1930
|
+
* @hidden This is not yet part of the public API; it is only exposed for use by
|
|
1931
|
+
* the runtime-async package.
|
|
1932
|
+
*/
|
|
2095
1933
|
function createRunnableModel(generatedModel) {
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
default:
|
|
2102
|
-
throw new Error(`Unable to identify generated model kind`);
|
|
2103
|
-
}
|
|
1934
|
+
switch (generatedModel.kind) {
|
|
1935
|
+
case "js": return initJsModel(generatedModel);
|
|
1936
|
+
case "wasm": return initWasmModel(generatedModel);
|
|
1937
|
+
default: throw new Error(`Unable to identify generated model kind`);
|
|
1938
|
+
}
|
|
2104
1939
|
}
|
|
1940
|
+
/**
|
|
1941
|
+
* Create a `ModelRunner` that runs a generated model on the JS thread.
|
|
1942
|
+
*
|
|
1943
|
+
* @param generatedModel A `JsModel` or `WasmModule` generated by the SDEverywhere transpiler.
|
|
1944
|
+
*/
|
|
2105
1945
|
function createSynchronousModelRunner(generatedModel) {
|
|
2106
|
-
|
|
2107
|
-
return createRunnerFromRunnableModel(runnableModel);
|
|
1946
|
+
return createRunnerFromRunnableModel(createRunnableModel(generatedModel));
|
|
2108
1947
|
}
|
|
1948
|
+
/**
|
|
1949
|
+
* Create a `ModelRunner` that runs the given model on the JS thread.
|
|
1950
|
+
*
|
|
1951
|
+
* @param model The runnable model instance.
|
|
1952
|
+
*/
|
|
2109
1953
|
function createRunnerFromRunnableModel(model) {
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
model.terminate();
|
|
2137
|
-
terminated = true;
|
|
2138
|
-
}
|
|
2139
|
-
})
|
|
2140
|
-
};
|
|
1954
|
+
const params = new ReferencedRunModelParams(model.modelListing ? new ModelListing(model.modelListing) : void 0);
|
|
1955
|
+
let terminated = false;
|
|
1956
|
+
const runModelSync = (inputs, outputs, options) => {
|
|
1957
|
+
params.updateFromParams(inputs, outputs, options);
|
|
1958
|
+
model.runModel(params);
|
|
1959
|
+
return outputs;
|
|
1960
|
+
};
|
|
1961
|
+
return {
|
|
1962
|
+
createOutputs: () => {
|
|
1963
|
+
return new Outputs(model.outputVarIds, model.startTime, model.endTime, model.saveFreq);
|
|
1964
|
+
},
|
|
1965
|
+
runModel: (inputs, outputs, options) => {
|
|
1966
|
+
if (terminated) return Promise.reject(/* @__PURE__ */ new Error("Model runner has already been terminated"));
|
|
1967
|
+
return Promise.resolve(runModelSync(inputs, outputs, options));
|
|
1968
|
+
},
|
|
1969
|
+
runModelSync: (inputs, outputs, options) => {
|
|
1970
|
+
if (terminated) throw new Error("Model runner has already been terminated");
|
|
1971
|
+
return runModelSync(inputs, outputs, options);
|
|
1972
|
+
},
|
|
1973
|
+
terminate: async () => {
|
|
1974
|
+
if (!terminated) {
|
|
1975
|
+
model.terminate();
|
|
1976
|
+
terminated = true;
|
|
1977
|
+
}
|
|
1978
|
+
}
|
|
1979
|
+
};
|
|
2141
1980
|
}
|
|
2142
|
-
|
|
2143
|
-
|
|
1981
|
+
//#endregion
|
|
1982
|
+
//#region src/model-scheduler/model-scheduler.ts
|
|
1983
|
+
/**
|
|
1984
|
+
* A high-level interface that schedules the underlying `ModelRunner`.
|
|
1985
|
+
*
|
|
1986
|
+
* When one or more input values are changed, this class will schedule a model
|
|
1987
|
+
* run to be completed as soon as possible. When the model run has completed,
|
|
1988
|
+
* `onOutputsChanged` is called to notify that new output data is available.
|
|
1989
|
+
*
|
|
1990
|
+
* The `ModelRunner` is pluggable to allow for running the model synchronously
|
|
1991
|
+
* (on the main JavaScript thread) or asynchronously (in a Web Worker or Node.js
|
|
1992
|
+
* worker thread).
|
|
1993
|
+
*/
|
|
2144
1994
|
var ModelScheduler = class {
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
}
|
|
2199
|
-
if (this.runNeeded) {
|
|
2200
|
-
this.runNeeded = false;
|
|
2201
|
-
setTimeout(() => {
|
|
2202
|
-
this.runModelNow();
|
|
2203
|
-
}, 0);
|
|
2204
|
-
} else {
|
|
2205
|
-
this.runNeeded = false;
|
|
2206
|
-
this.runInProgress = false;
|
|
2207
|
-
}
|
|
2208
|
-
});
|
|
2209
|
-
}
|
|
1995
|
+
/**
|
|
1996
|
+
* @param runner The model runner.
|
|
1997
|
+
* @param userInputs The input values, in the same order as in the spec file passed to `sde`.
|
|
1998
|
+
* @param outputs The structure into which the model outputs will be stored.
|
|
1999
|
+
*/
|
|
2000
|
+
constructor(runner, userInputs, outputs) {
|
|
2001
|
+
this.runner = runner;
|
|
2002
|
+
this.userInputs = userInputs;
|
|
2003
|
+
this.outputs = outputs;
|
|
2004
|
+
this.runNeeded = false;
|
|
2005
|
+
this.runInProgress = false;
|
|
2006
|
+
const afterSet = () => {
|
|
2007
|
+
this.runModelIfNeeded();
|
|
2008
|
+
};
|
|
2009
|
+
for (const userInput of userInputs) userInput.callbacks.onSet = afterSet;
|
|
2010
|
+
this.currentInputs = [];
|
|
2011
|
+
for (const userInput of userInputs) this.currentInputs.push(createSimpleInputValue(userInput.varId));
|
|
2012
|
+
}
|
|
2013
|
+
/**
|
|
2014
|
+
* Schedule a model run (if not already pending). When the run is
|
|
2015
|
+
* complete, save the outputs and call the `onOutputsChanged` callback.
|
|
2016
|
+
*/
|
|
2017
|
+
runModelIfNeeded() {
|
|
2018
|
+
this.runNeeded = true;
|
|
2019
|
+
if (this.runInProgress) return;
|
|
2020
|
+
else {
|
|
2021
|
+
this.runInProgress = true;
|
|
2022
|
+
setTimeout(() => {
|
|
2023
|
+
this.runModelNow();
|
|
2024
|
+
}, 0);
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* Run the model asynchronously using the current set of input values.
|
|
2029
|
+
*/
|
|
2030
|
+
async runModelNow() {
|
|
2031
|
+
for (let i = 0; i < this.userInputs.length; i++) this.currentInputs[i].set(this.userInputs[i].get());
|
|
2032
|
+
try {
|
|
2033
|
+
this.outputs = await this.runner.runModel(this.currentInputs, this.outputs);
|
|
2034
|
+
this.onOutputsChanged?.(this.outputs);
|
|
2035
|
+
} catch (e) {
|
|
2036
|
+
console.error(`ERROR: Failed to run model: ${e.message}`);
|
|
2037
|
+
}
|
|
2038
|
+
if (this.runNeeded) {
|
|
2039
|
+
this.runNeeded = false;
|
|
2040
|
+
setTimeout(() => {
|
|
2041
|
+
this.runModelNow();
|
|
2042
|
+
}, 0);
|
|
2043
|
+
} else {
|
|
2044
|
+
this.runNeeded = false;
|
|
2045
|
+
this.runInProgress = false;
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2210
2048
|
};
|
|
2049
|
+
/**
|
|
2050
|
+
* Create an `InputValue` that is only used to hold a copy of another input (no callbacks).
|
|
2051
|
+
* @hidden
|
|
2052
|
+
*/
|
|
2211
2053
|
function createSimpleInputValue(varId) {
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2054
|
+
let currentValue = 0;
|
|
2055
|
+
const get = () => {
|
|
2056
|
+
return currentValue;
|
|
2057
|
+
};
|
|
2058
|
+
const set = (newValue) => {
|
|
2059
|
+
currentValue = newValue;
|
|
2060
|
+
};
|
|
2061
|
+
const reset = () => {
|
|
2062
|
+
set(0);
|
|
2063
|
+
};
|
|
2064
|
+
return {
|
|
2065
|
+
varId,
|
|
2066
|
+
get,
|
|
2067
|
+
set,
|
|
2068
|
+
reset,
|
|
2069
|
+
callbacks: {}
|
|
2070
|
+
};
|
|
2223
2071
|
}
|
|
2224
|
-
|
|
2225
|
-
|
|
2072
|
+
//#endregion
|
|
2073
|
+
//#region src/model-scheduler/multi-context-model-scheduler.ts
|
|
2074
|
+
/**
|
|
2075
|
+
* A high-level interface that schedules running of the underlying `ModelRunner`.
|
|
2076
|
+
*
|
|
2077
|
+
* This class is similar to the (single context) `ModelScheduler` class, except
|
|
2078
|
+
* this one supports multiple contexts, each with its own distinct set of
|
|
2079
|
+
* inputs and outputs. This is useful for running the same underlying model
|
|
2080
|
+
* instance with different sets of inputs and outputs. For example, you can
|
|
2081
|
+
* use this to show the outputs for multiple scenarios in a single graph, or
|
|
2082
|
+
* multiple scenarios across different graphs.
|
|
2083
|
+
*
|
|
2084
|
+
* When input values are changed in one or more contexts, this class will schedule
|
|
2085
|
+
* a model run for each changed context to be completed as soon as possible.
|
|
2086
|
+
* When the model run has completed, the context's `onOutputsChanged` function
|
|
2087
|
+
* is called to notify that new output data is available for that context.
|
|
2088
|
+
*
|
|
2089
|
+
* The `ModelRunner` is pluggable to allow for running the model synchronously
|
|
2090
|
+
* (on the main JavaScript thread) or asynchronously (in a Web Worker or Node.js
|
|
2091
|
+
* worker thread).
|
|
2092
|
+
*/
|
|
2226
2093
|
var MultiContextModelScheduler = class {
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
});
|
|
2345
|
-
}
|
|
2346
|
-
/**
|
|
2347
|
-
* Run the model asynchronously using the current set of input values in the given context.
|
|
2348
|
-
*
|
|
2349
|
-
* @param context The context to use for the model run.
|
|
2350
|
-
*/
|
|
2351
|
-
runModelNowForContext(context) {
|
|
2352
|
-
return __async(this, null, function* () {
|
|
2353
|
-
var _a;
|
|
2354
|
-
if (this.currentInputs === void 0) {
|
|
2355
|
-
this.currentInputs = Array(context.inputsArray.length);
|
|
2356
|
-
}
|
|
2357
|
-
for (let i = 0; i < context.inputsArray.length; i++) {
|
|
2358
|
-
this.currentInputs[i] = context.inputsArray[i].get();
|
|
2359
|
-
}
|
|
2360
|
-
try {
|
|
2361
|
-
yield this.runner.runModel(this.currentInputs, context.outputs);
|
|
2362
|
-
(_a = context.onOutputsChanged) == null ? void 0 : _a.call(context);
|
|
2363
|
-
} catch (e) {
|
|
2364
|
-
console.error("ERROR: The scheduler encountered an error when running the model:", e);
|
|
2365
|
-
}
|
|
2366
|
-
});
|
|
2367
|
-
}
|
|
2094
|
+
/**
|
|
2095
|
+
* @param runner The model runner.
|
|
2096
|
+
* @param options Additional options for the scheduler.
|
|
2097
|
+
* @param options.initialOutputs An optional `Outputs` instance that will be reused
|
|
2098
|
+
* for the initial context. This is useful for saving memory when an `Outputs`
|
|
2099
|
+
* instance was already created for, e.g., a initial baseline/reference run.
|
|
2100
|
+
*/
|
|
2101
|
+
constructor(runner, options) {
|
|
2102
|
+
this.runner = runner;
|
|
2103
|
+
this.contexts = [];
|
|
2104
|
+
this.runNeeded = false;
|
|
2105
|
+
this.runInProgress = false;
|
|
2106
|
+
this.initialOutputs = options?.initialOutputs;
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Return true if the scheduler has started any model runs.
|
|
2110
|
+
*/
|
|
2111
|
+
isStarted() {
|
|
2112
|
+
return this.initialOutputs === void 0;
|
|
2113
|
+
}
|
|
2114
|
+
/**
|
|
2115
|
+
* Add a new context that holds a distinct set of model inputs and outputs.
|
|
2116
|
+
* These inputs and outputs are kept separate from those in other contexts,
|
|
2117
|
+
* which allows an application to use the same underlying model to run with
|
|
2118
|
+
* multiple I/O contexts.
|
|
2119
|
+
*
|
|
2120
|
+
* Note that the contexts created before the first scheduled model run
|
|
2121
|
+
* will inherit the data from `initialOutputs` passed to the constructor,
|
|
2122
|
+
* but contexts created after that will initially have output values set
|
|
2123
|
+
* to zero.
|
|
2124
|
+
*
|
|
2125
|
+
* @param inputs The input values, in the same order as in the spec file passed to `sde`.
|
|
2126
|
+
* @param options Additional options for the context.
|
|
2127
|
+
* @param options.externalData Additional data that is external to the model outputs.
|
|
2128
|
+
* For example, this can contain data that was captured from an initial reference
|
|
2129
|
+
* run, or other static data that is displayed in graphs alongside the model
|
|
2130
|
+
* output data in graphs.
|
|
2131
|
+
*/
|
|
2132
|
+
addContext(inputs, options) {
|
|
2133
|
+
let outputs;
|
|
2134
|
+
if (this.initialOutputs !== void 0) {
|
|
2135
|
+
if (this.contexts.length === 0) outputs = this.initialOutputs;
|
|
2136
|
+
else {
|
|
2137
|
+
outputs = this.runner.createOutputs();
|
|
2138
|
+
for (const varId of outputs.varIds) {
|
|
2139
|
+
const series0 = this.initialOutputs.getSeriesForVar(varId);
|
|
2140
|
+
const series1 = outputs.getSeriesForVar(varId);
|
|
2141
|
+
for (let i = 0; i < series0.points.length; i++) series1.points[i].y = series0.points[i].y;
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
} else outputs = this.runner.createOutputs();
|
|
2145
|
+
const context = new ModelContextImpl(inputs, outputs, options?.externalData);
|
|
2146
|
+
const afterSet = () => {
|
|
2147
|
+
context.runNeeded = true;
|
|
2148
|
+
this.runModelIfNeeded();
|
|
2149
|
+
};
|
|
2150
|
+
for (const input of inputs) input.callbacks.onSet = afterSet;
|
|
2151
|
+
this.contexts.push(context);
|
|
2152
|
+
return context;
|
|
2153
|
+
}
|
|
2154
|
+
/**
|
|
2155
|
+
* Remove the given context from the set of contexts managed by the scheduler.
|
|
2156
|
+
*
|
|
2157
|
+
* @param context The context to remove.
|
|
2158
|
+
*/
|
|
2159
|
+
removeContext(context) {
|
|
2160
|
+
const index = this.contexts.findIndex((c) => c === context);
|
|
2161
|
+
if (index >= 0) this.contexts.splice(index, 1);
|
|
2162
|
+
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Schedule a model run (if not already pending). When the run is
|
|
2165
|
+
* complete, save the outputs and call the `onOutputsChanged` callback.
|
|
2166
|
+
*/
|
|
2167
|
+
runModelIfNeeded() {
|
|
2168
|
+
this.runNeeded = true;
|
|
2169
|
+
if (this.runInProgress) return;
|
|
2170
|
+
else {
|
|
2171
|
+
this.runInProgress = true;
|
|
2172
|
+
setTimeout(() => {
|
|
2173
|
+
this.runModelNow();
|
|
2174
|
+
}, 0);
|
|
2175
|
+
}
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Run the model asynchronously for all relevant contexts.
|
|
2179
|
+
*/
|
|
2180
|
+
async runModelNow() {
|
|
2181
|
+
this.initialOutputs = void 0;
|
|
2182
|
+
for (const context of this.contexts) if (context.runNeeded) {
|
|
2183
|
+
context.runNeeded = false;
|
|
2184
|
+
await this.runModelNowForContext(context);
|
|
2185
|
+
}
|
|
2186
|
+
if (this.runNeeded) {
|
|
2187
|
+
this.runNeeded = false;
|
|
2188
|
+
setTimeout(() => {
|
|
2189
|
+
this.runModelNow();
|
|
2190
|
+
}, 0);
|
|
2191
|
+
} else {
|
|
2192
|
+
this.runNeeded = false;
|
|
2193
|
+
this.runInProgress = false;
|
|
2194
|
+
}
|
|
2195
|
+
}
|
|
2196
|
+
/**
|
|
2197
|
+
* Run the model asynchronously using the current set of input values in the given context.
|
|
2198
|
+
*
|
|
2199
|
+
* @param context The context to use for the model run.
|
|
2200
|
+
*/
|
|
2201
|
+
async runModelNowForContext(context) {
|
|
2202
|
+
if (this.currentInputs === void 0) this.currentInputs = Array(context.inputsArray.length);
|
|
2203
|
+
for (let i = 0; i < context.inputsArray.length; i++) this.currentInputs[i] = context.inputsArray[i].get();
|
|
2204
|
+
try {
|
|
2205
|
+
await this.runner.runModel(this.currentInputs, context.outputs);
|
|
2206
|
+
context.onOutputsChanged?.();
|
|
2207
|
+
} catch (e) {
|
|
2208
|
+
console.error("ERROR: The scheduler encountered an error when running the model:", e);
|
|
2209
|
+
}
|
|
2210
|
+
}
|
|
2368
2211
|
};
|
|
2369
2212
|
var ModelContextImpl = class {
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
const dataForSource = this.externalData.get(sourceName);
|
|
2402
|
-
if (dataForSource !== void 0) {
|
|
2403
|
-
return dataForSource.get(varId);
|
|
2404
|
-
} else {
|
|
2405
|
-
return void 0;
|
|
2406
|
-
}
|
|
2407
|
-
}
|
|
2408
|
-
}
|
|
2409
|
-
};
|
|
2410
|
-
export {
|
|
2411
|
-
BufferedRunModelParams,
|
|
2412
|
-
MockJsModel,
|
|
2413
|
-
MockWasmModule,
|
|
2414
|
-
ModelListing,
|
|
2415
|
-
ModelScheduler,
|
|
2416
|
-
MultiContextModelScheduler,
|
|
2417
|
-
Outputs,
|
|
2418
|
-
ReferencedRunModelParams,
|
|
2419
|
-
Series,
|
|
2420
|
-
createConstantDef,
|
|
2421
|
-
createInputValue,
|
|
2422
|
-
createLookupDef,
|
|
2423
|
-
createRunnableModel,
|
|
2424
|
-
createSynchronousModelRunner,
|
|
2425
|
-
decodeConstants,
|
|
2426
|
-
decodeLookups,
|
|
2427
|
-
encodeConstants,
|
|
2428
|
-
encodeLookups,
|
|
2429
|
-
encodeVarIndices,
|
|
2430
|
-
execJsModel,
|
|
2431
|
-
getEncodedConstantBufferLengths,
|
|
2432
|
-
getEncodedLookupBufferLengths,
|
|
2433
|
-
getEncodedVarIndicesLength,
|
|
2434
|
-
getJsModelFunctions,
|
|
2435
|
-
initJsModel,
|
|
2436
|
-
initWasmModel,
|
|
2437
|
-
perfElapsed,
|
|
2438
|
-
perfNow
|
|
2213
|
+
/**
|
|
2214
|
+
* @hidden This is intended for use by `MultiContextModelScheduler` only.
|
|
2215
|
+
*
|
|
2216
|
+
* @param inputs The input values, in the same order as in the spec file passed to `sde`.
|
|
2217
|
+
* @param outputs The structure into which the model outputs will be stored.
|
|
2218
|
+
* @param externalData Additional data that is external to the model outputs. For example, this can contain
|
|
2219
|
+
* data that was captured from an initial reference run, or other static data that is displayed in graphs
|
|
2220
|
+
* alongside the model output data in graphs.
|
|
2221
|
+
*/
|
|
2222
|
+
constructor(inputs, outputs, externalData) {
|
|
2223
|
+
this.externalData = externalData;
|
|
2224
|
+
this.runNeeded = false;
|
|
2225
|
+
this.inputsArray = Array.from(inputs);
|
|
2226
|
+
this.outputs = outputs;
|
|
2227
|
+
}
|
|
2228
|
+
/**
|
|
2229
|
+
* Return the series data for the given model output variable or external
|
|
2230
|
+
* dataset.
|
|
2231
|
+
*
|
|
2232
|
+
* @param varId The ID of the output variable associated with the data.
|
|
2233
|
+
* @param sourceName The external data source name (e.g. "Ref"), or
|
|
2234
|
+
* undefined to use the latest model output data from this context.
|
|
2235
|
+
*/
|
|
2236
|
+
getSeriesForVar(varId, sourceName) {
|
|
2237
|
+
if (sourceName === void 0) return this.outputs.getSeriesForVar(varId);
|
|
2238
|
+
else {
|
|
2239
|
+
const dataForSource = this.externalData.get(sourceName);
|
|
2240
|
+
if (dataForSource !== void 0) return dataForSource.get(varId);
|
|
2241
|
+
else return;
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2439
2244
|
};
|
|
2245
|
+
//#endregion
|
|
2246
|
+
export { BufferedRunModelParams, MockJsModel, MockWasmModule, ModelListing, ModelScheduler, MultiContextModelScheduler, Outputs, ReferencedRunModelParams, Series, createConstantDef, createInputValue, createLookupDef, createRunnableModel, createSynchronousModelRunner, decodeConstants, decodeLookups, encodeConstants, encodeLookups, encodeVarIndices, execJsModel, getEncodedConstantBufferLengths, getEncodedLookupBufferLengths, getEncodedVarIndicesLength, getJsModelFunctions, initJsModel, initWasmModel, perfElapsed, perfNow };
|
|
2247
|
+
|
|
2440
2248
|
//# sourceMappingURL=index.js.map
|