@sdeverywhere/cli 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +14 -0
- package/package.json +41 -0
- package/src/c/macros.c +4 -0
- package/src/c/macros.h +0 -0
- package/src/c/main.c +58 -0
- package/src/c/makefile +15 -0
- package/src/c/model.c +134 -0
- package/src/c/model.h +12 -0
- package/src/c/sde.h +73 -0
- package/src/c/vensim.c +466 -0
- package/src/c/vensim.h +89 -0
- package/src/main.js +77 -0
- package/src/sde-build.js +39 -0
- package/src/sde-bundle.js +54 -0
- package/src/sde-causes.js +35 -0
- package/src/sde-clean.js +33 -0
- package/src/sde-compare.js +106 -0
- package/src/sde-compile.js +57 -0
- package/src/sde-dev.js +52 -0
- package/src/sde-exec.js +48 -0
- package/src/sde-flatten.js +195 -0
- package/src/sde-generate.js +86 -0
- package/src/sde-log.js +104 -0
- package/src/sde-names.js +41 -0
- package/src/sde-run.js +37 -0
- package/src/sde-test.js +70 -0
- package/src/sde-which.js +20 -0
- package/src/utils.js +103 -0
package/src/c/vensim.c
ADDED
|
@@ -0,0 +1,466 @@
|
|
|
1
|
+
#include "sde.h"
|
|
2
|
+
|
|
3
|
+
extern double _time;
|
|
4
|
+
extern double _time_step;
|
|
5
|
+
|
|
6
|
+
double _epsilon = 1e-6;
|
|
7
|
+
|
|
8
|
+
//
|
|
9
|
+
// Vensim functions
|
|
10
|
+
// See the Vensim Reference Manual for descriptions of the functions.
|
|
11
|
+
// http://www.vensim.com/documentation/index.html?22300.htm
|
|
12
|
+
//
|
|
13
|
+
double _PULSE(double start, double width) {
|
|
14
|
+
double time_plus = _time + _time_step / 2.0;
|
|
15
|
+
if (width == 0.0) {
|
|
16
|
+
width = _time_step;
|
|
17
|
+
}
|
|
18
|
+
return (time_plus > start && time_plus < start + width) ? 1.0 : 0.0;
|
|
19
|
+
}
|
|
20
|
+
double _PULSE_TRAIN(double start, double width, double interval, double end) {
|
|
21
|
+
double n = floor((end - start) / interval);
|
|
22
|
+
for (double k = 0; k <= n; k++) {
|
|
23
|
+
if (_PULSE(start + k * interval, width) && _time <= end) {
|
|
24
|
+
return 1.0;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return 0.0;
|
|
28
|
+
}
|
|
29
|
+
double _RAMP(double slope, double start_time, double end_time) {
|
|
30
|
+
// Return 0 until the start time is exceeded.
|
|
31
|
+
// Interpolate from start time to end time.
|
|
32
|
+
// Hold at the end time value.
|
|
33
|
+
// Allow start time > end time.
|
|
34
|
+
if (_time > start_time) {
|
|
35
|
+
if (_time < end_time || start_time > end_time) {
|
|
36
|
+
return slope * (_time - start_time);
|
|
37
|
+
} else {
|
|
38
|
+
return slope * (end_time - start_time);
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
return 0.0;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
double _XIDZ(double a, double b, double x) { return fabs(b) < _epsilon ? x : a / b; }
|
|
45
|
+
double _ZIDZ(double a, double b) {
|
|
46
|
+
if (fabs(b) < _epsilon) {
|
|
47
|
+
return 0.0;
|
|
48
|
+
} else {
|
|
49
|
+
return a / b;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
//
|
|
54
|
+
// Lookups
|
|
55
|
+
//
|
|
56
|
+
Lookup* __new_lookup(size_t size, bool copy, double* data) {
|
|
57
|
+
// Make a new Lookup with "size" number of pairs given in x, y order in a flattened list.
|
|
58
|
+
Lookup* lookup = malloc(sizeof(Lookup));
|
|
59
|
+
lookup->n = size;
|
|
60
|
+
lookup->inverted_data = NULL;
|
|
61
|
+
lookup->data_is_owned = copy;
|
|
62
|
+
if (copy) {
|
|
63
|
+
// Copy array into the lookup data.
|
|
64
|
+
lookup->data = malloc(sizeof(double) * 2 * size);
|
|
65
|
+
memcpy(lookup->data, data, size * 2 * sizeof(double));
|
|
66
|
+
} else {
|
|
67
|
+
// Store a pointer to the lookup data (assumed to be static or owned elsewhere).
|
|
68
|
+
lookup->data = data;
|
|
69
|
+
}
|
|
70
|
+
lookup->last_input = DBL_MAX;
|
|
71
|
+
lookup->last_hit_index = 0;
|
|
72
|
+
return lookup;
|
|
73
|
+
}
|
|
74
|
+
void __delete_lookup(Lookup* lookup) {
|
|
75
|
+
if (lookup) {
|
|
76
|
+
if (lookup->data && lookup->data_is_owned) {
|
|
77
|
+
free(lookup->data);
|
|
78
|
+
}
|
|
79
|
+
if (lookup->inverted_data) {
|
|
80
|
+
free(lookup->inverted_data);
|
|
81
|
+
}
|
|
82
|
+
free(lookup);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
void __print_lookup(Lookup* lookup) {
|
|
86
|
+
if (lookup) {
|
|
87
|
+
for (size_t i = 0; i < lookup->n; i++) {
|
|
88
|
+
printf("(%g, %g)\n", *(lookup->data + 2 * i), *(lookup->data + 2 * i + 1));
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
double __lookup(Lookup* lookup, double input, bool use_inverted_data, LookupMode mode) {
|
|
94
|
+
// Interpolate the y value from an array of (x,y) pairs.
|
|
95
|
+
// NOTE: The x values are assumed to be monotonically increasing.
|
|
96
|
+
|
|
97
|
+
if (lookup == NULL) {
|
|
98
|
+
return _NA_;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const double* data = use_inverted_data ? lookup->inverted_data : lookup->data;
|
|
102
|
+
const size_t max = (lookup->n) * 2;
|
|
103
|
+
|
|
104
|
+
// Use the cached values for improved lookup performance, except in the case
|
|
105
|
+
// of `LOOKUP INVERT` (since it may not be accurate if calls flip back and forth
|
|
106
|
+
// between inverted and non-inverted data).
|
|
107
|
+
bool use_cached_values = !use_inverted_data;
|
|
108
|
+
size_t start_index;
|
|
109
|
+
if (use_cached_values && input >= lookup->last_input) {
|
|
110
|
+
start_index = lookup->last_hit_index;
|
|
111
|
+
} else {
|
|
112
|
+
start_index = 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
for (size_t xi = start_index; xi < max; xi += 2) {
|
|
116
|
+
double x = data[xi];
|
|
117
|
+
|
|
118
|
+
if (x >= input) {
|
|
119
|
+
// We went past the input, or hit it exactly.
|
|
120
|
+
if (use_cached_values) {
|
|
121
|
+
lookup->last_input = input;
|
|
122
|
+
lookup->last_hit_index = xi;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (xi == 0 || x == input) {
|
|
126
|
+
// The input is less than the first x, or this x equals the input; return the
|
|
127
|
+
// associated y without interpolation.
|
|
128
|
+
return data[xi + 1];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Calculate the y value depending on the lookup mode.
|
|
132
|
+
switch (mode) {
|
|
133
|
+
default:
|
|
134
|
+
case Interpolate: {
|
|
135
|
+
// Interpolate along the line from the last (x,y).
|
|
136
|
+
double last_x = data[xi - 2];
|
|
137
|
+
double last_y = data[xi - 1];
|
|
138
|
+
double y = data[xi + 1];
|
|
139
|
+
double dx = x - last_x;
|
|
140
|
+
double dy = y - last_y;
|
|
141
|
+
return last_y + ((dy / dx) * (input - last_x));
|
|
142
|
+
}
|
|
143
|
+
case Forward: {
|
|
144
|
+
// Return the next y value without interpolating.
|
|
145
|
+
return data[xi + 1];
|
|
146
|
+
}
|
|
147
|
+
case Backward:
|
|
148
|
+
// Return the previous y value without interpolating.
|
|
149
|
+
return data[xi - 1];
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// The input is greater than all the x values, so return the high end of the range.
|
|
155
|
+
if (use_cached_values) {
|
|
156
|
+
lookup->last_input = input;
|
|
157
|
+
lookup->last_hit_index = max;
|
|
158
|
+
}
|
|
159
|
+
return data[max - 1];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// This function is similar to `__lookup` in concept, but Vensim produces results for
|
|
163
|
+
// the GET DATA BETWEEN TIMES function that differ in unexpected ways from normal lookup
|
|
164
|
+
// behavior, so we implement it as a separate function here.
|
|
165
|
+
double __get_data_between_times(double* data, size_t n, double input, LookupMode mode) {
|
|
166
|
+
// Interpolate the y value from an array of (x,y) pairs.
|
|
167
|
+
// NOTE: The x values are assumed to be monotonically increasing.
|
|
168
|
+
const size_t max = n * 2;
|
|
169
|
+
|
|
170
|
+
switch (mode) {
|
|
171
|
+
case Forward: {
|
|
172
|
+
// Vensim appears to round non-integral input values down to a whole number
|
|
173
|
+
// when mode is 1 (look forward), so we will do the same
|
|
174
|
+
input = floor(input);
|
|
175
|
+
|
|
176
|
+
for (size_t xi = 0; xi < max; xi += 2) {
|
|
177
|
+
double x = data[xi];
|
|
178
|
+
if (x >= input) {
|
|
179
|
+
return data[xi + 1];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return data[max - 1];
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
case Backward: {
|
|
187
|
+
// Vensim appears to round non-integral input values down to a whole number
|
|
188
|
+
// when mode is -1 (hold backward), so we will do the same
|
|
189
|
+
input = floor(input);
|
|
190
|
+
|
|
191
|
+
for (size_t xi = 2; xi < max; xi += 2) {
|
|
192
|
+
double x = data[xi];
|
|
193
|
+
if (x >= input) {
|
|
194
|
+
return data[xi - 1];
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (max >= 4) {
|
|
199
|
+
return data[max - 3];
|
|
200
|
+
} else {
|
|
201
|
+
return data[1];
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
case Interpolate:
|
|
206
|
+
default: {
|
|
207
|
+
// NOTE: This function produces results that match Vensim output for GET DATA BETWEEN TIMES with a
|
|
208
|
+
// mode of 0 (interpolate), but only when the input values are integral (whole numbers). If the
|
|
209
|
+
// input value is fractional, Vensim produces bizarre/unexpected interpolated values.
|
|
210
|
+
// TODO: For now we print a warning, but ideally we would match the Vensim results exactly.
|
|
211
|
+
static bool warned = false;
|
|
212
|
+
if (input - floor(input) > 0) {
|
|
213
|
+
if (!warned) {
|
|
214
|
+
fprintf(stderr,
|
|
215
|
+
"WARNING: GET DATA BETWEEN TIMES was called with an input value (%f) that has a fractional part.\n",
|
|
216
|
+
input);
|
|
217
|
+
fprintf(stderr,
|
|
218
|
+
"When mode is 0 (interpolate) and the input value is not a whole number, Vensim produces unexpected\n");
|
|
219
|
+
fprintf(stderr, "results that may differ from those produced by SDEverywhere.\n");
|
|
220
|
+
warned = true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
for (size_t xi = 2; xi < max; xi += 2) {
|
|
225
|
+
double x = data[xi];
|
|
226
|
+
if (x >= input) {
|
|
227
|
+
double last_x = data[xi - 2];
|
|
228
|
+
double last_y = data[xi - 1];
|
|
229
|
+
double y = data[xi + 1];
|
|
230
|
+
double dx = x - last_x;
|
|
231
|
+
double dy = y - last_y;
|
|
232
|
+
return last_y + ((dy / dx) * (input - last_x));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return data[max - 1];
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
double _LOOKUP_INVERT(Lookup* lookup, double y) {
|
|
242
|
+
if (lookup->inverted_data == NULL) {
|
|
243
|
+
// Invert the matrix and cache it.
|
|
244
|
+
lookup->inverted_data = malloc(sizeof(double) * 2 * lookup->n);
|
|
245
|
+
double* pLookup = lookup->data;
|
|
246
|
+
double* pInvert = lookup->inverted_data;
|
|
247
|
+
for (size_t i = 0; i < lookup->n; i++) {
|
|
248
|
+
*pInvert++ = *(pLookup + 1);
|
|
249
|
+
*pInvert++ = *pLookup;
|
|
250
|
+
pLookup += 2;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
return __lookup(lookup, y, true, Interpolate);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
typedef struct {
|
|
257
|
+
double x;
|
|
258
|
+
int ind;
|
|
259
|
+
} dbl_ind;
|
|
260
|
+
|
|
261
|
+
#define DBL_IND_BUFSIZE 16
|
|
262
|
+
|
|
263
|
+
int sort_order = 1;
|
|
264
|
+
|
|
265
|
+
int __compare_dbl_ind(const void* a, const void* b) {
|
|
266
|
+
dbl_ind* arg1 = (dbl_ind*)a;
|
|
267
|
+
dbl_ind* arg2 = (dbl_ind*)b;
|
|
268
|
+
int result = 0;
|
|
269
|
+
if (arg1->x < arg2->x) {
|
|
270
|
+
result = -1;
|
|
271
|
+
} else if (arg1->x > arg2->x) {
|
|
272
|
+
result = 1;
|
|
273
|
+
}
|
|
274
|
+
return sort_order * result;
|
|
275
|
+
}
|
|
276
|
+
double* _VECTOR_SORT_ORDER(double* vector, size_t size, double direction) {
|
|
277
|
+
static dbl_ind d[DBL_IND_BUFSIZE];
|
|
278
|
+
static double result[DBL_IND_BUFSIZE];
|
|
279
|
+
if (size > DBL_IND_BUFSIZE) {
|
|
280
|
+
// TODO signal error
|
|
281
|
+
return NULL;
|
|
282
|
+
}
|
|
283
|
+
for (size_t i = 0; i < size; i++) {
|
|
284
|
+
d[i].x = vector[i];
|
|
285
|
+
d[i].ind = (int)i;
|
|
286
|
+
}
|
|
287
|
+
sort_order = direction > 0.0 ? 1 : -1;
|
|
288
|
+
qsort(d, size, sizeof(dbl_ind), __compare_dbl_ind);
|
|
289
|
+
for (size_t i = 0; i < size; i++) {
|
|
290
|
+
result[i] = d[i].ind;
|
|
291
|
+
}
|
|
292
|
+
return result;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
//
|
|
296
|
+
// ALLOCATE AVAILABLE
|
|
297
|
+
//
|
|
298
|
+
// Mathematical functions for calculating the normal pdf and cdf at a point x
|
|
299
|
+
double __pdf_normal(double x, double mu, double sigma) {
|
|
300
|
+
double base = 1.0 / (sigma * sqrt(2.0 * M_PI));
|
|
301
|
+
double exponent = -pow(x - mu, 2.0) / (2.0 * sigma * sigma);
|
|
302
|
+
return base * exp(exponent);
|
|
303
|
+
}
|
|
304
|
+
double __cdf_unit_normal_P(double x) {
|
|
305
|
+
// Zelen & Severo (1964) in Handbook Of Mathematical Functions, Abramowitz and Stegun, 26.2.17
|
|
306
|
+
double p = 0.2316419;
|
|
307
|
+
double b[5] = {0.31938153, -0.356563782, 1.781477937, -1.821255978, 1.330274429};
|
|
308
|
+
double t = 1.0 / (1.0 + p * x);
|
|
309
|
+
double y = 0.0;
|
|
310
|
+
double k = t;
|
|
311
|
+
for (size_t i = 0; i < 5; i++) {
|
|
312
|
+
y += b[i] * k;
|
|
313
|
+
k *= t;
|
|
314
|
+
}
|
|
315
|
+
return 1.0 - __pdf_normal(x, 0.0, 1.0) * y;
|
|
316
|
+
}
|
|
317
|
+
double __cdf_unit_normal_Q(double x) {
|
|
318
|
+
// Calculate the unit cumulative distribution function from x to +∞, often known as Q(x).
|
|
319
|
+
return x >= 0.0 ? 1.0 - __cdf_unit_normal_P(x) : __cdf_unit_normal_P(-x);
|
|
320
|
+
}
|
|
321
|
+
double __cdf_normal_Q(double x, double sigma) { return __cdf_unit_normal_Q(x / sigma); }
|
|
322
|
+
// Access the doubly-subscripted priority profiles array by pointer.
|
|
323
|
+
enum { PTYPE, PPRIORITY, PWIDTH, PEXTRA };
|
|
324
|
+
double __get_pp(double* pp, size_t iProfile, size_t iElement) {
|
|
325
|
+
const int NUM_PP = PEXTRA - PTYPE + 1;
|
|
326
|
+
return *(pp + iProfile * NUM_PP + iElement);
|
|
327
|
+
}
|
|
328
|
+
#define ALLOCATIONS_BUFSIZE 60
|
|
329
|
+
// #define PRINT_ALLOCATIONS_DEBUG_INFO
|
|
330
|
+
double* _ALLOCATE_AVAILABLE(
|
|
331
|
+
double* requested_quantities, double* priority_profiles, double available_resource, size_t num_requesters) {
|
|
332
|
+
// requested_quantities points to an array of length num_requesters.
|
|
333
|
+
// priority_profiles points to an array of num_requesters arrays of length 4.
|
|
334
|
+
// The priority profiles give the mean and standard deviation of normal curves used to allocate
|
|
335
|
+
// the available resource, with a higher mean indicating a higher priority. The search space for
|
|
336
|
+
// allocations that match the available resource is the x axis with tails on both ends of the curves.
|
|
337
|
+
static double allocations[ALLOCATIONS_BUFSIZE];
|
|
338
|
+
if (num_requesters > ALLOCATIONS_BUFSIZE) {
|
|
339
|
+
fprintf(stderr, "_ALLOCATE_AVAILABLE num_requesters exceeds internal maximum size of %d\n", ALLOCATIONS_BUFSIZE);
|
|
340
|
+
return NULL;
|
|
341
|
+
}
|
|
342
|
+
// Limit the search to this number of steps.
|
|
343
|
+
const size_t max_steps = 100;
|
|
344
|
+
// If the available resource is more than the total requests, clamp to the total requests so we don't overallocate.
|
|
345
|
+
double total_requests = 0.0;
|
|
346
|
+
for (size_t i = 0; i < num_requesters; i++) {
|
|
347
|
+
total_requests += requested_quantities[i];
|
|
348
|
+
}
|
|
349
|
+
available_resource = fmin(available_resource, total_requests);
|
|
350
|
+
#ifdef PRINT_ALLOCATIONS_DEBUG_INFO
|
|
351
|
+
fprintf(stderr, "\n_ALLOCATE_AVAILABLE time=%g num_requesters=%zu, available_resource=%f, total_requests=%f\n", _time,
|
|
352
|
+
num_requesters, available_resource, total_requests);
|
|
353
|
+
for (size_t i = 0; i < num_requesters; i++) {
|
|
354
|
+
fprintf(stderr, "[%2zu] requested_quantities=%17f mean=%8g sigma=%8g\n", i, requested_quantities[i],
|
|
355
|
+
__get_pp(priority_profiles, i, PPRIORITY), __get_pp(priority_profiles, i, PWIDTH));
|
|
356
|
+
}
|
|
357
|
+
#endif
|
|
358
|
+
// Find the minimum and maximum means in the priority curves.
|
|
359
|
+
double min_mean = DBL_MAX;
|
|
360
|
+
double max_mean = DBL_MIN;
|
|
361
|
+
for (size_t i = 0; i < num_requesters; i++) {
|
|
362
|
+
min_mean = fmin(__get_pp(priority_profiles, i, PPRIORITY), min_mean);
|
|
363
|
+
max_mean = fmax(__get_pp(priority_profiles, i, PPRIORITY), max_mean);
|
|
364
|
+
}
|
|
365
|
+
// Start the search in the midpoint of the means, with a big first jump scaled to the spread of the means.
|
|
366
|
+
double total_allocations = 0.0;
|
|
367
|
+
double x = (max_mean + min_mean) / 2.0;
|
|
368
|
+
double delta = (max_mean - min_mean) / 2.0;
|
|
369
|
+
size_t num_steps = 0;
|
|
370
|
+
double last_delta_sign = 1.0;
|
|
371
|
+
size_t num_jumps_in_same_direction = 0;
|
|
372
|
+
do {
|
|
373
|
+
// Calculate allocations for each requester.
|
|
374
|
+
for (size_t i = 0; i < num_requesters; i++) {
|
|
375
|
+
if (requested_quantities[i] > 0.0) {
|
|
376
|
+
double mean = __get_pp(priority_profiles, i, PPRIORITY);
|
|
377
|
+
double sigma = __get_pp(priority_profiles, i, PWIDTH);
|
|
378
|
+
// The allocation is the area under the requester's normal curve from x out to +∞
|
|
379
|
+
// scaled by the size of the request. We integrate over the right-hand side of the
|
|
380
|
+
// normal curve so that higher means have higher priority, that is, are allocated more.
|
|
381
|
+
// The unit cumulative distribution function integrates to one over all x,
|
|
382
|
+
// so we simply multiply by a constant to scale the area under the curve.
|
|
383
|
+
allocations[i] = requested_quantities[i] * __cdf_normal_Q(x - mean, sigma);
|
|
384
|
+
} else {
|
|
385
|
+
allocations[i] = 0.0;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Sum the allocations for comparison with the available resource.
|
|
389
|
+
total_allocations = 0.0;
|
|
390
|
+
for (size_t i = 0; i < num_requesters; i++) {
|
|
391
|
+
total_allocations += allocations[i];
|
|
392
|
+
}
|
|
393
|
+
#ifdef PRINT_ALLOCATIONS_DEBUG_INFO
|
|
394
|
+
fprintf(stderr, "x=%-+14g delta=%-+14g Δ=%-+14g total_allocations=%-+14g available_resource=%-+14g\n", x, delta,
|
|
395
|
+
fabs(total_allocations - available_resource), total_allocations, available_resource);
|
|
396
|
+
#endif
|
|
397
|
+
if (++num_steps >= max_steps) {
|
|
398
|
+
fprintf(stderr,
|
|
399
|
+
"_ALLOCATE_AVAILABLE failed to converge at time=%g with total_allocations=%18f, available_resource=%18f\n",
|
|
400
|
+
_time, total_allocations, available_resource);
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
403
|
+
// Set up the next x value by computing a new delta that is usually half the size of the
|
|
404
|
+
// previous delta, that is, do a binary search of the x axis. We may jump over the target
|
|
405
|
+
// x value, so we may need to change direction.
|
|
406
|
+
double delta_sign = total_allocations < available_resource ? -1.0 : 1.0;
|
|
407
|
+
// Too many jumps in the same direction can result in the search converging on a point
|
|
408
|
+
// that falls short of the target x value. Stop halving the delta when that happens until
|
|
409
|
+
// we jump over the target again.
|
|
410
|
+
num_jumps_in_same_direction = delta_sign == last_delta_sign ? num_jumps_in_same_direction + 1 : 0;
|
|
411
|
+
last_delta_sign = delta_sign;
|
|
412
|
+
delta = (delta_sign * fabs(delta)) / (num_jumps_in_same_direction < 3 ? 2.0 : 1.0);
|
|
413
|
+
x += delta;
|
|
414
|
+
// The search terminates when the total allocations are equal to the available resource
|
|
415
|
+
// up to a very small epsilon difference.
|
|
416
|
+
} while (fabs(total_allocations - available_resource) > _epsilon);
|
|
417
|
+
#ifdef PRINT_ALLOCATIONS_DEBUG_INFO
|
|
418
|
+
fprintf(stderr, "converged with Δ=%g in %zu steps\n", fabs(total_allocations - available_resource), num_steps);
|
|
419
|
+
fprintf(stderr, "total_allocations=%f, available_resource=%f\n", total_allocations, available_resource);
|
|
420
|
+
for (size_t i = 0; i < num_requesters; i++) {
|
|
421
|
+
fprintf(stderr, "[%2zu] %f\n", i, allocations[i]);
|
|
422
|
+
}
|
|
423
|
+
#endif
|
|
424
|
+
// Return a pointer to the allocations array the caller passed with the results filled in.
|
|
425
|
+
return allocations;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
//
|
|
429
|
+
// DELAY FIXED
|
|
430
|
+
//
|
|
431
|
+
FixedDelay* __new_fixed_delay(FixedDelay* fixed_delay, double delay_time, double initial_value) {
|
|
432
|
+
// Construct a FixedDelay struct with a ring buffer for the delay line.
|
|
433
|
+
// We don't know the size until runtime, so it must be dynamically allocated.
|
|
434
|
+
// The delay time is quantized to an integral number of time steps.
|
|
435
|
+
// The FixedDelay should be constructed at init time to latch the delay time and initial value.
|
|
436
|
+
// Allocate memory on the first call only. Pass the same pointer back in on subsequent runs.
|
|
437
|
+
if (fixed_delay == NULL) {
|
|
438
|
+
fixed_delay = malloc(sizeof(FixedDelay));
|
|
439
|
+
fixed_delay->n = (size_t)ceil(delay_time / _time_step);
|
|
440
|
+
fixed_delay->data = malloc(sizeof(double) * fixed_delay->n);
|
|
441
|
+
}
|
|
442
|
+
fixed_delay->data_index = 0;
|
|
443
|
+
fixed_delay->initial_value = initial_value;
|
|
444
|
+
return fixed_delay;
|
|
445
|
+
}
|
|
446
|
+
double _DELAY_FIXED(double input, FixedDelay* fixed_delay) {
|
|
447
|
+
// Cache input values in a ring buffer for the number of time steps equal to the delay time.
|
|
448
|
+
// Return the init value until the time reaches the delay time.
|
|
449
|
+
double result;
|
|
450
|
+
// Require the buffer size to be positive to protect from buffer overflows.
|
|
451
|
+
if (fixed_delay->n > 0) {
|
|
452
|
+
fixed_delay->data[fixed_delay->data_index] = input;
|
|
453
|
+
// Because DELAY FIXED is a level, get the value one time step ahead in the buffer.
|
|
454
|
+
fixed_delay->data_index = (fixed_delay->data_index + 1) % fixed_delay->n;
|
|
455
|
+
// Start pulling from the ring buffer when the next time step will reach the delay time.
|
|
456
|
+
if (_time < _initial_time + (fixed_delay->n - 1) * _time_step - 1e-6) {
|
|
457
|
+
result = fixed_delay->initial_value;
|
|
458
|
+
} else {
|
|
459
|
+
result = fixed_delay->data[fixed_delay->data_index];
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
// For a zero delay, take the value directly from the input.
|
|
463
|
+
result = input;
|
|
464
|
+
}
|
|
465
|
+
return result;
|
|
466
|
+
}
|
package/src/c/vensim.h
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
#pragma once
|
|
2
|
+
|
|
3
|
+
#ifdef __cplusplus
|
|
4
|
+
extern "C" {
|
|
5
|
+
#endif
|
|
6
|
+
|
|
7
|
+
//
|
|
8
|
+
// Helpers
|
|
9
|
+
//
|
|
10
|
+
#define _NA_ (-DBL_MAX)
|
|
11
|
+
#define bool_cond(cond) ((double)(cond) != 0.0)
|
|
12
|
+
|
|
13
|
+
//
|
|
14
|
+
// Vensim functions
|
|
15
|
+
// See the Vensim Reference Manual for descriptions of the functions.
|
|
16
|
+
// http://www.vensim.com/documentation/index.html?22300.htm
|
|
17
|
+
//
|
|
18
|
+
#define _ABS(x) fabs(x)
|
|
19
|
+
#define _COS(x) cos(x)
|
|
20
|
+
#define _EXP(x) exp(x)
|
|
21
|
+
#define _GAME(x) (x)
|
|
22
|
+
#define _IF_THEN_ELSE(c, t, f) (bool_cond(c) ? (t) : (f))
|
|
23
|
+
#define _INTEG(value, rate) ((value) + (rate) * _time_step)
|
|
24
|
+
#define _INTEGER(x) trunc(x)
|
|
25
|
+
#define _LN(x) log(x)
|
|
26
|
+
#define _MAX(a, b) fmax(a, b)
|
|
27
|
+
#define _MIN(a, b) fmin(a, b)
|
|
28
|
+
#define _MODULO(a, b) fmod(a, b)
|
|
29
|
+
#define _QUANTUM(a, b) ((b) <= 0 ? (a) : (b) * trunc((a) / (b)))
|
|
30
|
+
#define _SAMPLE_IF_TRUE(current, condition, input) (bool_cond(condition) ? (input) : (current))
|
|
31
|
+
#define _SIN(x) sin(x)
|
|
32
|
+
#define _SQRT(x) sqrt(x)
|
|
33
|
+
#define _STEP(height, step_time) (_time + _time_step / 2.0 > (step_time) ? (height) : 0.0)
|
|
34
|
+
|
|
35
|
+
double* _ALLOCATE_AVAILABLE(double* requested_quantities, double* priority_profiles, double available_resource, size_t num_requesters);
|
|
36
|
+
double _PULSE(double start, double width);
|
|
37
|
+
double _PULSE_TRAIN(double start, double width, double interval, double end);
|
|
38
|
+
double _RAMP(double slope, double start_time, double end_time);
|
|
39
|
+
double* _VECTOR_SORT_ORDER(double* vector, size_t size, double direction);
|
|
40
|
+
double _XIDZ(double a, double b, double x);
|
|
41
|
+
double _ZIDZ(double a, double b);
|
|
42
|
+
|
|
43
|
+
//
|
|
44
|
+
// Lookups
|
|
45
|
+
//
|
|
46
|
+
typedef enum {
|
|
47
|
+
Interpolate, Forward, Backward
|
|
48
|
+
} LookupMode;
|
|
49
|
+
|
|
50
|
+
typedef struct {
|
|
51
|
+
double* data;
|
|
52
|
+
size_t n;
|
|
53
|
+
double* inverted_data;
|
|
54
|
+
bool data_is_owned;
|
|
55
|
+
double last_input;
|
|
56
|
+
size_t last_hit_index;
|
|
57
|
+
} Lookup;
|
|
58
|
+
|
|
59
|
+
Lookup* __new_lookup(size_t size, bool copy, double* data);
|
|
60
|
+
void __delete_lookup(Lookup* lookup);
|
|
61
|
+
void __print_lookup(Lookup* lookup);
|
|
62
|
+
|
|
63
|
+
double __lookup(Lookup *lookup, double input, bool use_inverted_data, LookupMode mode);
|
|
64
|
+
#define _LOOKUP(lookup, x) __lookup(lookup, x, false, Interpolate)
|
|
65
|
+
#define _LOOKUP_FORWARD(lookup, x) __lookup(lookup, x, false, Forward)
|
|
66
|
+
#define _LOOKUP_BACKWARD(lookup, x) __lookup(lookup, x, false, Backward)
|
|
67
|
+
#define _WITH_LOOKUP(x, lookup) __lookup(lookup, x, false, Interpolate)
|
|
68
|
+
double _LOOKUP_INVERT(Lookup* lookup, double y);
|
|
69
|
+
|
|
70
|
+
double __get_data_between_times(double *data, size_t n, double input, LookupMode mode);
|
|
71
|
+
#define _GET_DATA_MODE_TO_LOOKUP_MODE(mode) ((mode) >= 1) ? Forward : (((mode) <= -1) ? Backward : Interpolate)
|
|
72
|
+
#define _GET_DATA_BETWEEN_TIMES(lookup, x, mode) __get_data_between_times((lookup)->data, (lookup)->n, x, _GET_DATA_MODE_TO_LOOKUP_MODE(mode))
|
|
73
|
+
|
|
74
|
+
//
|
|
75
|
+
// DELAY FIXED
|
|
76
|
+
//
|
|
77
|
+
typedef struct {
|
|
78
|
+
double* data;
|
|
79
|
+
size_t n;
|
|
80
|
+
size_t data_index;
|
|
81
|
+
double initial_value;
|
|
82
|
+
} FixedDelay;
|
|
83
|
+
|
|
84
|
+
double _DELAY_FIXED(double input, FixedDelay* fixed_delay);
|
|
85
|
+
FixedDelay* __new_fixed_delay(FixedDelay* fixed_delay, double delay_time, double initial_value);
|
|
86
|
+
|
|
87
|
+
#ifdef __cplusplus
|
|
88
|
+
}
|
|
89
|
+
#endif
|
package/src/main.js
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// SDEverywhere
|
|
4
|
+
// https://sdeverywhere.org
|
|
5
|
+
// Copyright © 2021 Todd Fincannon and Climate Interactive
|
|
6
|
+
// SDEverywhere may be freely distributed under the MIT license.
|
|
7
|
+
|
|
8
|
+
// Commands:
|
|
9
|
+
// bundle
|
|
10
|
+
// dev
|
|
11
|
+
// generate
|
|
12
|
+
// flatten
|
|
13
|
+
// compile
|
|
14
|
+
// exec
|
|
15
|
+
// log
|
|
16
|
+
// compare
|
|
17
|
+
// clean
|
|
18
|
+
// build - generate, compile
|
|
19
|
+
// run - build, exec
|
|
20
|
+
// test - run, log, compare
|
|
21
|
+
// names
|
|
22
|
+
// causes
|
|
23
|
+
// which
|
|
24
|
+
|
|
25
|
+
import fs from 'fs'
|
|
26
|
+
import path from 'path'
|
|
27
|
+
|
|
28
|
+
import yargs from 'yargs'
|
|
29
|
+
import sdeBundle from './sde-bundle.js'
|
|
30
|
+
import sdeDev from './sde-dev.js'
|
|
31
|
+
import sdeGenerate from './sde-generate.js'
|
|
32
|
+
import sdeFlatten from './sde-flatten.js'
|
|
33
|
+
import sdeCompile from './sde-compile.js'
|
|
34
|
+
import sdeExec from './sde-exec.js'
|
|
35
|
+
import sdeLog from './sde-log.js'
|
|
36
|
+
import sdeCompare from './sde-compare.js'
|
|
37
|
+
import sdeClean from './sde-clean.js'
|
|
38
|
+
import sdeBuild from './sde-build.js'
|
|
39
|
+
import sdeRun from './sde-run.js'
|
|
40
|
+
import sdeTest from './sde-test.js'
|
|
41
|
+
import sdeNames from './sde-names.js'
|
|
42
|
+
import sdeCauses from './sde-causes.js'
|
|
43
|
+
import sdeWhich from './sde-which.js'
|
|
44
|
+
|
|
45
|
+
// Workaround yargs issue where it doesn't find version from package.json
|
|
46
|
+
// automatically in all cases in ESM context
|
|
47
|
+
const srcDir = new URL('.', import.meta.url).pathname
|
|
48
|
+
const pkgFile = path.resolve(srcDir, '..', 'package.json')
|
|
49
|
+
const pkg = JSON.parse(fs.readFileSync(pkgFile))
|
|
50
|
+
|
|
51
|
+
const yarg = yargs(process.argv.slice(2))
|
|
52
|
+
yarg
|
|
53
|
+
.strict()
|
|
54
|
+
.scriptName('sde')
|
|
55
|
+
.usage('usage: $0 <command>')
|
|
56
|
+
.command(sdeBundle)
|
|
57
|
+
.command(sdeDev)
|
|
58
|
+
.command(sdeGenerate)
|
|
59
|
+
.command(sdeFlatten)
|
|
60
|
+
.command(sdeCompile)
|
|
61
|
+
.command(sdeExec)
|
|
62
|
+
.command(sdeLog)
|
|
63
|
+
.command(sdeCompare)
|
|
64
|
+
.command(sdeClean)
|
|
65
|
+
.command(sdeBuild)
|
|
66
|
+
.command(sdeRun)
|
|
67
|
+
.command(sdeTest)
|
|
68
|
+
.command(sdeNames)
|
|
69
|
+
.command(sdeCauses)
|
|
70
|
+
.command(sdeWhich)
|
|
71
|
+
.demandCommand(1)
|
|
72
|
+
.help()
|
|
73
|
+
.version(pkg.version)
|
|
74
|
+
.alias('h', 'help')
|
|
75
|
+
.alias('v', 'version')
|
|
76
|
+
.wrap(yarg.terminalWidth())
|
|
77
|
+
.parse()
|
package/src/sde-build.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { generate } from './sde-generate.js'
|
|
2
|
+
import { compile } from './sde-compile.js'
|
|
3
|
+
|
|
4
|
+
export let command = 'build [options] <model>'
|
|
5
|
+
export let describe = 'generate model code and compile it'
|
|
6
|
+
export let builder = {
|
|
7
|
+
spec: {
|
|
8
|
+
describe: 'pathname of the I/O specification JSON file',
|
|
9
|
+
type: 'string',
|
|
10
|
+
alias: 's'
|
|
11
|
+
},
|
|
12
|
+
builddir: {
|
|
13
|
+
describe: 'build directory',
|
|
14
|
+
type: 'string',
|
|
15
|
+
alias: 'b'
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export let handler = argv => {
|
|
19
|
+
build(argv.model, argv)
|
|
20
|
+
}
|
|
21
|
+
export let build = async (model, opts) => {
|
|
22
|
+
try {
|
|
23
|
+
opts.genc = true
|
|
24
|
+
await generate(model, opts)
|
|
25
|
+
compile(model, opts)
|
|
26
|
+
} catch (e) {
|
|
27
|
+
// Exit with a non-zero error code if any step failed
|
|
28
|
+
console.error(`ERROR: ${e.message}\n`)
|
|
29
|
+
process.exit(1)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
command,
|
|
35
|
+
describe,
|
|
36
|
+
builder,
|
|
37
|
+
handler,
|
|
38
|
+
build
|
|
39
|
+
}
|