Skip to content

Commit 0419b93

Browse files
Add support for XMILE's non-negative
Add support for XMILE's non-negative flag for stocks and flows, includying also the behavior section. Add support for XMILE's MIN and MAX with one argument.
1 parent bb74b6e commit 0419b93

File tree

14 files changed

+254
-43
lines changed

14 files changed

+254
-43
lines changed

docs/structure/xmile_translation.rst

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ Xmile element
3737
^^^^^^^^^^^^^
3838

3939
.. automodule:: pysd.translators.xmile.xmile_element
40-
:members: SubscriptRange, Element, Flaux, Gf, Stock
40+
:members: SubscriptRange, Element, Aux, Flow, Gf, Stock
4141
:undoc-members:
4242

4343

@@ -79,7 +79,7 @@ Not all the Xmile functions are included yet. The list of supported functions is
7979

8080
Stocks
8181
^^^^^^
82-
Stocks are supported with any number of inflows and outflows. Stocks are translated to the AST as `IntegStructure(flows, initial_value)`.
82+
Stocks are supported with any number of inflows and outflows. Stocks are translated to the AST as `IntegStructure(flows, initial_value, non_negative)`. Non-negative flag is parsed for both stocks and flows, this can be set element by element or using the `behavior section <http://docs.oasis-open.org/xmile/xmile/v1.0/errata01/csprd01/xmile-v1.0-errata01-csprd01-complete.html#_Toc442104128>`_. Flows with non-negative flags are read as flows with a maximum condition, while for stocks this information is saved in the :py:class:`pysd.translators.structures.abstract_expressions.IntegStructure` object.
8383

8484
Subscripts
8585
^^^^^^^^^^

docs/tables/functions.tab

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ Vensim Vensim example Xmile Xmile example Abstract Syntax Python Translation Ven
22
ABS ABS(A) abs abs(A) "CallStructure('abs', (A,))" numpy.abs(A)
33
MIN "MIN(A, B)" min "min(A, B)" "CallStructure('min', (A, B))" "numpy.minimum(A, B)"
44
MAX "MAX(A, B)" max "max(A, B)" "CallStructure('max', (A, B))" "numpy.maximum(A, B)"
5+
min "min(A)" "CallStructure('vmin_xmile', (A,))" pysd.functions.vmin(A)
6+
max "max(A)" "CallStructure('vmax_xmile', (A,))" pysd.functions.vmax(A)
57
SQRT SQRT(A) sqrt sqrt(A) "CallStructure('sqrt', (A,))" numpy.sqrt
68
EXP EXP(A) exp exp(A) "CallStructure('exp', (A,))" numpy.exp(A)
79
LN LN(A) ln ln(A) "CallStructure('ln', (A,))" numpy.log(A)

docs/whats_new.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
What's New
22
==========
3-
v3.10.0 (2023/??/??)
3+
v3.10.0 (2023/04/28)
44
--------------------
55
New Features
66
~~~~~~~~~~~~
77
- Parse TABBED ARRAYS Vensim function. (`@rogersamso <https://github.com/rogersamso>`_)
88
- Add support for Vensim's `POWER <https://www.vensim.com/documentation/fn_power.html>`_ function. (`@rogersamso <https://github.com/rogersamso>`_)
99
- Add possibility to pass data_files in netCDF format. (`@rogersamso <https://github.com/rogersamso>`_)
10+
- Add support for XMILE's non-negative flows and stocks. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
11+
- Add support for XMILE's MIN and MAX functions with one argument. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
1012

1113
Breaking changes
1214
~~~~~~~~~~~~~~~~
@@ -22,6 +24,7 @@ Bug fixes
2224
Documentation
2325
~~~~~~~~~~~~~
2426
- Add information about slack channel https://slofile.com/slack/sdtoolsandmet-slj3251. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
27+
- Update XMILE stocks section. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
2528

2629
Performance
2730
~~~~~~~~~~~
@@ -30,6 +33,7 @@ Internal Changes
3033
~~~~~~~~~~~~~~~~
3134
- Add a weekly scheduled run to all CI workflows, which run each Monday at 06:00 UTC. (`@EwoutH <https://github.com/EwoutH>`_)
3235
- Fix CI pipeline for Python 3.11 and remove Python 3.10 pipeline in favour of 3.11. (`@kinow <https://github.com/kinow>`_)
36+
- Add non_negative argument in :py:class:`pysd.translators.structures.abstract_expressions.IntegStructure`. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
3337

3438
v3.9.1 (2023/03/11)
3539
-------------------

pysd/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "3.9.1"
1+
__version__ = "3.10.0"

pysd/builders/python/python_expressions_builder.py

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1195,6 +1195,7 @@ def __init__(self, integ_str: IntegStructure, component: object):
11951195
"flow": integ_str.flow,
11961196
"initial": integ_str.initial
11971197
}
1198+
self.non_negative = integ_str.non_negative
11981199

11991200
def build(self, arguments: dict) -> BuildAST:
12001201
"""
@@ -1213,7 +1214,6 @@ def build(self, arguments: dict) -> BuildAST:
12131214
"""
12141215
self.component.type = "Stateful"
12151216
self.component.subtype = "Integ"
1216-
self.section.imports.add("statefuls", "Integ")
12171217

12181218
arguments["initial"].reshape(
12191219
self.section.subscripts, self.def_subs, True)
@@ -1224,11 +1224,24 @@ def build(self, arguments: dict) -> BuildAST:
12241224
self.element.identifier, prefix="_integ")
12251225

12261226
# Create the object
1227-
self.element.objects[arguments["name"]] = {
1228-
"name": arguments["name"],
1229-
"expression": "%(name)s = Integ(lambda: %(flow)s, "
1230-
"lambda: %(initial)s, '%(name)s')" % arguments
1231-
}
1227+
if self.non_negative:
1228+
# Non-negative stocks
1229+
self.section.imports.add("statefuls", "NonNegativeInteg")
1230+
self.element.objects[arguments["name"]] = {
1231+
"name": arguments["name"],
1232+
"expression": "%(name)s = NonNegativeInteg("
1233+
"lambda: %(flow)s, "
1234+
"lambda: %(initial)s, '%(name)s')" % arguments
1235+
}
1236+
else:
1237+
# Regular stocks
1238+
self.section.imports.add("statefuls", "Integ")
1239+
self.element.objects[arguments["name"]] = {
1240+
"name": arguments["name"],
1241+
"expression": "%(name)s = Integ(lambda: %(flow)s, "
1242+
"lambda: %(initial)s, '%(name)s')" % arguments
1243+
}
1244+
12321245
# Add other dependencies
12331246
self.element.other_dependencies[arguments["name"]] = {
12341247
"initial": arguments["initial"].calls,

pysd/builders/python/python_functions.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@
3535
"sum": ("sum(%(0)s, dim=%(axis)s)", ("functions", "sum")),
3636
"vmax": ("vmax(%(0)s, dim=%(axis)s)", ("functions", "vmax")),
3737
"vmin": ("vmin(%(0)s, dim=%(axis)s)", ("functions", "vmin")),
38+
"vmax_xmile": ("vmax(%(0)s)", ("functions", "vmax")),
39+
"vmin_xmile": ("vmin(%(0)s)", ("functions", "vmin")),
3840
"vector_select": (
3941
"vector_select(%(0)s, %(1)s, %(axis)s, %(2)s, %(3)s, %(4)s)",
4042
("functions", "vector_select")

pysd/py_backend/statefuls.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,32 @@ def export(self):
9898
return {'state': self.state, 'shape_info': self.shape_info}
9999

100100

101+
class NonNegativeInteg(Integ):
102+
"""
103+
Implements non negative INTEG function.
104+
105+
Parameters
106+
----------
107+
ddt: callable
108+
Derivate to integrate.
109+
initial_value: callable
110+
Initial value.
111+
py_name: str
112+
Python name to identify the object.
113+
114+
Attributes
115+
----------
116+
state: float or xarray.DataArray
117+
Current state of the object. Value of the stock.
118+
119+
"""
120+
def __init__(self, ddt, initial_value, py_name):
121+
super().__init__(ddt, initial_value, py_name)
122+
123+
def update(self, state):
124+
self.state = np.maximum(state, 0)
125+
126+
101127
class Delay(DynamicStateful):
102128
"""
103129
Implements DELAY function.

pysd/translators/structures/abstract_expressions.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
anything else.
1010
"""
1111
from dataclasses import dataclass
12-
from typing import Union
12+
from typing import Union, Optional
1313

1414

1515
class AbstractSyntax:
@@ -230,10 +230,13 @@ class IntegStructure(AbstractSyntax):
230230
The flow of the stock.
231231
initial: AST
232232
The initial value of the stock.
233+
non_negative: bool (optional)
234+
If True the stock cannot be negative. Default is False.
233235
234236
"""
235237
flow: Union[AbstractSyntax, float]
236238
initial: Union[AbstractSyntax, float]
239+
non_negative: Optional[bool] = False
237240

238241
def __str__(self) -> str: # pragma: no cover
239242
return "IntegStructure:\n\t%s,\n\t%s" % (

pysd/translators/vensim/vensim_element.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
The Element class allows parsing the LHS of a model equation.
33
Depending on the LHS value, either a SubscriptRange object or a Component
4-
object will be returned. There are 4 components types:
4+
object will be returned. There are four components types:
55
66
- Component: Regular component, defined with '='.
77
- UnchangeableConstant: Unchangeable constant, defined with '=='.

0 commit comments

Comments
 (0)