Skip to content

Commit

Permalink
Add support for XMILE's non-negative
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
enekomartinmartinez committed Apr 28, 2023
1 parent bb74b6e commit 0419b93
Show file tree
Hide file tree
Showing 14 changed files with 254 additions and 43 deletions.
4 changes: 2 additions & 2 deletions docs/structure/xmile_translation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ Xmile element
^^^^^^^^^^^^^

.. automodule:: pysd.translators.xmile.xmile_element
:members: SubscriptRange, Element, Flaux, Gf, Stock
:members: SubscriptRange, Element, Aux, Flow, Gf, Stock
:undoc-members:


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

Stocks
^^^^^^
Stocks are supported with any number of inflows and outflows. Stocks are translated to the AST as `IntegStructure(flows, initial_value)`.
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.

Subscripts
^^^^^^^^^^
Expand Down
2 changes: 2 additions & 0 deletions docs/tables/functions.tab
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ Vensim Vensim example Xmile Xmile example Abstract Syntax Python Translation Ven
ABS ABS(A) abs abs(A) "CallStructure('abs', (A,))" numpy.abs(A)
MIN "MIN(A, B)" min "min(A, B)" "CallStructure('min', (A, B))" "numpy.minimum(A, B)"
MAX "MAX(A, B)" max "max(A, B)" "CallStructure('max', (A, B))" "numpy.maximum(A, B)"
min "min(A)" "CallStructure('vmin_xmile', (A,))" pysd.functions.vmin(A)
max "max(A)" "CallStructure('vmax_xmile', (A,))" pysd.functions.vmax(A)
SQRT SQRT(A) sqrt sqrt(A) "CallStructure('sqrt', (A,))" numpy.sqrt
EXP EXP(A) exp exp(A) "CallStructure('exp', (A,))" numpy.exp(A)
LN LN(A) ln ln(A) "CallStructure('ln', (A,))" numpy.log(A)
Expand Down
6 changes: 5 additions & 1 deletion docs/whats_new.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
What's New
==========
v3.10.0 (2023/??/??)
v3.10.0 (2023/04/28)
--------------------
New Features
~~~~~~~~~~~~
- Parse TABBED ARRAYS Vensim function. (`@rogersamso <https://github.com/rogersamso>`_)
- Add support for Vensim's `POWER <https://www.vensim.com/documentation/fn_power.html>`_ function. (`@rogersamso <https://github.com/rogersamso>`_)
- Add possibility to pass data_files in netCDF format. (`@rogersamso <https://github.com/rogersamso>`_)
- Add support for XMILE's non-negative flows and stocks. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)
- Add support for XMILE's MIN and MAX functions with one argument. (`@enekomartinmartinez <https://github.com/enekomartinmartinez>`_)

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

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

v3.9.1 (2023/03/11)
-------------------
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.9.1"
__version__ = "3.10.0"
25 changes: 19 additions & 6 deletions pysd/builders/python/python_expressions_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,6 +1195,7 @@ def __init__(self, integ_str: IntegStructure, component: object):
"flow": integ_str.flow,
"initial": integ_str.initial
}
self.non_negative = integ_str.non_negative

def build(self, arguments: dict) -> BuildAST:
"""
Expand All @@ -1213,7 +1214,6 @@ def build(self, arguments: dict) -> BuildAST:
"""
self.component.type = "Stateful"
self.component.subtype = "Integ"
self.section.imports.add("statefuls", "Integ")

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

# Create the object
self.element.objects[arguments["name"]] = {
"name": arguments["name"],
"expression": "%(name)s = Integ(lambda: %(flow)s, "
"lambda: %(initial)s, '%(name)s')" % arguments
}
if self.non_negative:
# Non-negative stocks
self.section.imports.add("statefuls", "NonNegativeInteg")
self.element.objects[arguments["name"]] = {
"name": arguments["name"],
"expression": "%(name)s = NonNegativeInteg("
"lambda: %(flow)s, "
"lambda: %(initial)s, '%(name)s')" % arguments
}
else:
# Regular stocks
self.section.imports.add("statefuls", "Integ")
self.element.objects[arguments["name"]] = {
"name": arguments["name"],
"expression": "%(name)s = Integ(lambda: %(flow)s, "
"lambda: %(initial)s, '%(name)s')" % arguments
}

# Add other dependencies
self.element.other_dependencies[arguments["name"]] = {
"initial": arguments["initial"].calls,
Expand Down
2 changes: 2 additions & 0 deletions pysd/builders/python/python_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"sum": ("sum(%(0)s, dim=%(axis)s)", ("functions", "sum")),
"vmax": ("vmax(%(0)s, dim=%(axis)s)", ("functions", "vmax")),
"vmin": ("vmin(%(0)s, dim=%(axis)s)", ("functions", "vmin")),
"vmax_xmile": ("vmax(%(0)s)", ("functions", "vmax")),
"vmin_xmile": ("vmin(%(0)s)", ("functions", "vmin")),
"vector_select": (
"vector_select(%(0)s, %(1)s, %(axis)s, %(2)s, %(3)s, %(4)s)",
("functions", "vector_select")
Expand Down
26 changes: 26 additions & 0 deletions pysd/py_backend/statefuls.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,32 @@ def export(self):
return {'state': self.state, 'shape_info': self.shape_info}


class NonNegativeInteg(Integ):
"""
Implements non negative INTEG function.
Parameters
----------
ddt: callable
Derivate to integrate.
initial_value: callable
Initial value.
py_name: str
Python name to identify the object.
Attributes
----------
state: float or xarray.DataArray
Current state of the object. Value of the stock.
"""
def __init__(self, ddt, initial_value, py_name):
super().__init__(ddt, initial_value, py_name)

def update(self, state):
self.state = np.maximum(state, 0)


class Delay(DynamicStateful):
"""
Implements DELAY function.
Expand Down
5 changes: 4 additions & 1 deletion pysd/translators/structures/abstract_expressions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
anything else.
"""
from dataclasses import dataclass
from typing import Union
from typing import Union, Optional


class AbstractSyntax:
Expand Down Expand Up @@ -230,10 +230,13 @@ class IntegStructure(AbstractSyntax):
The flow of the stock.
initial: AST
The initial value of the stock.
non_negative: bool (optional)
If True the stock cannot be negative. Default is False.
"""
flow: Union[AbstractSyntax, float]
initial: Union[AbstractSyntax, float]
non_negative: Optional[bool] = False

def __str__(self) -> str: # pragma: no cover
return "IntegStructure:\n\t%s,\n\t%s" % (
Expand Down
2 changes: 1 addition & 1 deletion pysd/translators/vensim/vensim_element.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""
The Element class allows parsing the LHS of a model equation.
Depending on the LHS value, either a SubscriptRange object or a Component
object will be returned. There are 4 components types:
object will be returned. There are four components types:
- Component: Regular component, defined with '='.
- UnchangeableConstant: Unchangeable constant, defined with '=='.
Expand Down
Loading

0 comments on commit 0419b93

Please sign in to comment.