Skip to content

Commit

Permalink
Suport newer versions
Browse files Browse the repository at this point in the history
  • Loading branch information
enekomartinmartinez committed Feb 1, 2024
1 parent 5abe6b8 commit 6ab6edc
Show file tree
Hide file tree
Showing 17 changed files with 152 additions and 71 deletions.
2 changes: 1 addition & 1 deletion docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@
'xarray': ('https://docs.xarray.dev/en/stable/', None),
'numpy': ('https://numpy.org/doc/stable/', None),
'scipy': ('https://docs.scipy.org/doc/scipy/', None),
'pytest': ('https://docs.pytest.org/en/7.1.x/', None),
'pytest': ('https://docs.pytest.org/en/8.0.x/', None),
'openpyxl': ('https://openpyxl.readthedocs.io/en/stable', None)
}

Expand Down
4 changes: 2 additions & 2 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ PySD requires **Python 3.9** or above.

PySD builds on the core Python data analytics stack, and the following third party libraries:

* Numpy < 1.24
* Numpy >= 1.23
* Scipy
* Pandas (with Excel support: `pip install pandas[excel]`)
* Parsimonious
Expand All @@ -65,7 +65,7 @@ In order to plot model outputs as shown in :doc:`Getting started <../getting_sta

* Matplotlib

To export data to netCDF (*.nc*) files:
To export data to netCDF (*.nc*) files or to serialize external objects:

* netCDF4

Expand Down
27 changes: 27 additions & 0 deletions docs/whats_new.rst
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
What's New
==========
v3.13.3 (2024/02/02)
--------------------
New Features
~~~~~~~~~~~~

Breaking changes
~~~~~~~~~~~~~~~~

Deprecations
~~~~~~~~~~~~

Bug fixes
~~~~~~~~~

Documentation
~~~~~~~~~~~~~

Performance
~~~~~~~~~~~

Internal Changes
~~~~~~~~~~~~~~~~
- Support for Python 3.12. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
- Support for :py:mod:`numpy` >= 1.24. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
- Correct some warnings management in the tests. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
- Fix :py:mod:`numpy` requirements to >= 1.23 to follow `NEP29 <https://numpy.org/neps/nep-0029-deprecation_policy.html>`_. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)

v3.13.2 (2024/01/09)
--------------------
New Features
Expand Down
2 changes: 1 addition & 1 deletion pysd/_version.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
__version__ = "3.13.2"
__version__ = "3.13.3"
5 changes: 2 additions & 3 deletions pysd/builders/python/python_expressions_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,9 @@ def build_not_implemented(self, arguments: dict) -> BuildAST:
"""
final_subscripts = self.reorder(arguments)
warnings.warn(
"\n\nTrying to translate '"
+ self.function.upper().replace("_", " ")
"Trying to translate '" + self.function.upper().replace("_", " ")
+ "' which it is not implemented on PySD. The translated "
+ "model will crash... "
+ "model will crash..."
)
self.section.imports.add("functions", "not_implemented_function")

Expand Down
6 changes: 5 additions & 1 deletion pysd/py_backend/allocation.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,11 @@ def get_functions(cls, q0, pp, kind):
interval = interval.union(i)

# Full allocation function -> function to solve
def full_allocation(x): return np.sum([func(x) for func in functions])
def full_allocation(x):
if isinstance(x, np.ndarray):
# Fix to solve issues in the newest numpy versions
x = x.squeeze()[()]
return np.sum([func(x) for func in functions])

def_intervals = []
for subinterval in interval:
Expand Down
16 changes: 10 additions & 6 deletions pysd/py_backend/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -516,6 +516,11 @@ def initialize_external_data(self, externals=None):
--------
:func:`pysd.py_backend.model.Macro.serialize_externals`
Note
----
To load externals from a netCDF file you need to have installed
the optional dependency `netCDF4`.
"""

if not externals:
Expand Down Expand Up @@ -607,6 +612,11 @@ def serialize_externals(self, export_path="externals.nc",
--------
:func:`pysd.py_backend.model.Macro.initialize_external_data`
Note
----
To run this function you need to have installed the optional
dependency `netCDF4`.
"""
data = {}
metadata = {}
Expand Down Expand Up @@ -1125,12 +1135,6 @@ def _set_components(self, params, new):
new_function = self._constant_component(value, dims)
self._dependencies[func_name] = {}

# this won't handle other statefuls...
if '_integ_' + func_name in dir(self.components):
warnings.warn("Replacing the equation of stock "
"'{}' with params...".format(key),
stacklevel=2)

# copy attributes from the original object to proper working
# of internal functions
new_function.__name__ = func_name
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
numpy
numpy>=1.23
pandas[excel]
parsimonious
xarray>=2023.9
Expand Down
7 changes: 4 additions & 3 deletions tests/pytest_builders/pytest_python.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pytest
from pathlib import Path

import pytest

from pysd.builders.python.namespace import NamespaceManager
from pysd.builders.python.subscripts import SubscriptManager
from pysd.builders.python.python_model_builder import\
Expand Down Expand Up @@ -147,13 +148,13 @@ def test_referencebuilder_subscripts_warning(self, component,
component.section.subscripts.mapping = {
dim: [] for dim in subscripts}
component.section.namespace.namespace = namespace
warning_message =\
warn_message =\
f"The reference to '{origin_name}' in variable 'My Var' has "\
r"duplicated subscript ranges\. If mapping is used in one "\
r"of them, please, rewrite reference subscripts to avoid "\
r"duplicates\. Otherwise, the final model may crash\.\.\."\

with pytest.warns(UserWarning, match=warning_message):
with pytest.warns(UserWarning, match=warn_message):
ReferenceBuilder(reference_str, component)

@pytest.mark.parametrize(
Expand Down
24 changes: 17 additions & 7 deletions tests/pytest_pysd/pytest_errors.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import pytest
import re
import shutil

import pytest

from pysd import read_vensim, read_xmile, load


Expand Down Expand Up @@ -92,13 +94,21 @@ def test_loading_error(loader, model_path, raise_type, error_message):
]
)
def test_not_implemented_and_incomplete(model_path):
with pytest.warns(UserWarning) as ws:
with pytest.warns() as record:
model = read_vensim(model_path)
assert "'incomplete var' has no equation specified"\
in str(ws[0].message)
assert "Trying to translate 'MY FUNC' which it is not implemented"\
" on PySD. The translated model will crash..."\
in str(ws[1].message)

warn_message = "'incomplete var' has no equation specified"
assert any([
re.match(warn_message, str(warn.message))
for warn in record
]), f"Couldn't match warning:\n{warn_message}"

warn_message = "Trying to translate 'MY FUNC' which it is "\
"not implemented on PySD. The translated model will crash..."
assert any([
re.match(warn_message, str(warn.message))
for warn in record
]), f"Couldn't match warning:\n{warn_message}"

with pytest.warns(RuntimeWarning,
match="Call to undefined function, calling dependencies "
Expand Down
4 changes: 2 additions & 2 deletions tests/pytest_pysd/pytest_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -574,7 +574,7 @@ def test_get_time_value_errors(self, measure, relativeto,
lambda: 0, relativeto, np.random.randint(-100, 100), measure)

def test_vector_select(self):
warning_message =\
warn_message =\
r"Vensim's help says that numerical_action=5 computes the "\
r"product of selection_array \^ expression_array\. But, in fact,"\
r" Vensim is computing the product of expression_array \^ "\
Expand All @@ -584,7 +584,7 @@ def test_vector_select(self):
array = xr.DataArray([3, 10, 2], {'dim': ["A", "B", "C"]})
sarray = xr.DataArray([1, 0, 2], {'dim': ["A", "B", "C"]})

with pytest.warns(UserWarning, match=warning_message):
with pytest.warns(UserWarning, match=warn_message):
assert vector_select(sarray, array, ["dim"], np.nan, 5, 1)\
== 12

Expand Down
32 changes: 22 additions & 10 deletions tests/pytest_pysd/pytest_pysd.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from pathlib import Path

import pytest
Expand Down Expand Up @@ -140,13 +141,13 @@ def test_run_return_timestamps(self, model):

# assert one timestamp is not returned because is not multiple of
# the time step
warning_message =\
warn_message =\
"The returning time stamp '%s' seems to not be a multiple "\
"of the time step. This value will not be saved in the output. "\
"Please, modify the returning timestamps or the integration "\
"time step to avoid this."
# assert that return_timestamps works with float error
with pytest.warns(UserWarning, match=warning_message % 0.55):
with pytest.warns(UserWarning, match=warn_message % 0.55):
stocks = model.run(
time_step=0.1, return_timestamps=[0.3, 0.1, 0.55, 0.9])
assert 0.1 in stocks.index
Expand All @@ -155,7 +156,7 @@ def test_run_return_timestamps(self, model):
assert 0.55 not in stocks.index

with pytest.warns(UserWarning,
match=warning_message % "(0.15|0.55|0.95)"):
match=warn_message % "(0.15|0.55|0.95)"):
stocks = model.run(
time_step=0.1, return_timestamps=[0.3, 0.15, 0.55, 0.95])
assert 0.15 not in stocks.index
Expand Down Expand Up @@ -348,13 +349,19 @@ def test_set_component_with_real_name(self, model):
@pytest.mark.parametrize("model_path", [test_model])
def test_set_components_warnings(self, model):
"""Addresses https://github.com/SDXorg/pysd/issues/80"""
warn_message = r"Replacing the equation of stock "\
r"'Teacup Temperature' with params\.\.\."
with pytest.warns(UserWarning, match=warn_message):
warn_message = r"Replacing the value of Stateful variable with "\
r"an expression\. To set initial conditions use "\
r"`set_initial_condition` instead\.\.\."
with pytest.warns() as record:
model.set_components(
{"Teacup Temperature": 20, "Characteristic Time": 15}
) # set stock value using params

assert any([
re.match(warn_message, str(warn.message))
for warn in record
]), f"Couldn't match warning:\n{warn_message}"

@pytest.mark.parametrize("model_path", [test_model])
def test_set_components_with_function(self, model):
def test_func():
Expand Down Expand Up @@ -967,12 +974,17 @@ def test_set_initial_value_subscripted_value_with_numpy_error(self, model):

@pytest.mark.parametrize("model_path", [test_model])
def test_replace_stateful(self, model):
warn_message = "Replacing the value of Stateful variable with "\
"an expression. To set initial conditions use "\
"`set_initial_condition` instead..."
with pytest.warns(UserWarning, match=warn_message):
warn_message = r"Replacing the value of Stateful variable with "\
r"an expression\. To set initial conditions use "\
r"`set_initial_condition` instead\.\.\."
with pytest.warns() as record:
model.components.teacup_temperature = 3

assert any([
re.match(warn_message, str(warn.message))
for warn in record
]), f"Couldn't match warning:\n{warn_message}"

stocks = model.run()
assert np.all(stocks["Teacup Temperature"] == 3)

Expand Down
33 changes: 21 additions & 12 deletions tests/pytest_pysd/pytest_select_submodel.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@

import pytest
import re
import shutil
from pathlib import Path
import numpy as np

import pytest
import numpy as np

import pysd
from pysd.translators.vensim.vensim_file import VensimFile
Expand Down Expand Up @@ -60,7 +60,7 @@ class TestSubmodel:
"Lookup table dependencies",
"Stateful objects integrated with the selected variables"
]
warning = "Selecting submodel, "\
warn_message = "Selecting submodel, "\
+ "to run the full model again use model.reload()"
common_vars = {
'initial_time', 'time_step', 'final_time', 'time', 'saveper', 'stock'
Expand Down Expand Up @@ -141,11 +141,12 @@ def test_select_submodel(self, model, variables, modules,
assert "stock" in model._doc["Py Name"].to_list()

# select submodel
with pytest.warns(UserWarning) as record:
with pytest.warns() as record:
model.select_submodel(vars=variables, modules=modules)

# assert warning
assert str(record[0].message) == self.warning
record = [str(r.message) for r in record]
assert self.warn_message in record

# assert stateful elements change
assert len(model._dynamicstateful_elements) == 1
Expand All @@ -170,16 +171,20 @@ def test_select_submodel(self, model, variables, modules,
# running the model without redefining dependencies will
# produce nan values
assert "Exogenous components for the following variables are"\
+ " necessary but not given:" in str(record[-1].message)
+ " necessary but not given:" in record[-1]
assert "Please, set them before running the model using "\
+ "set_components method..." in str(record[-1].message)
+ "set_components method..." in record[-1]
for var in dep_vars:
assert var in str(record[-1].message)
assert var in record[-1]
assert np.any(np.isnan(model.run()))
# redefine dependencies
warn_message = "Replacing a variable by a constant value."
with pytest.warns(UserWarning, match=warn_message):
with pytest.warns(UserWarning) as record:
out = model.run(params=dep_vars)
assert any([
re.match(warn_message, str(warn.message))
for warn in record
]), f"Couldn't match warning:\n{warn_message}"
assert not np.any(np.isnan(out))

# select submodel using contour values
Expand Down Expand Up @@ -215,7 +220,7 @@ def test_select_submodel_copy(self, model, variables, modules,

# assert warning
record = [str(r.message) for r in record]
assert self.warning in record
assert self.warn_message in record

# assert original stateful elements
assert len(model._dynamicstateful_elements) == 2
Expand Down Expand Up @@ -263,8 +268,12 @@ def test_select_submodel_copy(self, model, variables, modules,
assert np.any(np.isnan(model2.run()))
# redefine dependencies
warn_message = "Replacing a variable by a constant value."
with pytest.warns(UserWarning, match=warn_message):
with pytest.warns(UserWarning) as record:
out = model2.run(params=dep_vars)
assert any([
re.match(warn_message, str(warn.message))
for warn in record
]), f"Couldn't match warning:\n{warn_message}"
assert not np.any(np.isnan(out))

# select submodel using contour values
Expand Down
10 changes: 7 additions & 3 deletions tests/pytest_translators/pytest_split_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ def test_read_vensim_split_model_warnings(self, model_file, subview_sep,


@pytest.mark.parametrize(
"model_path,subview_sep,warning_message",
"model_path,subview_sep,warn_message",
[
( # warning_noviews
Path("test-models/samples/teacup/teacup.mdl"),
Expand Down Expand Up @@ -306,6 +306,10 @@ def model(self, shared_tmpdir, model_path, _root):
shutil.copy(_root.joinpath(model_path), file)
return file

def test_split_view_warnings(self, model, subview_sep, warning_message):
with pytest.warns(UserWarning, match=warning_message):
def test_split_view_warnings(self, model, subview_sep, warn_message):
with pytest.warns() as record:
pysd.read_vensim(model, split_views=True, subview_sep=subview_sep)
assert any([
re.match(warn_message, str(warn.message))
for warn in record
]), f"Couldn't match warning:\n{warn_message}"

0 comments on commit 6ab6edc

Please sign in to comment.