Skip to content

Commit c67be25

Browse files
Fix stretch 0 random functions (#467)
* Add inf if scale 0 division * Add tests
1 parent 2cd61a0 commit c67be25

File tree

4 files changed

+96
-7
lines changed

4 files changed

+96
-7
lines changed

docs/whats_new.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
What's New
22
==========
3-
v3.14.3 (2025/03/XX)
3+
v3.14.3 (2025/03/23)
44
--------------------
55
New Features
66
~~~~~~~~~~~~
@@ -14,6 +14,7 @@ Deprecations
1414
Bug fixes
1515
~~~~~~~~~
1616
- Make XMILE operator parsing case-insensitive (:issue:`463`). (`@benslavin <https://github.com/benslavin>`_)
17+
- Avoid :py:class:`ZeroDivisionError` when stretch is 0 in RANDOM NORMAL and RANDOM EXPONENTIAL (:issue:`465`). (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
1718

1819
Documentation
1920
~~~~~~~~~~~~~

pysd/builders/python/python_functions.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -111,11 +111,14 @@
111111
"np.random.uniform(%(0)s, %(1)s, size=%(size)s)",
112112
(("numpy",),)),
113113
"random_normal": (
114-
"stats.truncnorm.rvs((%(0)s-%(2)s)/%(3)s, (%(1)s-%(2)s)/%(3)s,"
115-
" loc=%(2)s, scale=%(3)s, size=%(size)s)",
116-
(("scipy", "stats"),)),
114+
"stats.truncnorm.rvs("
115+
"xidz(%(0)s-%(2)s, %(3)s, -np.inf),"
116+
"xidz(%(1)s-%(2)s, %(3)s, np.inf),"
117+
"loc=%(2)s, scale=%(3)s, size=%(size)s)",
118+
(("scipy", "stats"), ("numpy",), ("functions", "xidz"))),
117119
"random_exponential": (
118-
"stats.truncexpon.rvs((%(1)s-np.maximum(%(0)s, %(2)s))/%(3)s,"
119-
" loc=np.maximum(%(0)s, %(2)s), scale=%(3)s, size=%(size)s)",
120-
(("scipy", "stats"), ("numpy",),)),
120+
"stats.truncexpon.rvs("
121+
"xidz(%(1)s-np.maximum(%(0)s, %(2)s), %(3)s, np.inf),"
122+
"loc=np.maximum(%(0)s, %(2)s), scale=%(3)s, size=%(size)s)",
123+
(("scipy", "stats"), ("numpy",), ("functions", "xidz"))),
121124
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{UTF-8}
2+
3+
normal1=
4+
RANDOM NORMAL(-1, 10, 5, 0, 1)
5+
~
6+
~ |
7+
8+
normal2=
9+
RANDOM NORMAL(5, 7, 6, 0, 1)
10+
~
11+
~ |
12+
13+
exponential1=
14+
RANDOM EXPONENTIAL(1, 3, 0, 0, 1)
15+
~
16+
~ |
17+
18+
exponential2=
19+
RANDOM EXPONENTIAL(0, 10, 7, 0, 1)
20+
~
21+
~ |
22+
23+
********************************************************
24+
.Control
25+
********************************************************~
26+
Simulation Control Parameters
27+
|
28+
29+
FINAL TIME = 2
30+
~ Month
31+
~ The final time for the simulation.
32+
|
33+
34+
INITIAL TIME = 0
35+
~ Month
36+
~ The initial time for the simulation.
37+
|
38+
39+
SAVEPER =
40+
TIME STEP
41+
~ Month [0,?]
42+
~ The frequency with which output is stored.
43+
|
44+
45+
TIME STEP = 1
46+
~ Month [0,?]
47+
~ The time step for the simulation.
48+
|

tests/pytest_pysd/pytest_random.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from scipy import stats
88

99
import pysd
10+
from pysd.py_backend.functions import xidz
1011
from pysd.translators.vensim.vensim_element import Component
1112
from pysd.builders.python.python_expressions_builder import\
1213
CallBuilder, NumericBuilder
@@ -78,6 +79,42 @@ def test_translate(self, model_path):
7879
assert len(np.unique(values)) == np.prod(values.shape)
7980

8081

82+
class TestRandomScale0Model:
83+
"""Submodel selecting class"""
84+
# messages for selecting submodules
85+
@pytest.fixture(scope="class")
86+
def model_path(self, shared_tmpdir, _root):
87+
"""
88+
Copy test folder to a temporary folder therefore we avoid creating
89+
PySD model files in the original folder
90+
"""
91+
new_file = shared_tmpdir.joinpath("test_random_scale0.mdl")
92+
shutil.copy(
93+
_root.joinpath("more-tests/random/test_random_scale0.mdl"),
94+
new_file
95+
)
96+
return new_file
97+
98+
def test_translate_run(self, model_path):
99+
"""
100+
Translate the model and run it.
101+
"""
102+
# expected file
103+
model = pysd.read_vensim(model_path)
104+
random_vars = [
105+
"normal1",
106+
"normal2",
107+
"exponential1",
108+
"exponential2"
109+
]
110+
out = model.run(return_columns=random_vars, flatten_output=False)
111+
# expected values
112+
assert np.all(out['normal1'] == 5)
113+
assert np.all(out['normal2'] == 6)
114+
assert np.all(out['exponential1'] == 1)
115+
assert np.all(out['exponential2'] == 7)
116+
117+
81118
class TestRandomVensim():
82119
@pytest.fixture(scope="function")
83120
def data_raw(self, input_file, _test_random):

0 commit comments

Comments
 (0)