@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/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()
@@ -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
+ }