@sdeverywhere/cli 0.7.42 → 0.7.44

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sdeverywhere/cli",
3
- "version": "0.7.42",
3
+ "version": "0.7.44",
4
4
  "description": "Contains the `sde` command line interface for the SDEverywhere tool suite.",
5
5
  "type": "module",
6
6
  "files": [
@@ -11,8 +11,8 @@
11
11
  "sde": "src/main.js"
12
12
  },
13
13
  "dependencies": {
14
- "@sdeverywhere/build": "^0.3.12",
15
- "@sdeverywhere/compile": "^0.7.30",
14
+ "@sdeverywhere/build": "^0.3.13",
15
+ "@sdeverywhere/compile": "^0.7.32",
16
16
  "byline": "^5.0.0",
17
17
  "ramda": "^0.27.0",
18
18
  "shelljs": "^0.10.0",
@@ -0,0 +1,634 @@
1
+ #include "sde.h"
2
+
3
+ // ALLOCATE AVAILABLE distributes a resource among requesters using a priority
4
+ // profile for each requester. The curve type specifies a complementary
5
+ // cumulative distribution function. The shape of the distribution is given by
6
+ // the priority (indicating the midpoint) and the width (spread). The search
7
+ // space for allocations that match the available resource is the x axis. A
8
+ // greater priority pushes the midpoint of the distribution to the right,
9
+ // resulting in more area under the curve at a given x and a larger allocation
10
+ // for that requester.
11
+
12
+ // FIND MARKET PRICE balances supply and demand by finding a price that
13
+ // results in total allocations that are as close as possible to total supply.
14
+ // The price can then be applied in DEMAND AT PRICE and SUPPLY AT PRICE to
15
+ // determine individual allocations. Note that the priority curve for demand
16
+ // increases allocations with decreasing price, while the priority curve for
17
+ // supply increases allocations with increasing price. This is modeled with
18
+ // a complementary cumulative distribution function for demand and a
19
+ // cumulative distribution function for supply.
20
+
21
+ // The number of agents receiving allocations is limited by this buffer size.
22
+ #define ALLOCATIONS_BUFSIZE 80
23
+ // Define this to print debug info during the allocation process.
24
+ // #define PRINT_ALLOCATIONS_DEBUG_INFO
25
+
26
+ // Return true if the value is near zero up to the epsilon tolerance.
27
+ static inline bool __isZero(double value) { return fabs(value) < _epsilon; }
28
+ // Compute the absolute difference when x or y is near zero, otherwise compute
29
+ // the relative difference, with y considered as the baseline.
30
+ static inline double __difference(double x, double y) {
31
+ double diff = 0.0;
32
+ if (__isZero(x) || __isZero(y)) {
33
+ diff = fabs(x - y);
34
+ } else {
35
+ diff = fabs(1.0 - x / y);
36
+ }
37
+ return diff;
38
+ }
39
+ // Return true if the values are equal up to the tolerance.
40
+ static inline bool __isEqual(double x, double y) { return __difference(x, y) < _epsilon; }
41
+ // Clamp x to the interval [0,1].
42
+ static inline double __clamp01(double x) {
43
+ if (x < 0.0) return 0.0;
44
+ if (x > 1.0) return 1.0;
45
+ return x;
46
+ }
47
+ // Priority profiles are arrays of 4 elements.
48
+ enum { PTYPE, PPRIORITY, PWIDTH, PEXTRA };
49
+ // Priority curve types in profiles specify a cumulative distribution function.
50
+ enum { PTYPE_FIXED, PTYPE_RECTANGULAR, PTYPE_TRIANGULAR, PTYPE_NORMAL, PTYPE_EXPONENTIAL };
51
+ // Access the doubly-subscripted priority profiles array by pointer.
52
+ static inline double __get_pp(double* pp, size_t iProfile, size_t iElement) {
53
+ const int NUM_PP = PEXTRA - PTYPE + 1;
54
+ return *(pp + iProfile * NUM_PP + iElement);
55
+ }
56
+
57
+ // Normal distribution
58
+ // Abramowitz and Stegun 26.2.17 (Hastings 5-term rational approximation),
59
+ // using 6-significant-digit rounded coefficients to best match Vensim.
60
+ static double __cdf_unit_normal(double x) {
61
+ double p = 0.231642;
62
+ double b[5] = {0.319382, -0.356564, 1.78148, -1.82126, 1.33027};
63
+ double t = 1.0 / (1.0 + p * x);
64
+ double y = 0.0;
65
+ double k = t;
66
+ for (size_t i = 0; i < 5; i++) {
67
+ y += b[i] * k;
68
+ k *= t;
69
+ }
70
+ static const double base = 0.39894228040143267794; // 1/sqrt(2*pi)
71
+ return 1.0 - (base * exp(-(x * x) / 2.0)) * y;
72
+ }
73
+ static double __cdf_normal(double x, double mu, double sigma) {
74
+ if (x < mu) {
75
+ return 1.0 - __cdf_unit_normal(-(x - mu) / sigma);
76
+ } else {
77
+ return __cdf_unit_normal((x - mu) / sigma);
78
+ }
79
+ }
80
+ static double __cdf_normal_Q(double x, double mu, double sigma) { return 1.0 - __cdf_normal(x, mu, sigma); }
81
+ // Rectangular CDF on [0,1] ramping over [a,b]
82
+ static double __cdf_rectangular(double x, double priority, double width) {
83
+ double a = priority - width / 2.0;
84
+ double b = priority + width / 2.0;
85
+ if (b <= a) return (x <= 0.0) ? 0.0 : 1.0;
86
+ if (x <= a) return 0.0;
87
+ if (x >= b) return 1.0;
88
+ return __clamp01((x - a) / (b - a));
89
+ }
90
+ static double __cdf_rectangular_Q(double x, double priority, double width) {
91
+ return 1.0 - __cdf_rectangular(x, priority, width);
92
+ }
93
+ // Triangular CDF extending from a to b
94
+ static double __cdf_triangular(double x, double priority, double width) {
95
+ double a = priority - width / 2.0;
96
+ double b = priority + width / 2.0;
97
+ double xLeft = fmin(a, b);
98
+ double xRight = fmax(a, b);
99
+ double mode = (xLeft + xRight) / 2.0;
100
+ if (x <= xLeft) return 0.0;
101
+ if (x >= xRight) return 1.0;
102
+ double c1 = (xRight - xLeft) * (mode - xLeft);
103
+ double c2 = (xRight - xLeft) * (xRight - mode);
104
+ if (x <= mode) return __clamp01(((x - xLeft) * (x - xLeft)) / c1);
105
+ return __clamp01(1.0 - ((xRight - x) * (xRight - x)) / c2);
106
+ }
107
+ static double __cdf_triangular_Q(double x, double priority, double width) {
108
+ return 1.0 - __cdf_triangular(x, priority, width);
109
+ }
110
+ // Exponential CDF using the Laplace distribution
111
+ static double __cdf_exponential(double x, double mu, double b) {
112
+ if (x < mu) {
113
+ return 0.5 * exp((x - mu) / b);
114
+ } else {
115
+ return 1.0 - 0.5 * exp(-(x - mu) / b);
116
+ }
117
+ }
118
+ static double __cdf_exponential_Q(double x, double mu, double b) { return 1.0 - __cdf_exponential(x, mu, b); }
119
+
120
+ // Return the fraction of the quantity allocated at x for the given priority profile.
121
+ static double __allocate_by_priority(int ptype, double x, double priority, double width, bool is_demand) {
122
+ switch (ptype) {
123
+ case PTYPE_RECTANGULAR:
124
+ return is_demand ? __cdf_rectangular_Q(x, priority, width) : __cdf_rectangular(x, priority, width);
125
+ case PTYPE_TRIANGULAR:
126
+ return is_demand ? __cdf_triangular_Q(x, priority, width) : __cdf_triangular(x, priority, width);
127
+ case PTYPE_NORMAL:
128
+ return is_demand ? __cdf_normal_Q(x, priority, width) : __cdf_normal(x, priority, width);
129
+ case PTYPE_EXPONENTIAL:
130
+ return is_demand ? __cdf_exponential_Q(x, priority, width) : __cdf_exponential(x, priority, width);
131
+ default:
132
+ fprintf(stderr, "Error: unknown priority type %d\n", ptype);
133
+ return 0.0;
134
+ }
135
+ }
136
+ // Compute allocations at the given price for either demanders or suppliers.
137
+ // The is_demand flag is true when allocating demand (using the complementary CDF).
138
+ // Set it false when allocating supply (using the CDF).
139
+ static double* __allocations_at_price(double* quantities, double* profiles, double price, size_t n, bool is_demand) {
140
+ static double allocations[ALLOCATIONS_BUFSIZE];
141
+ if (n > ALLOCATIONS_BUFSIZE) {
142
+ fprintf(stderr, "Error: the number of allocation agents exceeds the maximum size of %d\n", ALLOCATIONS_BUFSIZE);
143
+ return allocations;
144
+ }
145
+ int ptype = (int)__get_pp(profiles, 0, PTYPE);
146
+ if (ptype == PTYPE_FIXED) {
147
+ // For the fixed priority type, simply echo the quantities as allocations.
148
+ for (size_t i = 0; i < n; i++) {
149
+ allocations[i] = quantities[i];
150
+ }
151
+ } else {
152
+ for (size_t i = 0; i < n; i++) {
153
+ if (quantities[i] > 0.0) {
154
+ ptype = (int)__get_pp(profiles, i, PTYPE);
155
+ double priority = __get_pp(profiles, i, PPRIORITY);
156
+ double width = __get_pp(profiles, i, PWIDTH);
157
+ double fraction = __allocate_by_priority(ptype, price, priority, width, is_demand);
158
+ allocations[i] = quantities[i] * fraction;
159
+ } else {
160
+ allocations[i] = 0.0;
161
+ }
162
+ }
163
+ }
164
+ return allocations;
165
+ }
166
+
167
+ // Allocate the available resource to the requesters using their priority profiles.
168
+ double* _ALLOCATE_AVAILABLE(
169
+ double* requested_quantities, double* priority_profiles, double available_resource, size_t num_requesters) {
170
+ // requested_quantities points to an array of length num_requesters.
171
+ // priority_profiles points to an array of num_requesters arrays of length 4.
172
+ static double allocations[ALLOCATIONS_BUFSIZE];
173
+ if (num_requesters > ALLOCATIONS_BUFSIZE) {
174
+ fprintf(
175
+ stderr, "Error: _ALLOCATE_AVAILABLE num_requesters exceeds internal maximum size of %d\n", ALLOCATIONS_BUFSIZE);
176
+ memset(allocations, 0, sizeof(allocations));
177
+ return allocations;
178
+ }
179
+ if (available_resource <= 0.0) {
180
+ memset(allocations, 0, sizeof(allocations));
181
+ return allocations;
182
+ }
183
+ // Limit the search to this number of steps.
184
+ const size_t max_steps = 100;
185
+ // If the available resource is more than the total requests, clamp to the total requests so we don't overallocate.
186
+ double total_requests = 0.0;
187
+ for (size_t i = 0; i < num_requesters; i++) {
188
+ total_requests += requested_quantities[i];
189
+ }
190
+ available_resource = fmin(available_resource, total_requests);
191
+ #ifdef PRINT_ALLOCATIONS_DEBUG_INFO
192
+ fprintf(stderr, "\n_ALLOCATE_AVAILABLE time=%g num_requesters=%zu, available_resource=%f, total_requests=%f\n", _time,
193
+ num_requesters, available_resource, total_requests);
194
+ for (size_t i = 0; i < num_requesters; i++) {
195
+ fprintf(stderr, "[%2zu] requested_quantities=%17f priority=%8g width=%8g\n", i, requested_quantities[i],
196
+ __get_pp(priority_profiles, i, PPRIORITY), __get_pp(priority_profiles, i, PWIDTH));
197
+ }
198
+ #endif
199
+ // Find the minimum and maximum means in the priority curves.
200
+ double min_mean = DBL_MAX;
201
+ double max_mean = -DBL_MAX;
202
+ for (size_t i = 0; i < num_requesters; i++) {
203
+ min_mean = fmin(__get_pp(priority_profiles, i, PPRIORITY), min_mean);
204
+ max_mean = fmax(__get_pp(priority_profiles, i, PPRIORITY), max_mean);
205
+ }
206
+ // Start the search in the midpoint of the means, with a big first jump scaled
207
+ // to the spread of the means.
208
+ double total_allocations = 0.0;
209
+ double x = (max_mean + min_mean) / 2.0;
210
+ double delta = (max_mean - min_mean) / 2.0;
211
+ size_t num_steps = 0;
212
+ double last_delta_sign = 1.0;
213
+ size_t num_jumps_in_same_direction = 0;
214
+ do {
215
+ // Calculate allocations for each requester.
216
+ for (size_t i = 0; i < num_requesters; i++) {
217
+ if (requested_quantities[i] > 0.0) {
218
+ int ptype = (int)__get_pp(priority_profiles, i, PTYPE);
219
+ if (ptype == PTYPE_FIXED || __isEqual(min_mean, max_mean)) {
220
+ // The fixed priority type allocates proportionally to each request.
221
+ // This is also the fallback allocation when all priorities are equal.
222
+ if (total_requests > available_resource) {
223
+ allocations[i] = (requested_quantities[i] / total_requests) * available_resource;
224
+ } else {
225
+ allocations[i] = requested_quantities[i];
226
+ }
227
+ } else {
228
+ // Calculate the allocation using the specified priority curve.
229
+ double priority = __get_pp(priority_profiles, i, PPRIORITY);
230
+ double width = __get_pp(priority_profiles, i, PWIDTH);
231
+ double fraction = __allocate_by_priority(ptype, x, priority, width, true);
232
+ allocations[i] = requested_quantities[i] * fraction;
233
+ }
234
+ } else {
235
+ allocations[i] = 0.0;
236
+ }
237
+ }
238
+ // Sum the allocations for comparison with the available resource.
239
+ total_allocations = 0.0;
240
+ for (size_t i = 0; i < num_requesters; i++) {
241
+ total_allocations += allocations[i];
242
+ }
243
+ #ifdef PRINT_ALLOCATIONS_DEBUG_INFO
244
+ fprintf(stderr,
245
+ "x=%-+14g delta=%-+14g diff=%-14g%% total_allocations=%-+14g "
246
+ "available_resource=%-+14g\n",
247
+ x, delta, __difference(total_allocations, available_resource) * 100.0, total_allocations, available_resource);
248
+ #endif
249
+ if (++num_steps >= max_steps) {
250
+ fprintf(stderr,
251
+ "_ALLOCATE_AVAILABLE failed to converge at time=%g with total_allocations=%18f, available_resource=%18f\n",
252
+ _time, total_allocations, available_resource);
253
+ break;
254
+ }
255
+ // Set up the next x value by computing a new delta that is usually half the size of the
256
+ // previous delta, that is, do a binary search of the x axis. We may jump over the target
257
+ // x value, so we may need to change direction.
258
+ double delta_sign = total_allocations < available_resource ? -1.0 : 1.0;
259
+ // Too many jumps in the same direction can result in the search converging on a point
260
+ // that falls short of the target x value. Stop halving the delta when that happens until
261
+ // we jump over the target again.
262
+ num_jumps_in_same_direction = delta_sign == last_delta_sign ? num_jumps_in_same_direction + 1 : 0;
263
+ last_delta_sign = delta_sign;
264
+ delta = (delta_sign * fabs(delta)) / (num_jumps_in_same_direction < 3 ? 2.0 : 1.0);
265
+ x += delta;
266
+ // The search terminates when the total allocations are equal to the
267
+ // available resource up to the built-in tolerance.
268
+ } while (!__isEqual(total_allocations, available_resource));
269
+ #ifdef PRINT_ALLOCATIONS_DEBUG_INFO
270
+ fprintf(stderr, "converged with diff=%g%% in %zu steps\n",
271
+ __difference(total_allocations, available_resource) * 100.0, num_steps);
272
+ fprintf(stderr, "total_allocations=%f, available_resource=%f\n", total_allocations, available_resource);
273
+ for (size_t i = 0; i < num_requesters; i++) {
274
+ fprintf(stderr, "[%2zu] %f\n", i, allocations[i]);
275
+ }
276
+ #endif
277
+ // Return a pointer to the allocations array the caller passed with the results filled in.
278
+ return allocations;
279
+ }
280
+ // Find a market price that balances supply and demand.
281
+ double _FIND_MARKET_PRICE(double* demand_quantities, double* demand_profiles, double* supply_quantities,
282
+ double* supply_profiles, size_t num_demanders, size_t num_suppliers) {
283
+ // We assume that all demanders and suppliers use the same ptype.
284
+ static double demand_allocations[ALLOCATIONS_BUFSIZE];
285
+ static double supply_allocations[ALLOCATIONS_BUFSIZE];
286
+ if (num_demanders > ALLOCATIONS_BUFSIZE) {
287
+ fprintf(
288
+ stderr, "Error: _FIND_MARKET_PRICE num_demanders exceeds internal maximum size of %d\n", ALLOCATIONS_BUFSIZE);
289
+ return 0.0;
290
+ }
291
+ if (num_suppliers > ALLOCATIONS_BUFSIZE) {
292
+ fprintf(stderr, "_FIND_MARKET_PRICE num_suppliers exceeds internal maximum size of %d\n", ALLOCATIONS_BUFSIZE);
293
+ return 0.0;
294
+ }
295
+ double total_demand_allocations = 0.0;
296
+ double total_supply_allocations = 0.0;
297
+ // Set up the price search.
298
+ const size_t max_steps = 100;
299
+ double price = 0.0;
300
+ double min_price = DBL_MAX;
301
+ double max_price = DBL_MIN;
302
+ for (size_t i = 0; i < num_demanders; i++) {
303
+ min_price = fmin(__get_pp(demand_profiles, i, PPRIORITY), min_price);
304
+ max_price = fmax(__get_pp(demand_profiles, i, PPRIORITY), max_price);
305
+ }
306
+ for (size_t i = 0; i < num_suppliers; i++) {
307
+ min_price = fmin(__get_pp(supply_profiles, i, PPRIORITY), min_price);
308
+ max_price = fmax(__get_pp(supply_profiles, i, PPRIORITY), max_price);
309
+ }
310
+ double x = (max_price + min_price) / 2.0;
311
+ double delta = (max_price - min_price) / 2.0;
312
+ size_t num_steps = 0;
313
+ double last_delta_sign = 1.0;
314
+ size_t num_jumps_in_same_direction = 0;
315
+ // When a ptype is fixed, we need to set total allocations.
316
+ int demand_ptype = (int)__get_pp(demand_profiles, 0, PTYPE);
317
+ int supply_ptype = (int)__get_pp(supply_profiles, 0, PTYPE);
318
+ if (demand_ptype == PTYPE_FIXED || supply_ptype == PTYPE_FIXED) {
319
+ double total_demand = 0.0;
320
+ for (size_t i = 0; i < num_demanders; i++) {
321
+ total_demand += demand_quantities[i];
322
+ }
323
+ double total_supply = 0.0;
324
+ for (size_t i = 0; i < num_suppliers; i++) {
325
+ total_supply += supply_quantities[i];
326
+ }
327
+ // Clamp total allocations so we don't overallocate.
328
+ if (demand_ptype == PTYPE_FIXED) {
329
+ total_demand_allocations = fmin(total_demand, total_supply);
330
+ }
331
+ if (supply_ptype == PTYPE_FIXED) {
332
+ total_supply_allocations = fmin(total_supply, total_demand);
333
+ }
334
+ }
335
+ // Search for a price that matches demand with supply.
336
+ do {
337
+ if (demand_ptype != PTYPE_FIXED) {
338
+ // Allocate demand at the current price.
339
+ total_demand_allocations = 0.0;
340
+ for (size_t i = 0; i < num_demanders; i++) {
341
+ if (demand_quantities[i] > 0.0) {
342
+ double priority = __get_pp(demand_profiles, i, PPRIORITY);
343
+ double width = __get_pp(demand_profiles, i, PWIDTH);
344
+ double fraction = __allocate_by_priority(demand_ptype, x, priority, width, true);
345
+ demand_allocations[i] = demand_quantities[i] * fraction;
346
+ total_demand_allocations += demand_allocations[i];
347
+ } else {
348
+ demand_allocations[i] = 0.0;
349
+ }
350
+ }
351
+ }
352
+ if (supply_ptype != PTYPE_FIXED) {
353
+ // Allocate supply at the current price.
354
+ total_supply_allocations = 0.0;
355
+ for (size_t i = 0; i < num_suppliers; i++) {
356
+ if (supply_quantities[i] > 0.0) {
357
+ double priority = __get_pp(supply_profiles, i, PPRIORITY);
358
+ double width = __get_pp(supply_profiles, i, PWIDTH);
359
+ double fraction = __allocate_by_priority(supply_ptype, x, priority, width, false);
360
+ supply_allocations[i] = supply_quantities[i] * fraction;
361
+ total_supply_allocations += supply_allocations[i];
362
+ } else {
363
+ supply_allocations[i] = 0.0;
364
+ }
365
+ }
366
+ }
367
+ if (++num_steps >= max_steps) {
368
+ fprintf(stderr,
369
+ "_FIND_MARKET_PRICE failed to converge at time=%g with total_demand_allocations=%18f, "
370
+ "total_supply_allocations=%18f\n",
371
+ _time, total_demand_allocations, total_supply_allocations);
372
+ break;
373
+ }
374
+ double delta_sign = total_demand_allocations < total_supply_allocations ? -1.0 : 1.0;
375
+ num_jumps_in_same_direction = delta_sign == last_delta_sign ? num_jumps_in_same_direction + 1 : 0;
376
+ last_delta_sign = delta_sign;
377
+ delta = (delta_sign * fabs(delta)) / (num_jumps_in_same_direction < 3 ? 2.0 : 1.0);
378
+ price = x;
379
+ x += delta;
380
+ #ifdef PRINT_ALLOCATIONS_DEBUG_INFO
381
+ fprintf(stderr,
382
+ "price=%-+14g delta=%-+14g diff%%=%-14g total_demand_allocations=%-+14g "
383
+ "total_supply_allocations=%-+14g\n",
384
+ price, delta, __difference(total_demand_allocations, total_supply_allocations) * 100.0,
385
+ total_demand_allocations, total_supply_allocations);
386
+ #endif
387
+ } while (__difference(total_demand_allocations, total_supply_allocations) >= 2e-7);
388
+ #ifdef PRINT_ALLOCATIONS_DEBUG_INFO
389
+ fprintf(stderr, "converged with diff%%=%g at time %g in %zu steps\n",
390
+ __difference(total_demand_allocations, total_supply_allocations) * 100.0, _time, num_steps);
391
+ fprintf(stderr, "total_demand_allocations=%f, total_supply_allocations=%f\n", total_demand_allocations,
392
+ total_supply_allocations);
393
+ #endif
394
+ return price;
395
+ }
396
+ // Allocate the total demand among demanders at the given price according to their demand profiles.
397
+ double* _DEMAND_AT_PRICE(double* demand_quantities, double* demand_profiles, double price, size_t num_demanders) {
398
+ return __allocations_at_price(demand_quantities, demand_profiles, price, num_demanders, true);
399
+ }
400
+ // Allocate the total supply among suppliers at the given price according to their supply profiles.
401
+ double* _SUPPLY_AT_PRICE(double* supply_quantities, double* supply_profiles, double price, size_t num_suppliers) {
402
+ return __allocations_at_price(supply_quantities, supply_profiles, price, num_suppliers, false);
403
+ }
404
+
405
+ //
406
+ // Helper methods for allocate by priority
407
+ //
408
+ double __sum(double* arr, size_t n) {
409
+ double total = 0.0;
410
+ for (size_t i = 0; i < n; i++) {
411
+ total += arr[i];
412
+ }
413
+ return total;
414
+ }
415
+
416
+ //
417
+ // ALLOCATE BY PRIORITY
418
+ //
419
+ #define ALLOCATE_BY_PRIORITY_BUFSIZE 60
420
+ // #define PRINT_ALLOCATIONS_DEBUG_INFO
421
+
422
+ double* _ALLOCATE_BY_PRIORITY(
423
+ double* request_quantities, double* priority_values, double size, double width, double supply, size_t num_requesters) {
424
+
425
+ // request points to an array of length num_requesters.
426
+ // priority points to an array of length num_requesters.
427
+ // size is the number of elements across which allocation is being made.
428
+ // width specifies how big a gap in priority is required to have the allocation go first to
429
+ // higher priority with only leftovers going to lower priority.
430
+ // supply is the total supply available to fulfill all requests.
431
+
432
+ // Allocate by priority allocates supply to requesters based on order of priority. The way in
433
+ // which the rationing works is determined by the relative priorities and the width parameter.
434
+
435
+ static double allocations[ALLOCATE_BY_PRIORITY_BUFSIZE];
436
+ if (num_requesters > ALLOCATE_BY_PRIORITY_BUFSIZE) {
437
+ fprintf(stderr, "_ALLOCATE_BY_PRIORITY num_requesters exceeds internal maximum size of %d\n", ALLOCATE_BY_PRIORITY_BUFSIZE);
438
+ return NULL;
439
+ }
440
+
441
+ // Validate request values (must be non-negative)
442
+ for (size_t i = 0; i < num_requesters; i++) {
443
+ if (request_quantities[i] < -_epsilon) {
444
+ fprintf(stderr,
445
+ "_ALLOCATE_BY_PRIORITY encountered negative request value at index %zu: %f\n",
446
+ i, request_quantities[i]);
447
+ return NULL;
448
+ }
449
+ }
450
+
451
+ // Validate width (must not be negative)
452
+ if (width < -_epsilon) {
453
+ fprintf(stderr,
454
+ "_ALLOCATE_BY_PRIORITY encountered invalid width value: %f\n"
455
+ "Width must not be negative.\n",
456
+ width);
457
+ return NULL;
458
+ }
459
+
460
+ // Validate supply (must not be negative)
461
+ if (supply < -_epsilon) {
462
+ fprintf(stderr,
463
+ "_ALLOCATE_BY_PRIORITY encountered invalid supply value: %f\n"
464
+ "Supply must not be negative.\n",
465
+ supply);
466
+ return NULL;
467
+ }
468
+
469
+ // If supply > sum(request), return request
470
+ if (supply > __sum(request_quantities, num_requesters)) {
471
+ return request_quantities;
472
+ }
473
+
474
+ // If supply = 0, all targets get allocated 0
475
+ if(fabs(supply) < _epsilon) {
476
+ return allocations;
477
+ }
478
+
479
+ static double out_return[ALLOCATE_BY_PRIORITY_BUFSIZE];
480
+
481
+ // Remove request 0 targets and order by priority
482
+ bool is_0[ALLOCATE_BY_PRIORITY_BUFSIZE];
483
+ size_t idx[ALLOCATE_BY_PRIORITY_BUFSIZE];
484
+ size_t m = 0;
485
+
486
+ for (size_t i = 0; i < num_requesters; i++) {
487
+ is_0[i] = request_quantities[i] == 0.0;
488
+ if (!is_0[i]) {
489
+ idx[m++] = i;
490
+ }
491
+ }
492
+
493
+ // Sort indices in `idx` by descending `priority_values` (highest priority first)
494
+ for (size_t i = 0; i < m; i++) {
495
+ for (size_t j = i + 1; j < m; j++) {
496
+ if (priority_values[idx[j]] > priority_values[idx[i]]) {
497
+ size_t tmp = idx[i];
498
+ idx[i] = idx[j];
499
+ idx[j] = tmp;
500
+ }
501
+ }
502
+ }
503
+
504
+ // Populate local arrays with request and priority values reordered according to idx
505
+ double request[ALLOCATE_BY_PRIORITY_BUFSIZE];
506
+ double priority[ALLOCATE_BY_PRIORITY_BUFSIZE];
507
+
508
+ for (size_t i = 0; i < m; i++) {
509
+ request[i] = (double)request_quantities[idx[i]];
510
+ priority[i] = priority_values[idx[i]];
511
+ }
512
+
513
+ // Create the outputs array
514
+ for (size_t i = 0; i < num_requesters; i++) {
515
+ out_return[i] = 0.0;
516
+ }
517
+
518
+ double out[ALLOCATE_BY_PRIORITY_BUFSIZE] = {0.0};
519
+
520
+ // Compute the distances between target supply and next target start
521
+ double distances[ALLOCATE_BY_PRIORITY_BUFSIZE];
522
+
523
+ for (size_t i = 0; i < m; i++) {
524
+ distances[i] = NAN;
525
+ }
526
+
527
+ // Last target will have NaN as distances as there are no more targets after
528
+ for (size_t i = 0; i + 1 < m; i++) {
529
+ double d = -(priority[i + 1] - priority[i]) / width;
530
+ if (d > 1.0) d = 1.0;
531
+ distances[i] = d * request[i];
532
+ }
533
+
534
+ // Index of the current activated target
535
+ bool active[ALLOCATE_BY_PRIORITY_BUFSIZE] = {false};
536
+ active[0] = true;
537
+
538
+ // Index of the last activated target
539
+ size_t c_i = 0;
540
+
541
+ // Continue allocating until supply is exhausted
542
+ while (supply > _epsilon) {
543
+ // Check if there are any active targets left
544
+ bool any_active = false;
545
+ for (size_t i = 0; i < m; i++) {
546
+ if (active[i]) {
547
+ any_active = true;
548
+ break;
549
+ }
550
+ }
551
+ if (!any_active) {
552
+ break;
553
+ }
554
+
555
+ // Compute proportional allocation weights ("slopes") for active targets
556
+ double slopes[ALLOCATE_BY_PRIORITY_BUFSIZE];
557
+ double slope_sum = 0.0;
558
+
559
+ for (size_t i = 0; i < m; i++) {
560
+ if (active[i]) {
561
+ slopes[i] = request[i]; // weight based on requested amount
562
+ slope_sum += slopes[i];
563
+ } else {
564
+ slopes[i] = 0.0;
565
+ }
566
+ }
567
+
568
+ // Normalize slopes so total allocation proportion sums to 1
569
+ for (size_t i = 0; i < m; i++) {
570
+ slopes[i] /= slope_sum;
571
+ }
572
+
573
+ // Compute how much supply much be given to any target reach its request
574
+ double dx_next_top = NAN;
575
+
576
+ for (size_t i = 0; i < m; i++) {
577
+ if (active[i]) {
578
+ double val = (request[i] - out[i]) / slopes[i];
579
+ if (isnan(dx_next_top) || val < dx_next_top) {
580
+ dx_next_top = val;
581
+ }
582
+ }
583
+ }
584
+
585
+ // Compute how much supply is needed to activate the next target
586
+ // (last target will return nan)
587
+ double dx_next_start = (distances[c_i] - out[c_i]) / slopes[c_i];
588
+
589
+ // Determine next allocation step size:
590
+ // smallest of (next completion, next activation, remaining supply)
591
+ double dx = dx_next_top;
592
+
593
+ if (!isnan(dx_next_start) && dx_next_start < dx) {
594
+ dx = dx_next_start;
595
+ }
596
+ if (supply < dx) {
597
+ dx = supply;
598
+ }
599
+
600
+ // Distribute this increment of supply across active targets
601
+ for (size_t i = 0; i < m; i++) {
602
+ out[i] += slopes[i] * dx;
603
+ }
604
+
605
+ // If we reached the threshold to activate the next target
606
+ if (fabs(dx - dx_next_start) <= (1e-10 * fabs(dx_next_start) + 1e-16)) {
607
+ // A new target will start in the next loop
608
+ c_i++;
609
+
610
+ // Active the next targetif its request is different than 0
611
+ if (c_i < m) active[c_i] = true;
612
+ }
613
+
614
+ // If any targets have reached their requested amount, deactivate them
615
+ if (dx == dx_next_top) {
616
+ for (size_t i = 0; i < m; i++) {
617
+ if (fabs(out[i] - request[i]) <= 1e-12) {
618
+ active[i] = false;
619
+ }
620
+ }
621
+ }
622
+
623
+ // Reduce remaining supply
624
+ supply -= dx;
625
+ }
626
+
627
+ // Return the distributed supply in the original order
628
+ // adding to it again the request 0 if the where removed
629
+ for (size_t i = 0; i < m; i++) {
630
+ out_return[idx[i]] = out[i];
631
+ }
632
+
633
+ return out_return;
634
+ }
package/src/c/macros.c CHANGED
@@ -1,4 +1 @@
1
1
  #include "sde.h"
2
-
3
- extern double _time;
4
- extern double _time_step;
package/src/c/makefile CHANGED
@@ -1,5 +1,5 @@
1
1
  # Do "export P={c-file-basename}" before running this makefile.
2
- OBJECTS=main.o vensim.o model.o macros.o
2
+ OBJECTS=main.o vensim.o allocation.o model.o macros.o
3
3
  CFLAGS=-Wall -O2
4
4
  LDLIBS=
5
5
 
package/src/c/vensim.c CHANGED
@@ -1,8 +1,5 @@
1
1
  #include "sde.h"
2
2
 
3
- extern double _time;
4
- extern double _time_step;
5
-
6
3
  double _epsilon = 1e-6;
7
4
 
8
5
  //
@@ -371,367 +368,77 @@ double* _VECTOR_SORT_ORDER(double* vector, size_t size, double direction) {
371
368
  }
372
369
 
373
370
  //
374
- // ALLOCATE AVAILABLE
375
- //
376
- // Mathematical functions for calculating the normal pdf and cdf at a point x
377
- double __pdf_normal(double x, double mu, double sigma) {
378
- double base = 1.0 / (sigma * sqrt(2.0 * M_PI));
379
- double exponent = -pow(x - mu, 2.0) / (2.0 * sigma * sigma);
380
- return base * exp(exponent);
381
- }
382
- double __cdf_unit_normal_P(double x) {
383
- // Zelen & Severo (1964) in Handbook Of Mathematical Functions, Abramowitz and Stegun, 26.2.17
384
- double p = 0.2316419;
385
- double b[5] = {0.31938153, -0.356563782, 1.781477937, -1.821255978, 1.330274429};
386
- double t = 1.0 / (1.0 + p * x);
387
- double y = 0.0;
388
- double k = t;
389
- for (size_t i = 0; i < 5; i++) {
390
- y += b[i] * k;
391
- k *= t;
392
- }
393
- return 1.0 - __pdf_normal(x, 0.0, 1.0) * y;
394
- }
395
- double __cdf_unit_normal_Q(double x) {
396
- // Calculate the unit cumulative distribution function from x to +∞, often known as Q(x).
397
- return x >= 0.0 ? 1.0 - __cdf_unit_normal_P(x) : __cdf_unit_normal_P(-x);
398
- }
399
- double __cdf_normal_Q(double x, double sigma) { return __cdf_unit_normal_Q(x / sigma); }
400
- // Access the doubly-subscripted priority profiles array by pointer.
401
- enum { PTYPE, PPRIORITY, PWIDTH, PEXTRA };
402
- double __get_pp(double* pp, size_t iProfile, size_t iElement) {
403
- const int NUM_PP = PEXTRA - PTYPE + 1;
404
- return *(pp + iProfile * NUM_PP + iElement);
405
- }
406
- #define ALLOCATIONS_BUFSIZE 60
407
- // #define PRINT_ALLOCATIONS_DEBUG_INFO
408
- double* _ALLOCATE_AVAILABLE(
409
- double* requested_quantities, double* priority_profiles, double available_resource, size_t num_requesters) {
410
- // requested_quantities points to an array of length num_requesters.
411
- // priority_profiles points to an array of num_requesters arrays of length 4.
412
- // The priority profiles give the mean and standard deviation of normal curves used to allocate
413
- // the available resource, with a higher mean indicating a higher priority. The search space for
414
- // allocations that match the available resource is the x axis with tails on both ends of the curves.
415
- static double allocations[ALLOCATIONS_BUFSIZE];
416
- if (num_requesters > ALLOCATIONS_BUFSIZE) {
417
- fprintf(stderr, "_ALLOCATE_AVAILABLE num_requesters exceeds internal maximum size of %d\n", ALLOCATIONS_BUFSIZE);
418
- return NULL;
419
- }
420
- // Limit the search to this number of steps.
421
- const size_t max_steps = 100;
422
- // If the available resource is more than the total requests, clamp to the total requests so we don't overallocate.
423
- double total_requests = 0.0;
424
- for (size_t i = 0; i < num_requesters; i++) {
425
- total_requests += requested_quantities[i];
426
- }
427
- available_resource = fmin(available_resource, total_requests);
428
- #ifdef PRINT_ALLOCATIONS_DEBUG_INFO
429
- fprintf(stderr, "\n_ALLOCATE_AVAILABLE time=%g num_requesters=%zu, available_resource=%f, total_requests=%f\n", _time,
430
- num_requesters, available_resource, total_requests);
431
- for (size_t i = 0; i < num_requesters; i++) {
432
- fprintf(stderr, "[%2zu] requested_quantities=%17f mean=%8g sigma=%8g\n", i, requested_quantities[i],
433
- __get_pp(priority_profiles, i, PPRIORITY), __get_pp(priority_profiles, i, PWIDTH));
434
- }
435
- #endif
436
- // Find the minimum and maximum means in the priority curves.
437
- double min_mean = DBL_MAX;
438
- double max_mean = DBL_MIN;
439
- for (size_t i = 0; i < num_requesters; i++) {
440
- min_mean = fmin(__get_pp(priority_profiles, i, PPRIORITY), min_mean);
441
- max_mean = fmax(__get_pp(priority_profiles, i, PPRIORITY), max_mean);
442
- }
443
- // Start the search in the midpoint of the means, with a big first jump scaled to the spread of the means.
444
- double total_allocations = 0.0;
445
- double x = (max_mean + min_mean) / 2.0;
446
- double delta = (max_mean - min_mean) / 2.0;
447
- size_t num_steps = 0;
448
- double last_delta_sign = 1.0;
449
- size_t num_jumps_in_same_direction = 0;
450
- do {
451
- // Calculate allocations for each requester.
452
- for (size_t i = 0; i < num_requesters; i++) {
453
- if (requested_quantities[i] > 0.0) {
454
- double mean = __get_pp(priority_profiles, i, PPRIORITY);
455
- double sigma = __get_pp(priority_profiles, i, PWIDTH);
456
- // The allocation is the area under the requester's normal curve from x out to +∞
457
- // scaled by the size of the request. We integrate over the right-hand side of the
458
- // normal curve so that higher means have higher priority, that is, are allocated more.
459
- // The unit cumulative distribution function integrates to one over all x,
460
- // so we simply multiply by a constant to scale the area under the curve.
461
- allocations[i] = requested_quantities[i] * __cdf_normal_Q(x - mean, sigma);
462
- } else {
463
- allocations[i] = 0.0;
464
- }
465
- }
466
- // Sum the allocations for comparison with the available resource.
467
- total_allocations = 0.0;
468
- for (size_t i = 0; i < num_requesters; i++) {
469
- total_allocations += allocations[i];
470
- }
471
- #ifdef PRINT_ALLOCATIONS_DEBUG_INFO
472
- fprintf(stderr, "x=%-+14g delta=%-+14g Δ=%-+14g total_allocations=%-+14g available_resource=%-+14g\n", x, delta,
473
- fabs(total_allocations - available_resource), total_allocations, available_resource);
474
- #endif
475
- if (++num_steps >= max_steps) {
476
- fprintf(stderr,
477
- "_ALLOCATE_AVAILABLE failed to converge at time=%g with total_allocations=%18f, available_resource=%18f\n",
478
- _time, total_allocations, available_resource);
479
- break;
480
- }
481
- // Set up the next x value by computing a new delta that is usually half the size of the
482
- // previous delta, that is, do a binary search of the x axis. We may jump over the target
483
- // x value, so we may need to change direction.
484
- double delta_sign = total_allocations < available_resource ? -1.0 : 1.0;
485
- // Too many jumps in the same direction can result in the search converging on a point
486
- // that falls short of the target x value. Stop halving the delta when that happens until
487
- // we jump over the target again.
488
- num_jumps_in_same_direction = delta_sign == last_delta_sign ? num_jumps_in_same_direction + 1 : 0;
489
- last_delta_sign = delta_sign;
490
- delta = (delta_sign * fabs(delta)) / (num_jumps_in_same_direction < 3 ? 2.0 : 1.0);
491
- x += delta;
492
- // The search terminates when the total allocations are equal to the available resource
493
- // up to a very small epsilon difference.
494
- } while (fabs(total_allocations - available_resource) > _epsilon);
495
- #ifdef PRINT_ALLOCATIONS_DEBUG_INFO
496
- fprintf(stderr, "converged with Δ=%g in %zu steps\n", fabs(total_allocations - available_resource), num_steps);
497
- fprintf(stderr, "total_allocations=%f, available_resource=%f\n", total_allocations, available_resource);
498
- for (size_t i = 0; i < num_requesters; i++) {
499
- fprintf(stderr, "[%2zu] %f\n", i, allocations[i]);
500
- }
501
- #endif
502
- // Return a pointer to the allocations array the caller passed with the results filled in.
503
- return allocations;
504
- }
505
-
506
- //
507
- // Helper methods for allocate by priority
371
+ // INVERT MATRIX
508
372
  //
509
- double __sum(double* arr, size_t n) {
510
- double total = 0.0;
373
+ double* _INVERT_MATRIX(double* matrix, size_t n) {
374
+ // Invert the n x n matrix using Gauss-Jordan elimination with partial pivoting.
375
+ // The input matrix is not modified. The result buffer is reused across calls
376
+ // and grown as needed, so the caller must copy the values out before the next call.
377
+ static double* work = NULL;
378
+ static double* result = NULL;
379
+ static size_t max_n = 0;
380
+ if (n > max_n) {
381
+ work = realloc(work, n * 2 * n * sizeof(double));
382
+ result = realloc(result, n * n * sizeof(double));
383
+ max_n = n;
384
+ }
385
+ // Build the augmented matrix [A | I], with each row of width 2n.
386
+ size_t w = 2 * n;
511
387
  for (size_t i = 0; i < n; i++) {
512
- total += arr[i];
513
- }
514
- return total;
515
- }
516
-
517
- //
518
- // ALLOCATE BY PRIORITY
519
- //
520
- #define ALLOCATE_BY_PRIORITY_BUFSIZE 60
521
- #define PRINT_ALLOCATIONS_DEBUG_INFO
522
-
523
- double* _ALLOCATE_BY_PRIORITY(
524
- double* request_quantities, double* priority_values, double size, double width, double supply, size_t num_requesters) {
525
-
526
- // request points to an array of length num_requesters.
527
- // priority points to an array of length num_requesters.
528
- // size is the number of elements across which allocation is being made.
529
- // width specifies how big a gap in priority is required to have the allocation go first to
530
- // higher priority with only leftovers going to lower priority.
531
- // supply is the total supply available to fulfill all requests.
532
-
533
- // Allocate by priority allocates supply to requesters based on order of priority. The way in
534
- // which the rationing works is determined by the relative priorities and the width parameter.
535
-
536
- static double allocations[ALLOCATE_BY_PRIORITY_BUFSIZE];
537
- if (num_requesters > ALLOCATE_BY_PRIORITY_BUFSIZE) {
538
- fprintf(stderr, "_ALLOCATE_BY_PRIORITY num_requesters exceeds internal maximum size of %d\n", ALLOCATE_BY_PRIORITY_BUFSIZE);
539
- return NULL;
540
- }
541
-
542
- // Validate request values (must be non-negative)
543
- for (size_t i = 0; i < num_requesters; i++) {
544
- if (request_quantities[i] < -_epsilon) {
545
- fprintf(stderr,
546
- "_ALLOCATE_BY_PRIORITY encountered negative request value at index %zu: %f\n",
547
- i, request_quantities[i]);
548
- return NULL;
549
- }
550
- }
551
-
552
- // Validate width (must not be negative)
553
- if (width < -_epsilon) {
554
- fprintf(stderr,
555
- "_ALLOCATE_BY_PRIORITY encountered invalid width value: %f\n"
556
- "Width must not be negative.\n",
557
- width);
558
- return NULL;
559
- }
560
-
561
- // Validate supply (must not be negative)
562
- if (supply < -_epsilon) {
563
- fprintf(stderr,
564
- "_ALLOCATE_BY_PRIORITY encountered invalid supply value: %f\n"
565
- "Supply must not be negative.\n",
566
- supply);
567
- return NULL;
568
- }
569
-
570
- // If supply > sum(request), return request
571
- if (supply > __sum(request_quantities, num_requesters)) {
572
- return request_quantities;
573
- }
574
-
575
- // If supply = 0, all targets get allocated 0
576
- if(fabs(supply) < _epsilon) {
577
- return allocations;
578
- }
579
-
580
- static double out_return[ALLOCATE_BY_PRIORITY_BUFSIZE];
581
-
582
- // Remove request 0 targets and order by priority
583
- bool is_0[ALLOCATE_BY_PRIORITY_BUFSIZE];
584
- size_t idx[ALLOCATE_BY_PRIORITY_BUFSIZE];
585
- size_t m = 0;
586
-
587
- for (size_t i = 0; i < num_requesters; i++) {
588
- is_0[i] = request_quantities[i] == 0.0;
589
- if (!is_0[i]) {
590
- idx[m++] = i;
388
+ for (size_t j = 0; j < n; j++) {
389
+ work[i * w + j] = matrix[i * n + j];
390
+ work[i * w + n + j] = (i == j) ? 1.0 : 0.0;
591
391
  }
592
392
  }
593
-
594
- // Sort indices in `idx` by descending `priority_values` (highest priority first)
595
- for (size_t i = 0; i < m; i++) {
596
- for (size_t j = i + 1; j < m; j++) {
597
- if (priority_values[idx[j]] > priority_values[idx[i]]) {
598
- size_t tmp = idx[i];
599
- idx[i] = idx[j];
600
- idx[j] = tmp;
601
- }
602
- }
603
- }
604
-
605
- // Populate local arrays with request and priority values reordered according to idx
606
- double request[ALLOCATE_BY_PRIORITY_BUFSIZE];
607
- double priority[ALLOCATE_BY_PRIORITY_BUFSIZE];
608
-
609
- for (size_t i = 0; i < m; i++) {
610
- request[i] = (double)request_quantities[idx[i]];
611
- priority[i] = priority_values[idx[i]];
612
- }
613
-
614
- // Create the outputs array
615
- for (size_t i = 0; i < num_requesters; i++) {
616
- out_return[i] = 0.0;
617
- }
618
-
619
- double out[ALLOCATE_BY_PRIORITY_BUFSIZE] = {0.0};
620
-
621
- // Compute the distances between target supply and next target start
622
- double distances[ALLOCATE_BY_PRIORITY_BUFSIZE];
623
-
624
- for (size_t i = 0; i < m; i++) {
625
- distances[i] = NAN;
626
- }
627
-
628
- // Last target will have NaN as distances as there are no more targets after
629
- for (size_t i = 0; i + 1 < m; i++) {
630
- double d = -(priority[i + 1] - priority[i]) / width;
631
- if (d > 1.0) d = 1.0;
632
- distances[i] = d * request[i];
633
- }
634
-
635
- // Index of the current activated target
636
- bool active[ALLOCATE_BY_PRIORITY_BUFSIZE] = {false};
637
- active[0] = true;
638
-
639
- // Index of the last activated target
640
- size_t c_i = 0;
641
-
642
- // Continue allocating until supply is exhausted
643
- while (supply > _epsilon) {
644
- // Check if there are any active targets left
645
- bool any_active = false;
646
- for (size_t i = 0; i < m; i++) {
647
- if (active[i]) {
648
- any_active = true;
649
- break;
393
+ for (size_t k = 0; k < n; k++) {
394
+ // Find the pivot row with the largest absolute value in column k.
395
+ size_t pivot = k;
396
+ double max = fabs(work[k * w + k]);
397
+ for (size_t i = k + 1; i < n; i++) {
398
+ double v = fabs(work[i * w + k]);
399
+ if (v > max) {
400
+ max = v;
401
+ pivot = i;
650
402
  }
651
403
  }
652
- if (!any_active) {
653
- break;
654
- }
655
-
656
- // Compute proportional allocation weights ("slopes") for active targets
657
- double slopes[ALLOCATE_BY_PRIORITY_BUFSIZE];
658
- double slope_sum = 0.0;
659
-
660
- for (size_t i = 0; i < m; i++) {
661
- if (active[i]) {
662
- slopes[i] = request[i]; // weight based on requested amount
663
- slope_sum += slopes[i];
664
- } else {
665
- slopes[i] = 0.0;
404
+ if (max == 0.0) {
405
+ // The matrix is singular; fill the result with _NA_ values.
406
+ for (size_t i = 0; i < n * n; i++) {
407
+ result[i] = _NA_;
666
408
  }
409
+ return result;
667
410
  }
668
-
669
- // Normalize slopes so total allocation proportion sums to 1
670
- for (size_t i = 0; i < m; i++) {
671
- slopes[i] /= slope_sum;
672
- }
673
-
674
- // Compute how much supply much be given to any target reach its request
675
- double dx_next_top = NAN;
676
-
677
- for (size_t i = 0; i < m; i++) {
678
- if (active[i]) {
679
- double val = (request[i] - out[i]) / slopes[i];
680
- if (isnan(dx_next_top) || val < dx_next_top) {
681
- dx_next_top = val;
682
- }
411
+ if (pivot != k) {
412
+ for (size_t j = 0; j < w; j++) {
413
+ double tmp = work[k * w + j];
414
+ work[k * w + j] = work[pivot * w + j];
415
+ work[pivot * w + j] = tmp;
683
416
  }
684
417
  }
685
-
686
- // Compute how much supply is needed to activate the next target
687
- // (last target will return nan)
688
- double dx_next_start = (distances[c_i] - out[c_i]) / slopes[c_i];
689
-
690
- // Determine next allocation step size:
691
- // smallest of (next completion, next activation, remaining supply)
692
- double dx = dx_next_top;
693
-
694
- if (!isnan(dx_next_start) && dx_next_start < dx) {
695
- dx = dx_next_start;
696
- }
697
- if (supply < dx) {
698
- dx = supply;
699
- }
700
-
701
- // Distribute this increment of supply across active targets
702
- for (size_t i = 0; i < m; i++) {
703
- out[i] += slopes[i] * dx;
704
- }
705
-
706
- // If we reached the threshold to activate the next target
707
- if (fabs(dx - dx_next_start) <= (1e-10 * fabs(dx_next_start) + 1e-16)) {
708
- // A new target will start in the next loop
709
- c_i++;
710
-
711
- // Active the next targetif its request is different than 0
712
- if (c_i < m) active[c_i] = true;
418
+ // Scale the pivot row so the pivot element becomes 1.
419
+ double p = work[k * w + k];
420
+ for (size_t j = 0; j < w; j++) {
421
+ work[k * w + j] /= p;
713
422
  }
714
-
715
- // If any targets have reached their requested amount, deactivate them
716
- if (dx == dx_next_top) {
717
- for (size_t i = 0; i < m; i++) {
718
- if (fabs(out[i] - request[i]) <= 1e-12) {
719
- active[i] = false;
423
+ // Eliminate column k from all other rows.
424
+ for (size_t i = 0; i < n; i++) {
425
+ if (i != k) {
426
+ double f = work[i * w + k];
427
+ if (f != 0.0) {
428
+ for (size_t j = 0; j < w; j++) {
429
+ work[i * w + j] -= f * work[k * w + j];
430
+ }
720
431
  }
721
432
  }
722
433
  }
723
-
724
- // Reduce remaining supply
725
- supply -= dx;
726
434
  }
727
-
728
- // Return the distributed supply in the original order
729
- // adding to it again the request 0 if the where removed
730
- for (size_t i = 0; i < m; i++) {
731
- out_return[idx[i]] = out[i];
435
+ // The right half of the augmented matrix now holds the inverse.
436
+ for (size_t i = 0; i < n; i++) {
437
+ for (size_t j = 0; j < n; j++) {
438
+ result[i * n + j] = work[i * w + n + j];
439
+ }
732
440
  }
733
-
734
- return out_return;
441
+ return result;
735
442
  }
736
443
 
737
444
  //
package/src/c/vensim.h CHANGED
@@ -40,9 +40,14 @@ extern "C" {
40
40
 
41
41
  double* _ALLOCATE_AVAILABLE(double* requested_quantities, double* priority_profiles, double available_resource, size_t num_requesters);
42
42
  double* _ALLOCATE_BY_PRIORITY(double* request, double* priority, double size, double width, double supply, size_t num_requesters);
43
+ double* _DEMAND_AT_PRICE(double* demand_quantities, double* demand_profiles, double price, size_t num_demanders);
44
+ double _FIND_MARKET_PRICE(double* demand_quantities, double* demand_profiles, double* supply_quantities,
45
+ double* supply_profiles, size_t num_demanders, size_t num_suppliers);
46
+ double* _INVERT_MATRIX(double* matrix, size_t n);
43
47
  double _PULSE(double start, double width);
44
48
  double _PULSE_TRAIN(double start, double width, double interval, double end);
45
49
  double _RAMP(double slope, double start_time, double end_time);
50
+ double* _SUPPLY_AT_PRICE(double* supply_quantities, double* supply_profiles, double price, size_t num_suppliers);
46
51
  double* _VECTOR_SORT_ORDER(double* vector, size_t size, double direction);
47
52
  double _XIDZ(double a, double b, double x);
48
53
  double _ZIDZ(double a, double b);