Skip to content

Commit

Permalink
Merge pull request SDXorg#187 from alexprey/master
Browse files Browse the repository at this point in the history
Fix model reading for Python 2.7 and more...
  • Loading branch information
JamesPHoughton authored Jul 14, 2018
2 parents e348dba + f09c9bd commit 7c273d5
Show file tree
Hide file tree
Showing 10 changed files with 142 additions and 2,362 deletions.
7 changes: 0 additions & 7 deletions .idea/misc.xml

This file was deleted.

21 changes: 0 additions & 21 deletions .idea/pysd.iml

This file was deleted.

2,279 changes: 0 additions & 2,279 deletions .idea/workspace.xml

This file was deleted.

2 changes: 1 addition & 1 deletion pysd/py_backend/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
import pkg_resources
import textwrap
import warnings

import yapf

from io import open
from .._version import __version__
from ..py_backend import utils

Expand Down
47 changes: 29 additions & 18 deletions pysd/py_backend/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import imp
import warnings
import random
import inspect
import xarray as xr
from funcsigs import signature
import os
Expand Down Expand Up @@ -387,11 +388,9 @@ def set_components(self, params):
else:
new_function = self._constant_component(value)

if key in self.components._namespace.keys():
func_name = self.components._namespace[key]
elif key in self.components._namespace.values():
func_name = key
else:
func_name = utils.get_value_by_insensitive_key_or_value(key, self.components._namespace)

if func_name is None:
raise NameError('%s is not recognized as a model component' % key)

if 'integ_' + func_name in dir(self.components): # this won't handle other statefuls...
Expand Down Expand Up @@ -425,19 +424,29 @@ def set_state(self, t, state):
self.time.update(t)

for key, value in state.items():
if key in self.components._namespace.keys():
element_name = 'integ_%s' % self.components._namespace[key]
elif key in self.components._namespace.values():
element_name = 'integ_%s' % key
else: # allow the user to specify the stateful object directly
element_name = key
# TODO Implement map with reference between component and stateful element?
component_name = utils.get_value_by_insensitive_key_or_value(key, self.components._namespace)
if component_name is not None:
stateful_name = 'integ_%s' % component_name
else:
component_name = key
stateful_name = key

try:
element = getattr(self.components, element_name)
element.update(value)
except AttributeError:
print("'%s' has no state elements, assignment failed")
raise
# Try to update stateful component
if hasattr(self.components, stateful_name):
try:
element = getattr(self.components, stateful_name)
element.update(value)
except AttributeError:
print("'%s' has no state elements, assignment failed")
raise
else:
# Try to override component
try:
setattr(self.components, component_name, self._constant_component(value))
except AttributeError:
print("'%s' has no component, assignment failed")
raise

def clear_caches(self):
""" Clears the Caches for all model elements """
Expand Down Expand Up @@ -571,7 +580,9 @@ def _format_return_timestamps(self, return_timestamps=None):
self.components.final_time() + self.components.saveper(),
self.components.saveper(), dtype=np.float64
)
elif isinstance(return_timestamps, (list, int, float, range, np.ndarray)):
elif inspect.isclass(range) and isinstance(return_timestamps, range):
return_timestamps_array = np.array(return_timestamps, ndmin=1)
elif isinstance(return_timestamps, (list, int, float, np.ndarray)):
return_timestamps_array = np.array(return_timestamps, ndmin=1)
elif isinstance(return_timestamps, _pd.Series):
return_timestamps_array = return_timestamps.as_matrix()
Expand Down
13 changes: 12 additions & 1 deletion pysd/py_backend/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,7 +262,7 @@ def make_python_identifier(string, namespace=None, reserved_words=None,

if convert == 'hex':
# Convert invalid characters to hex. Note: \p{l} designates all Unicode letter characters (any language),
# \p{m} designates all mark symbols (e.g., vowel marks in Indian scrips, such as the final ે in નમસ્તે)
# \p{m} designates all mark symbols (e.g., vowel marks in Indian scrips, such as the final)
# and \p{n} designates all numbers. We allow any of these to be present in the regex.
s = ''.join([c.encode("hex") if re.findall('[^\p{l}\p{m}\p{n}_]', c) else c for c in s])

Expand Down Expand Up @@ -399,3 +399,14 @@ def visit_addresses(frame, return_addresses):
outdict[real_name] = frame[pyname]

return outdict


def get_value_by_insensitive_key_or_value(key, dict):
lower_key = key.lower()
for real_key, real_value in dict.items():
if real_key.lower() == lower_key:
return dict[real_key]
if real_value.lower() == lower_key:
return real_value

return None
2 changes: 1 addition & 1 deletion pysd/py_backend/vensim/table2py.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import pandas as pd
import warnings
from ...pysd import read_vensim

from io import open

def read_tabular(table_file, sheetname='Sheet1'):
"""
Expand Down
59 changes: 30 additions & 29 deletions pysd/py_backend/vensim/vensim2py.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pysd/pysd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
--------
August 15, 2014: created
June 6 2015: Major updates - version 0.2.5
Jan 2016: Rework to handle subscripts
Jan 2016: Rework to handle subscripts
May 2016: Updates to handle grammar refactoring
Sept 2016: Major refactor, putting most internal code into the Model and Macro objects
"""
Expand Down
72 changes: 68 additions & 4 deletions tests/unit_test_pysd.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,23 +349,23 @@ def test_replace_element(self):
self.assertGreater(stocks1['Teacup Temperature'].loc[10],
stocks2['Teacup Temperature'].loc[10])

def test_set_initial_condition(self):
def test_set_initial_condition_origin_full(self):
import pysd
model = pysd.read_vensim(test_model)
initial_temp = model.components.teacup_temperature()
initial_time = model.components.time()

new_state = {'Teacup Temperature': 500}
new_time = np.random.rand()
new_time = 10

model.set_initial_condition((new_time, new_state))
set_temp = model.components.teacup_temperature()
set_time = model.components.time()

self.assertNotEqual(set_temp, initial_temp)
self.assertNotEqual(set_temp, initial_temp, "Test definition is wrong, please change configuration")
self.assertEqual(set_temp, 500)

self.assertNotEqual(initial_time, new_time)
self.assertNotEqual(initial_time, new_time, "Test definition is wrong, please change configuration")
self.assertEqual(new_time, set_time)

model.set_initial_condition('original')
Expand All @@ -375,6 +375,70 @@ def test_set_initial_condition(self):
self.assertEqual(initial_temp, set_temp)
self.assertEqual(initial_time, set_time)

def test_set_initial_condition_origin_short(self):
import pysd
model = pysd.read_vensim(test_model)
initial_temp = model.components.teacup_temperature()
initial_time = model.components.time()

new_state = {'Teacup Temperature': 500}
new_time = 10

model.set_initial_condition((new_time, new_state))
set_temp = model.components.teacup_temperature()
set_time = model.components.time()

self.assertNotEqual(set_temp, initial_temp, "Test definition is wrong, please change configuration")
self.assertEqual(set_temp, 500)

self.assertNotEqual(initial_time, new_time, "Test definition is wrong, please change configuration")
self.assertEqual(new_time, set_time)

model.set_initial_condition('o')
set_temp = model.components.teacup_temperature()
set_time = model.components.time()

self.assertEqual(initial_temp, set_temp)
self.assertEqual(initial_time, set_time)

def test_set_initial_condition_for_stock_component(self):
import pysd
model = pysd.read_vensim(test_model)
initial_temp = model.components.teacup_temperature()
initial_time = model.components.time()

new_state = {'Teacup Temperature': 500}
new_time = 10

model.set_initial_condition((new_time, new_state))
set_temp = model.components.teacup_temperature()
set_time = model.components.time()

self.assertNotEqual(set_temp, initial_temp, "Test definition is wrong, please change configuration")
self.assertEqual(set_temp, 500)

self.assertNotEqual(initial_time, 10, "Test definition is wrong, please change configuration")
self.assertEqual(set_time, 10)

def test_set_initial_condition_for_constant_component(self):
import pysd
model = pysd.read_vensim(test_model)
initial_temp = model.components.teacup_temperature()
initial_time = model.components.time()

new_state = {'Room Temperature': 100}
new_time = 10

model.set_initial_condition((new_time, new_state))
set_temp = model.components.room_temperature()
set_time = model.components.time()

self.assertNotEqual(set_temp, initial_temp, "Test definition is wrong, please change configuration")
self.assertEqual(set_temp, 100)

self.assertNotEqual(initial_time, 10, "Test definition is wrong, please change configuration")
self.assertEqual(set_time, 10)

def test__build_euler_timeseries(self):
import pysd
model = pysd.read_vensim(test_model)
Expand Down

0 comments on commit 7c273d5

Please sign in to comment.