@sdeverywhere/cli 0.7.43 → 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 +3 -3
- package/src/c/allocation.c +634 -0
- package/src/c/macros.c +0 -3
- package/src/c/makefile +1 -1
- package/src/c/vensim.c +55 -348
- package/src/c/vensim.h +5 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sdeverywhere/cli",
|
|
3
|
-
"version": "0.7.
|
|
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.
|
|
15
|
-
"@sdeverywhere/compile": "^0.7.
|
|
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
package/src/c/makefile
CHANGED
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
|
-
//
|
|
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
|
|
510
|
-
|
|
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
|
-
|
|
513
|
-
|
|
514
|
-
|
|
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
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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 (
|
|
653
|
-
|
|
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
|
-
|
|
670
|
-
|
|
671
|
-
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (
|
|
719
|
-
|
|
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
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
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);
|