0
# Mathematical and Utility Functions
1
2
PySD's functions module provides a comprehensive set of mathematical and utility functions equivalent to Vensim/XMILE built-ins, enabling System Dynamics models to perform complex calculations and logic operations.
3
4
## Capabilities
5
6
### Mathematical Functions
7
8
Core mathematical operations with special handling for System Dynamics modeling requirements.
9
10
```python { .api }
11
def if_then_else(condition, val_if_true, val_if_false):
12
"""
13
Conditional logic implementation.
14
15
Parameters:
16
- condition: bool or array - Conditional expression
17
- val_if_true: scalar or array - Value when condition is True
18
- val_if_false: scalar or array - Value when condition is False
19
20
Returns:
21
scalar or array: Selected value based on condition
22
"""
23
24
def xidz(numerator, denominator, x):
25
"""
26
Division with specified result for 0/0.
27
28
Parameters:
29
- numerator: scalar or array - Numerator value
30
- denominator: scalar or array - Denominator value
31
- x: scalar or array - Value returned when both numerator and denominator are 0
32
33
Returns:
34
scalar or array: Division result or x for 0/0 case
35
"""
36
37
def zidz(numerator, denominator):
38
"""
39
Division returning 0 for 0/0.
40
41
Parameters:
42
- numerator: scalar or array - Numerator value
43
- denominator: scalar or array - Denominator value
44
45
Returns:
46
scalar or array: Division result or 0 for 0/0 case
47
"""
48
49
def integer(x):
50
"""
51
Integer truncation.
52
53
Parameters:
54
- x: scalar or array - Input value
55
56
Returns:
57
int or array: Truncated integer value
58
"""
59
60
def quantum(a, b):
61
"""
62
Quantum function - rounds a to nearest multiple of b.
63
64
Parameters:
65
- a: scalar or array - Value to round
66
- b: scalar or array - Multiple to round to
67
68
Returns:
69
scalar or array: Rounded value
70
"""
71
72
def modulo(x, m):
73
"""
74
Modulo operation.
75
76
Parameters:
77
- x: scalar or array - Input value
78
- m: scalar or array - Modulo divisor
79
80
Returns:
81
scalar or array: Remainder after division
82
"""
83
```
84
85
#### Usage Examples
86
87
```python
88
from pysd import functions
89
90
# Conditional logic
91
temperature = 25
92
comfort_level = functions.if_then_else(
93
temperature > 20,
94
"comfortable",
95
"cold"
96
)
97
98
# Safe division avoiding division by zero
99
growth_rate = functions.xidz(
100
new_population - old_population,
101
old_population,
102
0 # Return 0 if both are 0
103
)
104
105
# Integer operations
106
whole_units = functions.integer(3.7) # Returns 3
107
```
108
109
### Time Functions
110
111
Functions for time-based calculations and temporal patterns.
112
113
```python { .api }
114
def ramp(time, slope, start, finish=None):
115
"""
116
Ramp function over time.
117
118
Parameters:
119
- time: scalar or array - Current time value
120
- slope: scalar - Rate of increase per time unit
121
- start: scalar - Time when ramp begins
122
- finish: scalar or None - Time when ramp ends (None for no end)
123
124
Returns:
125
scalar or array: Ramp value at given time
126
"""
127
128
def step(time, value, tstep):
129
"""
130
Step function at specified time.
131
132
Parameters:
133
- time: scalar or array - Current time value
134
- value: scalar - Step height
135
- tstep: scalar - Time when step occurs
136
137
Returns:
138
scalar or array: 0 before tstep, value at and after tstep
139
"""
140
141
def pulse(time, start, repeat_time=0, width=None, magnitude=None, end=None):
142
"""
143
Pulse and pulse train functions.
144
145
Parameters:
146
- time: scalar or array - Current time value
147
- start: scalar - Time when first pulse begins
148
- repeat_time: scalar - Time between pulses (0 for single pulse)
149
- width: scalar or None - Pulse duration (default: time step)
150
- magnitude: scalar or None - Pulse height (default: 1/time_step)
151
- end: scalar or None - Final time of the pulse (None for no end)
152
153
Returns:
154
scalar or array: Pulse value at given time
155
"""
156
157
def get_time_value(time, relativeto, offset, measure):
158
"""
159
Extract time values with offsets.
160
161
Parameters:
162
- time: scalar - Current time
163
- relativeto: str - Reference point ("start", "end")
164
- offset: scalar - Time offset from reference
165
- measure: str - Time unit ("years", "months", "days", "hours", "minutes", "seconds")
166
167
Returns:
168
scalar: Calculated time value
169
"""
170
```
171
172
#### Usage Examples
173
174
```python
175
# Create time-based patterns
176
current_time = 15
177
178
# Gradual increase starting at time 10
179
production_ramp = functions.ramp(current_time, slope=2, start=10)
180
181
# Policy change at time 20
182
policy_effect = functions.step(current_time, value=100, tstep=20)
183
184
# Periodic maintenance every 12 time units
185
maintenance = functions.pulse(current_time, start=0, repeat_time=12, width=1)
186
```
187
188
### Array Functions
189
190
Operations on multi-dimensional arrays and subscripted variables.
191
192
```python { .api }
193
def sum(x, dim=None):
194
"""
195
Sum along specified dimensions.
196
197
Parameters:
198
- x: array - Input array
199
- dim: str or list or None - Dimensions to sum over (None for all)
200
201
Returns:
202
scalar or array: Sum result
203
"""
204
205
def prod(x, dim=None):
206
"""
207
Product along dimensions.
208
209
Parameters:
210
- x: array - Input array
211
- dim: str or list or None - Dimensions to multiply over
212
213
Returns:
214
scalar or array: Product result
215
"""
216
217
def vmin(x, dim=None):
218
"""
219
Minimum along dimensions.
220
221
Parameters:
222
- x: array - Input array
223
- dim: str or list or None - Dimensions to find minimum over
224
225
Returns:
226
scalar or array: Minimum value
227
"""
228
229
def vmax(x, dim=None):
230
"""
231
Maximum along dimensions.
232
233
Parameters:
234
- x: array - Input array
235
- dim: str or list or None - Dimensions to find maximum over
236
237
Returns:
238
scalar or array: Maximum value
239
"""
240
241
def invert_matrix(mat):
242
"""
243
Matrix inversion.
244
245
Parameters:
246
- mat: 2D array - Input matrix
247
248
Returns:
249
2D array: Inverted matrix
250
251
Raises:
252
LinAlgError: If matrix is singular
253
"""
254
```
255
256
#### Usage Examples
257
258
```python
259
import numpy as np
260
261
# Array operations on subscripted variables
262
population_by_region = np.array([1000, 1500, 800, 1200])
263
264
# Total population across all regions
265
total_pop = functions.sum(population_by_region)
266
267
# Find region with maximum population
268
max_pop = functions.vmax(population_by_region)
269
270
# Matrix operations for economic modeling
271
input_output_matrix = np.array([[0.2, 0.3], [0.4, 0.1]])
272
inverse_matrix = functions.invert_matrix(input_output_matrix)
273
```
274
275
### Vector Operations
276
277
Specialized functions for vector manipulation and analysis.
278
279
```python { .api }
280
def vector_select(selection_array, expression_array, dim, missing_vals, numerical_action, error_action):
281
"""
282
Vector selection operations implementing Vensim's VECTOR SELECT function.
283
284
Parameters:
285
- selection_array: xarray.DataArray - Selection array with zeros and non-zero values
286
- expression_array: xarray.DataArray - Expression that elements are selected from
287
- dim: list - Dimensions to apply the function over
288
- missing_vals: float - Value to use when there are only zeroes in selection array
289
- numerical_action: int - Action to take (0=sum, 1=product, 2=min, 3=max)
290
- error_action: int - Error handling action
291
292
Returns:
293
xarray.DataArray: Selected vector elements based on selection criteria
294
"""
295
296
def vector_sort_order(vector, direction=1):
297
"""
298
Vector sorting indices.
299
300
Parameters:
301
- vector: array - Input vector
302
- direction: int - Sort direction (1 for ascending, -1 for descending)
303
304
Returns:
305
array: Indices that would sort the vector
306
"""
307
308
def vector_reorder(vector, svector):
309
"""
310
Vector reordering based on sort indices.
311
312
Parameters:
313
- vector: array - Vector to reorder
314
- svector: array - Sort indices
315
316
Returns:
317
array: Reordered vector
318
"""
319
320
def vector_rank(vector, direction=1):
321
"""
322
Vector ranking.
323
324
Parameters:
325
- vector: array - Input vector
326
- direction: int - Ranking direction (1 for ascending, -1 for descending)
327
328
Returns:
329
array: Rank of each element (1-based)
330
"""
331
```
332
333
#### Usage Examples
334
335
```python
336
# Vector analysis
337
sales_data = np.array([150, 200, 120, 180, 220])
338
339
# Get sorting order
340
sort_indices = functions.vector_sort_order(sales_data, direction=-1) # Descending
341
342
# Reorder vector
343
sorted_sales = functions.vector_reorder(sales_data, sort_indices)
344
345
# Get rankings
346
sales_ranks = functions.vector_rank(sales_data, direction=-1)
347
print(f"Sales rankings: {sales_ranks}")
348
```
349
350
### Utility Functions
351
352
Helper functions for model development and debugging.
353
354
```python { .api }
355
def active_initial(stage, expr, init_val):
356
"""
357
Active initial value handling.
358
359
Parameters:
360
- stage: str - Initialization stage
361
- expr: callable - Expression to evaluate
362
- init_val: scalar - Initial value
363
364
Returns:
365
scalar: Appropriate value based on stage
366
"""
367
368
def incomplete(*args):
369
"""
370
Placeholder for incomplete functions.
371
372
Parameters:
373
- *args: Any arguments
374
375
Returns:
376
float: Returns 0.0
377
378
Note:
379
Used as placeholder during model development
380
"""
381
382
def not_implemented_function(*args):
383
"""
384
Placeholder for unimplemented functions.
385
386
Parameters:
387
- *args: Any arguments
388
389
Raises:
390
NotImplementedError: Always raises this error
391
392
Note:
393
Used to mark functions that need implementation
394
"""
395
```
396
397
### Function Usage in Models
398
399
These functions are automatically available within translated System Dynamics models and can also be used directly:
400
401
```python
402
import pysd
403
from pysd import functions
404
405
# Load model that uses these functions internally
406
model = pysd.read_vensim('complex_model.mdl')
407
408
# Functions are used automatically during simulation
409
results = model.run()
410
411
# Can also use functions directly for custom calculations
412
custom_result = functions.if_then_else(
413
results['Population'].iloc[-1] > 10000,
414
"High population",
415
"Low population"
416
)
417
```
418
419
### Error Handling
420
421
Functions include appropriate error handling for System Dynamics contexts:
422
423
- Division by zero returns appropriate defaults or specified values
424
- Array dimension mismatches are handled gracefully
425
- Missing values are managed according to SD conventions
426
427
```python
428
# Safe operations that won't crash simulations
429
safe_ratio = functions.zidz(numerator, 0) # Returns 0 instead of error
430
conditional_value = functions.if_then_else(
431
denominator != 0,
432
numerator / denominator,
433
0
434
)
435
```