Skip to content

Commit

Permalink
Merge pull request #337 from JamesPHoughton/dev
Browse files Browse the repository at this point in the history
Add support for ALLOCATE BY PRIORITY and correct subscripts bug
  • Loading branch information
enekomartinmartinez committed Jun 22, 2022
2 parents 300ecd3 + e7bb0df commit 1167289
Show file tree
Hide file tree
Showing 17 changed files with 571 additions and 30 deletions.
5 changes: 5 additions & 0 deletions docs/python_api/functions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ Functions
.. automodule:: pysd.py_backend.functions
:members:

Allocation functions
--------------------
.. automodule:: pysd.py_backend.allocation
:members:

Statefuls
---------
.. automodule:: pysd.py_backend.statefuls
Expand Down
12 changes: 10 additions & 2 deletions docs/structure/vensim_translation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,5 +116,13 @@ Vensim macros are supported. The macro content between the keywords \:MACRO: and

Planed New Functions and Features
---------------------------------
- ALLOCATE BY PRIORITY
- SHIFT IF TRUE


Discarded Functions and Features
--------------------------------

SHIFT IF TRUE
^^^^^^^^^^^^^
Due to the complexity of `SHIFT IF TRUE <https://www.vensim.com/documentation/fn_shift_if_true.html>`_ function and the need to mutate already computed values in another variable, the implementation of this function has been discarded for now.

However, it is possible to reproduce the same behaviour using IF THEN ELSE in the stock flow. A model with a workaround to avoid SHIFT IF TRUE and make the model work with PySD is given in :issue:`265`.
1 change: 1 addition & 0 deletions docs/tables/functions.tab
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@ VECTOR RANK VECTOR RANK(vec, direction) "CallStructure('vector_rank', (vec, di
VECTOR REORDER VECTOR REORDER(vec, svec) "CallStructure('vector_reorder', (vec, svec))" vector_reorder(vec, svec)
VECTOR SORT ORDER VECTOR SORT ORDER(vec, direction) "CallStructure('vector_sort_order', (vec, direction))" vector_sort_order(vec, direction)
GAME GAME(A) GameStructure(A) A
ALLOCATE BY PRIORITY "ALLOCATE BY PRIORITY(request, priority, size, width, supply)" "AllocateByPriorityStructure(request, priority, size, width, supply)" allocate_by_priority(request, priority, width, supply)
INITIAL INITIAL(value) init init(value) InitialStructure(value) pysd.statefuls.Initial
SAMPLE IF TRUE "SAMPLE IF TRUE(condition, input, initial_value)" "SampleIfTrueStructure(condition, input, initial_value)" pysd.statefuls.SampleIfTrue(...)
28 changes: 27 additions & 1 deletion docs/whats_new.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,33 @@

What's New
==========

v3.3.0 (2022/06/22)
-------------------

New Features
~~~~~~~~~~~~
- Add support for Vensim's `ALLOCATE BY PRIORITY <https://www.vensim.com/documentation/fn_allocate_by_priority.html>`_ (:func:`pysd.py_backend.allocation.allocate_by_priority`) function (:issue:`263`).

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

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

Bug fixes
~~~~~~~~~
- Fix bug of using subranges to define a bigger range (:issue:`335`).

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

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

Internal Changes
~~~~~~~~~~~~~~~~
- Improve error messages for :class:`pysd.py_backend.External` objects.

v3.2.0 (2022/06/10)
-------------------

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.2.0"
__version__ = "3.3.0"
7 changes: 4 additions & 3 deletions pysd/builders/python/imports.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ class ImportsManager():
_external_submodules = ["scipy"]
_internal_libs = [
"functions", "statefuls", "external", "data", "lookups", "utils",
"model"
"allocation", "model"
]

def __init__(self):
self._numpy, self._xarray = False, False
self._functions, self._statefuls, self._external, self._data,\
self._lookups, self._utils, self._scipy, self._model =\
set(), set(), set(), set(), set(), set(), set(), set()
self._lookups, self._utils, self._scipy, self._allocation,\
self._model =\
set(), set(), set(), set(), set(), set(), set(), set(), set()

def add(self, module: str, function: Union[str, None] = None) -> None:
"""
Expand Down
81 changes: 73 additions & 8 deletions pysd/builders/python/python_expressions_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,13 @@
from pysd.py_backend.utils import compute_shape

from pysd.translators.structures.abstract_expressions import\
AbstractSyntax, ArithmeticStructure, CallStructure, DataStructure,\
DelayFixedStructure, DelayStructure, DelayNStructure, ForecastStructure,\
GameStructure, GetConstantsStructure, GetDataStructure,\
GetLookupsStructure, InitialStructure, InlineLookupsStructure,\
IntegStructure, LogicStructure, LookupsStructure, ReferenceStructure,\
SampleIfTrueStructure, SmoothNStructure, SmoothStructure,\
SubscriptsReferenceStructure, TrendStructure
AbstractSyntax, AllocateByPriorityStructure, ArithmeticStructure,\
CallStructure, DataStructure, DelayFixedStructure, DelayStructure,\
DelayNStructure, ForecastStructure, GameStructure, GetConstantsStructure,\
GetDataStructure, GetLookupsStructure, InitialStructure,\
InlineLookupsStructure, IntegStructure, LogicStructure, LookupsStructure,\
ReferenceStructure, SampleIfTrueStructure, SmoothNStructure,\
SmoothStructure, SubscriptsReferenceStructure, TrendStructure

from .python_functions import functionspace
from .subscripts import SubscriptManager
Expand Down Expand Up @@ -191,7 +191,7 @@ def join_calls(arguments: dict) -> dict:
return {}
elif len(arguments) == 1:
# Only one argument
return arguments["0"].calls
return list(arguments.values())[0].calls
else:
# Several arguments
return merge_dependencies(
Expand Down Expand Up @@ -750,6 +750,70 @@ def _compute_axis(self, subscripts: dict) -> tuple:
return coords, axis


class AllocateByPriorityBuilder(StructureBuilder):
"""Builder for allocate_by_priority function."""

def __init__(self, allocate_str: AllocateByPriorityStructure,
component: object):
super().__init__(None, component)
self.arguments = {
"request": allocate_str.request,
"priority": allocate_str.priority,
"width": allocate_str.width,
"supply": allocate_str.supply
}

def build(self, arguments: dict) -> BuildAST:
"""
Build method.
Parameters
----------
arguments: dict
The dictionary of builded arguments.
Returns
-------
built_ast: BuildAST
The built object.
"""
self.section.imports.add("allocation", "allocate_by_priority")

calls = self.join_calls(arguments)

# the last sub of the request must be keep last sub of request and
# priority
last_sub = list(arguments["request"].subscripts)[-1]
# compute the merged subscripts
final_subscripts = self.get_final_subscripts(arguments)
# remove last sub from request
last_sub_value = final_subscripts[last_sub]
del final_subscripts[last_sub]

# Update the susbcripts of width and supply
arguments["width"].reshape(
self.section.subscripts, final_subscripts, True)
arguments["supply"].reshape(
self.section.subscripts, final_subscripts, True)

# Include last sub of request in the last position and update
# the subscripts of request and priority
final_subscripts[last_sub] = last_sub_value
arguments["request"].reshape(
self.section.subscripts, final_subscripts, True)
arguments["priority"].reshape(
self.section.subscripts, final_subscripts, True)

expression = "allocate_by_priority(%(request)s, %(priority)s, "\
"%(width)s, %(supply)s)"
return BuildAST(
expression=expression % arguments,
calls=calls,
subscripts=arguments["request"].subscripts,
order=0)


class ExtLookupBuilder(StructureBuilder):
"""Builder for External Lookups."""
def __init__(self, getlookup_str: GetLookupsStructure, component: object):
Expand Down Expand Up @@ -2025,6 +2089,7 @@ class ASTVisitor:
ReferenceStructure: ReferenceBuilder,
CallStructure: CallBuilder,
GameStructure: GameBuilder,
AllocateByPriorityStructure: AllocateByPriorityBuilder,
LogicStructure: OperationBuilder,
ArithmeticStructure: OperationBuilder,
int: NumericBuilder,
Expand Down
23 changes: 23 additions & 0 deletions pysd/builders/python/subscripts.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,29 @@ def subscripts(self, abstract_subscripts: List[AbstractSubscriptRange]):
self._subscripts[sub.name] =\
self._subscripts[sub.subscripts]

subs2visit = self.subscripts.keys()
while subs2visit:
# third loop for subscripts defined with subranges
updated = []
for dim in subs2visit:
if any(sub in self._subscripts
for sub in self._subscripts[dim]):
# a subrange name is being used to define the range
# subscripts
updated.append(dim)
new_subs = []
for sub in self._subscripts[dim]:
if sub in self.subscripts:
# append the subscripts of the subrange
new_subs += self._subscripts[sub]
else:
# append the same subscript
new_subs.append(sub)
self._subscripts[dim] = new_subs
# visit again the updated ranges as there could be several
# levels of subranges
subs2visit = updated.copy()

def _get_main_subscripts(self) -> dict:
"""
Reutrns a dictionary with the main ranges as keys and their
Expand Down

0 comments on commit 1167289

Please sign in to comment.