Skip to content

Commit

Permalink
coming together
Browse files Browse the repository at this point in the history
  • Loading branch information
JamesPHoughton committed Jul 1, 2016
1 parent bd4eb11 commit 50756f6
Show file tree
Hide file tree
Showing 18 changed files with 774 additions and 939 deletions.
891 changes: 521 additions & 370 deletions .idea/workspace.xml

Large diffs are not rendered by default.

11 changes: 2 additions & 9 deletions pysd/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,6 @@ def initial(value):
Returns
-------
The first value of `value` after the caches are reset
"""
return value

Expand All @@ -122,7 +120,6 @@ def ramp(slope, start, finish):
Examples
--------
"""

t = time()
Expand Down Expand Up @@ -180,12 +177,8 @@ def pulse_train(start, duration, repeat_time, end):

def lookup(x, xs, ys):
""" Provides the working mechanism for lookup functions the builder builds """
if not isinstance(xs, np.ndarray):
return np.interp(x, xs, ys)
resultarray = np.ndarray(np.shape(x))
for i, j in np.ndenumerate(x):
resultarray[i] = np.interp(j, np.array(xs)[i], np.array(ys)[i])
return resultarray
return np.interp(x, xs, ys)



def if_then_else(condition, val_if_true, val_if_false):
Expand Down
38 changes: 28 additions & 10 deletions pysd/pysd.py
Original file line number Diff line number Diff line change
Expand Up @@ -259,18 +259,30 @@ def set_components(self, params):
Examples
--------
>>> model.set_components({'birth_rate': 10})
>>> model.set_components({'Birth Rate': 10})
>>> br = pandas.Series(index=range(30), values=np.sin(range(30))
>>> model.set_components(birth_rate=br)
>>> model.set_components(birth_rate=10)
>>> model.set_components({'birth_rate': br})
"""
for key, value in params.iteritems():
if isinstance(value, _pd.Series):
new_function = self._timeseries_component(value)
else: # Todo: check here for valid value...
else:
new_function = self._constant_component(value)
setattr(self.components, key, new_function)
self.components._funcs[key] = new_function # facilitates lookups

if key in self.components._namespace.keys():
func_name = self.components._namespace[key]
elif key in self.components._namespace.values():
func_name = key
else:
raise NameError('%s is not recognized as a model component' % key)

setattr(self.components, func_name, new_function)
self.components._funcs[func_name] = new_function # facilitates lookups

def set_state(self, t, state):
""" Set the system state.
Expand Down Expand Up @@ -327,11 +339,15 @@ def set_initial_condition(self, initial_condition):
raise TypeError('Check documentation for valid entries')

def _build_euler_timeseries(self, return_timestamps=None):
# Todo: Add the returned timeseries into the integration array. Best we can do for now.

return np.arange(self.components.initial_time(),
self.components.final_time() + self.components.time_step(),
self.components.time_step(), dtype=np.float64)
# Adds the returned timeseries into the integration array. Best we can do for now.
# This does change the integration ever so slightly, but for well-specified
# models there shouldn't be sensitivity to a finer integration timestep.
ts = np.arange(self.components.initial_time(),
self.components.final_time() + self.components.time_step(),
self.components.time_step(), dtype=np.float64)
if return_timestamps is not None:
ts = np.sort(np.unique(np.append(ts, return_timestamps)))
return ts

def _format_return_timestamps(self, return_timestamps=None):
"""
Expand All @@ -353,10 +369,12 @@ def _format_return_timestamps(self, return_timestamps=None):

def _timeseries_component(self, series):
""" Internal function for creating a timeseries model element """
# Todo: check here for valid value...
return lambda: np.interp(self.components._t, series.index, series.values)

def _constant_component(self, value):
""" Internal function for creating a constant model element """
# Todo: check here for valid value...
return lambda: value

def _euler_step(self, ddt, state, dt):
Expand Down
9 changes: 6 additions & 3 deletions pysd/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def dict_find(in_dict, value):
# Todo: make this robust to missing values
return in_dict.keys()[in_dict.values().index(value)]


def xrmerge(das, accept_new=True):
"""
Merges xarrays with different dimension sets
Expand Down Expand Up @@ -59,6 +60,7 @@ def xrmerge(das, accept_new=True):
da = new_da.fillna(da) if accept_new else da.fillna(new_da)
return da


def find_subscript_name(subscript_dict, element):
"""
Given a subscript dictionary, and a member of a subscript family,
Expand Down Expand Up @@ -230,10 +232,10 @@ def make_python_identifier(string, namespace=None, reserved_words=None,
"""

if namespace is None:
namespace = {}
namespace = dict()

if reserved_words is None:
reserved_words = []
reserved_words = list()

if string in namespace:
return namespace[string], namespace
Expand Down Expand Up @@ -375,6 +377,7 @@ def visit_addresses(frame, return_addresses):
Returns
-------
outdict: dictionary
"""
outdict = dict()
Expand All @@ -388,4 +391,4 @@ def visit_addresses(frame, return_addresses):
else:
outdict[real_name] = frame[pyname]

return outdict
return outdict
15 changes: 10 additions & 5 deletions pysd/vensim2py.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def get_equation_components(equation_str):

# replace any amount of whitespace with a single space
equation_str = equation_str.replace('\\t', ' ')
equation_str = equation_str.replace('\\', ' ')
#equation_str = equation_str.replace('\\', ' ')
equation_str = re.sub(r"\s+", ' ', equation_str)


Expand Down Expand Up @@ -280,6 +280,9 @@ def visit_expression(self, n, vc):
def generic_visit(self, n, vc):
return ''.join(filter(None, vc)) or n.text

def visit__(self, n, vc):
return ' '

parse_object = ComponentParser(tree)

return {'real_name': parse_object.real_name,
Expand Down Expand Up @@ -417,14 +420,13 @@ def parse_general_expression(element, namespace=None, subscript_dict=None):
subscript_dict),
}


in_ops = {
"+": "+", "-": "-", "*": "*", "/": "/", "^": "**", "=": "==", "<=": "<=", "<>": "!=",
"<": "<", ">=": ">=", ">": ">", ":and:": "and",
":or:": "or"}
"<": "<", ">=": ">=", ">": ">",
":and:": " and ", ":or:": " or "} # spaces important for word-based operators

pre_ops = {
"-": "-", ":not:": "not",
"-": "-", ":not:": " not ", # spaces important for word-based operators
"+": " " # space is important, so that and empty string doesn't slip through generic
}

Expand Down Expand Up @@ -575,6 +577,9 @@ def visit_build_call(self, n, (call, _1, lp, _2, args, rp)):
self.new_structure += structure
return name

def visit__(self, n, vc):
return ''

def generic_visit(self, n, vc):
return ''.join(filter(None, vc)) or n.text

Expand Down
74 changes: 0 additions & 74 deletions tests/Plot Speedtests.ipynb

This file was deleted.

4 changes: 2 additions & 2 deletions tests/integration_test_factory.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os.path
import textwrap
import glob
from pysd import builder
from pysd import utils

test_dir = 'test-models/'
vensim_test_files = glob.glob(test_dir+'tests/*/*.mdl')
Expand All @@ -11,7 +11,7 @@
(path, file_name) = os.path.split(file_path)
(name, ext) = os.path.splitext(file_name)

test_name = builder.make_python_identifier(path.split('/')[-1])[0]
test_name = utils.make_python_identifier(path.split('/')[-1])[0]

test_func_string = """
def test_%(test_name)s(self):
Expand Down
10 changes: 0 additions & 10 deletions tests/profile_pysd.py

This file was deleted.

5 changes: 0 additions & 5 deletions tests/run_profiler.sh

This file was deleted.

Binary file removed tests/sandprofile
Binary file not shown.
65 changes: 0 additions & 65 deletions tests/speed_test.py

This file was deleted.

11 changes: 0 additions & 11 deletions tests/speedtest_results.json

This file was deleted.

0 comments on commit 50756f6

Please sign in to comment.