diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..665254c2 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +# Run CI tests with pytest and update coverage to coveralls + +name: CI + +on: [push, pull_request] + +jobs: + test: + #runs-on: ${{ matrix.os }} + runs-on: ubuntu-latest + strategy: + matrix: + #os: [ubuntu-latest, macos-latest, windows-latest] + python-version: [3.7, 3.9] + + steps: + - uses: actions/checkout@v2 + with: + submodules: recursive + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: pip install -e .[test] + - name: Test and coverage + run: pytest tests/ --cov=pysd + - name: Coveralls + if: matrix.python-version == 3.7 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: coveralls --service=github + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 8982ab48..00000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: python -python: - - "3.7" - - "3.9" -# command to install dependencies -cache: pip -install: - - pip install cython - - pip install --upgrade pip setuptools wheel - - pip install -e . - - pip install psutil - - pip install pytest pytest-cov - - pip install coveralls -# command to run tests -script: - - cd tests - - pytest --cov=pysd *.py - - coveralls diff --git a/dev-requirements.txt b/dev-requirements.txt new file mode 100644 index 00000000..34790784 --- /dev/null +++ b/dev-requirements.txt @@ -0,0 +1,5 @@ +pytest +pytest-cov +coverage +coveralls +psutil diff --git a/docs/advanced_usage.rst b/docs/advanced_usage.rst index b8d5f7a8..04f56341 100644 --- a/docs/advanced_usage.rst +++ b/docs/advanced_usage.rst @@ -42,16 +42,16 @@ We can substitute this function directly for the heat_loss_to_room model compone If you want to replace a subscripted variable, you need to ensure that the output from the new function is the same as the previous one. You can check the current coordinates and dimensions of a component by using :py:data:`.get_coords(variable_name)` as it is explained in :doc:`basic usage <../basic_usage>`. -Splitting Vensim views in different files ------------------------------------------ -In order to replicate the Vensim views in translated models, the user can set the `split_modules` argument to True in the :py:func:`read_vensim` function:: +Splitting Vensim views in separate Python files (modules) +--------------------------------------------------------- +In order to replicate the Vensim views in translated models, the user can set the `split_views` argument to True in the :py:func:`read_vensim` function:: - read_vensim("many_views_model.mdl", split_modules=True) + read_vensim("many_views_model.mdl", split_views=True) The option to split the model in views is particularly interesting for large models with tens of views. Translating those models into a single file may make the resulting Python model difficult to read and maintain. -In a Vensim model with three separate views (e.g. `view_1`, `view_2` and `view_3`), setting `split_modules` to True would create the following tree inside the directory where the `.mdl` model is located: +In a Vensim model with three separate views (e.g. `view_1`, `view_2` and `view_3`), setting `split_views` to True would create the following tree inside the directory where the `.mdl` model is located: | main-folder | ├── modules_many_views_model @@ -64,6 +64,13 @@ In a Vensim model with three separate views (e.g. `view_1`, `view_2` and `view_3 | ├── many_views_model.py | | + +.. note :: + Often, modelers wish to organise views further. To that end, a common practice is to include a particular character in the View name to indicate that what comes after it is the name of the subview. For instance, we could name one view as `ENERGY.Supply` and another one as `ENERGY.Demand`. + In that particular case, setting the `subview_sep` kwarg equal to `"."`, as in the code below, would name the translated views as `demand.py` and `supply.py` and place them inside the `ENERGY` folder:: + + read_vensim("many_views_model.mdl", split_views=True, subview_sep=".") + If macros are present, they will be self-contained in files named as the macro itself. The macro inner variables will be placed inside the module that corresponds with the view in which they were defined. diff --git a/pysd/_version.py b/pysd/_version.py index 2d986fc5..0a0a43a5 100644 --- a/pysd/_version.py +++ b/pysd/_version.py @@ -1 +1 @@ -__version__ = "1.8.1" +__version__ = "1.9.0" diff --git a/pysd/cli/main.py b/pysd/cli/main.py index e5a4c3cd..216b8e66 100644 --- a/pysd/cli/main.py +++ b/pysd/cli/main.py @@ -26,7 +26,7 @@ def main(args): options = parser.parse_args(args) model = load(options.model_file, options.missing_values, - options.split_modules) + options.split_views, subview_sep=options.subview_sep) if not options.run: print("\nFinished!") @@ -44,7 +44,7 @@ def main(args): sys.exit() -def load(model_file, missing_values, split_modules): +def load(model_file, missing_values, split_views, **kwargs): """ Translate and load model file. @@ -53,6 +53,19 @@ def load(model_file, missing_values, split_modules): model_file: str Vensim, Xmile or PySD model file. + split_views: bool (optional) + If True, the sketch is parsed to detect model elements in each + model view, and then translate each view in a separate python + file. Setting this argument to True is recommended for large + models split in many different views. Default is False. + + **kwargs: (optional) + Additional keyword arguments. + subview_sep:(str) + Character used to separate views and subviews. If provided, + and split_views=True, each submodule will be placed inside the + folder of the parent view. + Returns ------- pysd.model @@ -62,7 +75,7 @@ def load(model_file, missing_values, split_modules): print("\nTranslating model file...\n") return pysd.read_vensim(model_file, initialize=False, missing_values=missing_values, - split_modules=split_modules) + split_views=split_views, **kwargs) elif model_file.lower().endswith('.xmile'): print("\nTranslating model file...\n") return pysd.read_xmile(model_file, initialize=False, diff --git a/pysd/cli/parser.py b/pysd/cli/parser.py index a38929eb..df734e1d 100644 --- a/pysd/cli/parser.py +++ b/pysd/cli/parser.py @@ -218,9 +218,9 @@ def __call__(self, parser, namespace, values, option_string=None): '--saveper will be ignored') -################### -# Model arguments # -################### +######################### +# Translation arguments # +######################### trans_arguments = parser.add_argument_group( 'translation arguments', @@ -233,10 +233,18 @@ def __call__(self, parser, namespace, values, option_string=None): 'it does not run it after translation') trans_arguments.add_argument( - '--split-modules', dest='split_modules', + '--split-views', dest='split_views', action='store_true', default=False, help='parse the sketch to detect model elements in each model view,' - ' and then translate each view in a separate python file') + ' and then translate each view in a separate Python file') + +trans_arguments.add_argument( + '--subview-sep', dest='subview_sep', + action='store', type=str, default="", metavar='STRING', + help='further division of views split in subviews, by identifying the' + 'separator string in the view name, only availabe if --split-views' + ' is used') + ####################### # Warnings and errors # diff --git a/pysd/py_backend/builder.py b/pysd/py_backend/builder.py index 4fe22e28..36204229 100644 --- a/pysd/py_backend/builder.py +++ b/pysd/py_backend/builder.py @@ -117,20 +117,19 @@ def reset(cls): build_names = set() -def build_modular_model( - elements, subscript_dict, namespace, main_filename, elements_per_module -): +def build_modular_model(elements, subscript_dict, namespace, main_filename, + elements_per_view): """ This is equivalent to the build function, but is used when the - split_modules parameter is set to True in the read_vensim function. + split_views parameter is set to True in the read_vensim function. The main python model file will be named as the original model file, and stored in the same folder. The modules will be stored in a separate folder named modules + original_model_name. Three extra json files will be generated, containing the namespace, subscripts_dict and the module names plus the variables included in each module, respectively. - Setting split_modules=True is recommended for large models with many + Setting split_views=True is recommended for large models with many different views. Parameters @@ -164,32 +163,57 @@ def build_modular_model( # create modules directory if it does not exist os.makedirs(modules_dir, exist_ok=True) - modules_list = elements_per_module.keys() + # check if there are subviews or only main views + subviews = all(isinstance(n, dict) for n in elements_per_view.values()) + + all_views = elements_per_view.keys() # creating the rest of files per module (this needs to be run before the # main module, as it updates the import_modules) processed_elements = [] - for module in modules_list: - module_elems = [] - for element in elements: - if element.get("py_name", None) in elements_per_module[module] or\ - element.get("parent_name", None) in elements_per_module[module]: - module_elems.append(element) + for view_name in all_views: + view_elems = [] + if not subviews: # only main views + for element in elements: + if element.get("py_name", None) in \ + elements_per_view[view_name] or \ + element.get("parent_name", None) in \ + elements_per_view[view_name]: + view_elems.append(element) + + _build_separate_module(view_elems, subscript_dict, view_name, + modules_dir) + + else: + # create subdirectory + view_dir = os.path.join(modules_dir, view_name) + os.makedirs(view_dir, exist_ok=True) - _build_separate_module(module_elems, subscript_dict, - module, modules_dir) + for subview_name in elements_per_view[view_name].keys(): + subview_elems = [] + for element in elements: + if element.get("py_name", None) in \ + elements_per_view[view_name][subview_name] or \ + element.get("parent_name", None) in \ + elements_per_view[view_name][subview_name]: + subview_elems.append(element) - processed_elements += module_elems + _build_separate_module(subview_elems, subscript_dict, + subview_name, view_dir) + view_elems += subview_elems + + processed_elements += view_elems # the unprocessed will go in the main file unprocessed_elements = [ element for element in elements if element not in processed_elements ] # building main file using the build function - _build_main_module(unprocessed_elements, subscript_dict, main_filename) + _build_main_module(unprocessed_elements, subscript_dict, + main_filename, subviews) # create json file for the modules and corresponding model elements with open(os.path.join(modules_dir, "_modules.json"), "w") as outfile: - json.dump(elements_per_module, outfile, indent=4, sort_keys=True) + json.dump(elements_per_view, outfile, indent=4, sort_keys=True) # create single namespace in a separate json file with open( @@ -203,13 +227,11 @@ def build_modular_model( ) as outfile: json.dump(subscript_dict, outfile, indent=4, sort_keys=True) - return None - -def _build_main_module(elements, subscript_dict, file_name): +def _build_main_module(elements, subscript_dict, file_name, subviews): """ Constructs and writes the python representation of the main model - module, when the split_modules=True in the read_vensim function. + module, when the split_views=True in the read_vensim function. Parameters ---------- @@ -230,6 +252,10 @@ def _build_main_module(elements, subscript_dict, file_name): file_name: str Path of the file where the main module will be stored. + subviews: bool + True or false depending on whether the views are split in subviews or + not. + Returns ------- None or text: None or str @@ -276,15 +302,25 @@ def _build_main_module(elements, subscript_dict, file_name): text += _get_control_vars(control_vars) - text += textwrap.dedent(""" - # load modules from the modules_%(outfile)s directory - for module in _modules: - exec(open_module(_root, "%(outfile)s", module)) + if not subviews: + text += textwrap.dedent(""" + # load modules from the modules_%(outfile)s directory + for module in _modules: + exec(open_module(_root, "%(outfile)s", module)) - """ % { - "outfile": os.path.basename(file_name).split(".")[0], - - }) + """ % { + "outfile": os.path.basename(file_name).split(".")[0], + }) + else: + text += textwrap.dedent(""" + # load submodules from subdirs in modules_%(outfile)s directory + for mod_name, mod_submods in _modules.items(): + for submod_name in mod_submods.keys(): + exec(open_module(_root, "%(outfile)s", mod_name, submod_name)) + + """ % { + "outfile": os.path.basename(file_name).split(".")[0], + }) text += funcs text = black.format_file_contents(text, fast=True, mode=black.FileMode()) @@ -292,10 +328,6 @@ def _build_main_module(elements, subscript_dict, file_name): # Needed for various sessions build_names.clear() - # this is used for testing - if file_name == "return": - return text - with open(file_name, "w", encoding="UTF-8") as out: out.write(text) @@ -303,7 +335,7 @@ def _build_main_module(elements, subscript_dict, file_name): def _build_separate_module(elements, subscript_dict, module_name, module_dir): """ Constructs and writes the python representation of a specific model - module, when the split_modules=True in the read_vensim function + module, when the split_views=True in the read_vensim function Parameters ---------- @@ -1544,14 +1576,16 @@ def add_ext_data(identifier, file_name, tab, time_row_or_col, cell, subs, List of element construction dictionaries for the builder to assemble. """ - coords = utils.make_coord_dict(subs, subscript_dict, terse=False) + Imports.add("external", "ExtData") + + coords = utils.simplify_subscript_input( + utils.make_coord_dict(subs, subscript_dict, terse=False), + subscript_dict, return_full=False) keyword = ( "'%s'" % keyword.strip(":").lower() if isinstance(keyword, str) else keyword) name = utils.make_python_identifier("_ext_data_%s" % identifier)[0] - Imports.add("external", "ExtData") - # Check if the object already exists if name in build_names: # Create a new py_name with ADD_# ending @@ -1626,7 +1660,9 @@ def add_ext_constant(identifier, file_name, tab, cell, subs, subscript_dict): """ Imports.add("external", "ExtConstant") - coords = utils.make_coord_dict(subs, subscript_dict, terse=False) + coords = utils.simplify_subscript_input( + utils.make_coord_dict(subs, subscript_dict, terse=False), + subscript_dict, return_full=False) name = utils.make_python_identifier("_ext_constant_%s" % identifier)[0] # Check if the object already exists @@ -1663,9 +1699,8 @@ def add_ext_constant(identifier, file_name, tab, cell, subs, subscript_dict): return "%s()" % external["py_name"], [external] -def add_ext_lookup( - identifier, file_name, tab, x_row_or_col, cell, subs, subscript_dict -): +def add_ext_lookup(identifier, file_name, tab, x_row_or_col, cell, + subs, subscript_dict): """ Constructs a external object for handling Vensim's GET XLS LOOKUPS and GET DIRECT LOOKUPS functionality. @@ -1707,7 +1742,9 @@ def add_ext_lookup( """ Imports.add("external", "ExtLookup") - coords = utils.make_coord_dict(subs, subscript_dict, terse=False) + coords = utils.simplify_subscript_input( + utils.make_coord_dict(subs, subscript_dict, terse=False), + subscript_dict, return_full=False) name = utils.make_python_identifier("_ext_lookup_%s" % identifier)[0] # Check if the object already exists diff --git a/pysd/py_backend/external.py b/pysd/py_backend/external.py index 436799bd..10035aed 100644 --- a/pysd/py_backend/external.py +++ b/pysd/py_backend/external.py @@ -405,13 +405,20 @@ def _initialize_data(self, element_type): + "\t{}:\t{}\n".format(series_across, self.x_row_or_col) ) - # Check if the lookup/time dimension is strictly monotonous - if np.any(np.diff(series) <= 0) and self.missing != "keep": - raise ValueError(self.py_name + "\n" - + "Dimension given in:\n" - + self._file_sheet - + "\t{}:\t{}\n".format(series_across, self.x_row_or_col) - + " is not strictly monotonous") + # reorder data with increasing series + if not np.all(np.diff(series) > 0) and self.missing != "keep": + order = np.argsort(series) + series = series[order] + data = data[order] + # Check if the lookup/time dimension is well defined + if np.any(np.diff(series) == 0): + raise ValueError(self.py_name + "\n" + + "Dimension given in:\n" + + self._file_sheet + + "\t{}:\t{}\n".format( + series_across, self.x_row_or_col) + + " has repeated values") + # Check for missing values in data if np.any(np.isnan(data)) and self.missing != "keep": diff --git a/pysd/py_backend/utils.py b/pysd/py_backend/utils.py index 0df26058..f9dab9ab 100644 --- a/pysd/py_backend/utils.py +++ b/pysd/py_backend/utils.py @@ -714,6 +714,49 @@ def round_(x): return round(x) +def simplify_subscript_input(coords, subscript_dict, + return_full=True): + """ + Parameters + ---------- + coords: dict + Coordinates to write in the model file. + + subscript_dict: dict + The subscript dictionary of the model file. + + return_full: bool (optional) + If True the when coords == subscript_dict, '_subscript_dict' + will be returned. Default is True + + partial: bool (optional) + If True "_subscript_dict" will not be returned as possible dict. + Used when subscript_dict is not the full dictionary. Default is False. + + Returns + ------- + coords: str + The equations to generate the coord dicttionary in the model file. + + """ + + if coords == subscript_dict and return_full: + # variable defined with all the subscripts + return "_subscript_dict" + + coordsp = [] + for dim, coord in coords.items(): + # find dimensions can be retrieved from _subscript_dict + if coord == subscript_dict[dim]: + # use _subscript_dict + coordsp.append(f"'{dim}': _subscript_dict['{dim}']") + else: + # write whole dict + coordsp.append(f"'{dim}': {coord}") + + return "{" + ", ".join(coordsp) + "}" + + def add_entries_underscore(*dictionaries): """ Expands dictionaries adding new keys underscoring the white spaces @@ -785,10 +828,10 @@ def load_model_data(root_dir, model_name): return namespace, subscripts, modules -def open_module(root_dir, model_name, module): +def open_module(root_dir, model_name, module, submodule=None): """ Used to load model modules from the main model file, when - split_modules=True in the read_vensim function. + split_views=True in the read_vensim function. Parameters ---------- @@ -799,17 +842,46 @@ def open_module(root_dir, model_name, module): Name of the model without file type extension (e.g. "my_model"). module: str - Name of the module to open. + Name of the module folder or file to open. + + sub_module: str (optional) + Name of the submodule to open. Returns ------- str: Model file content. - """ + if not submodule: + rel_file_path = module + ".py" + else: + rel_file_path = os.path.join(module, submodule + ".py") + return open( - os.path.join(root_dir, "modules_" + model_name, module + ".py") - ).read() + os.path.join(root_dir, "modules_" + model_name, rel_file_path)).read() + + +def clean_file_names(*args): + """ + Removes special characters and makes clean file names + + Parameters + ---------- + *args: tuple + Any number of strings to to clean + + Returns + ------- + clean: list + List containing the clean strings + """ + clean = [] + for name in args: + clean.append(re.sub( + r"[\W]+", "", name.replace(" ", "_") + ).lstrip("0123456789") + ) + return clean class ProgressBar: diff --git a/pysd/py_backend/vensim/vensim2py.py b/pysd/py_backend/vensim/vensim2py.py index 1eb1ed79..ceaa4cb4 100644 --- a/pysd/py_backend/vensim/vensim2py.py +++ b/pysd/py_backend/vensim/vensim2py.py @@ -4,6 +4,7 @@ knowledge of vensim syntax should be here. """ +from inspect import cleandoc import os import re import warnings @@ -491,12 +492,12 @@ def parse_sketch_line(sketch_line, namespace): sketch_grammar = _include_common_grammar( r""" - line = var_definition / module_intro / module_title / module_definition / arrow / flow / other_objects / anything - module_intro = ~r"\s*Sketch.*?names$" / ~r"^V300.*?ignored$" - module_title = "*" module_name - module_name = ~r"(?<=\*)[^\n]+$" - module_definition = "$" color "," digit "," font_properties "|" ( ( color / ones_and_dashes ) "|")* module_code - var_definition = var_code "," var_number "," var_name "," position "," var_box_type "," arrows_in_allowed "," hide_level "," var_face "," var_word_position "," var_thickness "," var_rest_conf ","? ( ( ones_and_dashes / color) ",")* font_properties? + line = var_definition / view_intro / view_title / view_definition / arrow / flow / other_objects / anything + view_intro = ~r"\s*Sketch.*?names$" / ~r"^V300.*?ignored$" + view_title = "*" view_name + view_name = ~r"(?<=\*)[^\n]+$" + view_definition = "$" color "," digit "," font_properties "|" ( ( color / ones_and_dashes ) "|")* view_code + var_definition = var_code "," var_number "," var_name "," position "," var_box_type "," arrows_in_allowed "," hide_level "," var_face "," var_word_position "," var_thickness "," var_rest_conf ","? ( ( ones_and_dashes / color) ",")* font_properties? ","? extra_bytes? # elements used in a line defining the properties of a variable or stock var_name = element var_name = ~r"(?<=,)[^,]+(?=,)" @@ -509,6 +510,7 @@ def parse_sketch_line(sketch_line, namespace): var_word_position = ~r"(?<=,)\-*\d+(?=,)" var_thickness = digit var_rest_conf = digit "," ~r"\d+" + extra_bytes = ~r"\d+,\d+,\d+,\d+,\d+,\d+" # required since Vensim 8.2.1 arrow = arrow_code "," digit "," origin_var "," destination_var "," (digit ",")+ (ones_and_dashes ",")? ((color ",") / ("," ~r"\d+") / (font_properties "," ~r"\d+"))* "|(" position ")|" # arrow origin and destination (this may be useful if further # parsing is required) @@ -537,7 +539,7 @@ def parse_sketch_line(sketch_line, namespace): var_code = ~r"^10(?=,)" multipurpose_code = ~r"^12(?=,)" # source, sink, plot, comment other_objects_code = ~r"^(30|31)(?=,)" - module_code = ~r"\d+" "," digit "," digit "," ~r"\d+" # code at + view_code = ~r"\d+" "," digit "," digit "," ~r"\d+" # code at digit = ~r"(?<=,)\d+(?=,)" # comma separated value/s ones_and_dashes = ~r"\-1\-\-1\-\-1" @@ -550,22 +552,22 @@ def parse_sketch_line(sketch_line, namespace): class SketchParser(parsimonious.NodeVisitor): def __init__(self, ast, namespace): self.namespace = namespace - self.module_or_var = {"variable_name": "", "module_name": ""} + self.view_or_var = {"variable_name": "", "view_name": ""} self.visit(ast) - def visit_module_name(self, n, vc): - self.module_or_var["module_name"] = n.text + def visit_view_name(self, n, vc): + self.view_or_var["view_name"] = n.text def visit_var_definition(self, n, vc): if int(vc[10]) % 2 != 0: # not a shadow variable - self.module_or_var["variable_name"] = self.namespace.get(vc[4], - "") + self.view_or_var["variable_name"] = self.namespace.get(vc[4], + "") def generic_visit(self, n, vc): return "".join(filter(None, vc)) or n.text or "" - + tree = parser.parse(sketch_line) - return SketchParser(tree, namespace=namespace).module_or_var + return SketchParser(tree, namespace=namespace).view_or_var def parse_units(units_str): @@ -1263,43 +1265,17 @@ def visit_array(self, n, vc): else: datastr = n.text - # Following implementation makes cleaner the model file - if list(coords) == element["subs"]: - if len(coords) == len(subscript_dict): - # variable is defined with all subscrips - return builder.build_function_call( - functions_utils["DataArray"], - [datastr, "_subscript_dict", repr(list(coords))], - ) - - from_dict, no_from_dict = [], {} - for coord, sub in zip(coords, element["subs"]): - # find dimensions can be retrieved from _subscript_dict - if coord == sub: - from_dict.append(coord) - else: - no_from_dict[coord] = coords[coord] - - if from_dict and no_from_dict: - # some dimensons can be retrieved from _subscript_dict - coordsp = ( - "{**{dim: _subscript_dict[dim] for dim in %s}, " - % from_dict - + repr(no_from_dict)[1:] - ) - elif from_dict: - # all dimensons can be retrieved from _subscript_dict - coordsp = "{dim: _subscript_dict[dim] for dim in %s}" \ - % from_dict - else: - # no dimensons can be retrieved from _subscript_dict - coordsp = repr(no_from_dict) + # Create a cleaner dictionary of coords using _subscript_dict + # when possible + utils.simplify_subscript_input(coords, subscript_dict, + return_full=True) return builder.build_function_call( - functions_utils["DataArray"], [datastr, coordsp, repr(list( - coords))] - ) - + functions_utils["DataArray"], + [datastr, + utils.simplify_subscript_input(coords, subscript_dict), + repr(list(coords))] + ) else: return n.text.replace(" ", "") @@ -1477,7 +1453,7 @@ def generic_visit(self, n, vc): ) -def translate_section(section, macro_list, sketch, root_path): +def translate_section(section, macro_list, sketch, root_path, subview_sep=""): model_elements = get_model_elements(section["string"]) @@ -1577,16 +1553,15 @@ def translate_section(section, macro_list, sketch, root_path): ] # macros are built in their own separate files, and their inputs and - # outputs are put in modules + # outputs are put in views/subviews if sketch and (section["name"] == "_main_"): - - module_elements = _classify_elements_by_module(sketch, namespace) - - if len(module_elements.keys()) == 1: + module_elements = _classify_elements_by_module(sketch, namespace, + subview_sep) + if (len(module_elements.keys()) == 1) \ + and (isinstance(module_elements[list(module_elements)[0]], list)): warnings.warn( - "Only one module was detected. The model will be built " - "in a single file." - ) + "Only a single view with no subviews was detected. The model" + " will be built in a single file.") else: builder.build_modular_model( build_elements, @@ -1603,11 +1578,11 @@ def translate_section(section, macro_list, sketch, root_path): return section["file_name"] -def _classify_elements_by_module(sketch, namespace): +def _classify_elements_by_module(sketch, namespace, subview_sep): """ Takes the Vensim sketch as a string, parses it (line by line) and - returns a list of the model elements that belong to each vensim view - (here we call the modules). + returns a dictionary containing the views/subviews as keys and the model + elements that belong to each view/subview inside a list as values. Parameters ---------- @@ -1618,48 +1593,68 @@ def _classify_elements_by_module(sketch, namespace): Translation from original model element names (keys) to python safe function identifiers (values). + subview_sep: str + Character used to split view names into view + subview + (e.g. if a view is named ENERGY.Demand and suview_sep is set to ".", + then the Demand subview would be placed inside the ENERGY directory) + Returns ------- - module_elements_: dict - Dictionary containing view (module) names as keys and a list of - the corresponding variables as values. + views_dict: dict + Dictionary containing view names as keys and a list of the + corresponding variables as values. If the subview_sep is defined, + then the dictionary will have a nested dict containing the subviews. """ - # TODO how about macros??? are they also put in the sketch? - - # splitting the sketch in different modules + # split the sketch in different views sketch = list(map(lambda x: x.strip(), sketch.split("\\\\\\---/// "))) - modules_list = [] # list of all modules - module_elements = {} - + view_elements = {} for module in sketch: for sketch_line in module.split("\n"): + # line is a dict with keys "variable_name" and "view_name" line = parse_sketch_line(sketch_line.strip(), namespace) - # When a module name is found, the "new_module" becomes True. - # When a variable name is found, the "new_module" is set back to - # False - if line["module_name"]: - # remove characters that are not [a-zA-Z0-9_] from the module - # name - module_name = re.sub( - r"[\W]+", "", line["module_name"].replace(" ", "_") - ).lstrip( - "0123456789" - ) # there's probably a more elegant way to do it with regex - module_elements[module_name] = [] - if module_name not in modules_list: - modules_list.append(module_name) + + if line["view_name"]: + view_name = line["view_name"] + view_elements[view_name] = [] if line["variable_name"]: - if line["variable_name"] not in module_elements[module_name]: - module_elements[module_name].append(line["variable_name"]) - # removes modules that do not include any variable in them - module_elements_ = { - key.lower(): value for key, value in module_elements.items() if value + if line["variable_name"] not in view_elements[view_name]: + view_elements[view_name].append(line["variable_name"]) + + # removes views that do not include any variable in them + non_empty_views = { + key.lower(): value for key, value in view_elements.items() if value } - return module_elements_ + # split into subviews, if subview_sep is provided + views_dict = {} + + if subview_sep and any(filter(lambda x: subview_sep in x, + non_empty_views.keys())): + for name, elements in non_empty_views.items(): + # split and clean view/subview names as they are not yet safe + view_subview = name.split(subview_sep) + + if len(view_subview) == 2: + view, subview = utils.clean_file_names(*view_subview) + else: + view = utils.clean_file_names(*view_subview)[0] + subview = "" + + if view.upper() not in views_dict.keys(): + views_dict[view.upper()] = {} + if not subview: + views_dict[view.upper()][view.lower()] = elements + else: + views_dict[view.upper()][subview.lower()] = elements + else: + # clean file names + for view_name, elements in non_empty_views.items(): + views_dict[utils.clean_file_names(view_name)[0]] = elements + + return views_dict def _split_sketch(text): @@ -1694,7 +1689,7 @@ def _split_sketch(text): return text, sketch -def translate_vensim(mdl_file, split_modules): +def translate_vensim(mdl_file, split_views, **kwargs): """ Translate a vensim file. @@ -1703,11 +1698,14 @@ def translate_vensim(mdl_file, split_modules): mdl_file: str File path of a vensim model file to translate to python. - split_modules: bool + split_views: bool If True, the sketch is parsed to detect model elements in each model view, and then translate each view in a separate python file. Setting this argument to True is recommended for large - models split in many different views. + models that are split in many different views. + + **kwargs: (optional) + Additional parameters passed to the translate_vensim function Returns ------- @@ -1719,6 +1717,9 @@ def translate_vensim(mdl_file, split_modules): >>> translate_vensim('../tests/test-models/tests/subscript_3d_arrays/test_subscript_3d_arrays.mdl') """ + # character used to place subviews in the parent view folder + subview_sep = kwargs.get("subview_sep", "") + root_path = os.path.split(mdl_file)[0] with open(mdl_file, "r", encoding="UTF-8") as in_file: text = in_file.read() @@ -1734,7 +1735,7 @@ def translate_vensim(mdl_file, split_modules): outfile_name = mdl_insensitive.sub(".py", mdl_file) out_dir = os.path.dirname(outfile_name) - if split_modules: + if split_views: text, sketch = _split_sketch(text) else: sketch = "" @@ -1752,6 +1753,6 @@ def translate_vensim(mdl_file, split_modules): macro_list = [s for s in file_sections if s["name"] != "_main_"] for section in file_sections: - translate_section(section, macro_list, sketch, root_path) + translate_section(section, macro_list, sketch, root_path, subview_sep) return outfile_name diff --git a/pysd/pysd.py b/pysd/pysd.py index aeaacd6b..f6f21ee6 100644 --- a/pysd/pysd.py +++ b/pysd/pysd.py @@ -7,7 +7,7 @@ import sys -if sys.version_info[:2] < (3, 7): +if sys.version_info[:2] < (3, 7): # pragma: no cover raise RuntimeError( "\n\n" + "Your Python version is not longer supported by PySD.\n" @@ -61,9 +61,8 @@ def read_xmile(xmile_file, initialize=True, missing_values="warning"): return model -def read_vensim( - mdl_file, initialize=True, missing_values="warning", split_modules=False -): +def read_vensim(mdl_file, initialize=True, missing_values="warning", + split_views=False, **kwargs): """ Construct a model from Vensim `.mdl` file. @@ -84,12 +83,20 @@ def read_vensim( the missing values, this option may cause the integration to fail, but it may be used to check the quality of the data. - split_modules: bool (optional) + split_views: bool (optional) If True, the sketch is parsed to detect model elements in each model view, and then translate each view in a separate python file. Setting this argument to True is recommended for large models split in many different views. Default is False. + **kwargs: (optional) + Additional keyword arguments. + subview_sep:(str) + Character used to separate views and subviews. If provided, + and split_views=True, each submodule will be placed inside the + folder of the parent view. + + Returns ------- model: a PySD class object @@ -103,7 +110,7 @@ def read_vensim( """ from .py_backend.vensim.vensim2py import translate_vensim - py_model_file = translate_vensim(mdl_file, split_modules) + py_model_file = translate_vensim(mdl_file, split_views, **kwargs) model = load(py_model_file, initialize, missing_values) model.mdl_file = mdl_file return model diff --git a/setup.py b/setup.py index c19b7f15..f0ceb264 100755 --- a/setup.py +++ b/setup.py @@ -3,6 +3,8 @@ exec(open('pysd/_version.py').read()) print(__version__) +test_pckgs = open('dev-requirements.txt').read().strip().split('\n') + setup( name='pysd', version=__version__, @@ -28,6 +30,8 @@ 'Programming Language :: Python :: 3.9', ], install_requires=open('requirements.txt').read().strip().split('\n'), + tests_require=test_pckgs, + extras_require={"test": test_pckgs}, package_data={ 'py_backend': [ 'xmile/smile.grammar' diff --git a/tests/Makefile b/tests/Makefile index 34fff190..648c2e27 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -15,22 +15,22 @@ clean: tests: clean ifeq ($(NUM_PROC), 1) - pytest *.py + pytest else - pytest *.py -n $(NUM_PROC) + pytest -n $(NUM_PROC) endif cover: clean ifeq ($(NUM_PROC), 1) - pytest --cov=pysd --cov-report term *.py + pytest --cov=pysd --cov-report term else - pytest --cov=pysd --cov-report term *.py -n $(NUM_PROC) + pytest --cov=pysd --cov-report term -n $(NUM_PROC) endif coverhtml: clean ifeq ($(NUM_PROC), 1) - pytest --cov=pysd --cov-report html --cov-report term *.py + pytest --cov=pysd --cov-report html --cov-report term else - pytest --cov=pysd --cov-report html --cov-report term *.py -n $(NUM_PROC) + pytest --cov=pysd --cov-report html --cov-report term -n $(NUM_PROC) endif diff --git a/tests/data/input.xlsx b/tests/data/input.xlsx index 4f04faee..46a5695f 100644 Binary files a/tests/data/input.xlsx and b/tests/data/input.xlsx differ diff --git a/tests/integration_test_vensim_pathway.py b/tests/integration_test_vensim_pathway.py index c34f867a..d560df0f 100644 --- a/tests/integration_test_vensim_pathway.py +++ b/tests/integration_test_vensim_pathway.py @@ -3,97 +3,100 @@ Note that this file is autogenerated by `integration_test_factory.py` and changes are likely to be overwritten. """ - +import os import warnings import unittest from pysd.tools.benchmarking import runner, assert_frames_close rtol = .05 +_root = os.path.dirname(__file__) +test_models = os.path.join(_root, "test-models/tests") + class TestIntegrationExamples(unittest.TestCase): def test_abs(self): - output, canon = runner('test-models/tests/abs/test_abs.mdl') + output, canon = runner(test_models + '/abs/test_abs.mdl') assert_frames_close(output, canon, rtol=rtol) def test_active_initial(self): - output, canon = runner('test-models/tests/active_initial/test_active_initial.mdl') + output, canon = runner(test_models + '/active_initial/test_active_initial.mdl') assert_frames_close(output, canon, rtol=rtol) def test_arguments(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") - output, canon = runner('test-models/tests/arguments/test_arguments.mdl') + output, canon = runner(test_models + '/arguments/test_arguments.mdl') assert_frames_close(output, canon, rtol=rtol) def test_builtin_max(self): - output, canon = runner('test-models/tests/builtin_max/builtin_max.mdl') + output, canon = runner(test_models + '/builtin_max/builtin_max.mdl') assert_frames_close(output, canon, rtol=rtol) def test_builtin_min(self): - output, canon = runner('test-models/tests/builtin_min/builtin_min.mdl') + output, canon = runner(test_models + '/builtin_min/builtin_min.mdl') assert_frames_close(output, canon, rtol=rtol) def test_chained_initialization(self): - output, canon = runner('test-models/tests/chained_initialization/test_chained_initialization.mdl') + output, canon = runner(test_models + '/chained_initialization/test_chained_initialization.mdl') assert_frames_close(output, canon, rtol=rtol) def test_constant_expressions(self): - output, canon = runner('test-models/tests/constant_expressions/test_constant_expressions.mdl') + output, canon = runner(test_models + '/constant_expressions/test_constant_expressions.mdl') assert_frames_close(output, canon, rtol=rtol) def test_delay_fixed(self): # issue https://github.com/JamesPHoughton/pysd/issues/147 with warnings.catch_warnings(): warnings.simplefilter("ignore") - output, canon = runner('test-models/tests/delay_fixed/test_delay_fixed.mdl') + output, canon = runner(test_models + '/delay_fixed/test_delay_fixed.mdl') assert_frames_close(output, canon, rtol=rtol) def test_delay_numeric_error(self): # issue https://github.com/JamesPHoughton/pysd/issues/225 - output, canon = runner('test-models/tests/delay_numeric_error/test_delay_numeric_error.mdl') + output, canon = runner(test_models + '/delay_numeric_error/test_delay_numeric_error.mdl') assert_frames_close(output, canon, rtol=rtol) def test_delay_parentheses(self): - output, canon = runner('test-models/tests/delay_parentheses/test_delay_parentheses.mdl') + output, canon = runner(test_models + '/delay_parentheses/test_delay_parentheses.mdl') assert_frames_close(output, canon, rtol=rtol) def test_delay_pipeline(self): # issue https://github.com/JamesPHoughton/pysd/issues/147 with warnings.catch_warnings(): warnings.simplefilter("ignore") - output, canon = runner('test-models/tests/delay_pipeline/test_pipeline_delays.mdl') + output, canon = runner(test_models + '/delay_pipeline/test_pipeline_delays.mdl') assert_frames_close(output, canon, rtol=rtol) def test_delays(self): # issue https://github.com/JamesPHoughton/pysd/issues/147 - output, canon = runner('test-models/tests/delays/test_delays.mdl') + output, canon = runner(test_models + '/delays/test_delays.mdl') assert_frames_close(output, canon, rtol=rtol) def test_dynamic_final_time(self): # issue https://github.com/JamesPHoughton/pysd/issues/278 - output, canon = runner('test-models/tests/dynamic_final_time/test_dynamic_final_time.mdl') + output, canon = runner(test_models + '/dynamic_final_time/test_dynamic_final_time.mdl') assert_frames_close(output, canon, rtol=rtol) def test_euler_step_vs_saveper(self): - output, canon = runner('test-models/tests/euler_step_vs_saveper/test_euler_step_vs_saveper.mdl') + output, canon = runner(test_models + '/euler_step_vs_saveper/test_euler_step_vs_saveper.mdl') assert_frames_close(output, canon, rtol=rtol) def test_exp(self): - output, canon = runner('test-models/tests/exp/test_exp.mdl') + output, canon = runner(test_models + '/exp/test_exp.mdl') assert_frames_close(output, canon, rtol=rtol) def test_exponentiation(self): - output, canon = runner('test-models/tests/exponentiation/exponentiation.mdl') + output, canon = runner(test_models + '/exponentiation/exponentiation.mdl') assert_frames_close(output, canon, rtol=rtol) def test_function_capitalization(self): - output, canon = runner('test-models/tests/function_capitalization/test_function_capitalization.mdl') + output, canon = runner(test_models + '/function_capitalization/test_function_capitalization.mdl') assert_frames_close(output, canon, rtol=rtol) def test_game(self): - output, canon = runner('test-models/tests/game/test_game.mdl') + output, canon = runner(test_models + '/game/test_game.mdl') assert_frames_close(output, canon, rtol=rtol) def test_get_data_args_3d_xls(self): @@ -104,7 +107,7 @@ def test_get_data_args_3d_xls(self): good working of the builder """ output, canon = runner( - 'test-models/tests/get_data_args_3d_xls/' + test_models + '/get_data_args_3d_xls/' + 'test_get_data_args_3d_xls.mdl' ) assert_frames_close(output, canon, rtol=rtol) @@ -117,7 +120,7 @@ def test_get_lookups_data_3d_xls(self): good working of the builder """ output, canon = runner( - 'test-models/tests/get_lookups_data_3d_xls/' + test_models + '/get_lookups_data_3d_xls/' + 'test_get_lookups_data_3d_xls.mdl' ) assert_frames_close(output, canon, rtol=rtol) @@ -126,14 +129,14 @@ def test_get_lookups_subscripted_args(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") output, canon = runner( - 'test-models/tests/get_lookups_subscripted_args/' + test_models + '/get_lookups_subscripted_args/' + 'test_get_lookups_subscripted_args.mdl' ) assert_frames_close(output, canon, rtol=rtol) def test_get_lookups_subset(self): output, canon = runner( - 'test-models/tests/get_lookups_subset/' + test_models + '/get_lookups_subset/' + 'test_get_lookups_subset.mdl' ) assert_frames_close(output, canon, rtol=rtol) @@ -142,7 +145,7 @@ def test_get_with_missing_values_xlsx(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") output, canon = runner( - 'test-models/tests/get_with_missing_values_xlsx/' + test_models + '/get_with_missing_values_xlsx/' + 'test_get_with_missing_values_xlsx.mdl' ) @@ -150,7 +153,7 @@ def test_get_with_missing_values_xlsx(self): def test_get_mixed_definitions(self): output, canon = runner( - 'test-models/tests/get_mixed_definitions/' + test_models + '/get_mixed_definitions/' + 'test_get_mixed_definitions.mdl' ) assert_frames_close(output, canon, rtol=rtol) @@ -163,333 +166,333 @@ def test_get_subscript_3d_arrays_xls(self): good working of the builder """ output, canon = runner( - 'test-models/tests/get_subscript_3d_arrays_xls/' + test_models + '/get_subscript_3d_arrays_xls/' + 'test_get_subscript_3d_arrays_xls.mdl' ) assert_frames_close(output, canon, rtol=rtol) def test_get_xls_cellrange(self): output, canon = runner( - 'test-models/tests/get_xls_cellrange/' + test_models + '/get_xls_cellrange/' + 'test_get_xls_cellrange.mdl' ) assert_frames_close(output, canon, rtol=rtol) def test_if_stmt(self): - output, canon = runner('test-models/tests/if_stmt/if_stmt.mdl') + output, canon = runner(test_models + '/if_stmt/if_stmt.mdl') assert_frames_close(output, canon, rtol=rtol) def test_initial_function(self): - output, canon = runner('test-models/tests/initial_function/test_initial.mdl') + output, canon = runner(test_models + '/initial_function/test_initial.mdl') assert_frames_close(output, canon, rtol=rtol) def test_input_functions(self): - output, canon = runner('test-models/tests/input_functions/test_inputs.mdl') + output, canon = runner(test_models + '/input_functions/test_inputs.mdl') assert_frames_close(output, canon, rtol=rtol) def test_limits(self): - output, canon = runner('test-models/tests/limits/test_limits.mdl') + output, canon = runner(test_models + '/limits/test_limits.mdl') assert_frames_close(output, canon, rtol=rtol) def test_line_breaks(self): - output, canon = runner('test-models/tests/line_breaks/test_line_breaks.mdl') + output, canon = runner(test_models + '/line_breaks/test_line_breaks.mdl') assert_frames_close(output, canon, rtol=rtol) def test_line_continuation(self): - output, canon = runner('test-models/tests/line_continuation/test_line_continuation.mdl') + output, canon = runner(test_models + '/line_continuation/test_line_continuation.mdl') assert_frames_close(output, canon, rtol=rtol) def test_ln(self): - output, canon = runner('test-models/tests/ln/test_ln.mdl') + output, canon = runner(test_models + '/ln/test_ln.mdl') assert_frames_close(output, canon, rtol=rtol) def test_log(self): - output, canon = runner('test-models/tests/log/test_log.mdl') + output, canon = runner(test_models + '/log/test_log.mdl') assert_frames_close(output, canon, rtol=rtol) def test_logicals(self): - output, canon = runner('test-models/tests/logicals/test_logicals.mdl') + output, canon = runner(test_models + '/logicals/test_logicals.mdl') assert_frames_close(output, canon, rtol=rtol) def test_lookups(self): - output, canon = runner('test-models/tests/lookups/test_lookups.mdl') + output, canon = runner(test_models + '/lookups/test_lookups.mdl') assert_frames_close(output, canon, rtol=rtol) def test_lookups_without_range(self): - output, canon = runner('test-models/tests/lookups_without_range/test_lookups_without_range.mdl') + output, canon = runner(test_models + '/lookups_without_range/test_lookups_without_range.mdl') assert_frames_close(output, canon, rtol=rtol) def test_lookups_funcnames(self): - output, canon = runner('test-models/tests/lookups_funcnames/test_lookups_funcnames.mdl') + output, canon = runner(test_models + '/lookups_funcnames/test_lookups_funcnames.mdl') assert_frames_close(output, canon, rtol=rtol) def test_lookups_inline(self): - output, canon = runner('test-models/tests/lookups_inline/test_lookups_inline.mdl') + output, canon = runner(test_models + '/lookups_inline/test_lookups_inline.mdl') assert_frames_close(output, canon, rtol=rtol) def test_lookups_inline_bounded(self): - output, canon = runner('test-models/tests/lookups_inline_bounded/test_lookups_inline_bounded.mdl') + output, canon = runner(test_models + '/lookups_inline_bounded/test_lookups_inline_bounded.mdl') assert_frames_close(output, canon, rtol=rtol) def test_lookups_with_expr(self): - output, canon = runner('test-models/tests/lookups_with_expr/test_lookups_with_expr.mdl') + output, canon = runner(test_models + '/lookups_with_expr/test_lookups_with_expr.mdl') assert_frames_close(output, canon, rtol=rtol) def test_macro_cross_reference(self): - output, canon = runner('test-models/tests/macro_cross_reference/test_macro_cross_reference.mdl') + output, canon = runner(test_models + '/macro_cross_reference/test_macro_cross_reference.mdl') assert_frames_close(output, canon, rtol=rtol) def test_macro_expression(self): - output, canon = runner('test-models/tests/macro_expression/test_macro_expression.mdl') + output, canon = runner(test_models + '/macro_expression/test_macro_expression.mdl') assert_frames_close(output, canon, rtol=rtol) def test_macro_multi_expression(self): - output, canon = runner('test-models/tests/macro_multi_expression/test_macro_multi_expression.mdl') + output, canon = runner(test_models + '/macro_multi_expression/test_macro_multi_expression.mdl') assert_frames_close(output, canon, rtol=rtol) def test_macro_multi_macros(self): - output, canon = runner('test-models/tests/macro_multi_macros/test_macro_multi_macros.mdl') + output, canon = runner(test_models + '/macro_multi_macros/test_macro_multi_macros.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('working') def test_macro_output(self): - output, canon = runner('test-models/tests/macro_output/test_macro_output.mdl') + output, canon = runner(test_models + '/macro_output/test_macro_output.mdl') assert_frames_close(output, canon, rtol=rtol) def test_macro_stock(self): - output, canon = runner('test-models/tests/macro_stock/test_macro_stock.mdl') + output, canon = runner(test_models + '/macro_stock/test_macro_stock.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('do we need this?') def test_macro_trailing_definition(self): - output, canon = runner('test-models/tests/macro_trailing_definition/test_macro_trailing_definition.mdl') + output, canon = runner(test_models + '/macro_trailing_definition/test_macro_trailing_definition.mdl') assert_frames_close(output, canon, rtol=rtol) def test_model_doc(self): - output, canon = runner('test-models/tests/model_doc/model_doc.mdl') + output, canon = runner(test_models + '/model_doc/model_doc.mdl') assert_frames_close(output, canon, rtol=rtol) def test_nested_functions(self): - output, canon = runner('test-models/tests/nested_functions/test_nested_functions.mdl') + output, canon = runner(test_models + '/nested_functions/test_nested_functions.mdl') assert_frames_close(output, canon, rtol=rtol) def test_number_handling(self): - output, canon = runner('test-models/tests/number_handling/test_number_handling.mdl') + output, canon = runner(test_models + '/number_handling/test_number_handling.mdl') assert_frames_close(output, canon, rtol=rtol) def test_parentheses(self): - output, canon = runner('test-models/tests/parentheses/test_parens.mdl') + output, canon = runner(test_models + '/parentheses/test_parens.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('low priority') def test_reference_capitalization(self): """A properly formatted Vensim model should never create this failure""" - output, canon = runner('test-models/tests/reference_capitalization/test_reference_capitalization.mdl') + output, canon = runner(test_models + '/reference_capitalization/test_reference_capitalization.mdl') assert_frames_close(output, canon, rtol=rtol) def test_repeated_subscript(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") - output, canon = runner('test-models/tests/repeated_subscript/test_repeated_subscript.mdl') + output, canon = runner(test_models + '/repeated_subscript/test_repeated_subscript.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('in branch') def test_rounding(self): - output, canon = runner('test-models/tests/rounding/test_rounding.mdl') + output, canon = runner(test_models + '/rounding/test_rounding.mdl') assert_frames_close(output, canon, rtol=rtol) def test_sample_if_true(self): - output, canon = runner('test-models/tests/sample_if_true/test_sample_if_true.mdl') + output, canon = runner(test_models + '/sample_if_true/test_sample_if_true.mdl') assert_frames_close(output, canon, rtol=rtol) def test_smooth(self): - output, canon = runner('test-models/tests/smooth/test_smooth.mdl') + output, canon = runner(test_models + '/smooth/test_smooth.mdl') assert_frames_close(output, canon, rtol=rtol) def test_smooth_and_stock(self): - output, canon = runner('test-models/tests/smooth_and_stock/test_smooth_and_stock.mdl') + output, canon = runner(test_models + '/smooth_and_stock/test_smooth_and_stock.mdl') assert_frames_close(output, canon, rtol=rtol) def test_special_characters(self): - output, canon = runner('test-models/tests/special_characters/test_special_variable_names.mdl') + output, canon = runner(test_models + '/special_characters/test_special_variable_names.mdl') assert_frames_close(output, canon, rtol=rtol) def test_sqrt(self): - output, canon = runner('test-models/tests/sqrt/test_sqrt.mdl') + output, canon = runner(test_models + '/sqrt/test_sqrt.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subrange_merge(self): - output, canon = runner('test-models/tests/subrange_merge/test_subrange_merge.mdl') + output, canon = runner(test_models + '/subrange_merge/test_subrange_merge.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_multiples(self): - output, canon = runner('test-models/tests/subscript_multiples/test_multiple_subscripts.mdl') + output, canon = runner(test_models + '/subscript_multiples/test_multiple_subscripts.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_1d_arrays(self): - output, canon = runner('test-models/tests/subscript_1d_arrays/test_subscript_1d_arrays.mdl') + output, canon = runner(test_models + '/subscript_1d_arrays/test_subscript_1d_arrays.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_2d_arrays(self): - output, canon = runner('test-models/tests/subscript_2d_arrays/test_subscript_2d_arrays.mdl') + output, canon = runner(test_models + '/subscript_2d_arrays/test_subscript_2d_arrays.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_3d_arrays(self): - output, canon = runner('test-models/tests/subscript_3d_arrays/test_subscript_3d_arrays.mdl') + output, canon = runner(test_models + '/subscript_3d_arrays/test_subscript_3d_arrays.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_3d_arrays_lengthwise(self): - output, canon = runner('test-models/tests/subscript_3d_arrays_lengthwise/test_subscript_3d_arrays_lengthwise.mdl') + output, canon = runner(test_models + '/subscript_3d_arrays_lengthwise/test_subscript_3d_arrays_lengthwise.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_3d_arrays_widthwise(self): - output, canon = runner('test-models/tests/subscript_3d_arrays_widthwise/test_subscript_3d_arrays_widthwise.mdl') + output, canon = runner(test_models + '/subscript_3d_arrays_widthwise/test_subscript_3d_arrays_widthwise.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_aggregation(self): - output, canon = runner('test-models/tests/subscript_aggregation/test_subscript_aggregation.mdl') + output, canon = runner(test_models + '/subscript_aggregation/test_subscript_aggregation.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_constant_call(self): - output, canon = runner('test-models/tests/subscript_constant_call/test_subscript_constant_call.mdl') + output, canon = runner(test_models + '/subscript_constant_call/test_subscript_constant_call.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_copy(self): - output, canon = runner('test-models/tests/subscript_copy/test_subscript_copy.mdl') + output, canon = runner(test_models + '/subscript_copy/test_subscript_copy.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_docs(self): - output, canon = runner('test-models/tests/subscript_docs/subscript_docs.mdl') + output, canon = runner(test_models + '/subscript_docs/subscript_docs.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_element_name(self): # issue https://github.com/JamesPHoughton/pysd/issues/216 - output, canon = runner('test-models/tests/subscript_element_name/test_subscript_element_name.mdl') + output, canon = runner(test_models + '/subscript_element_name/test_subscript_element_name.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_individually_defined_1_of_2d_arrays(self): - output, canon = runner('test-models/tests/subscript_individually_defined_1_of_2d_arrays/subscript_individually_defined_1_of_2d_arrays.mdl') + output, canon = runner(test_models + '/subscript_individually_defined_1_of_2d_arrays/subscript_individually_defined_1_of_2d_arrays.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_individually_defined_1_of_2d_arrays_from_floats(self): - output, canon = runner('test-models/tests/subscript_individually_defined_1_of_2d_arrays_from_floats/subscript_individually_defined_1_of_2d_arrays_from_floats.mdl') + output, canon = runner(test_models + '/subscript_individually_defined_1_of_2d_arrays_from_floats/subscript_individually_defined_1_of_2d_arrays_from_floats.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_individually_defined_1d_arrays(self): - output, canon = runner('test-models/tests/subscript_individually_defined_1d_arrays/subscript_individually_defined_1d_arrays.mdl') + output, canon = runner(test_models + '/subscript_individually_defined_1d_arrays/subscript_individually_defined_1d_arrays.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_individually_defined_stocks(self): - output, canon = runner('test-models/tests/subscript_individually_defined_stocks/test_subscript_individually_defined_stocks.mdl') + output, canon = runner(test_models + '/subscript_individually_defined_stocks/test_subscript_individually_defined_stocks.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_mapping_simple(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") - output, canon = runner('test-models/tests/subscript_mapping_simple/test_subscript_mapping_simple.mdl') + output, canon = runner(test_models + '/subscript_mapping_simple/test_subscript_mapping_simple.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_mapping_vensim(self): with warnings.catch_warnings(): warnings.simplefilter("ignore") - output, canon = runner('test-models/tests/subscript_mapping_vensim/test_subscript_mapping_vensim.mdl') + output, canon = runner(test_models + '/subscript_mapping_vensim/test_subscript_mapping_vensim.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_mixed_assembly(self): - output, canon = runner('test-models/tests/subscript_mixed_assembly/test_subscript_mixed_assembly.mdl') + output, canon = runner(test_models + '/subscript_mixed_assembly/test_subscript_mixed_assembly.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_selection(self): - output, canon = runner('test-models/tests/subscript_selection/subscript_selection.mdl') + output, canon = runner(test_models + '/subscript_selection/subscript_selection.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_numeric_range(self): - output, canon = runner('test-models/tests/subscript_numeric_range/test_subscript_numeric_range.mdl') + output, canon = runner(test_models + '/subscript_numeric_range/test_subscript_numeric_range.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_subranges(self): - output, canon = runner('test-models/tests/subscript_subranges/test_subscript_subrange.mdl') + output, canon = runner(test_models + '/subscript_subranges/test_subscript_subrange.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_subranges_equal(self): - output, canon = runner('test-models/tests/subscript_subranges_equal/test_subscript_subrange_equal.mdl') + output, canon = runner(test_models + '/subscript_subranges_equal/test_subscript_subrange_equal.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_switching(self): - output, canon = runner('test-models/tests/subscript_switching/subscript_switching.mdl') + output, canon = runner(test_models + '/subscript_switching/subscript_switching.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_transposition(self): - output, canon = runner('test-models/tests/subscript_transposition/test_subscript_transposition.mdl') + output, canon = runner(test_models + '/subscript_transposition/test_subscript_transposition.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscript_updimensioning(self): - output, canon = runner('test-models/tests/subscript_updimensioning/test_subscript_updimensioning.mdl') + output, canon = runner(test_models + '/subscript_updimensioning/test_subscript_updimensioning.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscripted_delays(self): - output, canon = runner('test-models/tests/subscripted_delays/test_subscripted_delays.mdl') + output, canon = runner(test_models + '/subscripted_delays/test_subscripted_delays.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscripted_flows(self): - output, canon = runner('test-models/tests/subscripted_flows/test_subscripted_flows.mdl') + output, canon = runner(test_models + '/subscripted_flows/test_subscripted_flows.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscripted_if_then_else(self): - output, canon = runner('test-models/tests/subscripted_if_then_else/test_subscripted_if_then_else.mdl') + output, canon = runner(test_models + '/subscripted_if_then_else/test_subscripted_if_then_else.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscripted_logicals(self): - output, canon = runner('test-models/tests/subscripted_logicals/test_subscripted_logicals.mdl') + output, canon = runner(test_models + '/subscripted_logicals/test_subscripted_logicals.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscripted_smooth(self): # issue https://github.com/JamesPHoughton/pysd/issues/226 - output, canon = runner('test-models/tests/subscripted_smooth/test_subscripted_smooth.mdl') + output, canon = runner(test_models + '/subscripted_smooth/test_subscripted_smooth.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscripted_trend(self): # issue https://github.com/JamesPHoughton/pysd/issues/226 - output, canon = runner('test-models/tests/subscripted_trend/test_subscripted_trend.mdl') + output, canon = runner(test_models + '/subscripted_trend/test_subscripted_trend.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subscripted_xidz(self): - output, canon = runner('test-models/tests/subscripted_xidz/test_subscripted_xidz.mdl') + output, canon = runner(test_models + '/subscripted_xidz/test_subscripted_xidz.mdl') assert_frames_close(output, canon, rtol=rtol) def test_subset_duplicated_coord(self): - output, canon = runner('test-models/tests/subset_duplicated_coord/' + output, canon = runner(test_models + '/subset_duplicated_coord/' + 'test_subset_duplicated_coord.mdl') assert_frames_close(output, canon, rtol=rtol) def test_time(self): - output, canon = runner('test-models/tests/time/test_time.mdl') + output, canon = runner(test_models + '/time/test_time.mdl') assert_frames_close(output, canon, rtol=rtol) def test_trend(self): - output, canon = runner('test-models/tests/trend/test_trend.mdl') + output, canon = runner(test_models + '/trend/test_trend.mdl') assert_frames_close(output, canon, rtol=rtol) def test_trig(self): - output, canon = runner('test-models/tests/trig/test_trig.mdl') + output, canon = runner(test_models + '/trig/test_trig.mdl') assert_frames_close(output, canon, rtol=rtol) def test_variable_ranges(self): - output, canon = runner('test-models/tests/variable_ranges/test_variable_ranges.mdl') + output, canon = runner(test_models + '/variable_ranges/test_variable_ranges.mdl') assert_frames_close(output, canon, rtol=rtol) def test_unicode_characters(self): - output, canon = runner('test-models/tests/unicode_characters/unicode_test_model.mdl') + output, canon = runner(test_models + '/unicode_characters/unicode_test_model.mdl') assert_frames_close(output, canon, rtol=rtol) def test_xidz_zidz(self): - output, canon = runner('test-models/tests/xidz_zidz/xidz_zidz.mdl') + output, canon = runner(test_models + '/xidz_zidz/xidz_zidz.mdl') assert_frames_close(output, canon, rtol=rtol) def test_run_uppercase(self): - output, canon = runner('test-models/tests/case_sensitive_extension/teacup-upper.MDL') + output, canon = runner(test_models + '/case_sensitive_extension/teacup-upper.MDL') assert_frames_close(output, canon, rtol=rtol) def test_odd_number_quotes(self): - output, canon = runner('test-models/tests/odd_number_quotes/teacup_3quotes.mdl') + output, canon = runner(test_models + '/odd_number_quotes/teacup_3quotes.mdl') assert_frames_close(output, canon, rtol=rtol) diff --git a/tests/integration_test_xmile_pathway.py b/tests/integration_test_xmile_pathway.py index b4af0e32..3fcfc1ad 100644 --- a/tests/integration_test_xmile_pathway.py +++ b/tests/integration_test_xmile_pathway.py @@ -1,367 +1,370 @@ - +import os import unittest from pysd.tools.benchmarking import runner, assert_frames_close rtol = .05 +_root = os.path.dirname(__file__) +test_models = os.path.join(_root, "test-models/tests") + class TestIntegrationExamples(unittest.TestCase): def test_abs(self): - output, canon = runner('test-models/tests/abs/test_abs.xmile') + output, canon = runner(test_models + '/abs/test_abs.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('error in model file') def test_active_initial(self): - output, canon = runner('test-models/tests/active_initial/test_active_initial.xmile') + output, canon = runner(test_models + '/active_initial/test_active_initial.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing model file') def test_arguments(self): - output, canon = runner('test-models/tests/arguments/test_arguments.mdl') + output, canon = runner(test_models + '/arguments/test_arguments.mdl') assert_frames_close(output, canon, rtol=rtol) def test_builtin_max(self): - output, canon = runner('test-models/tests/builtin_max/builtin_max.xmile') + output, canon = runner(test_models + '/builtin_max/builtin_max.xmile') assert_frames_close(output, canon, rtol=rtol) def test_builtin_min(self): - output, canon = runner('test-models/tests/builtin_min/builtin_min.xmile') + output, canon = runner(test_models + '/builtin_min/builtin_min.xmile') assert_frames_close(output, canon, rtol=rtol) def test_chained_initialization(self): output, canon = runner( - 'test-models/tests/chained_initialization/test_chained_initialization.xmile') + test_models + '/chained_initialization/test_chained_initialization.xmile') assert_frames_close(output, canon, rtol=rtol) def test_comparisons(self): output, canon = runner( - 'test-models/tests/comparisons/comparisons.xmile') + test_models + '/comparisons/comparisons.xmile') assert_frames_close(output, canon, rtol=rtol) def test_constant_expressions(self): output, canon = runner( - 'test-models/tests/constant_expressions/test_constant_expressions.xmile') + test_models + '/constant_expressions/test_constant_expressions.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_delay_parentheses(self): output, canon = runner( - 'test-models/tests/delay_parentheses/test_delay_parentheses.xmile') + test_models + '/delay_parentheses/test_delay_parentheses.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_delays(self): - output, canon = runner('test-models/tests/delays/test_delays.mdl') + output, canon = runner(test_models + '/delays/test_delays.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_euler_step_vs_saveper(self): output, canon = runner( - 'test-models/tests/euler_step_vs_saveper/test_euler_step_vs_saveper.xmile') + test_models + '/euler_step_vs_saveper/test_euler_step_vs_saveper.xmile') assert_frames_close(output, canon, rtol=rtol) def test_eval_order(self): output, canon = runner( - 'test-models/tests/eval_order/eval_order.xmile') + test_models + '/eval_order/eval_order.xmile') assert_frames_close(output, canon, rtol=rtol) def test_exp(self): - output, canon = runner('test-models/tests/exp/test_exp.xmile') + output, canon = runner(test_models + '/exp/test_exp.xmile') assert_frames_close(output, canon, rtol=rtol) def test_exponentiation(self): - output, canon = runner('test-models/tests/exponentiation/exponentiation.xmile') + output, canon = runner(test_models + '/exponentiation/exponentiation.xmile') assert_frames_close(output, canon, rtol=rtol) def test_function_capitalization(self): output, canon = runner( - 'test-models/tests/function_capitalization/test_function_capitalization.xmile') + test_models + '/function_capitalization/test_function_capitalization.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('not sure if this is implemented in xmile?') def test_game(self): - output, canon = runner('test-models/tests/game/test_game.xmile') + output, canon = runner(test_models + '/game/test_game.xmile') assert_frames_close(output, canon, rtol=rtol) def test_if_stmt(self): - output, canon = runner('test-models/tests/if_stmt/if_stmt.xmile') + output, canon = runner(test_models + '/if_stmt/if_stmt.xmile') assert_frames_close(output, canon, rtol=rtol) def test_initial_function(self): - output, canon = runner('test-models/tests/initial_function/test_initial.xmile') + output, canon = runner(test_models + '/initial_function/test_initial.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile model') def test_input_functions(self): - output, canon = runner('test-models/tests/input_functions/test_inputs.mdl') + output, canon = runner(test_models + '/input_functions/test_inputs.mdl') assert_frames_close(output, canon, rtol=rtol) def test_limits(self): - output, canon = runner('test-models/tests/limits/test_limits.xmile') + output, canon = runner(test_models + '/limits/test_limits.xmile') assert_frames_close(output, canon, rtol=rtol) def test_line_breaks(self): - output, canon = runner('test-models/tests/line_breaks/test_line_breaks.xmile') + output, canon = runner(test_models + '/line_breaks/test_line_breaks.xmile') assert_frames_close(output, canon, rtol=rtol) def test_line_continuation(self): - output, canon = runner('test-models/tests/line_continuation/test_line_continuation.xmile') + output, canon = runner(test_models + '/line_continuation/test_line_continuation.xmile') assert_frames_close(output, canon, rtol=rtol) def test_ln(self): - output, canon = runner('test-models/tests/ln/test_ln.xmile') + output, canon = runner(test_models + '/ln/test_ln.xmile') assert_frames_close(output, canon, rtol=rtol) def test_log(self): - output, canon = runner('test-models/tests/log/test_log.xmile') + output, canon = runner(test_models + '/log/test_log.xmile') assert_frames_close(output, canon, rtol=rtol) def test_logicals(self): - output, canon = runner('test-models/tests/logicals/test_logicals.xmile') + output, canon = runner(test_models + '/logicals/test_logicals.xmile') assert_frames_close(output, canon, rtol=rtol) def test_lookups(self): - output, canon = runner('test-models/tests/lookups/test_lookups.xmile') + output, canon = runner(test_models + '/lookups/test_lookups.xmile') assert_frames_close(output, canon, rtol=rtol) def test_lookups_xscale(self): - output, canon = runner('test-models/tests/lookups/test_lookups_xscale.xmile') + output, canon = runner(test_models + '/lookups/test_lookups_xscale.xmile') assert_frames_close(output, canon, rtol=rtol) def test_lookups_xpts_sep(self): - output, canon = runner('test-models/tests/lookups/test_lookups_xpts_sep.xmile') + output, canon = runner(test_models + '/lookups/test_lookups_xpts_sep.xmile') assert_frames_close(output, canon, rtol=rtol) def test_lookups_ypts_sep(self): - output, canon = runner('test-models/tests/lookups/test_lookups_ypts_sep.xmile') + output, canon = runner(test_models + '/lookups/test_lookups_ypts_sep.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_lookups_funcnames(self): - output, canon = runner('test-models/tests/lookups_funcnames/test_lookups_funcnames.mdl') + output, canon = runner(test_models + '/lookups_funcnames/test_lookups_funcnames.mdl') assert_frames_close(output, canon, rtol=rtol) def test_lookups_inline(self): - output, canon = runner('test-models/tests/lookups_inline/test_lookups_inline.xmile') + output, canon = runner(test_models + '/lookups_inline/test_lookups_inline.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_lookups_inline_bounded(self): output, canon = runner( - 'test-models/tests/lookups_inline_bounded/test_lookups_inline_bounded.mdl') + test_models + '/lookups_inline_bounded/test_lookups_inline_bounded.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_macro_cross_reference(self): - output, canon = runner('test-models/tests/macro_cross_reference/test_macro_cross_reference.mdl') + output, canon = runner(test_models + '/macro_cross_reference/test_macro_cross_reference.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_macro_expression(self): - output, canon = runner('test-models/tests/macro_expression/test_macro_expression.xmile') + output, canon = runner(test_models + '/macro_expression/test_macro_expression.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_macro_multi_expression(self): output, canon = runner( - 'test-models/tests/macro_multi_expression/test_macro_multi_expression.xmile') + test_models + '/macro_multi_expression/test_macro_multi_expression.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_macro_multi_macros(self): output, canon = runner( - 'test-models/tests/macro_multi_macros/test_macro_multi_macros.xmile') + test_models + '/macro_multi_macros/test_macro_multi_macros.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_macro_output(self): - output, canon = runner('test-models/tests/macro_output/test_macro_output.mdl') + output, canon = runner(test_models + '/macro_output/test_macro_output.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_macro_stock(self): - output, canon = runner('test-models/tests/macro_stock/test_macro_stock.xmile') + output, canon = runner(test_models + '/macro_stock/test_macro_stock.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('do we need this?') def test_macro_trailing_definition(self): - output, canon = runner('test-models/tests/macro_trailing_definition/test_macro_trailing_definition.mdl') + output, canon = runner(test_models + '/macro_trailing_definition/test_macro_trailing_definition.mdl') assert_frames_close(output, canon, rtol=rtol) def test_model_doc(self): - output, canon = runner('test-models/tests/model_doc/model_doc.xmile') + output, canon = runner(test_models + '/model_doc/model_doc.xmile') assert_frames_close(output, canon, rtol=rtol) def test_number_handling(self): - output, canon = runner('test-models/tests/number_handling/test_number_handling.xmile') + output, canon = runner(test_models + '/number_handling/test_number_handling.xmile') assert_frames_close(output, canon, rtol=rtol) def test_parentheses(self): - output, canon = runner('test-models/tests/parentheses/test_parens.xmile') + output, canon = runner(test_models + '/parentheses/test_parens.xmile') assert_frames_close(output, canon, rtol=rtol) def test_reference_capitalization(self): """A properly formatted Vensim model should never create this failure""" output, canon = runner( - 'test-models/tests/reference_capitalization/test_reference_capitalization.xmile') + test_models + '/reference_capitalization/test_reference_capitalization.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('in branch') def test_rounding(self): - output, canon = runner('test-models/tests/rounding/test_rounding.mdl') + output, canon = runner(test_models + '/rounding/test_rounding.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_smooth(self): - output, canon = runner('test-models/tests/smooth/test_smooth.mdl') + output, canon = runner(test_models + '/smooth/test_smooth.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_smooth_and_stock(self): - output, canon = runner('test-models/tests/smooth_and_stock/test_smooth_and_stock.xmile') + output, canon = runner(test_models + '/smooth_and_stock/test_smooth_and_stock.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_special_characters(self): output, canon = runner( - 'test-models/tests/special_characters/test_special_variable_names.xmile') + test_models + '/special_characters/test_special_variable_names.xmile') assert_frames_close(output, canon, rtol=rtol) def test_sqrt(self): - output, canon = runner('test-models/tests/sqrt/test_sqrt.xmile') + output, canon = runner(test_models + '/sqrt/test_sqrt.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_subscript_multiples(self): output, canon = runner( - 'test-models/tests/subscript multiples/test_multiple_subscripts.xmile') + test_models + '/subscript multiples/test_multiple_subscripts.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_subscript_1d_arrays(self): output, canon = runner( - 'test-models/tests/subscript_1d_arrays/test_subscript_1d_arrays.xmile') + test_models + '/subscript_1d_arrays/test_subscript_1d_arrays.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_2d_arrays(self): output, canon = runner( - 'test-models/tests/subscript_2d_arrays/test_subscript_2d_arrays.xmile') + test_models + '/subscript_2d_arrays/test_subscript_2d_arrays.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_3d_arrays(self): - output, canon = runner('test-models/tests/subscript_3d_arrays/test_subscript_3d_arrays.mdl') + output, canon = runner(test_models + '/subscript_3d_arrays/test_subscript_3d_arrays.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_3d_arrays_lengthwise(self): - output, canon = runner('test-models/tests/subscript_3d_arrays_lengthwise/test_subscript_3d_arrays_lengthwise.mdl') + output, canon = runner(test_models + '/subscript_3d_arrays_lengthwise/test_subscript_3d_arrays_lengthwise.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_3d_arrays_widthwise(self): - output, canon = runner('test-models/tests/subscript_3d_arrays_widthwise/test_subscript_3d_arrays_widthwise.mdl') + output, canon = runner(test_models + '/subscript_3d_arrays_widthwise/test_subscript_3d_arrays_widthwise.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('in branch') def test_subscript_aggregation(self): - output, canon = runner('test-models/tests/subscript_aggregation/test_subscript_aggregation.mdl') + output, canon = runner(test_models + '/subscript_aggregation/test_subscript_aggregation.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_subscript_constant_call(self): output, canon = runner( - 'test-models/tests/subscript_constant_call/test_subscript_constant_call.xmile') + test_models + '/subscript_constant_call/test_subscript_constant_call.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_docs(self): - output, canon = runner('test-models/tests/subscript_docs/subscript_docs.mdl') + output, canon = runner(test_models + '/subscript_docs/subscript_docs.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_individually_defined_1_of_2d_arrays(self): - output, canon = runner('test-models/tests/subscript_individually_defined_1_of_2d_arrays/subscript_individually_defined_1_of_2d_arrays.mdl') + output, canon = runner(test_models + '/subscript_individually_defined_1_of_2d_arrays/subscript_individually_defined_1_of_2d_arrays.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_individually_defined_1_of_2d_arrays_from_floats(self): - output, canon = runner('test-models/tests/subscript_individually_defined_1_of_2d_arrays_from_floats/subscript_individually_defined_1_of_2d_arrays_from_floats.mdl') + output, canon = runner(test_models + '/subscript_individually_defined_1_of_2d_arrays_from_floats/subscript_individually_defined_1_of_2d_arrays_from_floats.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_individually_defined_1d_arrays(self): - output, canon = runner('test-models/tests/subscript_individually_defined_1d_arrays/subscript_individually_defined_1d_arrays.mdl') + output, canon = runner(test_models + '/subscript_individually_defined_1d_arrays/subscript_individually_defined_1d_arrays.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_individually_defined_stocks(self): - output, canon = runner('test-models/tests/subscript_individually_defined_stocks/test_subscript_individually_defined_stocks.mdl') + output, canon = runner(test_models + '/subscript_individually_defined_stocks/test_subscript_individually_defined_stocks.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_mixed_assembly(self): - output, canon = runner('test-models/tests/subscript_mixed_assembly/test_subscript_mixed_assembly.mdl') + output, canon = runner(test_models + '/subscript_mixed_assembly/test_subscript_mixed_assembly.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_selection(self): - output, canon = runner('test-models/tests/subscript_selection/subscript_selection.mdl') + output, canon = runner(test_models + '/subscript_selection/subscript_selection.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_subscript_subranges(self): output, canon = runner( - 'test-models/tests/subscript_subranges/test_subscript_subrange.xmile') + test_models + '/subscript_subranges/test_subscript_subrange.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_subscript_subranges_equal(self): output, canon = runner( - 'test-models/tests/subscript_subranges_equal/test_subscript_subrange_equal.xmile') + test_models + '/subscript_subranges_equal/test_subscript_subrange_equal.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscript_switching(self): - output, canon = runner('test-models/tests/subscript_switching/subscript_switching.mdl') + output, canon = runner(test_models + '/subscript_switching/subscript_switching.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('missing test model') def test_subscript_updimensioning(self): output, canon = runner( - 'test-models/tests/subscript_updimensioning/test_subscript_updimensioning.xmile') + test_models + '/subscript_updimensioning/test_subscript_updimensioning.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscripted_delays(self): - output, canon = runner('test-models/tests/subscripted_delays/test_subscripted_delays.mdl') + output, canon = runner(test_models + '/subscripted_delays/test_subscripted_delays.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_subscripted_flows(self): - output, canon = runner('test-models/tests/subscripted_flows/test_subscripted_flows.mdl') + output, canon = runner(test_models + '/subscripted_flows/test_subscripted_flows.mdl') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_time(self): - output, canon = runner('test-models/tests/time/test_time.mdl') + output, canon = runner(test_models + '/time/test_time.mdl') assert_frames_close(output, canon, rtol=rtol) def test_trig(self): - output, canon = runner('test-models/tests/trig/test_trig.xmile') + output, canon = runner(test_models + '/trig/test_trig.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_trend(self): - output, canon = runner('test-models/tests/trend/test_trend.xmile') + output, canon = runner(test_models + '/trend/test_trend.xmile') assert_frames_close(output, canon, rtol=rtol) @unittest.skip('no xmile') def test_xidz_zidz(self): - output, canon = runner('test-models/tests/xidz_zidz/xidz_zidz.xmile') + output, canon = runner(test_models + '/xidz_zidz/xidz_zidz.xmile') assert_frames_close(output, canon, rtol=rtol) diff --git a/tests/more-tests/split_model/test_split_model_subviews.mdl b/tests/more-tests/split_model/test_split_model_subviews.mdl new file mode 100644 index 00000000..e302c23c --- /dev/null +++ b/tests/more-tests/split_model/test_split_model_subviews.mdl @@ -0,0 +1,105 @@ +{UTF-8} +another var= + 3*Stock + ~ + ~ | + +"rate-1"= + "var-n" + ~ + ~ | + +"var-n"= + 5 + ~ + ~ | + +"variable-x"= + 6*another var + ~ + ~ | + +Stock= INTEG ( + "rate-1", + 1) + ~ + ~ | + +******************************************************** + .Control +********************************************************~ + Simulation Control Parameters + | + +FINAL TIME = 100 + ~ Month + ~ The final time for the simulation. + | + +INITIAL TIME = 0 + ~ Month + ~ The initial time for the simulation. + | + +SAVEPER = + TIME STEP + ~ Month [0,?] + ~ The frequency with which output is stored. + | + +TIME STEP = 1 + ~ Month [0,?] + ~ The time step for the simulation. + | + +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*View 1.Submodule 1 +$255-128-0,0,Times New Roman|12||0-0-0|0-0-0|0-192-192|-1--1--1|-1--1--1|96,96,100,0 +10,1,Stock,497,237,40,20,3,3,0,0,0,0,0,0 +12,2,48,297,243,10,8,0,3,0,0,-1,0,0,0 +1,3,5,1,4,0,0,22,0,0,0,-1--1--1,,1|(422,243)| +1,4,5,2,100,0,0,22,0,0,0,-1--1--1,,1|(341,243)| +11,5,48,382,243,6,8,34,3,0,0,1,0,0,0 +10,6,"rate-1",382,262,21,11,40,3,0,0,-1,0,0,0 +12,7,0,1141,258,150,150,3,12,0,0,1,0,0,0 +Stock +10,8,"var-n",207,367,18,11,8,3,0,0,0,0,0,0 +1,9,8,6,0,0,0,0,0,128,0,-1--1--1,,1|(288,318)| +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*View 1.Submodule 2 +$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|96,96,100,0 +10,1,another var,89,168,36,11,8,3,0,0,0,0,0,0 +10,2,Stock,334,243,29,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 +1,3,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(221,209)| +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*View 2 +$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|96,96,100,0 +10,1,"variable-x",191,176,32,11,8,3,0,0,0,0,0,0 +10,2,another var,223,395,45,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 +12,3,0,461,148,43,11,8,7,0,0,-1,0,0,0 +This is view 2 +1,4,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(208,292)| +///---\\\ +:L<%^E!@ +1:Current.vdf +9:Current +15:0,0,0,0,0,0 +19:100,0 +27:0, +34:0, +4:Time +5:another var +35:Date +36:YYYY-MM-DD +37:2000 +38:1 +39:1 +40:2 +41:0 +42:1 +24:0 +25:100 +26:100 diff --git a/tests/more-tests/split_model_vensim_8_2_1/test_split_model_vensim_8_2_1.mdl b/tests/more-tests/split_model_vensim_8_2_1/test_split_model_vensim_8_2_1.mdl new file mode 100644 index 00000000..d645c2d6 --- /dev/null +++ b/tests/more-tests/split_model_vensim_8_2_1/test_split_model_vensim_8_2_1.mdl @@ -0,0 +1,143 @@ +{UTF-8} +Heating= + (Cream Temperature - Room Temperature) / Characteristic Time + ~ + ~ | + +Cream Temperature= INTEG ( + -Heating, + 10) + ~ Degrees + ~ | + +Room Temperature= + 70 + ~ Degrees + ~ | + +Transfer Coef= + 0.37 + ~ + ~ | + +Heat Loss to Room= + (Teacup Temperature - Room Temperature) * Transfer Coef / Characteristic Time + ~ Degrees/Minute + ~ This is the rate at which heat flows from the cup into the room. We can \ + ignore it at this point. + | + +Characteristic Time= + 10 + ~ Minutes + ~ | + +Teacup Temperature= INTEG ( + -Heat Loss to Room, + 100) + ~ Degrees + ~ | + +******************************************************** + .Control +********************************************************~ + Simulation Control Parameters + | + +FINAL TIME = 30 + ~ Minute + ~ The final time for the simulation. + | + +INITIAL TIME = 0 + ~ Minute + ~ The initial time for the simulation. + | + +SAVEPER = + TIME STEP + ~ Minute [0,?] + ~ The frequency with which output is stored. + | + +TIME STEP = 0.125 + ~ Minute [0,?] + ~ The time step for the simulation. + | + +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*TeaCup +$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|72,72,100,0 +10,1,Teacup Temperature,307,224,40,20,3,3,0,0,0,0,0,0,0,0,0,0,0,0 +12,2,48,605,221,8,8,0,3,0,0,-1,0,0,0,0,0,0,0,0,0 +1,3,5,2,4,0,0,22,0,0,0,-1--1--1,,1|(508,220)| +1,4,5,1,100,0,0,22,0,0,0,-1--1--1,,1|(377,220)| +11,5,48,413,220,5,8,34,3,0,0,1,0,0,0,0,0,0,0,0,0 +10,6,Heat Loss to Room,413,232,49,8,40,3,0,0,-1,0,0,0,0,0,0,0,0,0 +10,7,Room Temperature,504,373,49,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0 +10,8,Characteristic Time,408,164,49,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0 +1,9,8,5,0,0,0,0,0,64,0,-1--1--1,,1|(412,187)| +1,10,1,6,1,0,0,0,0,64,0,-1--1--1,,1|(393,308)| +1,11,7,6,1,0,0,0,0,64,0,-1--1--1,,1|(477,336)| +10,12,Transfer Coef,541,164,33,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0 +1,13,12,6,0,0,0,0,0,64,0,-1--1--1,,1|(484,195)| +\\\---/// Sketch information - do not modify anything except names +V300 Do not put anything below this section - it will be ignored +*Cream +$192-192-192,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|255-255-255|72,72,100,0 +10,1,Cream Temperature,363,287,56,15,3,131,0,0,0,0,0,0,0,0,0,0,0,0 +12,2,48,680,284,8,8,0,3,0,0,-1,0,0,0,0,0,0,0,0,0 +1,3,5,2,4,0,0,22,0,0,0,-1--1--1,,1|(611,284)| +1,4,5,1,100,0,0,22,0,0,0,-1--1--1,,1|(480,284)| +11,5,0,545,284,5,8,34,3,0,0,1,0,0,0,0,0,0,0,0,0 +10,6,Heating,545,296,19,8,40,3,0,0,-1,0,0,0,0,0,0,0,0,0 +10,7,Room Temperature,532,407,35,16,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|0||128-128-128,0,0,0,0,0,0 +1,8,7,6,1,0,0,0,0,64,0,-1--1--1,,1|(608,364)| +10,9,Characteristic Time,544,188,35,17,8,130,0,3,-1,0,0,0,128-128-128,0-0-0,|0||128-128-128,0,0,0,0,0,0 +1,10,9,6,1,0,0,0,0,64,0,-1--1--1,,1|(593,241)| +1,11,1,6,1,0,0,0,0,64,0,-1--1--1,,1|(460,341)| +///---\\\ +:L<%^E!@ +4:Time +5:Teacup Temperature +9:Current +19:100,0 +24:0 +25:30 +26:30 +22:$,Dollar,Dollars,$s +22:Hour,Hours +22:Month,Months +22:Person,People,Persons +22:Unit,Units +22:Week,Weeks +22:Year,Years +22:Day,Days +23:0 +15:0,0,0,0,0,0 +27:0, +34:0, +42:1 +72:0 +73:0 +35:Date +36:YYYY-MM-DD +37:2000 +38:1 +39:1 +40:6 +41:0 +95:0 +96:0 +77:0 +78:0 +93:0 +94:0 +92:0 +91:0 +90:0 +87:0 +75: +43: + diff --git a/tests/pytest.ini b/tests/pytest.ini new file mode 100644 index 00000000..033e9f5f --- /dev/null +++ b/tests/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +python_files = unit_test_*.py integration_test_*.py \ No newline at end of file diff --git a/tests/unit_test_benchmarking.py b/tests/unit_test_benchmarking.py index 182edbd7..aae1a3df 100644 --- a/tests/unit_test_benchmarking.py +++ b/tests/unit_test_benchmarking.py @@ -1,8 +1,11 @@ +import os from unittest import TestCase # most of the features of this script are already tested indirectly when # running vensim and xmile integration tests +_root = os.path.dirname(__file__) + class TestErrors(TestCase): @@ -10,7 +13,7 @@ def test_canonical_file_not_found(self): from pysd.tools.benchmarking import runner with self.assertRaises(FileNotFoundError) as err: - runner('more-tests/not_existent.mdl') + runner(os.path.join(_root, "more-tests/not_existent.mdl")) self.assertIn( 'Canonical output file not found.', @@ -20,7 +23,9 @@ def test_non_valid_model(self): from pysd.tools.benchmarking import runner with self.assertRaises(ValueError) as err: - runner('more-tests/not_vensim/test_not_vensim.txt') + runner(os.path.join( + _root, + "more-tests/not_vensim/test_not_vensim.txt")) self.assertIn( 'Modelfile should be *.mdl or *.xmile', @@ -30,10 +35,16 @@ def test_non_valid_outputs(self): from pysd.tools.benchmarking import load_outputs with self.assertRaises(ValueError) as err: - load_outputs('more-tests/not_vensim/test_not_vensim.txt') + load_outputs( + os.path.join( + _root, + "more-tests/not_vensim/test_not_vensim.txt")) self.assertIn( - "Not able to read 'more-tests/not_vensim/test_not_vensim.txt'.", + "Not able to read '", + str(err.exception)) + self.assertIn( + "more-tests/not_vensim/test_not_vensim.txt'.", str(err.exception)) def test_different_frames_error(self): @@ -41,8 +52,9 @@ def test_different_frames_error(self): with self.assertRaises(AssertionError) as err: assert_frames_close( - load_outputs('data/out_teacup.csv'), - load_outputs('data/out_teacup_modified.csv')) + load_outputs(os.path.join(_root, "data/out_teacup.csv")), + load_outputs( + os.path.join(_root, "data/out_teacup_modified.csv"))) self.assertIn( "Following columns are not close:\n\tTeacup Temperature", @@ -62,8 +74,9 @@ def test_different_frames_error(self): with self.assertRaises(AssertionError) as err: assert_frames_close( - load_outputs('data/out_teacup.csv'), - load_outputs('data/out_teacup_modified.csv'), + load_outputs(os.path.join(_root, "data/out_teacup.csv")), + load_outputs( + os.path.join(_root, "data/out_teacup_modified.csv")), verbose=True) self.assertIn( @@ -88,8 +101,9 @@ def test_different_frames_warning(self): with catch_warnings(record=True) as ws: assert_frames_close( - load_outputs('data/out_teacup.csv'), - load_outputs('data/out_teacup_modified.csv'), + load_outputs(os.path.join(_root, "data/out_teacup.csv")), + load_outputs( + os.path.join(_root, "data/out_teacup_modified.csv")), assertion="warn") # use only user warnings @@ -114,8 +128,8 @@ def test_different_frames_warning(self): with catch_warnings(record=True) as ws: assert_frames_close( - load_outputs('data/out_teacup.csv'), - load_outputs('data/out_teacup_modified.csv'), + load_outputs(os.path.join(_root, "data/out_teacup.csv")), + load_outputs(os.path.join(_root, "data/out_teacup_modified.csv")), assertion="warn", verbose=True) # use only user warnings @@ -142,21 +156,23 @@ def test_transposed_frame(self): from pysd.tools.benchmarking import load_outputs, assert_frames_close assert_frames_close( - load_outputs('data/out_teacup.csv'), - load_outputs('data/out_teacup_transposed.csv', transpose=True)) + load_outputs(os.path.join(_root, "data/out_teacup.csv")), + load_outputs( + os.path.join(_root, "data/out_teacup_transposed.csv"), + transpose=True)) def test_load_columns(self): from pysd.tools.benchmarking import load_outputs out0 = load_outputs( - 'data/out_teacup.csv') + os.path.join(_root, "data/out_teacup.csv")) out1 = load_outputs( - 'data/out_teacup.csv', + os.path.join(_root, "data/out_teacup.csv"), columns=["Room Temperature", "Teacup Temperature"]) out2 = load_outputs( - 'data/out_teacup_transposed.csv', + os.path.join(_root, "data/out_teacup_transposed.csv"), transpose=True, columns=["Heat Loss to Room"]) diff --git a/tests/unit_test_cli.py b/tests/unit_test_cli.py index eb1d51dd..be0400a7 100644 --- a/tests/unit_test_cli.py +++ b/tests/unit_test_cli.py @@ -10,15 +10,21 @@ from pysd.tools.benchmarking import load_outputs, assert_frames_close from pysd import __version__ -test_model = 'test-models/samples/teacup/teacup.mdl' -test_model_xmile = 'test-models/samples/teacup/teacup.xmile' -test_model_subs = 'test-models/tests/subscript_2d_arrays/'\ - + 'test_subscript_2d_arrays.mdl' -test_model_look = 'test-models/tests/get_lookups_subscripted_args/'\ - + 'test_get_lookups_subscripted_args.mdl' - -out_tab_file = 'cli_output.tab' -out_csv_file = 'cli_output.csv' +_root = os.path.dirname(__file__) + +test_model = os.path.join(_root, 'test-models/samples/teacup/teacup.mdl') +test_model_xmile = os.path.join( + _root, 'test-models/samples/teacup/teacup.xmile') +test_model_subs = os.path.join( + _root, + 'test-models/tests/subscript_2d_arrays/test_subscript_2d_arrays.mdl') +test_model_look = os.path.join( + _root, + 'test-models/tests/get_lookups_subscripted_args/' + + 'test_get_lookups_subscripted_args.mdl') + +out_tab_file = os.path.join(_root, 'cli_output.tab') +out_csv_file = os.path.join(_root, 'cli_output.csv') encoding_stdout = sys.stdout.encoding or 'utf-8' encoding_stderr = sys.stderr.encoding or 'utf-8' @@ -52,7 +58,8 @@ class TestPySD(unittest.TestCase): """ These tests are similar to unit_test_pysd but adapted for cli """ def test_read_not_model(self): - model = 'more-tests/not_vensim/test_not_vensim.txt' + model = os.path.join( + _root, 'more-tests/not_vensim/test_not_vensim.txt') command = f'{call} {model}' out = subprocess.run(split_bash(command), capture_output=True) stderr = out.stderr.decode(encoding_stderr) @@ -65,7 +72,8 @@ def test_read_not_model(self): def test_read_model_not_exists(self): - model = 'more-tests/not_vensim/test_not_vensim.mdl' + model = os.path.join( + _root, 'more-tests/not_vensim/test_not_vensim.mdl') command = f'{call} {model}' out = subprocess.run(split_bash(command), capture_output=True) stderr = out.stderr.decode(encoding_stderr) @@ -77,7 +85,7 @@ def test_read_model_not_exists(self): def test_read_not_valid_output(self): - out_xls_file = 'cli_output.xls' + out_xls_file = os.path.join(_root, 'cli_output.xls') command = f'{call} -o {out_xls_file} {test_model}' out = subprocess.run(split_bash(command), capture_output=True) stderr = out.stderr.decode(encoding_stderr) @@ -198,7 +206,7 @@ def test_translate_file(self): def test_read_vensim_split_model(self): - root_dir = "more-tests/split_model/" + root_dir = os.path.join(_root, "more-tests/split_model") + "/" model_name = "test_split_model" namespace_filename = "_namespace_" + model_name + ".json" @@ -207,7 +215,7 @@ def test_read_vensim_split_model(self): modules_dirname = "modules_" + model_name model_name_mdl = root_dir + model_name + ".mdl" - command = f'{call} --translate --split-modules {model_name_mdl}' + command = f'{call} --translate --split-views {model_name_mdl}' out = subprocess.run(split_bash(command), capture_output=True) self.assertEqual(out.returncode, 0) @@ -240,6 +248,66 @@ def test_read_vensim_split_model(self): # remove newly created modules folder shutil.rmtree(root_dir + modules_dirname) + def test_read_vensim_split_model_subviews(self): + import pysd + from pysd.tools.benchmarking import assert_frames_close + + root_dir = os.path.join(_root, "more-tests/split_model/") + + model_name = "test_split_model_subviews" + model_name_mdl = root_dir + model_name + ".mdl" + + model_split = pysd.read_vensim( + root_dir + model_name + ".mdl", split_views=True, + subview_sep="." + ) + + namespace_filename = "_namespace_" + model_name + ".json" + subscript_dict_filename = "_subscripts_" + model_name + ".json" + modules_dirname = "modules_" + model_name + + separator = "." + command = f'{call} --translate --split-views '\ + f'--subview-sep={separator} {model_name_mdl}' + out = subprocess.run(split_bash(command), capture_output=True) + self.assertEqual(out.returncode, 0) + + # check that the modules folders were created + self.assertTrue(os.path.isdir(root_dir + modules_dirname + "/VIEW_1")) + self.assertTrue(os.path.isdir(root_dir + modules_dirname + "/VIEW_2")) + + # check creation of module files + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/VIEW_1/" + + "submodule_1.py")) + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/VIEW_1/" + + "submodule_2.py")) + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/VIEW_2/" + + "view_2.py")) + + # check that the results of the split model are the same than those + # without splitting + model_non_split = pysd.read_vensim( + root_dir + model_name + ".mdl", split_views=False + ) + + result_split = model_split.run() + result_non_split = model_non_split.run() + + # results of a split model are the same that those of the regular + # model (un-split) + assert_frames_close(result_split, result_non_split, atol=0, rtol=0) + + # remove newly created files + os.remove(root_dir + model_name + ".py") + os.remove(root_dir + namespace_filename) + os.remove(root_dir + subscript_dict_filename) + + # remove newly created modules folder + shutil.rmtree(root_dir + modules_dirname) + def test_run_return_timestamps(self): timestamps = np.round(np.random.rand(5).cumsum(), 4).astype(str) @@ -271,7 +339,7 @@ def test_run_return_columns(self): os.remove(out_csv_file) # from txt - txt_file = 'return_columns.txt' + txt_file = os.path.join(_root, 'return_columns.txt') return_columns = ['Room Temperature', 'Teacup Temperature'] with open(txt_file, 'w') as file: file.write("\n".join(return_columns)) diff --git a/tests/unit_test_external.py b/tests/unit_test_external.py index b6164737..a3cf641b 100644 --- a/tests/unit_test_external.py +++ b/tests/unit_test_external.py @@ -6,7 +6,10 @@ import xarray as xr _root = os.path.dirname(__file__) -_exp = SourceFileLoader('expected_data', 'data/expected_data.py').load_module() +_exp = SourceFileLoader( + 'expected_data', + os.path.join(_root, 'data/expected_data.py') + ).load_module() class TestExcels(unittest.TestCase): @@ -19,7 +22,7 @@ def test_read_clean(self): """ import pysd - file_name = "data/input.xlsx" + file_name = os.path.join(_root, "data/input.xlsx") sheet_name = "Vertical" sheet_name2 = "Horizontal" @@ -47,7 +50,7 @@ def test_read_clean_opyxl(self): import pysd from openpyxl import Workbook - file_name = "data/input.xlsx" + file_name = os.path.join(_root, "data/input.xlsx") # reading a file excel = pysd.external.Excels.read_opyxl(file_name) @@ -78,7 +81,7 @@ def test_close_file(self): # number of files already open n_files = len(p.open_files()) - file_name = "data/input.xlsx" + file_name = os.path.join(_root, "data/input.xlsx") sheet_name = "Vertical" sheet_name2 = "Horizontal" @@ -2684,10 +2687,10 @@ def test_data_interp_vnss(self): data.initialize() # Following test are independent of the reading option - def test_data_interp_hnnm(self): + def test_data_interp_hnnwd(self): """ Test for error in series when the series is not - strictly monotonous + well defined """ import pysd @@ -2697,7 +2700,7 @@ def test_data_interp_hnnm(self): cell = "data_1d" coords = {} interp = None - py_name = "test_data_interp_hnnm" + py_name = "test_data_interp_hnnwd" data = pysd.external.ExtData(file_name=file_name, sheet=sheet, @@ -2708,9 +2711,64 @@ def test_data_interp_hnnm(self): interp=interp, py_name=py_name) - with self.assertRaises(ValueError): + with self.assertRaises(ValueError) as err: data.initialize() + self.assertIn("has repeated values", str(err.exception)) + + def test_data_raw_hnnm(self): + """ + Test for error in series when the series is not monotonous + """ + import pysd + + file_name = "data/input.xlsx" + sheet = "No monotonous" + time_row_or_col = "10" + cell = "C12" + coords = {} + interp = None + py_name = "test_data_interp_hnnnm" + + data = pysd.external.ExtData(file_name=file_name, + sheet=sheet, + time_row_or_col=time_row_or_col, + root=_root, + cell=cell, + coords=coords, + interp=interp, + py_name=py_name) + + data.initialize() + + expected = {-1: 2, 0: 2, 1: 2, 2: 3, + 3: -1, 4: -1, 5: 1, 6: 1, + 7: 0, 8: 0, 9: 0} + + for i in range(-1, 9): + self.assertEqual(data(i), expected[i]) + + time_row_or_col = "11" + py_name = "test_data_interp_hnnnm2" + + data = pysd.external.ExtData(file_name=file_name, + sheet=sheet, + time_row_or_col=time_row_or_col, + root=_root, + cell=cell, + coords=coords, + interp=interp, + py_name=py_name) + + data.initialize() + + expected = {-1: 0, 0: 0, 1: 0, 2: 1, + 3: 2, 4: 3, 5: -1, 6: -1, + 7: 1, 8: 2, 9: 2} + + for i in range(-1, 9): + self.assertEqual(data(i), expected[i]) + def test_data_h3d_interpnv(self): """ ExtData test for error when the interpolation method is not valid diff --git a/tests/unit_test_pysd.py b/tests/unit_test_pysd.py index a47d07af..c602bb88 100644 --- a/tests/unit_test_pysd.py +++ b/tests/unit_test_pysd.py @@ -6,14 +6,18 @@ import numpy as np import xarray as xr -test_model = "test-models/samples/teacup/teacup.mdl" -test_model_subs = ( - "test-models/tests/subscript_2d_arrays/" + "test_subscript_2d_arrays.mdl" -) -test_model_look = ( +_root = os.path.dirname(__file__) + +test_model = os.path.join(_root, "test-models/samples/teacup/teacup.mdl") +test_model_subs = os.path.join( + _root, + "test-models/tests/subscript_2d_arrays/test_subscript_2d_arrays.mdl") +test_model_look = os.path.join( + _root, "test-models/tests/get_lookups_subscripted_args/" - + "test_get_lookups_subscripted_args.mdl" -) + + "test_get_lookups_subscripted_args.mdl") + +more_tests = os.path.join(_root, "more-tests") class TestPySD(unittest.TestCase): @@ -22,22 +26,22 @@ def test_load_different_version_error(self): # old PySD major version with self.assertRaises(ImportError): - pysd.load("more-tests/version/test_old_version.py") + pysd.load(more_tests + "/version/test_old_version.py") # current PySD major version - pysd.load("more-tests/version/test_current_version.py") + pysd.load(more_tests + "/version/test_current_version.py") def test_load_type_error(self): import pysd with self.assertRaises(ImportError): - pysd.load("more-tests/type_error/test_type_error.py") + pysd.load(more_tests + "/type_error/test_type_error.py") def test_read_not_model_vensim(self): import pysd with self.assertRaises(ValueError): - pysd.read_vensim('more-tests/not_vensim/test_not_vensim.txt') + pysd.read_vensim(more_tests + "/not_vensim/test_not_vensim.txt") def test_run(self): import pysd @@ -56,10 +60,14 @@ def test_run(self): def test_run_ignore_missing(self): import pysd - model_mdl = 'test-models/tests/get_with_missing_values_xlsx/'\ - + 'test_get_with_missing_values_xlsx.mdl' - model_py = 'test-models/tests/get_with_missing_values_xlsx/'\ - + 'test_get_with_missing_values_xlsx.py' + model_mdl = os.path.join( + _root, + 'test-models/tests/get_with_missing_values_xlsx/' + + 'test_get_with_missing_values_xlsx.mdl') + model_py = os.path.join( + _root, + 'test-models/tests/get_with_missing_values_xlsx/' + + 'test_get_with_missing_values_xlsx.py') with catch_warnings(record=True) as ws: # warnings for missing values @@ -89,11 +97,11 @@ def test_read_vensim_split_model(self): import pysd from pysd.tools.benchmarking import assert_frames_close - root_dir = "more-tests/split_model/" + root_dir = more_tests + "/split_model/" model_name = "test_split_model" model_split = pysd.read_vensim( - root_dir + model_name + ".mdl", split_modules=True + root_dir + model_name + ".mdl", split_views=True ) namespace_filename = "_namespace_" + model_name + ".json" @@ -127,10 +135,168 @@ def test_read_vensim_split_model(self): self.assertIn("view2", model_split.components._modules.keys()) self.assertIsInstance(model_split.components._subscript_dict, dict) + with open(root_dir + model_name + ".py", 'r') as file: + file_content = file.read() + + # assert that the functions are not defined in the main file + self.assertNotIn("def another_var()", file_content) + self.assertNotIn("def rate1()", file_content) + self.assertNotIn("def varn()", file_content) + self.assertNotIn("def variablex()", file_content) + self.assertNotIn("def stock()", file_content) + + # check that the results of the split model are the same than those + # without splitting + model_non_split = pysd.read_vensim( + root_dir + model_name + ".mdl", split_views=False + ) + + result_split = model_split.run() + result_non_split = model_non_split.run() + + # results of a split model are the same that those of the regular + # model (un-split) + assert_frames_close(result_split, result_non_split, atol=0, rtol=0) + + with open(root_dir + model_name + ".py", 'r') as file: + file_content = file.read() + + # assert that the functions are in the main file for regular trans + self.assertIn("def another_var()", file_content) + self.assertIn("def rate1()", file_content) + self.assertIn("def varn()", file_content) + self.assertIn("def variablex()", file_content) + self.assertIn("def stock()", file_content) + + # remove newly created files + os.remove(root_dir + model_name + ".py") + os.remove(root_dir + namespace_filename) + os.remove(root_dir + subscript_dict_filename) + + # remove newly created modules folder + shutil.rmtree(root_dir + modules_dirname) + + def test_read_vensim_split_model_vensim_8_2_1(self): + import pysd + from pysd.tools.benchmarking import assert_frames_close + + root_dir = os.path.join(_root, "more-tests/split_model_vensim_8_2_1/") + + model_name = "test_split_model_vensim_8_2_1" + model_split = pysd.read_vensim( + root_dir + model_name + ".mdl", split_views=True, subview_sep="." + ) + + namespace_filename = "_namespace_" + model_name + ".json" + subscript_dict_filename = "_subscripts_" + model_name + ".json" + modules_filename = "_modules.json" + modules_dirname = "modules_" + model_name + + # check that _namespace and _subscript_dict json files where created + self.assertTrue(os.path.isfile(root_dir + namespace_filename)) + self.assertTrue(os.path.isfile(root_dir + subscript_dict_filename)) + + # check that the main model file was created + self.assertTrue(os.path.isfile(root_dir + model_name + ".py")) + + # check that the modules folder was created + self.assertTrue(os.path.isdir(root_dir + modules_dirname)) + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/" + modules_filename) + ) + + # check creation of module files + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/" + "teacup.py")) + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/" + "cream.py")) + + # check dictionaries + self.assertIn("Cream Temperature", + model_split.components._namespace.keys()) + self.assertIn("cream", model_split.components._modules.keys()) + self.assertIsInstance(model_split.components._subscript_dict, dict) + + with open(root_dir + model_name + ".py", 'r') as file: + file_content = file.read() + + # assert that the functions are not defined in the main file + self.assertNotIn("def teacup_temperature()", file_content) + self.assertNotIn("def cream_temperature()", file_content) + + # check that the results of the split model are the same than those + # without splitting + model_non_split = pysd.read_vensim( + root_dir + model_name + ".mdl", split_views=False + ) + + result_split = model_split.run() + result_non_split = model_non_split.run() + + # results of a split model are the same that those of the regular + # model (un-split) + assert_frames_close(result_split, result_non_split, atol=0, rtol=0) + + with open(root_dir + model_name + ".py", 'r') as file: + file_content = file.read() + + # assert that the functions are in the main file for regular trans + self.assertIn("def teacup_temperature()", file_content) + self.assertIn("def cream_temperature()", file_content) + + # remove newly created files + os.remove(root_dir + model_name + ".py") + os.remove(root_dir + namespace_filename) + os.remove(root_dir + subscript_dict_filename) + + # remove newly created modules folder + shutil.rmtree(root_dir + modules_dirname) + + def test_read_vensim_split_model_subviews(self): + import pysd + from pysd.tools.benchmarking import assert_frames_close + + root_dir = os.path.join(_root, "more-tests/split_model/") + + model_name = "test_split_model_subviews" + model_split = pysd.read_vensim( + root_dir + model_name + ".mdl", split_views=True, + subview_sep="." + ) + + namespace_filename = "_namespace_" + model_name + ".json" + subscript_dict_filename = "_subscripts_" + model_name + ".json" + modules_dirname = "modules_" + model_name + + # check that the modules folders were created + self.assertTrue(os.path.isdir(root_dir + modules_dirname + "/VIEW_1")) + self.assertTrue(os.path.isdir(root_dir + modules_dirname + "/VIEW_2")) + + # check creation of module files + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/VIEW_1/" + + "submodule_1.py")) + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/VIEW_1/" + + "submodule_2.py")) + self.assertTrue( + os.path.isfile(root_dir + modules_dirname + "/VIEW_2/" + + "view_2.py")) + + with open(root_dir + model_name + ".py", 'r') as file: + file_content = file.read() + + # assert that the functions are not defined in the main file + self.assertNotIn("def another_var()", file_content) + self.assertNotIn("def rate1()", file_content) + self.assertNotIn("def varn()", file_content) + self.assertNotIn("def variablex()", file_content) + self.assertNotIn("def stock()", file_content) + # check that the results of the split model are the same than those # without splitting model_non_split = pysd.read_vensim( - root_dir + model_name + ".mdl", split_modules=False + root_dir + model_name + ".mdl", split_views=False ) result_split = model_split.run() @@ -140,6 +306,16 @@ def test_read_vensim_split_model(self): # model (un-split) assert_frames_close(result_split, result_non_split, atol=0, rtol=0) + with open(root_dir + model_name + ".py", 'r') as file: + file_content = file.read() + + # assert that the functions are in the main file for regular trans + self.assertIn("def another_var()", file_content) + self.assertIn("def rate1()", file_content) + self.assertIn("def varn()", file_content) + self.assertIn("def variablex()", file_content) + self.assertIn("def stock()", file_content) + # remove newly created files os.remove(root_dir + model_name + ".py") os.remove(root_dir + namespace_filename) @@ -152,11 +328,11 @@ def test_read_vensim_split_model_with_macro(self): import pysd from pysd.tools.benchmarking import assert_frames_close - root_dir = "more-tests/split_model_with_macro/" + root_dir = more_tests + "/split_model_with_macro/" model_name = "test_split_model_with_macro" model_split = pysd.read_vensim( - root_dir + model_name + ".mdl", split_modules=True + root_dir + model_name + ".mdl", split_views=True ) namespace_filename = "_namespace_" + model_name + ".json" @@ -166,7 +342,7 @@ def test_read_vensim_split_model_with_macro(self): # check that the results of the split model are the same # than those without splitting model_non_split = pysd.read_vensim( - root_dir + model_name + ".mdl", split_modules=False + root_dir + model_name + ".mdl", split_views=False ) result_split = model_split.run() @@ -186,18 +362,19 @@ def test_read_vensim_split_model_with_macro(self): def test_read_vensim_split_model_warning(self): import pysd - # setting the split_modules=True when the model has a single + # setting the split_views=True when the model has a single # view should generate a warning with catch_warnings(record=True) as ws: pysd.read_vensim( - test_model, split_modules=True + test_model, split_views=True ) # set stock value using params wu = [w for w in ws if issubclass(w.category, UserWarning)] self.assertEqual(len(wu), 1) self.assertTrue( - "Only one module was detected" in str(wu[0].message) + "Only a single view with no subviews was detected" in str( + wu[0].message) ) # check that warning references the stock def test_run_includes_last_value(self): @@ -350,7 +527,9 @@ def test_run_export_import(self): assert_frames_close(stocks2, stocks.loc[[20, 30]]) # delays - test_delays = 'test-models/tests/delays/test_delays.mdl' + test_delays = os.path.join( + _root, + 'test-models/tests/delays/test_delays.mdl') model = pysd.read_vensim(test_delays) stocks = model.run(return_timestamps=20) model.initialize() @@ -369,7 +548,9 @@ def test_run_export_import(self): assert_frames_close(stocks2, stocks) # delay fixed - test_delayf = 'test-models/tests/delay_fixed/test_delay_fixed.mdl' + test_delayf = os.path.join( + _root, + 'test-models/tests/delay_fixed/test_delay_fixed.mdl') model = pysd.read_vensim(test_delayf) stocks = model.run(return_timestamps=20) model.initialize() @@ -388,8 +569,10 @@ def test_run_export_import(self): assert_frames_close(stocks2, stocks) # smooth - test_smooth = 'test-models/tests/subscripted_smooth/'\ - 'test_subscripted_smooth.mdl' + test_smooth = os.path.join( + _root, + 'test-models/tests/subscripted_smooth/' + + 'test_subscripted_smooth.mdl') model = pysd.read_vensim(test_smooth) stocks = model.run(return_timestamps=20, flatten_output=True) model.initialize() @@ -409,8 +592,10 @@ def test_run_export_import(self): assert_frames_close(stocks2, stocks) # trend - test_trend = 'test-models/tests/subscripted_trend/'\ - 'test_subscripted_trend.mdl' + test_trend = os.path.join( + _root, + 'test-models/tests/subscripted_trend/' + + 'test_subscripted_trend.mdl') model = pysd.read_vensim(test_trend) stocks = model.run(return_timestamps=20, flatten_output=True) model.initialize() @@ -430,8 +615,8 @@ def test_run_export_import(self): assert_frames_close(stocks2, stocks) # initial - test_initial = 'test-models/tests/initial_function/'\ - 'test_initial.mdl' + test_initial = os.path.join( + _root, 'test-models/tests/initial_function/test_initial.mdl') model = pysd.read_vensim(test_initial) stocks = model.run(return_timestamps=20) model.initialize() @@ -450,8 +635,9 @@ def test_run_export_import(self): assert_frames_close(stocks2, stocks) # sample if true - test_sample_if_true = 'test-models/tests/sample_if_true/'\ - 'test_sample_if_true.mdl' + test_sample_if_true = os.path.join( + _root, + 'test-models/tests/sample_if_true/test_sample_if_true.mdl') model = pysd.read_vensim(test_sample_if_true) stocks = model.run(return_timestamps=20, flatten_output=True) model.initialize() @@ -989,10 +1175,10 @@ def test_docs_multiline_eqn(self): """ Test that the model prints some documentation """ import pysd - path2model = ( + path2model = os.path.join( + _root, "test-models/tests/multiple_lines_def/" + - "test_multiple_lines_def.mdl" - ) + "test_multiple_lines_def.mdl") model = pysd.read_vensim(path2model) doc = model.doc() @@ -1133,8 +1319,8 @@ def test_initialize(self): def test_initialize_order(self): import pysd - model = pysd.load('more-tests/initialization_order/' - 'test_initialization_order.py') + model = pysd.load(more_tests + "/initialization_order/" + "test_initialization_order.py") if model._stateful_elements[0].py_name.endswith('stock_a'): # we want to have stock b first always @@ -1624,7 +1810,8 @@ def test_default_returns_with_construction_functions(self): """ import pysd - model = pysd.read_vensim("test-models/tests/delays/test_delays.mdl") + model = pysd.read_vensim(os.path.join( + _root, "test-models/tests/delays/test_delays.mdl")) ret = model.run() self.assertTrue( { @@ -1646,7 +1833,8 @@ def test_default_returns_with_lookups(self): """ import pysd - model = pysd.read_vensim("test-models/tests/lookups/test_lookups.mdl") + model = pysd.read_vensim(os.path.join( + _root, "test-models/tests/lookups/test_lookups.mdl")) ret = model.run() self.assertTrue( {"accumulation", "rate", "lookup function call"} <= @@ -1674,9 +1862,11 @@ def test_incomplete_model(self): with catch_warnings(record=True) as w: simplefilter("always") - model = pysd.read_vensim( - "test-models/tests/incomplete_equations/test_incomplete_model.mdl" - ) + model = pysd.read_vensim(os.path.join( + _root, + "test-models/tests/incomplete_equations/" + + "test_incomplete_model.mdl" + )) self.assertTrue(any([warn.category == SyntaxWarning for warn in w])) with catch_warnings(record=True) as w: @@ -1701,8 +1891,10 @@ def test_multiple_load(self): """ import pysd - model_1 = pysd.read_vensim("test-models/samples/teacup/teacup.mdl") - model_2 = pysd.read_vensim("test-models/samples/SIR/SIR.mdl") + model_1 = pysd.read_vensim(os.path.join( + _root, "test-models/samples/teacup/teacup.mdl")) + model_2 = pysd.read_vensim(os.path.join( + _root, "test-models/samples/SIR/SIR.mdl")) self.assertNotIn("teacup_temperature", dir(model_2.components)) self.assertIn("susceptible", dir(model_2.components)) @@ -1722,8 +1914,10 @@ def test_no_crosstalk(self): # Todo: this test could be made more comprehensive import pysd - model_1 = pysd.read_vensim("test-models/samples/teacup/teacup.mdl") - model_2 = pysd.read_vensim("test-models/samples/SIR/SIR.mdl") + model_1 = pysd.read_vensim(os.path.join( + _root, "test-models/samples/teacup/teacup.mdl")) + model_2 = pysd.read_vensim(os.path.join( + _root, "test-models/samples/SIR/SIR.mdl")) model_1.components.initial_time = lambda: 10 self.assertNotEqual(model_2.components.initial_time, 10) @@ -1754,7 +1948,8 @@ def test_circular_reference(self): with self.assertRaises(ValueError) as err: pysd.load( - "more-tests/circular_reference/test_circular_reference.py") + more_tests + + "/circular_reference/test_circular_reference.py") self.assertIn("_integ_integ", str(err.exception)) self.assertIn("_delay_delay", str(err.exception)) @@ -1789,8 +1984,9 @@ class TestMultiRun(unittest.TestCase): def test_delay_reinitializes(self): import pysd - model = pysd.read_vensim( - "../tests/test-models/tests/delays/test_delays.mdl") + model = pysd.read_vensim(os.path.join( + _root, + "test-models/tests/delays/test_delays.mdl")) res1 = model.run() res2 = model.run() self.assertTrue(all(res1 == res2)) diff --git a/tests/unit_test_table2py.py b/tests/unit_test_table2py.py index 36424022..bbe33e7c 100644 --- a/tests/unit_test_table2py.py +++ b/tests/unit_test_table2py.py @@ -1,11 +1,15 @@ +import os import unittest import pandas as pd +_root = os.path.dirname(__file__) + class TestReadTabular(unittest.TestCase): def test_read_tab_file(self): import pysd - model = pysd.read_tabular('test-models/samples/teacup/teacup_mdl.tab') + model = pysd.read_tabular(os.path.join( + _root, 'test-models/samples/teacup/teacup_mdl.tab')) result = model.run() self.assertTrue(isinstance(result, pd.DataFrame)) # return a dataframe self.assertTrue('Teacup Temperature' in result.columns.values) # contains correct column diff --git a/tests/unit_test_vensim2py.py b/tests/unit_test_vensim2py.py index ecb8a368..a969ffe6 100644 --- a/tests/unit_test_vensim2py.py +++ b/tests/unit_test_vensim2py.py @@ -557,9 +557,7 @@ def test_subscript_float_initialization(self): # hoewever eval is not detecting _subscript_dict variable self.assertEqual( string, - "xr.DataArray(3.32,{dim: " - + "_subscript_dict[dim] for dim in " - + "['Dim1']},['Dim1'])", + "xr.DataArray(3.32,{'Dim1': _subscript_dict['Dim1']},['Dim1'])", ) a = xr.DataArray( 3.32, {dim: _subscript_dict[dim] for dim in ["Dim1"]}, ["Dim1"] @@ -580,11 +578,10 @@ def test_subscript_1d_constant(self): string = element[0]["py_expr"] # TODO we should use a = eval(string) # hoewever eval is not detecting _subscript_dict variable - self.assertTrue( + self.assertEqual( string, - "xr.DataArray([1.,2.,3.]," - + "{dim: _subscript_dict[dim]" - + " for dim in ['Dim1']}, ['Dim1'])", + "xr.DataArray([1.,2.,3.],{'Dim1': _subscript_dict['Dim1']}," + "['Dim1'])", ) a = xr.DataArray([1.0, 2.0, 3.0], {dim: _subscript_dict[dim] for dim in ["Dim1"]}, @@ -787,7 +784,7 @@ def test_parse_sketch_line(self): for num, line in enumerate(lines): res = parse_sketch_line(line.strip(), namespace) self.assertEqual(res["variable_name"], expected_var[num]) - self.assertEqual(res["module_name"], expected_mod[num]) + self.assertEqual(res["view_name"], expected_mod[num]) class TestParse_private_functions(unittest.TestCase): diff --git a/tests/unit_test_xmile2py.py b/tests/unit_test_xmile2py.py index bf5c5bdd..78c8e1ea 100644 --- a/tests/unit_test_xmile2py.py +++ b/tests/unit_test_xmile2py.py @@ -5,8 +5,8 @@ from pysd.py_backend.xmile.xmile2py import translate_xmile - -TARGET_STMX_FILE = 'test-models/tests/game/test_game.stmx' +_root = os.path.dirname(__file__) +TARGET_STMX_FILE = os.path.join(_root, "test-models/tests/game/test_game.stmx") class TestXmileConversion(unittest.TestCase):