Skip to content

Commit c9142dd

Browse files
Break test using parametrize
1 parent f4edff0 commit c9142dd

File tree

3 files changed

+182
-152
lines changed

3 files changed

+182
-152
lines changed

pysd/py_backend/output.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -362,6 +362,9 @@ def __create_ds_vars(self, model, capture_elements, time_dim=True):
362362
var = self.ds.createVariable(key, "f8", dims, **kwargs)
363363
# adding metadata for each var from the model.doc
364364
for col in model.doc.columns:
365+
if col in ["Subscripts", "Limits"]:
366+
# pass those that cannot be saved as attributes
367+
continue
365368
var.setncattr(
366369
col,
367370
model.doc.loc[model.doc["Py Name"] == key, col].values[0]

tests/pytest_pysd/pytest_output.py

Lines changed: 178 additions & 151 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from pathlib import Path
2-
from warnings import simplefilter, catch_warnings
2+
import shutil
33

44
import pytest
55
import numpy as np
@@ -14,29 +14,40 @@
1414
import pysd
1515

1616

17-
_root = Path(__file__).parent.parent
18-
19-
test_model_look = _root.joinpath(
17+
test_model_look = Path(
2018
"test-models/tests/get_lookups_subscripted_args/"
21-
+ "test_get_lookups_subscripted_args.mdl")
22-
test_model_constants = _root.joinpath(
19+
"test_get_lookups_subscripted_args.mdl"
20+
)
21+
test_model_constants = Path(
2322
"test-models/tests/get_constants_subranges/"
2423
"test_get_constants_subranges.mdl"
2524
)
26-
test_model_numeric_coords = _root.joinpath(
25+
test_model_numeric_coords = Path(
2726
"test-models/tests/subscript_1d_arrays/"
2827
"test_subscript_1d_arrays.mdl"
2928
)
30-
test_variable_step = _root.joinpath(
29+
test_variable_step = Path(
3130
"test-models/tests/control_vars/"
3231
"test_control_vars.mdl"
3332
)
34-
test_partial_definitions = _root.joinpath(
33+
test_partial_definitions = Path(
3534
"test-models/tests/partial_range_definitions/"
3635
"test_partial_range_definitions.mdl"
3736
)
3837

3938

39+
@pytest.fixture
40+
@pytest.mark.filterwarnings("ignore")
41+
def model(_root, tmp_path, model_path):
42+
"""
43+
Copy model to the tmp_path and translate it
44+
"""
45+
46+
target = tmp_path / model_path.parent
47+
shutil.copytree(_root / model_path.parent, target)
48+
return pysd.read_vensim(target / model_path.name)
49+
50+
4051
class TestOutput():
4152

4253
def test_output_handler_interface(self):
@@ -124,151 +135,167 @@ class EmptyHandler(OutputHandlerInterface):
124135
EmptyHandler.add_run_elements(
125136
EmptyHandler, "model", "capture")
126137

127-
def test_output_nc(self, shared_tmpdir):
128-
model = pysd.read_vensim(test_model_look)
129-
model.progress = False
130-
131-
out_file = shared_tmpdir.joinpath("results.nc")
132-
133-
with catch_warnings(record=True) as w:
134-
simplefilter("always")
135-
model.run(output_file=out_file)
138+
@pytest.mark.parametrize(
139+
"model_path,dims,values",
140+
[
141+
(
142+
test_model_look,
143+
{
144+
"Rows": 2,
145+
"Dim": 2,
146+
"time": 61
147+
},
148+
{
149+
"lookup_1d_time": (("time",), None),
150+
"d2d": (("time", "Rows", "Dim"), None),
151+
"initial_time": (tuple(), 0),
152+
"final_time": (tuple(), 30),
153+
"saveper": (tuple(), 0.5),
154+
"time_step": (tuple(), 0.5)
155+
}
156+
157+
),
158+
(
159+
test_model_constants,
160+
{
161+
"dim1": 5,
162+
"dim1a": 2,
163+
"dim1c": 3,
164+
'time': 2
165+
},
166+
{
167+
"constant": (
168+
("dim1",),
169+
np.array([0., 0., 1., 15., 50.])
170+
)
171+
}
172+
),
173+
(
174+
test_model_numeric_coords,
175+
{
176+
"One Dimensional Subscript": 3,
177+
'time': 101
178+
},
179+
{
180+
"rate_a": (
181+
("One Dimensional Subscript",),
182+
np.array([0.01, 0.02, 0.03])),
183+
"stock_a": (
184+
("time", "One Dimensional Subscript"),
185+
np.array([
186+
np.arange(0, 1.0001, 0.01),
187+
np.arange(0, 2.0001, 0.02),
188+
np.arange(0, 3.0001, 0.03)],
189+
dtype=float).transpose()),
190+
"time": (("time",), np.arange(0.0, 101.0, 1.0))
191+
}
192+
),
193+
(
194+
test_variable_step,
195+
{
196+
"time": 25
197+
},
198+
{
199+
"final_time": (
200+
("time",),
201+
np.array([
202+
10., 10., 10., 10., 10., 10.,
203+
50., 50., 50., 50., 50., 50.,
204+
50., 50., 50., 50., 50., 50.,
205+
50., 50., 50., 50., 50., 50., 50.
206+
])),
207+
"initial_time": (
208+
("time",),
209+
np.array([
210+
0., 0., 0., 0.2, 0.2, 0.2,
211+
0.2, 0.2, 0.2, 0.2, 0.2, 0.2,
212+
0.2, 0.2, 0.2, 0.2, 0.2, 0.2,
213+
0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2
214+
])),
215+
"time_step": (
216+
("time",),
217+
np.array([
218+
1., 1., 1., 1., 0.5, 0.5,
219+
0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
220+
0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
221+
0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5
222+
])),
223+
"saveper": (
224+
("time",),
225+
np.array([
226+
1., 1., 1., 1., 0.5, 0.5,
227+
0.5, 0.5, 0.5, 0.5, 0.5, 0.5,
228+
0.5, 0.5, 0.5, 0.5, 5., 5.,
229+
5., 5., 5., 5., 5., 5., 5.]))
230+
}
231+
),
232+
(
233+
test_partial_definitions,
234+
{
235+
"my range": 5,
236+
"time": 11
237+
},
238+
{
239+
"partial_data": (("time", "my range"), None),
240+
"partial_constants": (("my range",), None)
241+
}
242+
)
243+
],
244+
ids=["lookups", "constants", "numeric_coords", "variable_step",
245+
"partial_definitions"]
246+
)
247+
@pytest.mark.filterwarnings("ignore")
248+
def test_output_nc(self, tmp_path, model, dims, values):
249+
250+
out_file = tmp_path.joinpath("results.nc")
251+
252+
model.run(output_file=out_file)
136253

137254
with nc.Dataset(out_file, "r") as ds:
138255
assert ds.ncattrs() == [
139256
'description', 'model_file', 'timestep', 'initial_time',
140257
'final_time']
141-
assert list(ds.dimensions.keys()) == ["Rows", "Dim", "time"]
258+
assert list(ds.dimensions) == list(dims)
142259
# dimensions are stored as variables
143-
assert ds["Rows"].size == 2
144-
assert "Rows" in ds.variables.keys()
145-
assert "time" in ds.variables.keys()
146-
# scalars do not have the time dimension
147-
assert ds["initial_time"].size == 1
148-
# cache step variables have the "time" dimension
149-
assert ds["lookup_1d_time"].dimensions == ("time",)
150-
151-
assert ds["d2d"].dimensions == ("time", "Rows", "Dim")
152-
153-
with catch_warnings(record=True) as w:
154-
simplefilter("always")
155-
assert ds["d2d"].Comment == "Missing"
156-
assert ds["d2d"].Units == "Missing"
157-
158-
# test cache run variables with dimensions
159-
model2 = pysd.read_vensim(test_model_constants)
160-
model2.progress = False
161-
162-
out_file2 = shared_tmpdir.joinpath("results2.nc")
163-
164-
with catch_warnings(record=True) as w:
165-
simplefilter("always")
166-
model2.run(output_file=out_file2)
167-
168-
with nc.Dataset(out_file2, "r") as ds:
169-
assert ds["time_step"].size == 1
170-
assert "constant" in list(ds.variables.keys())
171-
assert ds["constant"].dimensions == ("dim1",)
172-
173-
with catch_warnings(record=True) as w:
174-
simplefilter("always")
175-
assert ds["dim1"][:].data.dtype == "S1"
176-
177-
# dimension with numeric coords
178-
model3 = pysd.read_vensim(test_model_numeric_coords)
179-
model3.progress = False
180-
181-
out_file3 = shared_tmpdir.joinpath("results3.nc")
182-
183-
with catch_warnings(record=True) as w:
184-
simplefilter("always")
185-
model3.run(output_file=out_file3)
186-
187-
# using xarray instead of netCDF4 to load the dataset
188-
189-
with catch_warnings(record=True) as w:
190-
simplefilter("always")
191-
ds = xr.open_dataset(out_file3, engine="netcdf4")
192-
193-
assert "time" in ds.dims
194-
assert ds["rate_a"].dims == ("One Dimensional Subscript",)
195-
assert ds["stock_a"].dims == ("time", "One Dimensional Subscript")
196-
197-
# coordinates get dtype=object when their length is different
198-
assert ds["One Dimensional Subscript"].data.dtype == "O"
199-
200-
# check data
201-
assert np.array_equal(
202-
ds["time"].data, np.arange(0.0, 101.0, 1.0))
203-
assert np.allclose(
204-
ds["stock_a"][0, :].data, np.array([0.0, 0.0, 0.0]))
205-
assert np.allclose(
206-
ds["stock_a"][-1, :].data, np.array([1.0, 2.0, 3.0]))
207-
assert ds["rate_a"].shape == (3,)
208-
209-
# variable attributes
210-
assert list(model.doc.columns) == list(ds["stock_a"].attrs.keys())
211-
212-
# global attributes
213-
assert list(ds.attrs.keys()) == [
214-
'description', 'model_file', 'timestep', 'initial_time',
215-
'final_time']
216-
217-
model4 = pysd.read_vensim(test_variable_step)
218-
model4.progress = False
219-
220-
out_file4 = shared_tmpdir.joinpath("results4.nc")
221-
222-
with catch_warnings(record=True) as w:
223-
simplefilter("always")
224-
model4.run(output_file=out_file4)
225-
226-
with catch_warnings(record=True) as w:
227-
simplefilter("always")
228-
ds = xr.open_dataset(out_file4, engine="netcdf4")
229-
230-
# global attributes for variable timestep
231-
assert ds.attrs["timestep"] == "Variable"
232-
assert ds.attrs["final_time"] == "Variable"
233-
234-
# saveper final_time and time_step have time dimension
235-
assert ds["saveper"].dims == ("time",)
236-
assert ds["time_step"].dims == ("time",)
237-
assert ds["final_time"].dims == ("time",)
238-
239-
assert np.unique(ds["time_step"]).size == 2
240-
241-
def test_output_nc2(self, shared_tmpdir):
242-
# dimension with numeric coords
243-
with catch_warnings(record=True) as w:
244-
simplefilter("always")
245-
model5 = pysd.read_vensim(test_partial_definitions)
246-
model5.progress = False
247-
248-
out_file5 = shared_tmpdir.joinpath("results5.nc")
249-
250-
with catch_warnings(record=True) as w:
251-
simplefilter("always")
252-
model5.run(output_file=out_file5)
253-
254-
# using xarray instead of netCDF4 to load the dataset
255-
256-
with catch_warnings(record=True) as w:
257-
simplefilter("always")
258-
ds = xr.open_dataset(out_file5, engine="netcdf4")
259-
260-
print(ds)
261-
262-
@pytest.mark.parametrize("fmt,sep", [("csv", ","), ("tab", "\t")])
263-
def test_output_csv(self, fmt, sep, capsys, shared_tmpdir):
264-
model = pysd.read_vensim(test_model_look)
265-
model.progress = False
266-
267-
out_file = shared_tmpdir.joinpath("results." + fmt)
268-
269-
with catch_warnings(record=True) as w:
270-
simplefilter("always")
271-
model.run(output_file=out_file)
260+
for dim, n in dims.items():
261+
# check dimension size
262+
assert ds[dim].size == n
263+
assert dim in ds.variables.keys()
264+
# check dimension type
265+
if dim != "time":
266+
assert ds[dim].dtype in ["S1", str]
267+
else:
268+
assert ds[dim].dtype == float
269+
270+
for var, (dim, val) in values.items():
271+
# check variable dimensions
272+
assert ds[var].dimensions == dim
273+
if val is not None:
274+
# check variable values if given
275+
assert np.all(np.isclose(ds[var][:].data, val))
276+
277+
# Check variable attributes
278+
doc = model.doc
279+
doc.set_index("Py Name", drop=False, inplace=True)
280+
doc.drop(columns=["Subscripts", "Limits"], inplace=True)
281+
282+
for var in doc["Py Name"]:
283+
if doc.loc[var, "Type"] == "Lookup":
284+
continue
285+
for key in doc.columns:
286+
assert getattr(ds[var], key) == (doc.loc[var, key]
287+
or "Missing")
288+
289+
@pytest.mark.parametrize(
290+
"model_path,fmt,sep",
291+
[
292+
(test_model_look, "csv", ","),
293+
(test_model_look, "tab", "\t")])
294+
@pytest.mark.filterwarnings("ignore")
295+
def test_output_csv(self, fmt, sep, capsys, model, tmp_path):
296+
out_file = tmp_path.joinpath("results." + fmt)
297+
298+
model.run(output_file=out_file)
272299

273300
captured = capsys.readouterr() # capture stdout
274301
assert f"Data saved in '{out_file}'" in captured.out
@@ -282,10 +309,10 @@ def test_output_csv(self, fmt, sep, capsys, shared_tmpdir):
282309
assert "lookup 3d time[B;Row1]" in df.columns or \
283310
"lookup 3d time[B,Row1]" in df.columns
284311

285-
def test_dataset_handler_step_setter(self, shared_tmpdir):
286-
model = pysd.read_vensim(test_model_look)
312+
@pytest.mark.parametrize("model_path", [test_model_look])
313+
def test_dataset_handler_step_setter(self, tmp_path, model):
287314
capture_elements = set()
288-
results = shared_tmpdir.joinpath("results.nc")
315+
results = tmp_path.joinpath("results.nc")
289316
output = ModelOutput(model, capture_elements, results)
290317

291318
# Dataset handler step cannot be modified from the outside

0 commit comments

Comments
 (0)