├── .github ├── dependabot.yml ├── pull_request_template.md └── workflows │ ├── build-docs.yml │ ├── ci.yml │ ├── link-check.yml │ └── python-publish.yml ├── .gitignore ├── .gitmodules ├── .readthedocs.yaml ├── LICENSE ├── MANIFEST.in ├── README.md ├── citation.cff ├── docs ├── Makefile ├── PYSD-Purpose-and-Capabilities.pdf ├── PySD-Intro-Paper-Preprint.pdf ├── about.rst ├── advanced_usage.rst ├── command_line_usage.rst ├── complement.rst ├── conf.py ├── development │ ├── adding_functions.rst │ ├── development_index.rst │ ├── guidelines.rst │ ├── pathway.rst │ └── pysd_architecture_views │ │ ├── 4+1view_model.rst │ │ └── images │ │ ├── development-view │ │ ├── packages.png │ │ └── relationship-modules.png │ │ ├── logical-view │ │ ├── builder-module.png │ │ ├── decorators-module.png │ │ ├── external-simply.png │ │ ├── external1.png │ │ ├── external2.png │ │ ├── functions-simply.png │ │ ├── functions1.png │ │ ├── functions2.png │ │ ├── grammar1.png │ │ ├── grammar2.png │ │ ├── main-modules.png │ │ ├── utils-module.png │ │ └── vensim2py-simply.png │ │ └── process-view │ │ ├── divide-sentences2.png │ │ ├── macro-list3.png │ │ ├── main1.png │ │ ├── namespace5.png │ │ ├── parse-components6.png │ │ └── translate-section4.png ├── generate_tables.py ├── getting_started.rst ├── images │ ├── PySD_Logo.ico │ ├── PySD_Logo.png │ ├── PySD_Logo.svg │ ├── PySD_Logo_letters.png │ ├── PySD_Logo_letters.svg │ ├── Teacup.png │ ├── Teacup_Cooling.png │ ├── Vensim_file.svg │ └── abstract_model.png ├── index.rst ├── installation.rst ├── python_api │ ├── functions.rst │ ├── model_class.rst │ ├── model_loading.rst │ └── python_api_index.rst ├── readme.md ├── reporting_bugs.rst ├── requirements.txt ├── structure │ ├── abstract_model.rst │ ├── python_builder.rst │ ├── structure_index.rst │ ├── vensim_translation.rst │ └── xmile_translation.rst ├── tables │ ├── arithmetic.tab │ ├── binary.tab │ ├── delay_functions.tab │ ├── functions.tab │ ├── get_functions.tab │ └── unary.tab ├── tools.rst └── whats_new.rst ├── paper ├── CLD_example.pdf ├── CLD_example.svg ├── abstract_model.pdf ├── abstract_model.svg ├── paper.bib └── paper.md ├── pysd ├── __init__.py ├── __main__.py ├── _version.py ├── builders │ ├── __init__.py │ └── python │ │ ├── __init__.py │ │ ├── imports.py │ │ ├── namespace.py │ │ ├── python_expressions_builder.py │ │ ├── python_functions.py │ │ ├── python_model_builder.py │ │ └── subscripts.py ├── cli │ ├── __init__.py │ ├── main.py │ └── parser.py ├── py_backend │ ├── __init__.py │ ├── allocation.py │ ├── cache.py │ ├── components.py │ ├── data.py │ ├── external.py │ ├── functions.py │ ├── lookups.py │ ├── model.py │ ├── output.py │ ├── statefuls.py │ └── utils.py ├── pysd.py ├── tools │ ├── __init__.py │ ├── benchmarking.py │ └── ncfiles.py └── translators │ ├── __init__.py │ ├── structures │ ├── __init__.py │ ├── abstract_expressions.py │ └── abstract_model.py │ ├── vensim │ ├── __init__.py │ ├── parsing_grammars │ │ ├── common_grammar.peg │ │ ├── components.peg │ │ ├── element_object.peg │ │ ├── file_sections.peg │ │ ├── lookups.peg │ │ ├── section_elements.peg │ │ └── sketch.peg │ ├── vensim_element.py │ ├── vensim_file.py │ ├── vensim_section.py │ ├── vensim_structures.py │ └── vensim_utils.py │ └── xmile │ ├── __init__.py │ ├── parsing_grammars │ └── equations.peg │ ├── xmile_element.py │ ├── xmile_file.py │ ├── xmile_section.py │ ├── xmile_structures.py │ └── xmile_utils.py ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── Makefile ├── README.md ├── __init__.py ├── conftest.py ├── data ├── expected_data.py ├── input.csv ├── input.tab ├── input.txt ├── input.xlsx ├── input2.csv ├── out_teacup.csv ├── out_teacup_modified.csv ├── out_teacup_no_head.csv └── out_teacup_transposed.csv ├── integration_test_factory.py ├── more-tests ├── circular_reference │ └── test_circular_reference.py ├── constant_pipeline │ └── test_constant_pipeline.mdl ├── data_model │ ├── all_data.nc │ ├── data1.nc │ ├── data1.tab │ ├── data2.tab │ ├── data3.tab │ └── test_data_model.mdl ├── initialization_order │ └── test_initialization_order.py ├── not_implemented_and_incomplete │ └── test_not_implemented_and_incomplete.mdl ├── not_vensim │ ├── output.csv │ └── test_not_vensim.txt ├── old_version │ └── test_old_version.py ├── random │ ├── test_random.mdl │ └── test_random_scale0.mdl ├── split_model │ ├── input.xlsx │ ├── test_split_model.mdl │ ├── test_split_model_sub_subviews.mdl │ ├── test_split_model_subviews.mdl │ └── test_split_multiviews_vars.mdl ├── split_model_vensim_8_2_1 │ └── test_split_model_vensim_8_2_1.mdl ├── split_model_with_macro │ └── test_split_model_with_macro.mdl ├── stepper_cache │ └── stepper_cache.mdl ├── subscript_individually_defined_stocks2 │ └── test_subscript_individually_defined_stocks2.mdl └── type_error │ └── test_type_error.py ├── pytest.ini ├── pytest_builders └── pytest_python.py ├── pytest_integration ├── pytest_integration_euler.py ├── pytest_integration_vensim_pathway.py └── pytest_integration_xmile_pathway.py ├── pytest_pysd ├── pytest_allocation.py ├── pytest_cli.py ├── pytest_errors.py ├── pytest_functions.py ├── pytest_model_attributes.py ├── pytest_output.py ├── pytest_pysd.py ├── pytest_random.py ├── pytest_select_submodel.py ├── pytest_serialize_externals.py └── pytest_utils.py ├── pytest_tools ├── pytest_benchmarking.py └── pytest_ncfile.py ├── pytest_translators ├── pytest_split_views.py └── pytest_vensim.py ├── pytest_types ├── components │ └── pytest_components.py ├── data │ ├── pytest_columns.py │ ├── pytest_data.py │ └── pytest_data_with_model.py ├── external │ └── pytest_external.py ├── lookup │ └── pytest_lookups.py └── statefuls │ └── pytest_statefuls.py └── requirements.txt /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | # Check for updates to GitHub Actions every week 8 | interval: "weekly" 9 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | (add the description of your changes here) 4 | 5 | ## Related issues 6 | 7 | (add any related issues here) 8 | 9 | ## Type of change 10 | 11 | (please delete options that are not relevant) 12 | 13 | - Bug fix (non-breaking change which fixes an issue) 14 | - New feature (non-breaking change which adds functionality) 15 | - Breaking change (fix or feature that would cause existing functionality to not work as expected) 16 | - Other (specify) 17 | 18 | ## PR verification (to be filled by reviewers) 19 | 20 | - [ ] The code follows the [PEP 8 style](https://peps.python.org/pep-0008/) 21 | - [ ] The new code has been tested properly for all the possible cases 22 | - [ ] The overall coverage has not dropped and other features have not been broken. 23 | - [ ] If new features have been added, they have been properly documented 24 | - [ ] *docs/whats_new.rst* has been updated 25 | - [ ] PySD version has been updated 26 | -------------------------------------------------------------------------------- /.github/workflows/build-docs.yml: -------------------------------------------------------------------------------- 1 | 2 | # Build html docs. Any warning building the docs will produce an error. 3 | 4 | name: Build docs 5 | 6 | on: [push, pull_request] 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | submodules: recursive 16 | - name: Set up Python 17 | uses: actions/setup-python@v5 18 | with: 19 | python-version: '3.12' 20 | - name: Install dependencies 21 | run: | 22 | pip install . 23 | pip install -r docs/requirements.txt 24 | - name: Test build html 25 | run: | 26 | cd docs 27 | make html SPHINXOPTS="-W" 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | # Run CI tests with pytest and update coverage to coveralls 2 | 3 | name: CI 4 | 5 | on: 6 | push: 7 | pull_request: 8 | workflow_dispatch: 9 | schedule: 10 | - cron: '0 6 * * 1' 11 | 12 | jobs: 13 | test: 14 | runs-on: ${{ matrix.os }} 15 | strategy: 16 | matrix: 17 | os: [ubuntu-latest, windows-latest] 18 | python-version: ['3.9', '3.12'] 19 | 20 | steps: 21 | - uses: actions/checkout@v4 22 | with: 23 | submodules: recursive 24 | - name: Set up Python ${{ matrix.python-version }} 25 | uses: actions/setup-python@v5 26 | with: 27 | python-version: ${{ matrix.python-version }} 28 | - name: Install dependencies 29 | run: | 30 | pip install -U pip 31 | pip install -U wheel 32 | pip install --prefer-binary -r tests/requirements.txt 33 | pip install --prefer-binary -e . 34 | - name: Test 35 | run: | 36 | pytest tests/ -n 2 37 | if: ${{ matrix.python-version != 3.9 || matrix.os != 'ubuntu-latest' }} 38 | - name: Test and coverage 39 | run: | 40 | pytest tests/ --cov=pysd -n 2 41 | coverage xml 42 | if: ${{ matrix.python-version == 3.9 && matrix.os == 'ubuntu-latest' }} 43 | - name: Coveralls 44 | uses: coverallsapp/github-action@v2 45 | with: 46 | file: coverage.xml 47 | if: ${{ matrix.python-version == 3.9 && matrix.os == 'ubuntu-latest' }} 48 | env: 49 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 50 | 51 | -------------------------------------------------------------------------------- /.github/workflows/link-check.yml: -------------------------------------------------------------------------------- 1 | name: Link check 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | linkChecker: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v4 12 | 13 | - name: Link Checker 14 | uses: lycheeverse/lychee-action@v2.4.0 15 | with: 16 | fail: true 17 | env: 18 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 19 | -------------------------------------------------------------------------------- /.github/workflows/python-publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow will upload a Python Package using Twine when a release is created 2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries 3 | 4 | name: Upload Python Package 5 | 6 | on: 7 | release: 8 | types: [created] 9 | 10 | jobs: 11 | deploy: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v4 17 | - name: Set up Python 18 | uses: actions/setup-python@v5 19 | with: 20 | python-version: '3.9' 21 | - name: Install dependencies 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install setuptools wheel twine 25 | - name: Build and publish 26 | env: 27 | TWINE_USERNAME: __token__ 28 | TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }} 29 | TWINE_REPOSITORY: pypi 30 | run: | 31 | python setup.py sdist bdist_wheel 32 | twine upload --verbose dist/* 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .ipynb_checkpoints/ 3 | build/ 4 | *.vdf 5 | *.2mdl 6 | *.swp 7 | pysd.egg* 8 | .vscode/ 9 | tests/.coverage 10 | tests/cover/ 11 | tests/htmlcov/ 12 | .idea/* 13 | docs/_build/* 14 | docs/tables/*.csv 15 | tests/.coverage* 16 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "tests/test-models"] 2 | path = tests/test-models 3 | url = https://github.com/SDXorg/test-models 4 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | # Read the Docs configuration file 3 | # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details 4 | 5 | # Required 6 | version: 2 7 | 8 | # Build documentation in the docs/ directory with Sphinx 9 | sphinx: 10 | configuration: docs/conf.py 11 | 12 | build: 13 | os: "ubuntu-22.04" 14 | tools: 15 | python: "3.12" 16 | 17 | python: 18 | install: 19 | - requirements: docs/requirements.txt 20 | - method: pip 21 | path: . 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2013-2022 PySD contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include requirements.txt 2 | include README.md 3 | include LICENSE 4 | include docs/images/PySD_Logo* 5 | graft pysd/translators/*/parsing_grammars 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PySD 2 | 3 | [![Maintained](https://img.shields.io/badge/Maintained-Yes-brightgreen.svg)](https://github.com/SDXorg/pysd/pulse) 4 | [![Coverage Status](https://coveralls.io/repos/github/SDXorg/pysd/badge.svg?branch=master)](https://coveralls.io/github/SDXorg/pysd?branch=master) 5 | [![Anaconda-Server Badge](https://anaconda.org/conda-forge/pysd/badges/version.svg)](https://anaconda.org/conda-forge/pysd) 6 | [![PyPI version](https://badge.fury.io/py/pysd.svg)](https://badge.fury.io/py/pysd) 7 | [![PyPI status](https://img.shields.io/pypi/status/pysd.svg)](https://pypi.python.org/pypi/pysd/) 8 | [![Py version](https://img.shields.io/pypi/pyversions/pysd.svg)](https://pypi.python.org/pypi/pysd/) 9 | [![JOSS](https://joss.theoj.org/papers/10.21105/joss.04329/status.svg)](https://doi.org/10.21105/joss.04329) 10 | [![Contributions](https://img.shields.io/badge/contributions-welcome-blue.svg)](https://pysd.readthedocs.io/en/latest/development/development_index.html) 11 | [![Docs](https://readthedocs.org/projects/pysd/badge/?version=latest)](https://pysd.readthedocs.io/en/latest/?badge=latest) 12 | 13 | ![PySD Logo](https://raw.githubusercontent.com/SDXorg/pysd/5cc4fe5dc65e6b5140a00e87a1be9d261570ee8d/docs/images/PySD_Logo_letters.svg?style=centerme) 14 | 15 | This project is a library for running [System Dynamics](http://en.wikipedia.org/wiki/System_dynamics) (SD) models in Python, with the purpose of improving integration of *Big Data* and *Machine Learning* into the SD workflow. 16 | 17 | **The current version needs to run at least Python 3.9.** 18 | 19 | ## Resources 20 | 21 | See the [project documentation](http://pysd.readthedocs.org/) for information about: 22 | 23 | - [Installation](http://pysd.readthedocs.org/en/latest/installation.html) 24 | - [Getting Started](http://pysd.readthedocs.org/en/latest/getting_started.html) 25 | - [Release Notes](http://pysd.readthedocs.org/en/latest/whats_new.html) 26 | - [Citing PySD](https://pysd.readthedocs.io/en/latest/#citing) 27 | 28 | For standard methods for data analysis with SD models, see the [PySD Cookbook](https://github.com/SDXorg/PySD-Cookbook), containing (for example): 29 | 30 | - [Model Fitting](http://nbviewer.ipython.org/github/SDXorg/PySD-Cookbook/blob/master/source/analyses/fitting/Fitting_with_Optimization.ipynb) 31 | - [Surrogating model components with machine learning regressions](http://nbviewer.ipython.org/github/SDXorg/PySD-Cookbook/blob/master/source/analyses/surrogating_functions/Surrogating_with_regression.ipynb) 32 | - [Multi-Scale geographic comparison of model predictions](http://nbviewer.ipython.org/github/SDXorg/PySD-Cookbook/blob/master/source/analyses/geo/Exploring_models_across_geographic_scales.ipynb) 33 | 34 | ## Why create a new SD simulation engine? 35 | 36 | There are a number of great SD programs out there ([Vensim](http://vensim.com/), [iThink](http://www.iseesystems.com/Softwares/Business/ithinkSoftware.aspx), [AnyLogic](http://www.anylogic.com/system-dynamics), [Insight Maker](http://insightmaker.com/), and [others](https://en.wikipedia.org/wiki/Comparison_of_system_dynamics_software)). In order not to waste our effort, or fall victim to the [Not-Invented-Here](http://en.wikipedia.org/wiki/Not_invented_here) fallacy, we should have a very good reason for starting a new project. 37 | 38 | That reason is this: There is a whole world of computational tools being developed in the larger data science community. **System dynamicists should directly use the tools that other people are building, instead of replicating their functionality in SD specific software.** The best way to do this is to bring specific SD functionality to the domain where those other tools are being developed. 39 | 40 | This approach allows SD modelers to take advantage of the most recent developments in data science, and focus our efforts on improving the part of the stack that is unique to System Dynamics modeling. 41 | 42 | ## Cloning this repository 43 | 44 | If you'd like to work with this repository directly, you'll need to use a recursive git checkout in order to properly load the test suite (sorry..) 45 | 46 | The command should be something like: 47 | 48 | ```shell 49 | git clone --recursive https://github.com/SDXorg/pysd.git 50 | ``` 51 | 52 | ## Extensions 53 | 54 | You can use PySD in [R](https://www.r-project.org/) via the [PySD2R](https://github.com/JimDuggan/pysd2r) package, also available on [CRAN](https://CRAN.R-project.org/package=pysd2r). 55 | 56 | ## Contributing 57 | 58 | PySD is currently a community-maintained project, any contribution is welcome. 59 | 60 | Many people have contributed to developing this project - by [submitting code](https://github.com/SDXorg/pysd/graphs/contributors), bug reports, and advice. Main historic changes in PySD are described in the [About PySD section](https://pysd.readthedocs.io/en/latest/about.html). The [Developer Documentation](https://pysd.readthedocs.io/en/latest/development/development_index.html) could help new developers. 61 | 62 | The code for this package is available at: https://github.com/SDXorg/pysd 63 | 64 | Join our slack channel in [sd-tools-and-methodology-community](https://slofile.com/slack/sdtoolsandmet-slj3251). 65 | -------------------------------------------------------------------------------- /citation.cff: -------------------------------------------------------------------------------- 1 | cff-version: 1.2.0 2 | title: 'PySD: System Dynamics Modeling in Python' 3 | message: 'If you use this software, please cite it as below.' 4 | type: software 5 | authors: 6 | - given-names: Eneko 7 | family-names: Martin-Martinez 8 | email: eneko.martin.martinez@gmail.com 9 | affiliation: >- 10 | Centre for Ecological Research and Forestry 11 | Applications (CREAF) 12 | orcid: 'https://orcid.org/0000-0002-9213-7818' 13 | - given-names: Roger 14 | family-names: Samsó 15 | affiliation: >- 16 | Centre for Ecological Research and Forestry 17 | Applications (CREAF) 18 | orcid: 'https://orcid.org/0000-0003-0348-3047' 19 | - family-names: Houghton 20 | given-names: James 21 | affiliation: >- 22 | Computational Social Science Lab, University of 23 | Pennsylvania 24 | email: james.p.houghton@gmail.com 25 | - given-names: Jordi 26 | family-names: Solé 27 | orcid: 'https://orcid.org/0000-0002-2371-1652' 28 | affiliation: >- 29 | Centre for Ecological Research and Forestry 30 | Applications (CREAF); 31 | Department of Earth and Ocean Dynamics, 32 | Faculty of Earth Sciences, 33 | University of Barcelona 34 | version: 3.0.1 35 | doi: 10.5281/zenodo.6583528 36 | date-released: 2022-05-26 37 | journal: "Journal of Open Source Software" 38 | keywords: 39 | - System Dynamics 40 | - Vensim 41 | - XMILE 42 | - Python 43 | license: MIT 44 | repository: "https://github.com/JamesPHoughton/pysd" 45 | url: "https://github.com/JamesPHoughton/pysd" 46 | year: 2022 47 | month: 6 48 | references: 49 | - type: conference-paper 50 | scope: Cite alos the PySD introductory paper (conference-paper) 51 | authors: 52 | - family-names: Houghton 53 | given-names: James 54 | - family-names: Siegel 55 | given-names: Michael 56 | title: "Advanced data analytics for system dynamics models using PySD." 57 | year: 2015 58 | publisher: "System Dynamics Society" 59 | journal: "33rd International Conference of the System Dynamics Society 2015" 60 | isbn: 9781510815056 61 | url: "https://github.com/JamesPHoughton/pysd/blob/master/docs/PySD%20Intro%20Paper%20Preprint.pdf" 62 | 63 | preferred-citation: 64 | type: article 65 | authors: 66 | - given-names: Eneko 67 | family-names: Martin-Martinez 68 | email: eneko.martin.martinez@gmail.com 69 | affiliation: >- 70 | Centre for Ecological Research and Forestry 71 | Applications (CREAF) 72 | orcid: 'https://orcid.org/0000-0002-9213-7818' 73 | - given-names: Roger 74 | family-names: Samsó 75 | affiliation: >- 76 | Centre for Ecological Research and Forestry 77 | Applications (CREAF) 78 | orcid: 'https://orcid.org/0000-0003-0348-3047' 79 | - family-names: Houghton 80 | given-names: James 81 | affiliation: >- 82 | Computational Social Science Lab, University of 83 | Pennsylvania 84 | email: james.p.houghton@gmail.com 85 | - given-names: Jordi 86 | family-names: Solé 87 | orcid: 'https://orcid.org/0000-0002-2371-1652' 88 | affiliation: >- 89 | Centre for Ecological Research and Forestry 90 | Applications (CREAF); 91 | Department of Earth and Ocean Dynamics, 92 | Faculty of Earth Sciences, 93 | University of Barcelona 94 | doi: 10.5281/zenodo.6583528 95 | title: 'PySD: System Dynamics Modeling in Python' 96 | journal: "Journal of Open Source Software" 97 | year: 2022 98 | url: "https://github.com/JamesPHoughton/pysd" 99 | -------------------------------------------------------------------------------- /docs/PYSD-Purpose-and-Capabilities.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/PYSD-Purpose-and-Capabilities.pdf -------------------------------------------------------------------------------- /docs/PySD-Intro-Paper-Preprint.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/PySD-Intro-Paper-Preprint.pdf -------------------------------------------------------------------------------- /docs/complement.rst: -------------------------------------------------------------------------------- 1 | Complementary Projects 2 | ====================== 3 | 4 | The most valuable component for better integrating models with *basically anything else* is a standard language for communicating the structure of those models. That language is `XMILE `_. The draft specifications for this have been finalized and the standard should be approved in the next few months. 5 | 6 | A Python library for analyzing system dynamics models called the `Exploratory Modeling and Analysis (EMA) Workbench `_ developed by Erik Pruyt and Jan Kwakkel at TU Delft. This package implements a variety of analysis methods that are unique to dynamic models, and could work very tightly with PySD. 7 | 8 | An web-based app called `Simlin `_ created by Bobby Powers exists as a standalone SD engine. Allows building models and exporting them to XMILE output. 9 | 10 | The `Behavior Analysis and Testing Software (BATS) `_ developed by Gönenç Yücel includes a really neat method for categorizing behavior modes and exploring parameter space to determine the boundaries between them. 11 | 12 | The `SDQC library `_ developed by Eneko Martin Martinez may be used to check the quality of the data imported by Vensim models from spreadsheet files. 13 | 14 | The `excels2vensim library `_, also developed by Eneko Martin Martinez, aims to simplify the incorporation of equations from external data into Vensim. -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PySD documentation build configuration file, created by 4 | # sphinx-quickstart on Thu Jun 18 10:02:28 2015. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import os 16 | import sys 17 | 18 | import mock 19 | import sphinx_rtd_theme 20 | 21 | sys.path.insert(0, os.path.abspath('../')) 22 | 23 | from docs.generate_tables import generate_tables 24 | 25 | 26 | # Generate tables used for documentation 27 | generate_tables() 28 | 29 | MOCK_MODULES = [ 30 | 'numpy', 'scipy', 'matplotlib', 'matplotlib.pyplot', 'scipy.stats', 31 | 'scipy.integrate', 'pandas', 'parsimonious', 'parsimonious.nodes', 32 | 'xarray', 'autopep8', 'scipy.linalg', 'parsimonious.exceptions', 33 | 'scipy.stats.distributions', 'progressbar', 'black', 'scipy.optimize' 34 | ] 35 | 36 | for mod_name in MOCK_MODULES: 37 | sys.modules[mod_name] = mock.Mock() 38 | 39 | 40 | html_theme = "sphinx_rtd_theme" 41 | 42 | 43 | # -- General configuration ------------------------------------------------ 44 | 45 | # Add any Sphinx extension module names here, as strings. They can be 46 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 47 | # ones. 48 | extensions = [ 49 | 'sphinx.ext.autodoc', 50 | 'sphinx.ext.todo', 51 | 'sphinx.ext.coverage', 52 | 'sphinx.ext.mathjax', 53 | 'sphinx.ext.viewcode', 54 | 'sphinx.ext.napoleon', 55 | 'sphinx.ext.intersphinx', 56 | 'sphinx.ext.extlinks' 57 | ] 58 | 59 | extlinks = { 60 | "issue": ("https://github.com/SDXorg/pysd/issues/%s", "issue #%s"), 61 | "pull": ("https://github.com/SDXorg/pysd/pull/%s", "PR #%s"), 62 | } 63 | 64 | # Add any paths that contain templates here, relative to this directory. 65 | templates_path = ['_templates'] 66 | 67 | # The suffix(es) of source filenames. 68 | # You can specify multiple suffix as a list of string: 69 | source_suffix = '.rst' 70 | 71 | # The master toctree document. 72 | master_doc = 'index' 73 | 74 | # General information about the project. 75 | project = 'PySD' 76 | copyright = '2022, PySD contributors' 77 | author = 'PySD contributors' 78 | 79 | # The version info for the project you're documenting, acts as replacement for 80 | # |version| and |release|, also used in various other places throughout the 81 | # built documents. 82 | # 83 | 84 | # The short X.Y version. 85 | __version__ = "x.x.x" 86 | exec(open('../pysd/_version.py').read()) 87 | version = '.'.join(__version__.split('.')[:-1]) 88 | 89 | # The full version, including alpha/beta/rc tags. 90 | release = __version__ 91 | 92 | # The language for content autogenerated by Sphinx. Refer to documentation 93 | # for a list of supported languages. 94 | # 95 | # This is also used if you do content translation via gettext catalogs. 96 | # Usually you set "language" from the command line for these cases. 97 | language = 'en' 98 | 99 | # List of patterns, relative to source directory, that match files and 100 | # directories to ignore when looking for source files. 101 | exclude_patterns = ['_build'] 102 | 103 | # The name of the Pygments (syntax highlighting) style to use. 104 | pygments_style = 'sphinx' 105 | 106 | # If true, `todo` and `todoList` produce output, else they produce nothing. 107 | todo_include_todos = True 108 | 109 | 110 | # -- Options for HTML output ---------------------------------------------- 111 | 112 | # Output file base name for HTML help builder. 113 | htmlhelp_basename = 'PySDdoc' 114 | html_logo = "images/PySD_Logo_letters.png" 115 | html_favicon = "images/PySD_Logo.ico" 116 | html_theme_options = { 117 | 'logo_only': True, 118 | } 119 | 120 | 121 | # -- Options for LaTeX output --------------------------------------------- 122 | 123 | latex_elements = {} 124 | 125 | # Grouping the document tree into LaTeX files. List of tuples 126 | # (source start file, target name, title, 127 | # author, documentclass [howto, manual, or own class]). 128 | latex_documents = [ 129 | (master_doc, 'PySD.tex', 'PySD Documentation', 130 | 'PySD contributors', 'manual'), 131 | ] 132 | 133 | # -- Options for manual page output --------------------------------------- 134 | 135 | # One entry per manual page. List of tuples 136 | # (source start file, name, description, authors, manual section). 137 | man_pages = [ 138 | (master_doc, 'pysd', 'PySD Documentation', 139 | [author], 1) 140 | ] 141 | 142 | # -- Options for Texinfo output ------------------------------------------- 143 | 144 | # Grouping the document tree into Texinfo files. List of tuples 145 | # (source start file, target name, title, author, 146 | # dir menu entry, description, category) 147 | texinfo_documents = [ 148 | (master_doc, 'PySD', 'PySD Documentation', 149 | author, 'PySD', 'One line description of project.', 150 | 'Miscellaneous'), 151 | ] 152 | 153 | intersphinx_mapping = { 154 | 'python': ('https://docs.python.org/3.9', None), 155 | 'pysdcookbook': ('http://pysd-cookbook.readthedocs.org/en/latest/', None), 156 | 'pandas': ('https://pandas.pydata.org/docs/', None), 157 | 'xarray': ('https://docs.xarray.dev/en/stable/', None), 158 | 'numpy': ('https://numpy.org/doc/stable/', None), 159 | 'scipy': ('https://docs.scipy.org/doc/scipy/', None), 160 | 'pytest': ('https://docs.pytest.org/en/8.0.x/', None), 161 | 'openpyxl': ('https://openpyxl.readthedocs.io/en/stable', None) 162 | } 163 | 164 | # -- Options for autodoc -------------------------------------------------- 165 | autodoc_member_order = 'bysource' 166 | -------------------------------------------------------------------------------- /docs/development/development_index.rst: -------------------------------------------------------------------------------- 1 | Developer Documentation 2 | ======================= 3 | 4 | .. toctree:: 5 | :hidden: 6 | 7 | guidelines 8 | pathway 9 | adding_functions 10 | pysd_architecture_views/4+1view_model 11 | 12 | In order to contribute to PySD check the :doc:`guidelines` and the :doc:`pathway`. 13 | You also will find helpful the :doc:`Structure of the PySD library <../../structure/structure_index>` to understand better how it works. 14 | If you want to add a missing function to the PySD Python builder you may find useful the example in :doc:`adding_functions`. 15 | 16 | 17 | -------------------------------------------------------------------------------- /docs/development/guidelines.rst: -------------------------------------------------------------------------------- 1 | Development Guidelines 2 | ====================== 3 | 4 | 5 | If you are interested in helping to develop PySD, the :doc:`pathway` lists areas that are ripe 6 | for contribution. 7 | 8 | To get started, you can fork the repository and make contributions to your own version. 9 | When you are happy with your edits, submit a pull request to the main branch. 10 | 11 | .. note:: 12 | In order to open a pull request, the new features and changes should be thoroughly tested. 13 | To do so, unit tests of new features or translated functions should be added, please check the Development Tools section below. When opening a pull request all tests are run and the coverage and pep8 style are checked. 14 | 15 | Development Tools 16 | ----------------- 17 | There are a number of tools that you might find helpful in development: 18 | 19 | Test Suite 20 | ^^^^^^^^^^ 21 | PySD uses the common model test suite found `on github `_ 22 | which are run using `pytest_integration_test_vensim_pathway.py` and `pytest_integration_test_xmile_pathway.py`. 23 | PySD also has own tests for internal functionality, `pytest_*.py` files 24 | of the `/tests/` directory. 25 | 26 | In order to run all the tests :py:mod:`pytest` should be used. 27 | A `Makefile` is given to run easier the tests with :py:mod:`pytest`, check 28 | `tests/README `_ 29 | for more information. 30 | 31 | These tests run quickly and should be executed when any changes are made to ensure 32 | that current functionality remains intact. If any new functionality is added or a 33 | bug is corrected, the tests should be updated with new models in test suite or 34 | complementary tests in the corresponding `pytest_*.py` file. 35 | 36 | .. note:: 37 | If your changes correct some existing bug related to the translation or running 38 | of a Vensim (or Xmile) model. You should add a new test in the `test suite repo `_ reproducing the solved bug and add the necessary lines in `pytest_integration/pytest_integration_test_vensim_pathway.py` (or `pytest_integration/pytest_integration_test_xmile_pathway.py`) to run the new test. Then, it is encouraged to add also unit test in `pytest_translators` reproducing the translation of the new function and test of the workflow in 39 | `pytest_types/functions/pytest_functions.py` (or `pytest_types/statefuls/pytest_statefuls.py`). 40 | 41 | Speed Tests 42 | ^^^^^^^^^^^ 43 | Speed tests may be developed in the future. Any contribution is welcome. 44 | 45 | 46 | Profiler 47 | ^^^^^^^^ 48 | Profiling the code can help to identify bottlenecks in operation. To understand how changes to the 49 | code influence its speed, we should construct a profiling test that executes the PySD components in 50 | question. 51 | 52 | The profiler depends on :py:mod:`cProfile` and `cprofilev `_ 53 | 54 | 55 | Python Linter 56 | ^^^^^^^^^^^^^ 57 | `Pylint `_ is a module that checks that your code meets proper Python 58 | coding practices. It is helpful for making sure that the code will be easy for other people to read, 59 | and also is good fast feedback for improving your coding practice. The lint checker can be run for 60 | the entire packages, and for individual Python modules or classes. It should be run at a local level 61 | (ie, on specific files) whenever changes are made, and globally before the package is committed. 62 | It doesn't need to be perfect, but we should aspire always to move in a positive direction.' 63 | 64 | 65 | PySD Design Philosophy 66 | ---------------------- 67 | Understanding that a focussed project is both more robust and maintainable, PySD adheres to the 68 | following philosophy: 69 | 70 | 71 | * Do as little as possible. 72 | 73 | * Anything that is not endemic to System Dynamics (such as plotting, integration, fitting, etc) 74 | should either be implemented using external tools, or omitted. 75 | * Stick to SD. Let other disciplines (ABM, Discrete Event Simulation, etc) create their own tools. 76 | * Use external model creation tools. 77 | 78 | * Use the core language of System Dynamics. 79 | 80 | * Limit implementation to the basic XMILE standard. 81 | * Resist the urge to include everything that shows up in all vendors' tools. 82 | 83 | * Emphasize ease of use. Let SD practitioners who haven't used Python before understand the basics. 84 | * Take advantage of general Python constructions and best practices. 85 | * Develop and use strong testing and profiling components. Share your work early. Find bugs early. 86 | * Avoid firefighting or rushing to add features quickly. SD knows enough about short term thinking 87 | in software development to know where that path leads. 88 | -------------------------------------------------------------------------------- /docs/development/pathway.rst: -------------------------------------------------------------------------------- 1 | PySD Development Pathway 2 | ======================== 3 | 4 | High priority features, bugs, and other elements of active effort are listed on the `github issue 5 | tracker. `_ To get involved see :doc:`guidelines`. 6 | 7 | 8 | High Priority 9 | ------------- 10 | * Improve running speed using numpy.arrays instead of xarray.DataArrays 11 | * Adding unit and full tests for Xmile translation 12 | 13 | 14 | Medium Priority 15 | --------------- 16 | * Improve model execution speed using cython, theano, numba, or another package 17 | 18 | 19 | Low Priority 20 | ------------ 21 | * Import model component documentation in a way that enables doctest, to enable writing unit tests within the modeling environment 22 | * Handle simulating over timeseries 23 | * Implement run memoization to improve speed of larger analyses 24 | * Implement an interface for running the model over a range of conditions, build in intelligent parallelization. 25 | 26 | 27 | Not Planned 28 | ----------- 29 | * Model Construction 30 | * Outputting models to XMILE or other formats 31 | 32 | 33 | Ideas for Other Projects 34 | ------------------------ 35 | * SD-lint checker (units, modeling conventions, bounds/limits, etc) 36 | * Contribution to external Data Science tools to make them more appropriate for dynamic assistant 37 | 38 | 39 | Current Features 40 | ---------------- 41 | 42 | * Basic XMILE and Vensim parser 43 | * Established library structure and data formats 44 | * Simulation using existing Python integration tools 45 | * Integration with basic Python Data Science functionality 46 | * Run-at-a-time parameter modification 47 | * Time-variant exogenous inputs 48 | * Extended backends for storing parameters and output values 49 | * Demonstration of integration with Machine Learning/Monte Carlo/Statistical Methods 50 | * Python methods for programmatically manipulating SD model structure 51 | * Turn off and on 'traces' or records of the values of variables 52 | -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/development-view/packages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/development-view/packages.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/development-view/relationship-modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/development-view/relationship-modules.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/builder-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/builder-module.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/decorators-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/decorators-module.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/external-simply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/external-simply.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/external1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/external1.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/external2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/external2.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/functions-simply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/functions-simply.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/functions1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/functions1.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/functions2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/functions2.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/grammar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/grammar1.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/grammar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/grammar2.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/main-modules.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/main-modules.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/utils-module.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/utils-module.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/logical-view/vensim2py-simply.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/logical-view/vensim2py-simply.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/process-view/divide-sentences2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/process-view/divide-sentences2.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/process-view/macro-list3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/process-view/macro-list3.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/process-view/main1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/process-view/main1.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/process-view/namespace5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/process-view/namespace5.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/process-view/parse-components6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/process-view/parse-components6.png -------------------------------------------------------------------------------- /docs/development/pysd_architecture_views/images/process-view/translate-section4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/development/pysd_architecture_views/images/process-view/translate-section4.png -------------------------------------------------------------------------------- /docs/generate_tables.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | 3 | import pandas as pd 4 | 5 | 6 | def generate(table, columns, output): 7 | """Generate markdown table.""" 8 | # select only the given columns 9 | subtable = table[columns] 10 | # remove the rows where the first column is na 11 | subtable = subtable[~subtable[columns[0]].isna()] 12 | 13 | if all(subtable[columns[-1]].isna()): 14 | # if the comments columns (last) is all na, do not save it 15 | subtable = subtable[columns[:-1]] 16 | 17 | # Place an empty string where na values 18 | subtable.values[subtable.isna()] = "" 19 | 20 | if len(subtable.index) > 1: 21 | # Save values only if the table has rows 22 | print(f"Table generated: {output}") 23 | subtable.to_csv(output, index=None) 24 | 25 | 26 | def generate_tables(): 27 | """Generate markdown tables for documentation.""" 28 | 29 | tables_dir = Path(__file__).parent / "tables" 30 | 31 | # different tables to load 32 | tables = { 33 | "binary": tables_dir / "binary.tab", 34 | "unary": tables_dir / "unary.tab", 35 | "functions": tables_dir / "functions.tab", 36 | "delay_functions": tables_dir / "delay_functions.tab", 37 | "get_functions": tables_dir / "get_functions.tab" 38 | } 39 | 40 | # different combinations to generate 41 | contents = { 42 | "vensim": [ 43 | "Vensim", "Vensim example", "Abstract Syntax", "Vensim comments" 44 | ], 45 | "xmile": [ 46 | "Xmile", "Xmile example", "Abstract Syntax", "Xmile comments" 47 | ], 48 | "python": [ 49 | "Abstract Syntax", "Python Translation", "Python comments" 50 | ] 51 | } 52 | 53 | # load the tables 54 | tables = {key: pd.read_table(value) for key, value in tables.items()} 55 | 56 | # generate the tables 57 | for table, df in tables.items(): 58 | for language, content in contents.items(): 59 | generate( 60 | df, 61 | content, 62 | tables_dir / f"{table}_{language}.csv" 63 | ) 64 | 65 | # transform arithmetic order table 66 | file = tables_dir / "arithmetic.tab" 67 | pd.read_table(file).to_csv(file.with_suffix(".csv"), index=None) 68 | -------------------------------------------------------------------------------- /docs/images/PySD_Logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/images/PySD_Logo.ico -------------------------------------------------------------------------------- /docs/images/PySD_Logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/images/PySD_Logo.png -------------------------------------------------------------------------------- /docs/images/PySD_Logo_letters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/images/PySD_Logo_letters.png -------------------------------------------------------------------------------- /docs/images/Teacup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/images/Teacup.png -------------------------------------------------------------------------------- /docs/images/Teacup_Cooling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/images/Teacup_Cooling.png -------------------------------------------------------------------------------- /docs/images/abstract_model.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/docs/images/abstract_model.png -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | ============ 3 | 4 | Installing via pip 5 | ------------------ 6 | To install the PySD package from the Python package index use the pip command: 7 | 8 | .. code-block:: bash 9 | 10 | pip install pysd 11 | 12 | Installing with conda 13 | --------------------- 14 | To install PySD with conda, using the conda-forge channel, use the following command: 15 | 16 | .. code-block:: bash 17 | 18 | conda install -c conda-forge pysd 19 | 20 | Installing from source 21 | ---------------------- 22 | To install from source, clone the project with git: 23 | 24 | .. code-block:: bash 25 | 26 | git clone https://github.com/SDXorg/pysd.git 27 | 28 | or download the latest version from the project repository: https://github.com/SDXorg/pysd 29 | 30 | In the source directory use the command: 31 | 32 | .. code-block:: bash 33 | 34 | python setup.py install 35 | 36 | 37 | 38 | Required Dependencies 39 | --------------------- 40 | PySD requires **Python 3.9** or above. 41 | 42 | PySD builds on the core Python data analytics stack, and the following third party libraries: 43 | 44 | * Numpy >= 1.23 45 | * Scipy 46 | * Pandas (with Excel support: `pip install pandas[excel]`) 47 | * Parsimonious 48 | * xarray >= 2023.09 49 | * lxml 50 | * regex 51 | * chardet 52 | * black 53 | * openpyxl >= 3.1 54 | * progressbar2 55 | * portion 56 | 57 | These modules should build automatically if you are installing via `pip`. If you are building from 58 | source, or if pip fails to load them, they can be loaded with the same `pip` syntax as 59 | above. 60 | 61 | 62 | Optional Dependencies 63 | --------------------- 64 | In order to plot model outputs as shown in :doc:`Getting started <../getting_started>`: 65 | 66 | * Matplotlib 67 | 68 | To export data to netCDF (*.nc*) files or to serialize external objects: 69 | 70 | * netCDF4 71 | 72 | To export netCDF data to comma or tab separated files with parallel processing: 73 | 74 | * dask[array] 75 | * dask[diagnostics] 76 | * dask[distributed] 77 | 78 | 79 | These Python libraries bring additional data analytics capabilities to the analysis of SD models: 80 | 81 | * PyMC: a library for performing Markov chain Monte Carlo analysis 82 | * Scikit-learn: a library for performing machine learning in Python 83 | * NetworkX: a library for constructing networks 84 | * GeoPandas: a library for manipulating geographic data 85 | 86 | Additionally, the System Dynamics Translator utility developed by Robert Ward is useful for 87 | translating models from other system dynamics formats into the XMILE standard, to be read by PySD. 88 | 89 | These modules can be installed using pip with a syntax similar to the above. 90 | 91 | 92 | Additional Resources 93 | -------------------- 94 | The `PySD Cookbook `_ contains recipes that can help you get set up with PySD. 95 | 96 | -------------------------------------------------------------------------------- /docs/python_api/functions.rst: -------------------------------------------------------------------------------- 1 | Python functions and stateful objects 2 | ===================================== 3 | Functions 4 | --------- 5 | .. automodule:: pysd.py_backend.functions 6 | :members: 7 | 8 | Allocation functions 9 | -------------------- 10 | .. automodule:: pysd.py_backend.allocation 11 | :members: 12 | 13 | Statefuls 14 | --------- 15 | .. automodule:: pysd.py_backend.statefuls 16 | :members: -------------------------------------------------------------------------------- /docs/python_api/model_class.rst: -------------------------------------------------------------------------------- 1 | Python model class 2 | ================== 3 | 4 | Model class 5 | ----------- 6 | .. autoclass:: pysd.py_backend.model.Model 7 | :members: 8 | 9 | 10 | Macro class 11 | ----------- 12 | .. autoclass:: pysd.py_backend.model.Macro 13 | :members: 14 | :exclude-members: export 15 | -------------------------------------------------------------------------------- /docs/python_api/model_loading.rst: -------------------------------------------------------------------------------- 1 | Model loading 2 | ============= 3 | For loading a translated model with Python the function :py:func:`pysd.load` can be used: 4 | 5 | .. autofunction:: pysd.load 6 | 7 | To translate a load a model the :py:func:`pysd.read_vensim` and :py:func:`pysd.read_xmile` functions can be used: 8 | 9 | .. autofunction:: pysd.read_vensim 10 | 11 | .. autofunction:: pysd.read_xmile -------------------------------------------------------------------------------- /docs/python_api/python_api_index.rst: -------------------------------------------------------------------------------- 1 | Python API 2 | ========== 3 | 4 | .. toctree:: 5 | :hidden: 6 | 7 | model_loading 8 | model_class 9 | functions 10 | 11 | This sections describes the main functions and functionalities to translate 12 | models to Python and run them. If you need more detailed description about 13 | the translation and building process, please see the :doc:`../structure/structure_index` section. 14 | 15 | The model loading information can be found in :doc:`model_loading` and consists of the following functions: 16 | 17 | .. list-table:: Translating and loading functions 18 | :widths: 25 75 19 | :header-rows: 0 20 | 21 | * - :py:func:`pysd.read_vensim` 22 | - Translates a Vensim file to Python and returns a :py:class:`Model` object. 23 | * - :py:func:`pysd.read_xmile` 24 | - Translates a Xmile file to Python and returns a :py:class:`Model` object. 25 | * - :py:func:`pysd.load` 26 | - Loads a translated Python file and returns a :py:class:`Model` object. 27 | 28 | The Model and Macro classes information ad public methods and attributes can be found in :doc:`model_class`. 29 | 30 | .. list-table:: Translating and loading functions 31 | :widths: 25 75 32 | :header-rows: 0 33 | 34 | * - :py:class:`pysd.py_backend.model.Model` 35 | - Implements functionalities to load a translated model and interact with it. The :py:class:`Model` class inherits from :py:class:`Macro`, therefore, some public methods and properties are defined in the :py:class:`Macro` class. 36 | * - :py:class:`pysd.py_backend.model.Macro` 37 | - Implements functionalities to load a translated macro and interact with it. Most of its core methods are also use by :py:class:`Model` class. 38 | 39 | 40 | Provided functions and stateful classes to integrate python models are described in :doc:`functions`. 41 | -------------------------------------------------------------------------------- /docs/readme.md: -------------------------------------------------------------------------------- 1 | Docs use [sphinx doc](http://www.sphinx-doc.org/en/stable/) 2 | 3 | To build, in this directory run: 4 | ``` 5 | make clean 6 | make html 7 | ``` -------------------------------------------------------------------------------- /docs/reporting_bugs.rst: -------------------------------------------------------------------------------- 1 | Reporting bugs 2 | ============== 3 | 4 | Before reporting any bug, please make sure that you are using the latest version of PySD. You can get the version number by running `python -m pysd -v` on the command line. 5 | 6 | All bugs must be reported in the project's `issue tracker on github `_. 7 | 8 | .. note:: 9 | Not all the features and functions are implemented. If you are in trouble while translating or running a Vensim or Xmile model check the :ref:`Vensim supported functions ` or :ref:`Xmile supported functions ` and consider that when opening a new issue. 10 | 11 | Bugs during translation 12 | ----------------------- 13 | 1. Check the line where it happened and try to identify if it is due to a missing function or feature or for any other reason. 14 | 2. See if there is any open issue with the same or a similar bug. If there is, you can add your specific problem there. 15 | 3. If not previously reported, open a new issue. Try to use a descriptive title such us `Missing subscripts support for Xmile models`, avoid titles like `Error while parsing Xmile model`. Provide the given error information and, if possible, a small model reproducing the same error. 16 | 17 | Bugs during runtime 18 | ------------------- 19 | 1. Check if a similar bug has been reported on the issue tracker. If that is not the case, open a new issue with a descriptive title. 20 | 2. Provide the error information and all the relevant lines you used to execute the model. 21 | 3. If possible, provide a small model reproducing the bug. 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | # File: docs/requirements.txt 2 | 3 | sphinx>=8.0.0 4 | sphinx_rtd_theme>=3.0.0 5 | readthedocs-sphinx-search>=0.3.2 6 | jinja2>=3.0.0 7 | mock 8 | pandas -------------------------------------------------------------------------------- /docs/structure/abstract_model.rst: -------------------------------------------------------------------------------- 1 | Abstract Model Representation 2 | ============================= 3 | The Abstract Model representation allows a separation of concern between 4 | translation and building. Translation involves anything that 5 | happens from the moment the source code of the original model is loaded 6 | into memory up to the creation of the Abstract Model representation. Similarly, 7 | the building will be everything that takes place between the Abstract Model and the 8 | source code of the model written in a programming language different than that 9 | of the original model.This approach allows to easily include new code to the translation or or building process, 10 | without the the risk of affecting one another. 11 | 12 | The :py:class:`AbstractModel` object should retain as much information from the 13 | original model as possible. Although the information is not used 14 | in the output code, it may be necessary for other future output languages 15 | or for improvements in the currently supported outputs. For example, currently 16 | unchangeable constants (== defined in Vensim) are treated as regular 17 | components with Python, but in the future we may want to protect them 18 | from user interaction. 19 | 20 | The lowest level of this representation is the :py:class:`AbstractSyntax` Tree (AST). 21 | This includes all the operations and calls in a given component expression. 22 | 23 | Main abstract structures 24 | ------------------------ 25 | .. automodule:: pysd.translators.structures.abstract_model 26 | :members: 27 | 28 | Abstract structures for the AST 29 | ------------------------------- 30 | .. automodule:: pysd.translators.structures.abstract_expressions 31 | :members: 32 | -------------------------------------------------------------------------------- /docs/structure/python_builder.rst: -------------------------------------------------------------------------------- 1 | Python builder 2 | ============== 3 | 4 | The Python builder allows to build models that can be run with the PySD Model class. 5 | 6 | The use of a one-to-one dictionary in translation means that the breadth of functionality is inherently limited. In the case where no direct Python equivalent is available, PySD provides a library of functions such as `pulse`, `step`, etc. that are specific to dynamic model behavior. 7 | 8 | In addition to translating individual commands between Vensim/XMILE and Python, PySD reworks component identifiers to be Python-safe by replacing spaces with underscores. The translator allows source identifiers to make use of alphanumeric characters, spaces, or special characters. In order to make that possible a namespace is created, which links the original name of the variable with the Python-safe name. The namespace is also available in the PySD model class to allow users working with both original names and Python-safe names. 9 | 10 | The resulting Python code from building the model is formatted with :py:mod:`black` library and it is written to the same folder where the original model is. 11 | 12 | 13 | Main builders 14 | ------------- 15 | .. automodule:: pysd.builders.python.python_model_builder 16 | :members: 17 | 18 | Expression builders 19 | ------------------- 20 | .. automodule:: pysd.builders.python.python_expressions_builder 21 | :members: 22 | 23 | 24 | .. _Python supported functions: 25 | 26 | Supported expressions examples 27 | ------------------------------ 28 | Operators 29 | ^^^^^^^^^ 30 | 31 | .. csv-table:: Supported unary operators 32 | :file: ../tables/unary_python.csv 33 | :header-rows: 1 34 | 35 | .. csv-table:: Supported binary operators 36 | :file: ../tables/binary_python.csv 37 | :header-rows: 1 38 | 39 | Functions 40 | ^^^^^^^^^ 41 | 42 | .. csv-table:: Supported basic functions 43 | :file: ../tables/functions_python.csv 44 | :header-rows: 1 45 | 46 | .. csv-table:: Supported delay functions 47 | :file: ../tables/delay_functions_python.csv 48 | :header-rows: 1 49 | 50 | .. csv-table:: Supported get functions 51 | :file: ../tables/get_functions_python.csv 52 | :header-rows: 1 53 | 54 | Namespace manager 55 | ----------------- 56 | .. automodule:: pysd.builders.python.namespace 57 | :members: NamespaceManager 58 | 59 | 60 | Subscript manager 61 | ----------------- 62 | .. automodule:: pysd.builders.python.subscripts 63 | :members: 64 | 65 | 66 | Imports manager 67 | --------------- 68 | .. automodule:: pysd.builders.python.imports 69 | :members: 70 | -------------------------------------------------------------------------------- /docs/structure/structure_index.rst: -------------------------------------------------------------------------------- 1 | Structure of the PySD library 2 | ============================= 3 | 4 | PySD provides translators that allow to convert the original model into an Abstract Model Representation (AMR), or :doc:`Abstract Model ` for short. This representation allows to gather all the model equations and behavior into a number of Python data classes. Therefore, the AMR is Python code, hence independent of the programming language used to write the original model. The AMR is then passed to a builder, which converts it to source code of a programming language of our choice. See the example of the complete process in the figure below. 5 | 6 | .. image:: ../images/abstract_model.png 7 | :width: 700 px 8 | :align: center 9 | 10 | Currently, PySD can translate Vensim models (mdl format) or models in Xmile format (exported from Vensim, Stella or other software) into an AMR. The only builder available at the moment builds the models in Python. 11 | 12 | For models translated to Python, all the necessary functions and classes to run them are included in PySD. The :py:class:`Model` class is the main class that allows loading and running a model, as well as modifying the values of its parameters, among many other possibilities. 13 | 14 | Translation 15 | ----------- 16 | 17 | .. toctree:: 18 | :hidden: 19 | 20 | vensim_translation 21 | xmile_translation 22 | abstract_model 23 | 24 | PySD currently supports translation :doc:`from Vensim ` and :doc:`from Xmile `. 25 | 26 | PySD can import models in Vensim's \*.mdl file format and in XMILE format (\*.xml, \*.xmile, or \*.stmx file). `Parsimonious `_ is the Parsing Expression Grammar `(PEG) `_ parser library used in PySD to parse the original models and construct an abstract syntax tree. The translators then crawl the tree, using a set of classes to define the :doc:`Abstract Model `. 27 | 28 | When parsing the expressions of any language, the order of operations must be taken into account. The order is shown in the following table and is used to create :py:class:`ArithmeticStructure` and :py:class:`LogicalStructure` objects correctly. The following expression :py:data:`1+2*3-5` will be translated to:: 29 | 30 | ArithmeticStructure(operators=['+', '-'], arguments=(1, ArithmeticStructure(operators=['*'], arguments=(2, 3)), 5)) 31 | 32 | While something like :py:data:`1<5 and 5>3`:: 33 | 34 | LogicStructure(operators=[':AND:'], arguments=(LogicStructure(operators=['<'], arguments=(1, 5)), LogicStructure(operators=['>'], arguments=(5, 3)))) 35 | 36 | The parenthesis also affects same order operators, for example :py:data:`1+2-3` is translated to:: 37 | 38 | ArithmeticStructure(operators=['+', '-'], arguments=(1, 2, 3)) 39 | 40 | While :py:data:`1+(2-3)` is translated to:: 41 | 42 | ArithmeticStructure(operators=['+'], arguments=(1, ArithmeticStructure(operators=['-'], arguments=(2, 3)))) 43 | 44 | It is important to maintain this order because although these operations by definition are commutative due to the numerical error due to the precision, they may not be commutative in the integration. 45 | 46 | .. csv-table:: Arithmetic order 47 | :file: ../tables/arithmetic.csv 48 | :header-rows: 1 49 | 50 | 51 | Building the model 52 | ------------------ 53 | 54 | .. toctree:: 55 | :hidden: 56 | 57 | python_builder 58 | 59 | The builders allow to build the final model in any programming language (so long as there is a builder for that particular language). To do so, they use a series of classes that obtain the information from the :doc:`Abstract Model ` and convert it into the desired code. Currently PySD only includes a :doc:`builder to build the models in Python ` . Any contribution to add new builders (and solvers) for other programming languages is welcome. 60 | 61 | 62 | The Python model 63 | ---------------- 64 | 65 | For loading a translated model with Python see :doc:`Getting started <../../getting_started>` or :doc:`Model loading <../../python_api/model_loading>`. The Python builder constructs a Python class that represents the system dynamics model. The class maintains a dictionary representing the current values of each of the system stocks, and the current simulation time, making it a `stateful` model in much the same way that the system itself has a specific state at any point in time. 66 | 67 | The :doc:`Model class <../../python_api/model_class>` also contains a function for each of the model components, representing the essential model equations. Each function contains its units, subscripts type information and documentation as translated from the original model file. A query to any of the model functions will calculate and return its value according to the stored state of the system. 68 | 69 | The :doc:`Model class <../../python_api/model_class>` maintains only a single state of the system in memory, meaning that all functions must obey the Markov property - that the future state of the system can be calculated entirely based upon its current state. In addition to simplifying integration, this requirement enables analyses that interact with the model at a step-by-step level. 70 | 71 | Lastly, the :doc:`Model class <../../python_api/model_class>` provides a set of methods that are used to facilitate simulation. The :py:meth:`.run` method returns to the user a Pandas dataframe representing the output of their simulation run. A variety of options allow the user to specify which components of the model they would like returned, and the timestamps at which they would like those measurements. Additional parameters make parameter changes to the model, modify its starting conditions, or specify how simulation results should be logged. 72 | -------------------------------------------------------------------------------- /docs/structure/xmile_translation.rst: -------------------------------------------------------------------------------- 1 | Xmile Translation 2 | ================= 3 | 4 | PySD allows parsing a Xmile file and translates the result to an :py:class:`AbstractModel` object that can be used to build the model. 5 | 6 | 7 | .. warning:: 8 | Currently no Xmile users are working on the development of PySD. This is causing a gap between the Xmile and Vensim developments. Stella users are encouraged to take part in the development of PySD by including new `test models `_ and adding support for new functions and features. 9 | 10 | 11 | The translation workflow 12 | ------------------------- 13 | The following translation workflow allows splitting the Xmile file while parsing each part of it to build an :py:class:`AbstractModel` type object. The workflow may be summarized as follows: 14 | 15 | 1. **Xmile file**: Parses the file with etree library and creates a section for the model. 16 | 2. **Xmile section**: Full set of variables and definitions that can be integrated. Allows splitting the model elements. 17 | 3. **Xmile element**: A variable definition. It includes units and comments. Allows parsing the expressions it contains and saving them inside AbstractComponents, that are part of an AbstractElement. 18 | 19 | Once the model is parsed and split following the previous steps. The :py:class:`AbstractModel` can be returned. 20 | 21 | 22 | Xmile file 23 | ^^^^^^^^^^ 24 | 25 | .. automodule:: pysd.translators.xmile.xmile_file 26 | :members: XmileFile 27 | :undoc-members: 28 | 29 | Xmile section 30 | ^^^^^^^^^^^^^ 31 | 32 | .. automodule:: pysd.translators.xmile.xmile_section 33 | :members: Section 34 | :undoc-members: 35 | 36 | Xmile element 37 | ^^^^^^^^^^^^^ 38 | 39 | .. automodule:: pysd.translators.xmile.xmile_element 40 | :members: SubscriptRange, Element, Aux, Flow, Gf, Stock 41 | :undoc-members: 42 | 43 | 44 | .. _Xmile supported functions: 45 | 46 | Supported Functions and Features 47 | -------------------------------- 48 | 49 | Ongoing development of the translator will support the full set of Xmile functionality. The current release supports the following operators, functions and features: 50 | 51 | .. warning:: 52 | Not all the supported functions and features are properly tested. Any new test model to cover the missing functions test will be welcome. 53 | 54 | Operators 55 | ^^^^^^^^^ 56 | All the basic operators are supported, this includes the ones shown in the tables below.: 57 | 58 | .. csv-table:: Supported unary operators 59 | :file: ../tables/unary_xmile.csv 60 | :header-rows: 1 61 | 62 | .. csv-table:: Supported binary operators 63 | :file: ../tables/binary_xmile.csv 64 | :header-rows: 1 65 | 66 | 67 | Functions 68 | ^^^^^^^^^ 69 | Not all the Xmile functions are included yet. The list of supported functions is shown below: 70 | 71 | .. csv-table:: Supported basic functions 72 | :file: ../tables/functions_xmile.csv 73 | :header-rows: 1 74 | 75 | .. csv-table:: Supported delay functions 76 | :file: ../tables/delay_functions_xmile.csv 77 | :header-rows: 1 78 | 79 | 80 | Stocks 81 | ^^^^^^ 82 | Stocks are supported with any number of inflows and outflows. Stocks are translated to the AST as `IntegStructure(flows, initial_value, non_negative)`. Non-negative flag is parsed for both stocks and flows, this can be set element by element or using the `behavior section `_. Flows with non-negative flags are read as flows with a maximum condition, while for stocks this information is saved in the :py:class:`pysd.translators.structures.abstract_expressions.IntegStructure` object. 83 | 84 | Subscripts 85 | ^^^^^^^^^^ 86 | Several subscript related features are supported. These include: 87 | 88 | - Basic subscript operations with different ranges. 89 | - Subscript ranges and subranges definitions. 90 | 91 | Graphical functions 92 | ^^^^^^^^^^^^^^^^^^^ 93 | Xmile graphical functions (gf), also known as lookups, are supported. They can be hardcoded or inlined. 94 | 95 | .. warning:: 96 | Interpolation methods 'extrapolate' and 'discrete' are implemented but not tested. Full integration models with these methods are required. 97 | 98 | Supported in Vensim but not in Xmile 99 | ------------------------------------ 100 | Macro 101 | ^^^^^ 102 | Currently Xmile macros are not supported. In Vensim, macros are classified as an independent section of the model. If they are properly parsed in the :py:class:`XmileFile`, adding support for Xmile should be easy. 103 | 104 | Planed New Functions and Features 105 | --------------------------------- 106 | Nothing yet. 107 | -------------------------------------------------------------------------------- /docs/tables/arithmetic.tab: -------------------------------------------------------------------------------- 1 | Arithmetic order Operators Operations 2 | 0 "(), None" "parenthesis, function call, references, no-operations" 3 | 1 "\-" negative value 4 | 2 ^ exponentiation 5 | 3 "\*, /" "multiplication, division" 6 | 4 "%" modulo 7 | 5 "+, -" "addition, subtraction" 8 | 6 "=, <>, <, <=, >, >=" comparison 9 | 7 "not" unary logical operation 10 | 8 "and, or" binary logical operations 11 | -------------------------------------------------------------------------------- /docs/tables/binary.tab: -------------------------------------------------------------------------------- 1 | Vensim Vensim example Xmile Xmile example Abstract Syntax Python Translation Vensim comments Xmile comments Python comments 2 | ^ A ^ B ^ A ^ B "ArithmeticStructure(['^'], (A, B))" A**B 3 | "\*" A * B "\*" A * B "ArithmeticStructure(['*'], (A, B))" A*B 4 | / A / B / A / B "ArithmeticStructure(['/'], (A, B))" A/B 5 | mod A mod B "CallStructure('modulo', (A, B))" "pysd.functions.modulo(A, B)" 6 | "\+" A + B "\+" A + B "ArithmeticStructure(['+'], (A, B))" A+B 7 | "\-" A - B "\-" A - B "ArithmeticStructure(['-'], (A, B))" A-B 8 | = A = B = A = B "LogicStructure(['='], (A, B))" A == B 9 | <> A <> B <> A <> B "LogicStructure(['<>'], (A, B))" A != B 10 | < A < B < A < B "LogicStructure(['<'], (A, B))" A < B 11 | > A > B > A > B "LogicStructure(['>'], (A, B))" A > B 12 | >= A >= B >= A >= B "LogicStructure(['>='], (A, B))" A >= B 13 | <= A <= B <= A <= B "LogicStructure(['<='], (A, B))" A <= B 14 | "\:AND:" A :AND: B and A and B "LogicStructure([':AND:'], (A, B))" "numpy.and(A, B)" 15 | "\:OR:" A :OR: B or A or B "LogicStructure([':OR:'], (A, B))" "numpy.or(A, B)" 16 | -------------------------------------------------------------------------------- /docs/tables/delay_functions.tab: -------------------------------------------------------------------------------- 1 | Vensim Vensim example Xmile Xmile example Abstract Syntax Python Translation Vensim comments Xmile comments Python comments 2 | DELAY1I "DELAY1I(input, delay_time, initial_value)" delay1 "delay1(input, delay_time, initial_value)" "DelayStructure(input, delay_time, initial_value, 1)" pysd.statefuls.Delay(...) Not tested for Xmile! 3 | DELAY1 "DELAY1(input, delay_time)" delay1 "delay1(input, delay_time)" "DelayStructure(input, delay_time, input, 1)" pysd.statefuls.Delay(...) Not tested for Xmile! 4 | DELAY3I "DELAY3I(input, delay_time, initial_value)" delay3 "delay3(input, delay_time, initial_value)" "DelayStructure(input, delay_time, initial_value, 3)" pysd.statefuls.Delay(...) Not tested for Xmile! 5 | DELAY3 "DELAY3(input, delay_time)" delay3 "delay3(input, delay_time)" "DelayStructure(input, delay_time, input, 3)" pysd.statefuls.Delay(...) Not tested for Xmile! 6 | DELAY N "DELAY N(input, delay_time, initial_value, n)" delayn "delayn(input, delay_time, n, initial_value)" "DelayNStructure(input, delay_time, initial_value, n)" pysd.statefuls.DelayN(...) Not tested for Xmile! 7 | delayn "delayn(input, delay_time, n)" "DelayNStructure(input, delay_time, input, n)" pysd.statefuls.DelayN(...) Not tested for Xmile! 8 | DELAY FIXED "DELAY FIXED(input, delay_time, initial_value)" delay "delay(input, delay_time, initial_value)" "DelayFixed(input, delay_time, initial_value)" pysd.statefuls.DelayFixed(...) 9 | delay "delay(input, delay_time)" "DelayFixed(input, delay_time, input)" pysd.statefuls.DelayFixed(...) 10 | SMOOTHI "SMOOTH1I(input, delay_time, initial_value)" smth1 "smth1(input, smth_time, initial_value)" "SmoothStructure(input, smth_time, initial_value, 1)" pysd.statefuls.Smooth(...) Not tested for Xmile! 11 | SMOOTH "SMOOTH1(input, delay_time)" smth1 "smth1(input, smth_time)" "SmoothStructure(input, smth_time, input, 1)" pysd.statefuls.Smooth(...) Not tested for Xmile! 12 | SMOOTH3I "SMOOTH3I(input, delay_time, initial_value)" smth3 "smth3(input, smth_time, initial_value)" "SmoothStructure(input, smth_time, initial_value, 3)" pysd.statefuls.Smooth(...) Not tested for Xmile! 13 | SMOOTH3 "SMOOTH3(input, delay_time)" smth3 "smth3(input, smth_time)" "SmoothStructure(input, smth_time, input, 3)" pysd.statefuls.Smooth(...) Not tested for Xmile! 14 | SMOOTH N "SMOOTH N(input, delay_time, initial_value, n)" smthn "smthn(input, smth_time, n, initial_value)" "SmoothNStructure(input, smth_time, initial_value, n)" pysd.statefuls.SmoothN(...) Not tested for Xmile! 15 | smthn "smthn(input, smth_time, n)" "SmoothNStructure(input, smth_time, input, n)" pysd.statefuls.SmoothN(...) Not tested for Xmile! 16 | forcst "forcst(input, average_time, horizon, initial_trend)" "ForecastStructure(input, average_time, horizon, initial_trend)" pysd.statefuls.Forecast(...) Not tested for Xmile! 17 | FORECAST "FORECAST(input, average_time, horizon)" forcst "forcst(input, average_time, horizon)" "ForecastStructure(input, average_time, horizon, 0)" pysd.statefuls.Forecast(...) Not tested for Xmile! 18 | TREND "TREND(input, average_time, initial_trend)" trend "trend(input, average_time, initial_trend)" "TrendStructure(input, average_time, initial_trend)" pysd.statefuls.Trend(...) Not tested for Xmile! 19 | trend "trend(input, average_time)" "TrendStructure(input, average_time, 0)" pysd.statefuls.Trend(...) Not tested for Xmile! 20 | -------------------------------------------------------------------------------- /docs/tables/functions.tab: -------------------------------------------------------------------------------- 1 | Vensim Vensim example Xmile Xmile example Abstract Syntax Python Translation Vensim comments Xmile comments Python comments 2 | ABS ABS(A) abs abs(A) "CallStructure('abs', (A,))" numpy.abs(A) 3 | MIN "MIN(A, B)" min "min(A, B)" "CallStructure('min', (A, B))" "numpy.minimum(A, B)" 4 | MAX "MAX(A, B)" max "max(A, B)" "CallStructure('max', (A, B))" "numpy.maximum(A, B)" 5 | min "min(A)" "CallStructure('vmin_xmile', (A,))" pysd.functions.vmin(A) 6 | max "max(A)" "CallStructure('vmax_xmile', (A,))" pysd.functions.vmax(A) 7 | SQRT SQRT(A) sqrt sqrt(A) "CallStructure('sqrt', (A,))" numpy.sqrt 8 | EXP EXP(A) exp exp(A) "CallStructure('exp', (A,))" numpy.exp(A) 9 | LN LN(A) ln ln(A) "CallStructure('ln', (A,))" numpy.log(A) 10 | pi pi() "CallStructure('pi', (,))" numpy.py 11 | SIN SIN(A) sin sin(A) "CallStructure('sin', (A,))" numpy.sin(A) 12 | COS COS(A) cos cos(A) "CallStructure('cos', (A,))" numpy.cos(A) 13 | TAN TAN(A) tan tan(A) "CallStructure('tan', (A,))" numpy.tan(A) 14 | ARCSIN ARCSIN(A) arcsin arcsin(A) "CallStructure('arcsin', (A,))" numpy.arcsin(A) 15 | ARCCOS ARCCOS(A) arccos arccos(A) "CallStructure('arccos', (A,))" numpy.arccos(A) 16 | ARCTAN ARCTAN(A) arctan arctan(A) "CallStructure('arctan', (A,))" numpy.arctan(A) 17 | INVERT MATRIX INVERT MATRIX(A) "CallStructure('invert_matrix', (A,))" pysd.functions.invert_matrix(A) 18 | ELMCOUNT ELMCOUNT(A) "CallStructure('elmcount', (A,))" len(A) 19 | INTEGER INTEGER(A) int int(A) "CallStructure('int', (A,))" pysd.functions.integer(A) 20 | QUANTUM "QUANTUM(A, B)" "CallStructure('quantum', (A, B))" "pysd.functions.quantum(A, B)" 21 | MODULO "MODULO(A, B)" "CallStructure('modulo', (A, B))" "pysd.functions.modulo(A, B)" 22 | IF THEN ELSE "IF THEN ELSE(A, B, C)" if_then_else "if_then_else(A, B, C)" "CallStructure('if_then_else', (A, B))" "pysd.functions.if_then_else(A, lambda: B, lambda: C)" 23 | IF condition THEN value_true ELSE value_false IF A THEN B ELSE C "CallStructure('if_then_else', (A, B))" "pysd.functions.if_then_else(A, lambda: B, lambda: C)" 24 | XIDZ "XIDZ(A, B, X)" safediv "safediv(A, B, X)" "CallStructure('xidz', (A, B, X))" "pysd.functions.xidz(A, B, X)" 25 | ZIDZ "ZIDZ(A, B)" safediv "safediv(A, B)" "CallStructure('zidz', (A, B))" "pysd.functions.zidz(A, B)" 26 | 27 | VMIN VMIN(A[dim!]) "CallStructure('vmin', (A,))" pysd.functions.vmin(A, ['dim!']) 28 | VMAX VMAX(A[dim!]) "CallStructure('vmax', (A,))" pysd.functions.vmax(A, ['dim!']) 29 | SUM SUM(A[dim!]) "CallStructure('sum', (A,))" pysd.functions.sum(A, ['dim!']) 30 | PROD PROD(A[dim!]) "CallStructure('prod', (A,))" pysd.functions.prod(A, ['dim!']) 31 | 32 | PULSE PULSE(start, width) "CallStructure('pulse', (start, width))" pysd.functions.pulse(start, width=width) 33 | pulse pulse(magnitude, start) "CallStructure('Xpulse', (start, magnitude))" pysd.functions.pulse(start, magnitude=magnitude) Not tested for Xmile! 34 | pulse pulse(magnitude, start, interval) "CallStructure('Xpulse_train', (start, interval, magnitude))" pysd.functions.pulse(start, repeat_time=interval, magnitude=magnitude) Not tested for Xmile! 35 | PULSE TRAIN PULSE TRAIN(start, width, tbetween, end) "CallStructure('pulse_train', (start, tbetween, width, end))" pysd.functions.pulse(start, repeat_time=tbetween, width=width, end=end) 36 | RAMP RAMP(slope, start_time, end_time) ramp ramp(slope, start_time, end_time) "CallStructure('ramp', (slope, start_time, end_time))" pysd.functions.ramp(time, slope, start_time, end_time) Not tested for Xmile! 37 | ramp ramp(slope, start_time) "CallStructure('ramp', (slope, start_time))" pysd.functions.ramp(time, slope, start_time) Not tested for Xmile! 38 | STEP STEP(height, step_time) step step(height, step_time) "CallStructure('step', (height, step_time))" pysd.functions.step(time, height, step_time) Not tested for Xmile! 39 | GET TIME VALUE GET TIME VALUE(relativeto, offset, measure) "CallStructure('get_time_value', (relativeto, offset, measure))" pysd.functions.get_time_value(time, relativeto, offset, measure) Not all the cases implemented! 40 | VECTOR SELECT VECTOR SELECT(sel_array[dim!], exp_array[dim!], miss_val, n_action, e_action) "CallStructure('vector_select', (sel_array, exp_array, miss_val, n_action, e_action))" pysd.functions.vector_select(sel_array, exp_array, ['dim!'], miss_val, n_action, e_action) 41 | VECTOR RANK VECTOR RANK(vec, direction) "CallStructure('vector_rank', (vec, direction))" vector_rank(vec, direction) 42 | VECTOR REORDER VECTOR REORDER(vec, svec) "CallStructure('vector_reorder', (vec, svec))" vector_reorder(vec, svec) 43 | VECTOR SORT ORDER VECTOR SORT ORDER(vec, direction) "CallStructure('vector_sort_order', (vec, direction))" vector_sort_order(vec, direction) 44 | GAME GAME(A) GameStructure(A) A 45 | ALLOCATE AVAILABLE "ALLOCATE AVAILABLE(request, pp, avail)" "AllocateAvailableStructure(request, pp, avail)" allocate_available(request, pp, avail) Not all the priority profiles are included. 46 | ALLOCATE BY PRIORITY "ALLOCATE BY PRIORITY(request, priority, size, width, supply)" "AllocateByPriorityStructure(request, priority, size, width, supply)" allocate_by_priority(request, priority, width, supply) 47 | INITIAL INITIAL(value) init init(value) InitialStructure(value) pysd.statefuls.Initial 48 | SAMPLE IF TRUE "SAMPLE IF TRUE(condition, input, initial_value)" "SampleIfTrueStructure(condition, input, initial_value)" pysd.statefuls.SampleIfTrue(...) 49 | RANDOM 0 1 "RANDOM 0 1()" "CallStructure('random_0_1', ())" np.random.uniform(0, 1, size=final_shape) 50 | RANDOM UNIFORM "RANDOM UNIFORM(m, x, s)" "CallStructure('random_uniform', (m, x, s))" np.random.uniform(m, x, size=final_shape) 51 | RANDOM NORMAL "RANDOM NORMAL(m, x, h, r, s)" "CallStructure('random_normal', (m, x, h, r, s))" stats.truncnorm.rvs((m-h)/r, (x-h)/r, loc=h, scale=r, size=final_shape) 52 | RANDOM EXPONENTIAL "RANDOM EXPONENTIAL(m, x, h, r, s)" "CallStructure('random_exponential', (m, x, h, r, s))" stats.truncexpon.rvs((x-np.maximum(m, h))/r, loc=np.maximum(m, h), scale=r, size=final_shape) 53 | -------------------------------------------------------------------------------- /docs/tables/get_functions.tab: -------------------------------------------------------------------------------- 1 | Vensim Vensim example Xmile Xmile example Abstract Syntax Python Translation Vensim comments Xmile comments Python comments 2 | GET XLS DATA "GET XLS DATA('file', 'tab', 'time_row_or_col', 'cell')" "GetDataStructure('file', 'tab', 'time_row_or_col', 'cell')" pysd.external.ExtData(...) 3 | GET DIRECT DATA "GET DIRECT DATA('file', 'tab', 'time_row_or_col', 'cell')" "GetDataStructure('file', 'tab', 'time_row_or_col', 'cell')" pysd.external.ExtData(...) 4 | GET XLS LOOKUPS "GET XLS LOOKUPS('file', 'tab', 'x_row_or_col', 'cell')" "GetLookupsStructure('file', 'tab', 'x_row_or_col', 'cell')" pysd.external.ExtLookup(...) 5 | GET DIRECT LOOKUPS "GET DIRECT LOOKUPS('file', 'tab', 'x_row_or_col', 'cell')" "GetLookupsStructure('file', 'tab', 'x_row_or_col', 'cell')" pysd.external.ExtLookup(...) 6 | GET XLS CONSTANTS "GET XLS CONSTANTS('file', 'tab', 'cell')" "GetConstantsStructure('file', 'tab', 'cell')" pysd.external.ExtConstant(...) 7 | GET DIRECT CONSTANTS "GET DIRECT CONSTANTS('file', 'tab', 'cell')" "GetConstantsStructure('file', 'tab', 'cell')" pysd.external.ExtConstant(...) 8 | GET XLS SUBSCRIPT "GET XLS SUBSCRIPT('file', 'tab', 'first_cell', 'last_cell', 'prefix')" pysd.external.ExtSubscript(...) 9 | GET DIRECT SUBSCRIPT "GET DIRECT SUBSCRIPT('file', 'tab', 'first_cell', 'last_cell', 'prefix')" pysd.external.ExtSubscript(...) 10 | -------------------------------------------------------------------------------- /docs/tables/unary.tab: -------------------------------------------------------------------------------- 1 | Vensim Vensim example Xmile Xmile example Abstract Syntax Python Translation Vensim comments Xmile comments Python comments 2 | "\-" -A "\-" -A "LogicStructure(['negative'], (A,))" -A 3 | "\+" +A "\+" +A A A 4 | "\:NOT:" "\:NOT: A" not not A "LogicStructure([':NOT:'], (A,))" numpy.not(A) 5 | -------------------------------------------------------------------------------- /docs/tools.rst: -------------------------------------------------------------------------------- 1 | Tools 2 | ===== 3 | 4 | Some additional tools are provided with the library. 5 | 6 | Benchmarking 7 | ------------ 8 | .. automodule:: pysd.tools.benchmarking 9 | :members: 10 | 11 | Exporting netCDF data_vars to csv or tab 12 | ---------------------------------------- 13 | 14 | Simulation results can be stored as netCDF (*.nc*) files (see :doc:`Storing simulation results on a file <./getting_started>`). 15 | 16 | The :py:class:`pysd.tools.ncfiles.NCFile` allows loading netCDF files generated with PySD as an :py:class:`xarray.Dataset`. When passing the argument `parallel=True` to the constructor, :py:class:`xarray.DataArray` inside the Dataset will be loded as `dask arrays `_, with `chunks=-1 `_. 17 | 18 | Once the Dataset is loaded, a subset (or all) of the data_vars can be exported into: 19 | 20 | * A :py:class:`pandas.DataFrame`, using the :py:meth:`pysd.tools.ncfiles.NCFile.to_df` method 21 | * A `*.csv` or `*.tab` files, using the the :py:meth:`pysd.tools.ncfiles.NCFile.to_text_file` method 22 | 23 | Alternatively, to get further control of the chunking, users can load the :py:class:`xarray.Dataset` using :py:meth:`xarray.open_dataset` and then use the :py:meth:`pysd.tools.ncfiles.NCFile.ds_to_df` or :py:meth:`pysd.tools.ncfiles.NCFile.df_to_text_file` static methods. 24 | 25 | .. automodule:: pysd.tools.ncfiles 26 | :members: -------------------------------------------------------------------------------- /paper/CLD_example.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/paper/CLD_example.pdf -------------------------------------------------------------------------------- /paper/abstract_model.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/paper/abstract_model.pdf -------------------------------------------------------------------------------- /pysd/__init__.py: -------------------------------------------------------------------------------- 1 | from .pysd import read_vensim, read_xmile, load 2 | from .py_backend import functions, statefuls, utils, external 3 | from .py_backend.components import Component 4 | from ._version import __version__ 5 | -------------------------------------------------------------------------------- /pysd/__main__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from .cli import main 3 | 4 | if __name__ == "__main__": 5 | if len(sys.argv) == 1: 6 | main(["--help"]) 7 | else: 8 | main(sys.argv[1:]) 9 | -------------------------------------------------------------------------------- /pysd/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "3.14.3" 2 | -------------------------------------------------------------------------------- /pysd/builders/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/pysd/builders/__init__.py -------------------------------------------------------------------------------- /pysd/builders/python/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/pysd/builders/python/__init__.py -------------------------------------------------------------------------------- /pysd/builders/python/imports.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | 3 | 4 | class ImportsManager(): 5 | """ 6 | Class to save the imported modules information for intelligent import 7 | """ 8 | _external_libs = {"numpy": "np", "xarray": "xr"} 9 | _external_submodules = ["scipy"] 10 | _internal_libs = [ 11 | "functions", "statefuls", "external", "data", "lookups", "utils", 12 | "allocation", "model" 13 | ] 14 | 15 | def __init__(self): 16 | self._numpy, self._xarray = False, False 17 | self._functions, self._statefuls, self._external, self._data,\ 18 | self._lookups, self._utils, self._scipy, self._allocation,\ 19 | self._model =\ 20 | set(), set(), set(), set(), set(), set(), set(), set(), set() 21 | 22 | def add(self, module: str, function: Union[str, None] = None) -> None: 23 | """ 24 | Add a function from module. 25 | 26 | Parameters 27 | ---------- 28 | module: str 29 | module name. 30 | 31 | function: str or None 32 | function name. If None module will be set to true. 33 | 34 | """ 35 | if function: 36 | getattr(self, f"_{module}").add(function) 37 | else: 38 | setattr(self, f"_{module}", True) 39 | 40 | def get_header(self, outfile: str) -> str: 41 | """ 42 | Returns the importing information to print in the model file 43 | 44 | Parameters 45 | ---------- 46 | outfile: str 47 | Name of the outfile to print in the header. 48 | 49 | Returns 50 | ------- 51 | text: str 52 | Header of the translated model file. 53 | 54 | """ 55 | text =\ 56 | f'"""\nPython model \'{outfile}\'\nTranslated using PySD\n"""\n\n' 57 | 58 | text += "from pathlib import Path\n" 59 | 60 | for module, shortname in self._external_libs.items(): 61 | if getattr(self, f"_{module}"): 62 | text += f"import {module} as {shortname}\n" 63 | 64 | for module in self._external_submodules: 65 | if getattr(self, f"_{module}"): 66 | text += "from %(module)s import %(submodules)s\n" % { 67 | "module": module, 68 | "submodules": ", ".join(getattr(self, f"_{module}"))} 69 | 70 | text += "\n" 71 | 72 | for module in self._internal_libs: 73 | if getattr(self, f"_{module}"): 74 | text += "from pysd.py_backend.%(module)s import %(methods)s\n"\ 75 | % { 76 | "module": module, 77 | "methods": ", ".join(getattr(self, f"_{module}"))} 78 | 79 | text += "from pysd import Component\n" 80 | 81 | return text 82 | -------------------------------------------------------------------------------- /pysd/builders/python/python_functions.py: -------------------------------------------------------------------------------- 1 | 2 | # functions that can be diretcly applied over an array 3 | functionspace = { 4 | # directly build functions without dependencies 5 | "elmcount": ("len(%(0)s)", ()), 6 | 7 | # directly build numpy based functions 8 | "pi": ("np.pi", (("numpy",),)), 9 | "abs": ("np.abs(%(0)s)", (("numpy",),)), 10 | "power": ("np.power(%(0)s,%(1)s)", (("numpy",),)), 11 | "min": ("np.minimum(%(0)s, %(1)s)", (("numpy",),)), 12 | "max": ("np.maximum(%(0)s, %(1)s)", (("numpy",),)), 13 | "exp": ("np.exp(%(0)s)", (("numpy",),)), 14 | "sin": ("np.sin(%(0)s)", (("numpy",),)), 15 | "cos": ("np.cos(%(0)s)", (("numpy",),)), 16 | "tan": ("np.tan(%(0)s)", (("numpy",),)), 17 | "arcsin": ("np.arcsin(%(0)s)", (("numpy",),)), 18 | "arccos": ("np.arccos(%(0)s)", (("numpy",),)), 19 | "arctan": ("np.arctan(%(0)s)", (("numpy",),)), 20 | "sinh": ("np.sinh(%(0)s)", (("numpy",),)), 21 | "cosh": ("np.cosh(%(0)s)", (("numpy",),)), 22 | "tanh": ("np.tanh(%(0)s)", (("numpy",),)), 23 | "sqrt": ("np.sqrt(%(0)s)", (("numpy",),)), 24 | "ln": ("np.log(%(0)s)", (("numpy",),)), 25 | "log": ("(np.log(%(0)s)/np.log(%(1)s))", (("numpy",),)), 26 | # NUMPY: "invert_matrix": ("np.linalg.inv(%(0)s)", (("numpy",),)), 27 | 28 | # vector functions with axis to apply over 29 | # NUMPY: 30 | # "prod": "np.prod(%(0)s, axis=%(axis)s)", (("numpy",),)), 31 | # "sum": "np.sum(%(0)s, axis=%(axis)s)", (("numpy",),)), 32 | # "vmax": "np.max(%(0)s, axis=%(axis)s)", ("numpy",),)), 33 | # "vmin": "np.min(%(0)s, axis=%(axis)s)", (("numpy",),)) 34 | "prod": ("prod(%(0)s, dim=%(axis)s)", (("functions", "prod"),)), 35 | "sum": ("sum(%(0)s, dim=%(axis)s)", (("functions", "sum"),)), 36 | "vmax": ("vmax(%(0)s, dim=%(axis)s)", (("functions", "vmax"),)), 37 | "vmin": ("vmin(%(0)s, dim=%(axis)s)", (("functions", "vmin"),)), 38 | "vmax_xmile": ("vmax(%(0)s)", (("functions", "vmax"),)), 39 | "vmin_xmile": ("vmin(%(0)s)", (("functions", "vmin"),)), 40 | "vector_select": ( 41 | "vector_select(%(0)s, %(1)s, %(axis)s, %(2)s, %(3)s, %(4)s)", 42 | (("functions", "vector_select"),) 43 | ), 44 | 45 | # functions defined in pysd.py_bakcend.functions 46 | "active_initial": ( 47 | "active_initial(__data[\"time\"].stage, lambda: %(0)s, %(1)s)", 48 | (("functions", "active_initial"),)), 49 | "if_then_else": ( 50 | "if_then_else(%(0)s, lambda: %(1)s, lambda: %(2)s)", 51 | (("functions", "if_then_else"),)), 52 | "integer": ( 53 | "integer(%(0)s)", 54 | (("functions", "integer"),)), 55 | "invert_matrix": ( # NUMPY: remove 56 | "invert_matrix(%(0)s)", 57 | (("functions", "invert_matrix"),)), # NUMPY: remove 58 | "modulo": ( 59 | "modulo(%(0)s, %(1)s)", 60 | (("functions", "modulo"),)), 61 | "pulse": ( 62 | "pulse(__data['time'], %(0)s, width=%(1)s)", 63 | (("functions", "pulse"),)), 64 | "Xpulse": ( 65 | "pulse(__data['time'], %(0)s, magnitude=%(1)s)", 66 | (("functions", "pulse"),)), 67 | "pulse_train": ( 68 | "pulse(__data['time'], %(0)s, repeat_time=%(1)s, width=%(2)s, "\ 69 | "end=%(3)s)", 70 | (("functions", "pulse"),)), 71 | "Xpulse_train": ( 72 | "pulse(__data['time'], %(0)s, repeat_time=%(1)s, magnitude=%(2)s)", 73 | (("functions", "pulse"),)), 74 | "get_time_value": ( 75 | "get_time_value(__data['time'], %(0)s, %(1)s, %(2)s)", 76 | (("functions", "get_time_value"),)), 77 | "quantum": ( 78 | "quantum(%(0)s, %(1)s)", 79 | (("functions", "quantum"),)), 80 | "Xramp": ( 81 | "ramp(__data['time'], %(0)s, %(1)s)", 82 | (("functions", "ramp"),)), 83 | "ramp": ( 84 | "ramp(__data['time'], %(0)s, %(1)s, %(2)s)", 85 | (("functions", "ramp"),)), 86 | "step": ( 87 | "step(__data['time'], %(0)s, %(1)s)", 88 | (("functions", "step"),)), 89 | "xidz": ( 90 | "xidz(%(0)s, %(1)s, %(2)s)", 91 | (("functions", "xidz"),)), 92 | "zidz": ( 93 | "zidz(%(0)s, %(1)s)", 94 | (("functions", "zidz"),)), 95 | "vector_sort_order": ( 96 | "vector_sort_order(%(0)s, %(1)s)", 97 | (("functions", "vector_sort_order"),)), 98 | "vector_reorder": ( 99 | "vector_reorder(%(0)s, %(1)s)", 100 | (("functions", "vector_reorder"),)), 101 | "vector_rank": ( 102 | "vector_rank(%(0)s, %(1)s)", 103 | (("functions", "vector_rank"),)), 104 | 105 | # random functions must have the shape of the component subscripts 106 | # most of them are shifted, scaled and truncated 107 | "random_0_1": ( 108 | "np.random.uniform(0, 1, size=%(size)s)", 109 | (("numpy",),)), 110 | "random_uniform": ( 111 | "np.random.uniform(%(0)s, %(1)s, size=%(size)s)", 112 | (("numpy",),)), 113 | "random_normal": ( 114 | "stats.truncnorm.rvs(" 115 | "xidz(%(0)s-%(2)s, %(3)s, -np.inf)," 116 | "xidz(%(1)s-%(2)s, %(3)s, np.inf)," 117 | "loc=%(2)s, scale=%(3)s, size=%(size)s)", 118 | (("scipy", "stats"), ("numpy",), ("functions", "xidz"))), 119 | "random_exponential": ( 120 | "stats.truncexpon.rvs(" 121 | "xidz(%(1)s-np.maximum(%(0)s, %(2)s), %(3)s, np.inf)," 122 | "loc=np.maximum(%(0)s, %(2)s), scale=%(3)s, size=%(size)s)", 123 | (("scipy", "stats"), ("numpy",), ("functions", "xidz"))), 124 | } 125 | -------------------------------------------------------------------------------- /pysd/cli/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Command line parsing and executing scripts 3 | """ 4 | from .main import main 5 | -------------------------------------------------------------------------------- /pysd/cli/main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | from pathlib import Path 4 | 5 | from datetime import datetime 6 | 7 | import pysd 8 | from pysd.translators.vensim.vensim_utils import supported_extensions as\ 9 | vensim_extensions 10 | from pysd.translators.xmile.xmile_utils import supported_extensions as\ 11 | xmile_extensions 12 | 13 | from .parser import parser 14 | 15 | 16 | def main(args): 17 | """ 18 | Main function. Reads user arguments, loads the models, 19 | runs it and saves the output 20 | 21 | Parameters 22 | ---------- 23 | args: list 24 | User arguments. 25 | 26 | Returns 27 | ------- 28 | None 29 | 30 | """ 31 | options = parser.parse_args(args) 32 | 33 | model = load(options.model_file, options.data_files, 34 | options.missing_values, options.split_views, 35 | subview_sep=options.subview_sep) 36 | 37 | if not options.run: 38 | print("\nFinished!") 39 | sys.exit() 40 | 41 | model.initialize() 42 | 43 | if not options.output_file: 44 | options.output_file = os.path.splitext(os.path.basename( 45 | options.model_file 46 | ))[0]\ 47 | + datetime.now().strftime("_output_%Y_%m_%d-%H_%M_%S_%f.tab") 48 | 49 | model.run(**create_configuration(model, options)) 50 | 51 | if options.export_file: 52 | model.export(options.export_file) 53 | 54 | print("\nFinished!") 55 | sys.exit() 56 | 57 | 58 | def load(model_file, data_files, missing_values, split_views, **kwargs): 59 | """ 60 | Translate and load model file. 61 | 62 | Paramters 63 | --------- 64 | model_file: str 65 | Vensim, Xmile or PySD model file. 66 | 67 | data_files: list 68 | If given the list of files where the necessary data to run the model 69 | is given. 70 | 71 | missing_values : str ("warning", "error", "ignore", "keep") 72 | What to do with missing values. If "warning" (default) 73 | shows a warning message and interpolates the values. 74 | If "raise" raises an error. If "ignore" interpolates 75 | the values without showing anything. If "keep" it will keep 76 | the missing values, this option may cause the integration to 77 | fail, but it may be used to check the quality of the data. 78 | 79 | split_views: bool (optional) 80 | If True, the sketch is parsed to detect model elements in each 81 | model view, and then translate each view in a separate Python 82 | file. Setting this argument to True is recommended for large 83 | models split in many different views. Default is False. 84 | 85 | **kwargs: (optional) 86 | Additional keyword arguments. 87 | subview_sep:(str) 88 | Character used to separate views and subviews. If provided, 89 | and split_views=True, each submodule will be placed inside the 90 | folder of the parent view. 91 | 92 | Returns 93 | ------- 94 | pysd.model 95 | 96 | """ 97 | model_file_suffix = Path(model_file).suffix.lower() 98 | if model_file_suffix in vensim_extensions: 99 | print("\nTranslating model file...\n") 100 | return pysd.read_vensim(model_file, initialize=False, 101 | data_files=data_files, 102 | missing_values=missing_values, 103 | split_views=split_views, **kwargs) 104 | elif model_file_suffix in xmile_extensions: 105 | print("\nTranslating model file...\n") 106 | return pysd.read_xmile(model_file, initialize=False, 107 | data_files=data_files, 108 | missing_values=missing_values) 109 | else: 110 | return pysd.load(model_file, initialize=False, 111 | data_files=data_files, 112 | missing_values=missing_values) 113 | 114 | 115 | def create_configuration(model, options): 116 | """ 117 | create configuration dict to pass to the run method. 118 | 119 | Parameters 120 | ---------- 121 | model: pysd.model object 122 | 123 | options: argparse.Namespace 124 | 125 | Returns 126 | ------- 127 | conf_dict: dict 128 | 129 | """ 130 | conf_dict = { 131 | "progress": options.progress, 132 | "params": options.new_values["param"], 133 | "initial_condition": (options.initial_time or model.time(), 134 | options.new_values["initial"]), 135 | "return_columns": options.return_columns, 136 | "final_time": options.final_time, 137 | "time_step": options.time_step, 138 | "saveper": options.saveper, 139 | "flatten_output": True, # need to return totally flat DF 140 | "return_timestamps": options.return_timestamps, # given or None, 141 | "output_file": options.output_file 142 | } 143 | 144 | if options.import_file: 145 | conf_dict["initial_condition"] = options.import_file 146 | 147 | return conf_dict 148 | -------------------------------------------------------------------------------- /pysd/py_backend/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/pysd/py_backend/__init__.py -------------------------------------------------------------------------------- /pysd/py_backend/cache.py: -------------------------------------------------------------------------------- 1 | """ 2 | These are the decorators used by the functions in the model file. 3 | functions.py 4 | """ 5 | from functools import wraps 6 | import inspect 7 | 8 | 9 | class Cache(object): 10 | """ 11 | This is the class for the chache. Several cache types can be saved 12 | in dictionaries and acces using cache.data[cache_type]. 13 | """ 14 | def __init__(self): 15 | self.cached_funcs = set() 16 | self.data = {} 17 | 18 | def __call__(self, func, *args): 19 | """ Decorator for caching """ 20 | 21 | @wraps(func) 22 | def cached_func(*args): 23 | """ Cache function """ 24 | try: 25 | return self.data[func.__name__] 26 | except KeyError: 27 | value = func(*args) 28 | self.data[func.__name__] = value 29 | return value 30 | return cached_func 31 | 32 | def clean(self): 33 | """ Cleans the cache """ 34 | self.data = {} 35 | 36 | 37 | def constant_cache(function, *args): 38 | """ 39 | Constant cache decorator for all the run 40 | The original function is saved in 'function' attribuite so we can 41 | recover it later. 42 | """ 43 | function.function = function 44 | function.value = function(*args) 45 | 46 | @wraps(function) 47 | def wrapper(*args): 48 | return function.value 49 | 50 | return wrapper 51 | -------------------------------------------------------------------------------- /pysd/tools/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/pysd/tools/__init__.py -------------------------------------------------------------------------------- /pysd/translators/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/pysd/translators/__init__.py -------------------------------------------------------------------------------- /pysd/translators/structures/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/pysd/translators/structures/__init__.py -------------------------------------------------------------------------------- /pysd/translators/vensim/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/pysd/translators/vensim/__init__.py -------------------------------------------------------------------------------- /pysd/translators/vensim/parsing_grammars/common_grammar.peg: -------------------------------------------------------------------------------- 1 | # Parsing Expression Grammar: common_grammar 2 | 3 | name = basic_id / escape_group 4 | 5 | # This takes care of models with Unicode variable names 6 | basic_id = id_start id_continue* 7 | 8 | id_start = ~r"[\w]"IU 9 | id_continue = id_start / ~r"[0-9\'\$\s\_]" 10 | 11 | # between quotes, either escaped quote or character that is not a quote 12 | escape_group = "\"" ( "\\\"" / ~r"[^\"]" )* "\"" 13 | 14 | number = raw_number 15 | raw_number = ("+"/"-")? _ (~r"\d+\.?\d*([eE][+-]?\d+)?" / ~r"\.\d+([eE][+-]?\d+)?") 16 | string = "\'" (~r"[^\']"IU)* "\'" 17 | limits = _ "[" ~r"[^\]]*" "]" _ "," 18 | 19 | _ = ~r"[\s\\]*" 20 | -------------------------------------------------------------------------------- /pysd/translators/vensim/parsing_grammars/components.peg: -------------------------------------------------------------------------------- 1 | # Parsing Expression Grammar: components 2 | 3 | expr_type = tabbed_array_call / array / final_expr 4 | 5 | final_expr = logic_expr (_ logic_oper _ logic_expr)* # logic operators (:and:, :or:) 6 | logic_expr = not_oper? _ comp_expr # :not: operator 7 | comp_expr = add_expr (_ comp_oper _ add_expr)? # comparison (e.g. '<', '=>') 8 | add_expr = prod_expr (_ add_oper _ prod_expr)* # addition and substraction 9 | prod_expr = exp_expr (_ prod_oper _ exp_expr)* # product and division 10 | exp_expr = neg_expr (_ exp_oper _ neg_expr)* # exponential 11 | neg_expr = pre_oper? _ expr # pre operators (-, +) 12 | expr = lookup_with_def / call / parens / number / reference / nan 13 | 14 | lookup_with_def = ~r"(WITH\ LOOKUP)"I _ "(" _ final_expr _ "," _ "(" _ limits? ( _ "(" _ raw_number _ "," _ raw_number _ ")" _ ","? _ )+ _ ")" _ ")" 15 | 16 | tabbed_array_call = "TABBED ARRAY" _ "(" _ array_tabbed _ ")" 17 | 18 | nan = ":NA:" 19 | 20 | arguments = ((string / final_expr) _ ","? _)* 21 | parens = "(" _ final_expr _ ")" 22 | 23 | call = reference _ "(" _ arguments _ ")" 24 | 25 | reference = (name _ subscript_list) / name # check first for subscript 26 | subscript_list = "[" _ (name _ "!"? _ ","? _)+ _ "]" 27 | 28 | array = (raw_number _ ("," / ";")? _)+ !~r"." # negative lookahead for 29 | 30 | array_tabbed = ( raw_number _ )+ 31 | 32 | logic_oper = ~r"(%(logic_ops)s)"IU 33 | not_oper = ~r"(%(not_ops)s)"IU 34 | comp_oper = ~r"(%(comp_ops)s)"IU 35 | add_oper = ~r"(%(add_ops)s)"IU 36 | prod_oper = ~r"(%(prod_ops)s)"IU 37 | exp_oper = ~r"(%(exp_ops)s)"IU 38 | pre_oper = ~r"(%(pre_ops)s)"IU 39 | -------------------------------------------------------------------------------- /pysd/translators/vensim/parsing_grammars/element_object.peg: -------------------------------------------------------------------------------- 1 | # Parsing Expression Grammar: element_object 2 | 3 | entry = constraint_definition / test_inputs_definition / unchangeable_constant / component / data_definition / subscript_definition / lookup_definition / subscript_copy 4 | 5 | # Regular component definition "=" 6 | component = name _ subscript_component? _ "=" _ expression 7 | 8 | # Unchangeable constant definition "==" 9 | unchangeable_constant = name _ subscript_component? _ "==" _ expression 10 | 11 | # Lookup definition "()", uses lookahead assertion to capture whole group 12 | lookup_definition = name _ subscript_component? &"(" _ expression 13 | 14 | 15 | # Data type definition ":=" or empty with keyword 16 | data_definition = component_data_definition / empty_data_definition 17 | component_data_definition = name _ subscript_component? _ keyword? _ ":=" _ expression 18 | empty_data_definition = name _ subscript_component? _ keyword 19 | 20 | # Constraint definition 21 | constraint_definition = name _ subscript_component? _ ":THE CONDITION:" _ expression 22 | 23 | # Test inputs definition 24 | test_inputs_definition = name _ subscript_component? _ ":TEST INPUT:" _ expression 25 | 26 | # Subscript ranges 27 | # Subcript range regular definition ":" 28 | subscript_definition = name _ ":" _ (imported_subscript / literal_subscript) _ subscript_mapping_list? 29 | imported_subscript = basic_id _ "(" _ (string _ ","? _)* ")" 30 | literal_subscript = (subscript_range / subscript) _ ("," _ (subscript_range / subscript) _)* 31 | subscript_range = "(" _ basic_id _ "-" _ basic_id _ ")" 32 | 33 | # Subcript range definition by copy "<->" 34 | subscript_copy = name _ "<->" _ name_mapping 35 | 36 | # Subscript mapping 37 | subscript_mapping_list = "->" _ subscript_mapping _ ("," _ subscript_mapping _)* 38 | subscript_mapping = (_ name_mapping _) / (_ "(" _ name_mapping _ ":" _ index_list _ ")" ) 39 | name_mapping = basic_id / escape_group 40 | 41 | # Subscript except match 42 | subscript_list_except = ":EXCEPT:" _ subscript_except_group (_ ',' _ subscript_except_group)* 43 | subscript_except_group = '[' _ subscript_except _ ("," _ subscript_except _)* _ ']' 44 | subscript_except = basic_id / escape_group 45 | 46 | # Subscript match 47 | subscript_list = "[" _ index_list _ "]" 48 | index_list = subscript _ ("," _ subscript _)* 49 | subscript = basic_id / escape_group 50 | 51 | # Other definitions 52 | subscript_component = subscript_list _ subscript_list_except? 53 | expression = ~r".*" # expression could be anything, at this point. 54 | keyword = ":" _ basic_id _ ":" 55 | -------------------------------------------------------------------------------- /pysd/translators/vensim/parsing_grammars/file_sections.peg: -------------------------------------------------------------------------------- 1 | # Parsing Expression Grammar: file_sections 2 | 3 | # full file 4 | file = encoding? _ ((macro / main) _)+ 5 | 6 | # macro definition 7 | macro = ":MACRO:" _ name _ "(" _ (name _ ","? _)+ _ ":"? _ (name _ ","? _)* _ ")" ~r".+?(?=:END OF MACRO:)" ":END OF MACRO:" 8 | 9 | # regular expressions 10 | main = main_part / main_end 11 | main_part = !":MACRO:" ~r".+(?=:MACRO:)" 12 | main_end = !":MACRO:" ~r".+" 13 | 14 | # encoding 15 | encoding = ~r"\{[^\}]*\}" 16 | -------------------------------------------------------------------------------- /pysd/translators/vensim/parsing_grammars/lookups.peg: -------------------------------------------------------------------------------- 1 | # Parsing Expression Grammar: lookups 2 | 3 | lookup = _ "(" _ (regularLookup / excelLookup) _ ")" 4 | regularLookup = limits? _ ( "(" _ number _ "," _ number _ ")" _ ","? _ )+ 5 | excelLookup = ~"GET( |_)(XLS|DIRECT)( |_)LOOKUPS"I _ "(" _ (string _ ","? _)+ ")" 6 | -------------------------------------------------------------------------------- /pysd/translators/vensim/parsing_grammars/section_elements.peg: -------------------------------------------------------------------------------- 1 | # Parsing Expression Grammar: section_elements 2 | 3 | model = (entry / section)+ sketch? 4 | entry = element "~" element "~" doc ("~" element)? "|" 5 | section = element "~" element "|" 6 | sketch = ~r".*" #anything 7 | 8 | # Either an escape group, or a character that is not tilde or pipe 9 | element = ( escape_group / ~r"[^~|]")* 10 | 11 | # Anything other that is not a tilde or pipe 12 | doc = (~r"[^~|]")* 13 | -------------------------------------------------------------------------------- /pysd/translators/vensim/parsing_grammars/sketch.peg: -------------------------------------------------------------------------------- 1 | # Parsing Expression Grammar: sketch 2 | 3 | line = var_definition / view_intro / view_title / view_definition / arrow / flow / other_objects / anything 4 | view_intro = ~r"\s*Sketch.*?names$" / ~r"^V300.*?ignored$" 5 | view_title = "*" view_name 6 | view_name = ~r"(?<=\*)[^\n]+$" 7 | view_definition = "$" color "," digit "," font_properties "|" ( ( color / ones_and_dashes ) "|")* view_code 8 | 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? 9 | 10 | # elements used in a line defining the properties of a variable or stock 11 | var_name = element 12 | var_name = ~r"(?<=,)[^,]+(?=,)" 13 | var_number = digit 14 | var_box_type = ~r"(?<=,)\d+,\d+,\d+(?=,)" # improve this regex 15 | arrows_in_allowed = ~r"(?<=,)\d+(?=,)" # if this is an even number it's a shadow variable 16 | hide_level = digit 17 | var_face = digit 18 | var_word_position = ~r"(?<=,)\-*\d+(?=,)" 19 | var_thickness = digit 20 | var_rest_conf = digit "," ~r"\d+" 21 | extra_bytes = ~r"\d+,\d+,\d+,\d+,\d+,\d+" # required since Vensim 8.2.1 22 | arrow = arrow_code "," digit "," origin_var "," destination_var "," (digit ",")+ (ones_and_dashes ",")? ((color ",") / ("," ~r"\d+") / (font_properties "," ~r"\d+"))* "|(" position ")|" 23 | 24 | # arrow origin and destination (this may be useful if further parsing is required) 25 | origin_var = digit 26 | destination_var = digit 27 | 28 | # flow arrows 29 | flow = source_or_sink_or_plot / flow_arrow 30 | 31 | # if you want to extend the parsing, these three would be a good starting point (they are followed by "anything") 32 | source_or_sink_or_plot = multipurpose_code "," anything 33 | flow_arrow = flow_arrow_code "," anything 34 | other_objects = other_objects_code "," anything 35 | 36 | # fonts 37 | font_properties = font_name? "|" font_size? "|" font_style? "|" color 38 | font_style = ("B" / "I" / "U" / "S" / "V")+ # italics, bold, underline, etc 39 | font_size = ~r"\d+" # this needs to be made a regex to match any font 40 | font_name = ~r"(?<=,)[^\|\d]+(?=\|)" 41 | 42 | # x and y within the view layout. This may be useful if further parsing is required 43 | position = ~r"-*\d+,-*\d+" 44 | 45 | # rgb color (e.g. 255-255-255) 46 | color = ~r"((?", "<=", "<", ">=", ">"], 53 | "add_ops": ["+", "-"], 54 | "prod_ops": ["*", "/"], 55 | "exp_ops": ["^"], 56 | "pre_ops": ["+", "-"] 57 | } 58 | 59 | 60 | parsing_ops = { 61 | key: "|".join(re.escape(x) for x in values) 62 | for key, values in operators.items() 63 | } 64 | -------------------------------------------------------------------------------- /pysd/translators/vensim/vensim_utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import warnings 3 | import uuid 4 | 5 | import parsimonious 6 | from typing import Dict 7 | from pathlib import Path 8 | from chardet import detect 9 | 10 | 11 | supported_extensions = [".mdl"] 12 | 13 | 14 | class Grammar(): 15 | _common_grammar = None 16 | _grammar_path: Path = Path(__file__).parent.joinpath("parsing_grammars") 17 | _grammar: Dict = {} 18 | 19 | @classmethod 20 | def get(cls, grammar: str, subs: dict = {}) -> parsimonious.Grammar: 21 | """Get parsimonious grammar for parsing""" 22 | if grammar not in cls._grammar: 23 | # include grammar in the class singleton 24 | cls._grammar[grammar] = parsimonious.Grammar( 25 | cls._read_grammar(grammar) % subs 26 | ) 27 | 28 | return cls._grammar[grammar] 29 | 30 | @classmethod 31 | def _read_grammar(cls, grammar: str) -> str: 32 | """Read grammar from a file and include common grammar""" 33 | with cls._gpath(grammar).open(encoding="ascii") as gfile: 34 | source_grammar: str = gfile.read() 35 | 36 | return cls._include_common_grammar(source_grammar) 37 | 38 | @classmethod 39 | def _include_common_grammar(cls, source_grammar: str) -> str: 40 | """Include common grammar""" 41 | if not cls._common_grammar: 42 | with cls._gpath("common_grammar").open(encoding="ascii") as gfile: 43 | cls._common_grammar: str = gfile.read() 44 | 45 | return r"{source_grammar}{common_grammar}".format( 46 | source_grammar=source_grammar, common_grammar=cls._common_grammar 47 | ) 48 | 49 | @classmethod 50 | def _gpath(cls, grammar: str) -> Path: 51 | """Get the grammar file path""" 52 | return cls._grammar_path.joinpath(grammar).with_suffix(".peg") 53 | 54 | 55 | def split_arithmetic(structure: object, parsing_ops: dict, 56 | expression: str, elements: dict, 57 | negatives: set = set()) -> object: 58 | """ 59 | Split arithmetic pattern and return the corresponding object. 60 | 61 | Parameters 62 | ---------- 63 | structure: callable 64 | Callable that generates the arithmetic object to return. 65 | parsing_ops: dict 66 | The parsing operators dictionary. 67 | expression: str 68 | Original expression with the operator and the hex code to the objects. 69 | elements: dict 70 | Dictionary of the hex identifiers and the objects that represent. 71 | negative: set 72 | Set of element hex values that must change their sign. 73 | 74 | Returns 75 | ------- 76 | object: structure 77 | Final object of the arithmetic operation or initial object if 78 | no operations are performed. 79 | 80 | """ 81 | pattern = re.compile(parsing_ops) 82 | parts = pattern.split(expression) # list of elements ids 83 | ops = pattern.findall(expression) # operators list 84 | if not ops: 85 | # no operators return original object 86 | if parts[0] in negatives: 87 | # make original object negative 88 | negatives.remove(parts[0]) 89 | return add_element( 90 | elements, 91 | structure(["negative"], (elements[parts[0]],))) 92 | else: 93 | return expression 94 | else: 95 | if not negatives: 96 | # create arithmetic object 97 | return add_element( 98 | elements, 99 | structure( 100 | ops, 101 | tuple([elements[id] for id in parts]))) 102 | else: 103 | # manage negative expressions 104 | current_id = parts.pop() 105 | current = elements[current_id] 106 | if current_id in negatives: 107 | negatives.remove(current_id) 108 | current = structure(["negative"], (current,)) 109 | while ops: 110 | current_id = parts.pop() 111 | current = structure( 112 | [ops.pop()], 113 | (elements[current_id], current)) 114 | if current_id in negatives: 115 | negatives.remove(current_id) 116 | current = structure(["negative"], (current,)) 117 | 118 | return add_element(elements, current) 119 | 120 | 121 | def add_element(elements: dict, element: object) -> str: 122 | """ 123 | Add element to elements dict using an unique hex identifier 124 | 125 | Parameters 126 | ---------- 127 | elements: dict 128 | Dictionary of all elements. 129 | 130 | element: object 131 | Element to add. 132 | 133 | Returns 134 | ------- 135 | id: str (hex) 136 | The name of the key where element is saved in elements. 137 | 138 | """ 139 | id = uuid.uuid4().hex 140 | elements[id] = element 141 | return id 142 | 143 | 144 | def _detect_encoding_from_file(mdl_file: Path) -> str: 145 | """Detect and return the encoding from a Vensim file""" 146 | try: 147 | with mdl_file.open("rb") as in_file: 148 | f_line: bytes = in_file.readline() 149 | f_line: str = f_line.decode(detect(f_line)['encoding']) 150 | return re.search(r"(?<={)(.*)(?=})", f_line).group() 151 | except (AttributeError, UnicodeDecodeError): 152 | warnings.warn( 153 | "No encoding specified or detected to translate the model " 154 | "file. 'UTF-8' encoding will be used.") 155 | return "UTF-8" 156 | -------------------------------------------------------------------------------- /pysd/translators/xmile/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/pysd/translators/xmile/__init__.py -------------------------------------------------------------------------------- /pysd/translators/xmile/parsing_grammars/equations.peg: -------------------------------------------------------------------------------- 1 | # Parsing Expression Grammar: components 2 | 3 | expr_type = array / final_expr 4 | 5 | final_expr = conditional_statement / logic2_expr 6 | 7 | logic2_expr = logic_expr (_ logic_oper _ logic_expr)* # logic operators (:and:, :or:) 8 | logic_expr = not_oper? _ comp_expr # :not: operator 9 | comp_expr = add_expr (_ comp_oper _ add_expr)? # comparison (e.g. '<', '=>') 10 | add_expr = mod_expr (_ add_oper _ mod_expr)* # addition and substraction 11 | mod_expr = prod_expr (_ "mod" _ prod_expr)? # modulo 12 | prod_expr = exp_expr (_ prod_oper _ exp_expr)* # product and division 13 | exp_expr = neg_expr (_ exp_oper _ neg_expr)* # exponential 14 | neg_expr = pre_oper? _ expr # pre operators (-, +) 15 | expr = call / parens / number / reference 16 | 17 | arguments = (final_expr _ ","? _)* 18 | parens = "(" _ final_expr _ ")" 19 | 20 | call = reference _ "(" _ arguments _ ")" 21 | conditional_statement = "IF" _ logic2_expr _ "THEN" _ logic2_expr _ "ELSE" _ logic2_expr 22 | 23 | reference = (name _ subscript_list) / name # check first for subscript 24 | subscript_list = "[" _ (name _ "!"? _ ","? _)+ _ "]" 25 | 26 | array = (raw_number _ ("," / ";")? _)+ !~r"." # negative lookahead for 27 | 28 | logic_oper = ~r"(%(logic_ops)s)"IU 29 | not_oper = ~r"(%(not_ops)s)"IU 30 | comp_oper = ~r"(%(comp_ops)s)"IU 31 | add_oper = ~r"(%(add_ops)s)"IU 32 | prod_oper = ~r"(%(prod_ops)s)"IU 33 | exp_oper = ~r"(%(exp_ops)s)"IU 34 | pre_oper = ~r"(%(pre_ops)s)"IU 35 | 36 | _ = spacechar* 37 | spacechar = " "* ~"\t"* 38 | 39 | name = basic_id / escape_group 40 | 41 | # This takes care of models with Unicode variable names 42 | basic_id = id_start id_continue* 43 | 44 | id_start = ~r"[\w]"IU 45 | id_continue = id_start / ~r"[0-9\'\$\_]" 46 | 47 | # between quotes, either escaped quote or character that is not a quote 48 | escape_group = "\"" ( "\\\"" / ~r"[^\"]" )* "\"" 49 | 50 | number = raw_number 51 | raw_number = ("+"/"-")? (~r"\d+\.?\d*([eE][+-]?\d+)?" / ~r"\.\d+([eE][+-]?\d+)?") 52 | -------------------------------------------------------------------------------- /pysd/translators/xmile/xmile_file.py: -------------------------------------------------------------------------------- 1 | """ 2 | The XmileFile class allows reading the original Xmile model file and 3 | parsing it into Section elements. The final result can be exported to an 4 | AbstractModel class in order to build a model in another programming language. 5 | """ 6 | from typing import Union 7 | from pathlib import Path 8 | from lxml import etree 9 | 10 | from ..structures.abstract_model import AbstractModel 11 | 12 | from .xmile_section import Section 13 | from .xmile_utils import supported_extensions 14 | 15 | 16 | class XmileFile(): 17 | """ 18 | The XmileFile class allows parsing an Xmile file. 19 | When the object is created, the model file is automatically opened 20 | and parsed with lxml.etree. 21 | 22 | Parameters 23 | ---------- 24 | xmile_path: str or pathlib.Path 25 | Path to the Xmile model. 26 | 27 | """ 28 | def __init__(self, xmile_path: Union[str, Path]): 29 | self.xmile_path = Path(xmile_path) 30 | self.root_path = self.xmile_path.parent 31 | self.xmile_root = self._get_root() 32 | self.ns = self.xmile_root.nsmap[None] # namespace of the xmile 33 | self.view_elements = None 34 | 35 | def __str__(self): # pragma: no cover 36 | return "\nXmile model file, loaded from:\n\t%s\n" % self.xmile_path 37 | 38 | @property 39 | def _verbose(self) -> str: # pragma: no cover 40 | """Get model information.""" 41 | text = self.__str__() 42 | for section in self.sections: 43 | text += section._verbose 44 | 45 | return text 46 | 47 | @property 48 | def verbose(self): # pragma: no cover 49 | """Print model information to standard output.""" 50 | print(self._verbose) 51 | 52 | def _get_root(self) -> etree._Element: 53 | """ 54 | Read an Xmile file and assign its content to self.model_text 55 | 56 | Returns 57 | ------- 58 | lxml.etree._Element: parsed xml object 59 | 60 | """ 61 | # check for model extension 62 | if self.xmile_path.suffix.lower() not in supported_extensions: 63 | raise ValueError( 64 | "The file to translate, '%s' " % self.xmile_path 65 | + "is not a Xmile model. It must end with any of " 66 | + "%s extensions." % ', '.join(supported_extensions) 67 | ) 68 | 69 | return etree.parse( 70 | str(self.xmile_path), 71 | parser=etree.XMLParser(encoding="utf-8", recover=True) 72 | ).getroot() 73 | 74 | def parse(self, parse_all: bool = True) -> None: 75 | """ 76 | Create a XmileSection object from the model content and parse it. 77 | As macros are currently not supported, all models will 78 | have a single section. This function should split the macros in 79 | independent sections in the future. 80 | 81 | Parameters 82 | ---------- 83 | parse_all: bool (optional) 84 | If True, the created XmileSection objects will be 85 | automatically parsed. Otherwise, these objects will only be 86 | added to self.sections but not parsed. Default is True. 87 | 88 | """ 89 | # TODO: in order to make macros work we need to split them here 90 | # in several sections 91 | # We keep everything in a single section 92 | self.sections = [Section( 93 | name="__main__", 94 | path=self.xmile_path.with_suffix(".py"), 95 | section_type="main", 96 | params=[], 97 | returns=[], 98 | content_root=self.xmile_root, 99 | namespace=self.ns, 100 | split=False, 101 | views_dict=None)] 102 | 103 | if parse_all: 104 | for section in self.sections: 105 | section.parse() 106 | 107 | def get_abstract_model(self) -> AbstractModel: 108 | """ 109 | Get Abstract Model used for building. This, method should be 110 | called after parsing the model (self.parse). This automatically 111 | calls the get_abstract_section method from the model sections. 112 | 113 | Returns 114 | ------- 115 | AbstractModel: AbstractModel 116 | Abstract Model object that can be used for building the model 117 | in another language. 118 | 119 | """ 120 | return AbstractModel( 121 | original_path=self.xmile_path, 122 | sections=tuple(section.get_abstract_section() 123 | for section in self.sections)) 124 | -------------------------------------------------------------------------------- /pysd/translators/xmile/xmile_structures.py: -------------------------------------------------------------------------------- 1 | import re 2 | from ..structures import abstract_expressions as ae 3 | 4 | 5 | structures = { 6 | "reference": ae.ReferenceStructure, 7 | "subscripts_ref": ae.SubscriptsReferenceStructure, 8 | "arithmetic": ae.ArithmeticStructure, 9 | "logic": ae.LogicStructure, 10 | "inline_lookup": ae.InlineLookupsStructure, 11 | "lookup": ae.LookupsStructure, 12 | "call": ae.CallStructure, 13 | "init": ae.InitialStructure, 14 | "stock": ae.IntegStructure, 15 | "delay": { 16 | 2: lambda x, y: ae.DelayFixedStructure(x, y, x), 17 | 3: lambda x, y, z: ae.DelayFixedStructure(x, y, z) 18 | }, 19 | "delay1": { 20 | 2: lambda x, y: ae.DelayStructure(x, y, x, 1), 21 | 3: lambda x, y, z: ae.DelayStructure(x, y, z, 1) 22 | }, 23 | "delay3": { 24 | 2: lambda x, y: ae.DelayStructure(x, y, x, 3), 25 | 3: lambda x, y, z: ae.DelayStructure(x, y, z, 3), 26 | }, 27 | "delayn": { 28 | 3: lambda x, y, n: ae.DelayNStructure(x, y, x, n), 29 | 4: lambda x, y, n, z: ae.DelayNStructure(x, y, z, n), 30 | }, 31 | "smth1": { 32 | 2: lambda x, y: ae.SmoothStructure(x, y, x, 1), 33 | 3: lambda x, y, z: ae.SmoothStructure(x, y, z, 1) 34 | }, 35 | "smth3": { 36 | 2: lambda x, y: ae.SmoothStructure(x, y, x, 3), 37 | 3: lambda x, y, z: ae.SmoothStructure(x, y, z, 3) 38 | }, 39 | "smthn": { 40 | 3: lambda x, y, n: ae.SmoothNStructure(x, y, x, n), 41 | 4: lambda x, y, n, z: ae.SmoothNStructure(x, y, z, n) 42 | }, 43 | "trend": { 44 | 2: lambda x, y: ae.TrendStructure(x, y, 0), 45 | 3: ae.TrendStructure, 46 | }, 47 | "forcst": { 48 | 3: lambda x, y, z: ae.ForecastStructure(x, y, z, 0), 49 | 4: ae.ForecastStructure 50 | }, 51 | "safediv": { 52 | 2: lambda x, y: ae.CallStructure( 53 | ae.ReferenceStructure("zidz"), (x, y)), 54 | 3: lambda x, y, z: ae.CallStructure( 55 | ae.ReferenceStructure("xidz"), (x, y, z)) 56 | }, 57 | "if_then_else": lambda x, y, z: ae.CallStructure( 58 | ae.ReferenceStructure("if_then_else"), (x, y, z)), 59 | "ramp": { 60 | 2: lambda x, y: ae.CallStructure( 61 | ae.ReferenceStructure("Xramp"), (x, y)), 62 | 3: lambda x, y, z: ae.CallStructure( 63 | ae.ReferenceStructure("ramp"), (x, y, z)) 64 | }, 65 | "pulse": { 66 | 2: lambda magnitude, start: ae.CallStructure( 67 | ae.ReferenceStructure("Xpulse"), (start, magnitude)), 68 | 3: lambda magnitude, start, interval: ae.CallStructure( 69 | ae.ReferenceStructure("Xpulse_train"), (start, interval, magnitude) 70 | ) 71 | }, 72 | "max": { 73 | 1: lambda x: ae.CallStructure( 74 | ae.ReferenceStructure("vmax_xmile"), (x,)), 75 | 2: lambda x, y: ae.CallStructure( 76 | ae.ReferenceStructure("max"), (x, y)) 77 | }, 78 | "min": { 79 | 1: lambda x: ae.CallStructure( 80 | ae.ReferenceStructure("vmin_xmile"), (x,)), 81 | 2: lambda x, y: ae.CallStructure( 82 | ae.ReferenceStructure("min"), (x, y)) 83 | }, 84 | "negative": lambda x: ae.ArithmeticStructure(["negative"], (x,)), 85 | "int": lambda x: ae.CallStructure( 86 | ae.ReferenceStructure("integer"), (x,)) 87 | } 88 | 89 | 90 | operators = { 91 | "logic_ops": ["and", "or"], 92 | "not_ops": ["not"], 93 | "comp_ops": ["=", "<>", "<=", "<", ">=", ">"], 94 | "add_ops": ["+", "-"], 95 | "prod_ops": ["*", "/"], 96 | "exp_ops": ["^"], 97 | "pre_ops": ["+", "-"] 98 | } 99 | 100 | 101 | parsing_ops = { 102 | key: "|".join(re.escape(x) for x in values) 103 | for key, values in operators.items() 104 | } 105 | -------------------------------------------------------------------------------- /pysd/translators/xmile/xmile_utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | import uuid 3 | 4 | import parsimonious 5 | from typing import Dict 6 | from pathlib import Path 7 | 8 | 9 | supported_extensions = [".xmile", ".xml", ".stmx"] 10 | 11 | 12 | class Grammar(): 13 | _common_grammar = None 14 | _grammar_path: Path = Path(__file__).parent.joinpath("parsing_grammars") 15 | _grammar: Dict = {} 16 | 17 | @classmethod 18 | def get(cls, grammar: str, subs: dict = {}) -> parsimonious.Grammar: 19 | """Get parsimonious grammar for parsing""" 20 | if grammar not in cls._grammar: 21 | # include grammar in the class singleton 22 | cls._grammar[grammar] = parsimonious.Grammar( 23 | cls._read_grammar(grammar) % subs 24 | ) 25 | 26 | return cls._grammar[grammar] 27 | 28 | @classmethod 29 | def _read_grammar(cls, grammar: str) -> str: 30 | """Read grammar from a file and include common grammar""" 31 | with cls._gpath(grammar).open(encoding="ascii") as gfile: 32 | source_grammar: str = gfile.read() 33 | 34 | return source_grammar 35 | 36 | @classmethod 37 | def _gpath(cls, grammar: str) -> Path: 38 | """Get the grammar file path""" 39 | return cls._grammar_path.joinpath(grammar).with_suffix(".peg") 40 | 41 | 42 | def split_arithmetic(structure: object, parsing_ops: dict, 43 | expression: str, elements: dict, 44 | negatives: set = set()) -> object: 45 | """ 46 | Split arithmetic pattern and return the corresponding object. 47 | 48 | Parameters 49 | ---------- 50 | structure: callable 51 | Callable that generates the arithmetic object to return. 52 | parsing_ops: dict 53 | The parsing operators dictionary. 54 | expression: str 55 | Original expression with the operator and the hex code to the objects. 56 | elements: dict 57 | Dictionary of the hex identifiers and the objects that represent. 58 | negative: set 59 | Set of element hex values that must change their sign. 60 | 61 | Returns 62 | ------- 63 | object: structure 64 | Final object of the arithmetic operation or initial object if 65 | no operations are performed. 66 | 67 | """ 68 | pattern = re.compile(parsing_ops, re.IGNORECASE) 69 | parts = pattern.split(expression) # list of elements ids 70 | ops = pattern.findall(expression) # operators list 71 | ops = list(map( 72 | lambda x: re.sub(r'(?i)and', ':AND:', 73 | re.sub(r'(?i)or', ':OR:', x)), 74 | ops 75 | )) 76 | if not ops: 77 | # no operators return original object 78 | if parts[0] in negatives: 79 | # make original object negative 80 | negatives.remove(parts[0]) 81 | return add_element( 82 | elements, 83 | structure(["negative"], (elements[parts[0]],))) 84 | else: 85 | return expression 86 | else: 87 | if not negatives: 88 | # create arithmetic object 89 | return add_element( 90 | elements, 91 | structure( 92 | ops, 93 | tuple([elements[id] for id in parts]))) 94 | else: 95 | # manage negative expressions 96 | current_id = parts.pop() 97 | current = elements[current_id] 98 | if current_id in negatives: 99 | negatives.remove(current_id) 100 | current = structure(["negative"], (current,)) 101 | while ops: 102 | current_id = parts.pop() 103 | current = structure( 104 | [ops.pop()], 105 | (elements[current_id], current)) 106 | if current_id in negatives: 107 | negatives.remove(current_id) 108 | current = structure(["negative"], (current,)) 109 | 110 | return add_element(elements, current) 111 | 112 | 113 | def add_element(elements: dict, element: object) -> str: 114 | """ 115 | Add element to elements dict using an unique hex identifier 116 | 117 | Parameters 118 | ---------- 119 | elements: dict 120 | Dictionary of all elements. 121 | 122 | element: object 123 | Element to add. 124 | 125 | Returns 126 | ------- 127 | id: str (hex) 128 | The name of the key where element is saved in elements. 129 | 130 | """ 131 | id = uuid.uuid4().hex 132 | elements[id] = element 133 | return id 134 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | numpy>=1.23 2 | pandas[excel] 3 | parsimonious 4 | xarray>=2023.9 5 | lxml 6 | regex 7 | chardet 8 | black 9 | openpyxl>=3.1.2 10 | scipy 11 | progressbar2 12 | portion 13 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | exec(open('pysd/_version.py').read()) 4 | print(__version__) 5 | 6 | setup( 7 | name='pysd', 8 | version=__version__, 9 | python_requires='>=3.9', 10 | author='PySD contributors', 11 | packages=find_packages(exclude=['docs', 'tests', 'dist', 'build']), 12 | url='https://github.com/SDXorg/pysd', 13 | license='LICENSE', 14 | description='System Dynamics Modeling in Python', 15 | long_description=open('README.md').read(), 16 | long_description_content_type='text/markdown', 17 | keywords=['System Dynamics', 'Vensim', 'XMILE'], 18 | classifiers=[ 19 | 'Development Status :: 4 - Beta', 20 | 'License :: OSI Approved :: MIT License', 21 | 'Topic :: Scientific/Engineering :: Mathematics', 22 | 'Topic :: Scientific/Engineering :: Information Analysis', 23 | 'Intended Audience :: Science/Research', 24 | 25 | 'Programming Language :: Python :: 3.9', 26 | 'Programming Language :: Python :: 3.10', 27 | 'Programming Language :: Python :: 3.11', 28 | 'Programming Language :: Python :: 3.12', 29 | ], 30 | install_requires=open('requirements.txt').read().strip().split('\n'), 31 | package_data={ 32 | 'translators': [ 33 | '*/parsing_grammars/*.peg' 34 | ] 35 | }, 36 | include_package_data=True 37 | ) 38 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | 2 | # You can set these variables from the command line. 3 | NUM_PROC = 1 4 | 5 | 6 | help: 7 | @echo "Please use \`make ' where is one of" 8 | @echo " tests to run tests" 9 | @echo " cover to run tests with coverage information" 10 | @echo " coverhtml to run tests and make cover report with HTML files in cover folder" 11 | @echo " clean to remove coverage information" 12 | 13 | clean: 14 | rm -rf htmlcov .coverage 15 | 16 | tests: clean 17 | ifeq ($(NUM_PROC), 1) 18 | pytest 19 | else 20 | pytest -n $(NUM_PROC) 21 | endif 22 | 23 | cover: clean 24 | ifeq ($(NUM_PROC), 1) 25 | pytest --cov=pysd --cov-report term 26 | else 27 | pytest --cov=pysd --cov-report term -n $(NUM_PROC) 28 | endif 29 | 30 | coverhtml: clean 31 | ifeq ($(NUM_PROC), 1) 32 | pytest --cov=pysd --cov-report html --cov-report term 33 | else 34 | pytest --cov=pysd --cov-report html --cov-report term -n $(NUM_PROC) 35 | endif 36 | 37 | -------------------------------------------------------------------------------- /tests/README.md: -------------------------------------------------------------------------------- 1 | Test suite 2 | ========== 3 | 4 | Preparing the test suite 5 | ------------------------ 6 | 7 | In order to run the test: 8 | 9 | 1. Clone the repo with the --recursive flag (if not done yet). 10 | 2. Potentially create or enter a virtual environment. 11 | 3. Install test dependencies using *pip install -r tests/requirements.txt* or *conda install --file tests/requirements.txt*. 12 | 13 | Running the test suite 14 | ---------------------- 15 | 16 | To run tests: 17 | 18 | ```shell 19 | make tests 20 | ``` 21 | 22 | To have coverage information: 23 | 24 | ```shell 25 | make cover 26 | ``` 27 | 28 | To have information of visit lines in HTML files in `htmlcov` folder: 29 | 30 | ```shell 31 | make coverhtml 32 | ``` 33 | 34 | To run in multiple CPUs (e.g: for 4 CPUs): 35 | 36 | ```shell 37 | make command NUM_PROC=4 38 | ``` 39 | 40 | where `command` is any of above. 41 | 42 | You can also run the test using `pytest`: 43 | 44 | ```shell 45 | python -m pytest test/ 46 | ``` 47 | 48 | for running individual test, filtering warnings or some other configurations check [pytest documentation](https://docs.pytest.org). 49 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | from pathlib import Path 3 | from dataclasses import dataclass 4 | 5 | 6 | import pytest 7 | 8 | from pysd import read_vensim, read_xmile, load 9 | from pysd.translators.vensim.vensim_utils import supported_extensions as\ 10 | vensim_extensions 11 | from pysd.translators.xmile.xmile_utils import supported_extensions as\ 12 | xmile_extensions 13 | 14 | from pysd.builders.python.imports import ImportsManager 15 | 16 | 17 | @pytest.fixture(scope="session") 18 | def _root(): 19 | # root directory 20 | return Path(__file__).parent.resolve() 21 | 22 | 23 | @pytest.fixture(scope="session") 24 | def _test_models(_root): 25 | # test-models directory 26 | return _root.joinpath("test-models/tests") 27 | 28 | 29 | @pytest.fixture(scope="session") 30 | def _test_random(_root): 31 | # test-models directory 32 | return _root.joinpath("test-models/random") 33 | 34 | 35 | @pytest.fixture(scope="class") 36 | def shared_tmpdir(tmp_path_factory): 37 | # shared temporary directory for each class 38 | return tmp_path_factory.mktemp("shared") 39 | 40 | 41 | @pytest.fixture 42 | def model(_root, tmp_path, model_path): 43 | """ 44 | Copy model to the tmp_path and translate it 45 | """ 46 | assert (_root / model_path).exists(), "The model doesn't exist" 47 | 48 | target = tmp_path / model_path.parent.name 49 | new_path = target / model_path.name 50 | shutil.copytree(_root / model_path.parent, target) 51 | 52 | if model_path.suffix.lower() in vensim_extensions: 53 | return read_vensim(new_path) 54 | elif model_path.suffix.lower() in xmile_extensions: 55 | return read_xmile(new_path) 56 | elif model_path.suffix.lower() == ".py": 57 | return load(new_path) 58 | else: 59 | return ValueError("Invalid model") 60 | 61 | 62 | @pytest.fixture(scope="session") 63 | def ignore_warns(): 64 | # warnings to be ignored in the integration tests 65 | return [ 66 | "numpy.ndarray size changed, may indicate binary incompatibility.", 67 | "Creating an ndarray from ragged nested sequences.*", 68 | "datetime.datetime.* is deprecated and scheduled for removal in a " 69 | "future version. Use timezone-aware objects to represent datetimes " 70 | "in UTC.*", 71 | "__array__ implementation doesn't accept a copy keyword, so passing " 72 | "copy=False failed. __array__ must implement 'dtype' and 'copy' " 73 | "keyword arguments.", 74 | ] 75 | 76 | 77 | @pytest.fixture(scope="session") 78 | def random_size(): 79 | # size of generated random samples 80 | return int(1e6) 81 | 82 | 83 | @dataclass 84 | class FakeComponent: 85 | element: str 86 | section: object 87 | subscripts_dict: dict 88 | 89 | 90 | @dataclass 91 | class FakeSection: 92 | namespace: object 93 | macrospace: dict 94 | imports: object 95 | 96 | 97 | @dataclass 98 | class FakeNamespace: 99 | cleanspace: dict 100 | 101 | 102 | @pytest.fixture(scope="function") 103 | def fake_component(): 104 | # fake_component used to translate random functions to python 105 | return FakeComponent( 106 | '', 107 | FakeSection(FakeNamespace({}), {}, ImportsManager()), 108 | {} 109 | ) 110 | -------------------------------------------------------------------------------- /tests/data/input.csv: -------------------------------------------------------------------------------- 1 | ,1,2,3,4,5,6,7,8,9,10,, 2 | A,0,0,1,1,-1,-1,0,0,,, 3 | B,0,1,1,-1,-1,0,0,0,,, 4 | C,1,1,-1,-1,0,0,0,0,,, 5 | ,,,,,,,,,,,, 6 | -------------------------------------------------------------------------------- /tests/data/input.tab: -------------------------------------------------------------------------------- 1 | 1 2 3 4 5 6 7 8 9 10 2 | A 0 0 1 1 -1 -1 0 0 3 | B 0 1 1 -1 -1 0 0 0 4 | C 1 1 -1 -1 0 0 0 0 5 | 6 | -------------------------------------------------------------------------------- /tests/data/input.txt: -------------------------------------------------------------------------------- 1 | =1=2=3=4=5=6=7=8=9=10== 2 | A=0=0=1=1=-1=-1=0=0=== 3 | B=0=1=1=-1=-1=0=0=0=== 4 | C=1=1=-1=-1=0=0=0=0=== 5 | ============ 6 | -------------------------------------------------------------------------------- /tests/data/input.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/tests/data/input.xlsx -------------------------------------------------------------------------------- /tests/data/input2.csv: -------------------------------------------------------------------------------- 1 | ;1;2;3;4;5;6;7;8;9;10;; 2 | A;0;0;1;1;-1;-1;0;0;;; 3 | B;0;1;1;-1;-1;0;0;0;;; 4 | C;1;1;-1;-1;0;0;0;0;;; 5 | ;;;;;;;;;;;; 6 | -------------------------------------------------------------------------------- /tests/data/out_teacup_transposed.csv: -------------------------------------------------------------------------------- 1 | Time,0,0.125,0.25,0.375,0.5,0.625,0.75,0.875,1,1.125,1.25,1.375,1.5,1.625,1.75,1.875,2,2.125,2.25,2.375,2.5,2.625,2.75,2.875,3,3.125,3.25,3.375,3.5,3.625,3.75,3.875,4,4.125,4.25,4.375,4.5,4.625,4.75,4.875,5,5.125,5.25,5.375,5.5,5.625,5.75,5.875,6,6.125,6.25,6.375,6.5,6.625,6.75,6.875,7,7.125,7.25,7.375,7.5,7.625,7.75,7.875,8,8.125,8.25,8.375,8.5,8.625,8.75,8.875,9,9.125,9.25,9.375,9.5,9.625,9.75,9.875,10,10.125,10.25,10.375,10.5,10.625,10.75,10.875,11,11.125,11.25,11.375,11.5,11.625,11.75,11.875,12,12.125,12.25,12.375,12.5,12.625,12.75,12.875,13,13.125,13.25,13.375,13.5,13.625,13.75,13.875,14,14.125,14.25,14.375,14.5,14.625,14.75,14.875,15,15.125,15.25,15.375,15.5,15.625,15.75,15.875,16,16.125,16.25,16.375,16.5,16.625,16.75,16.875,17,17.125,17.25,17.375,17.5,17.625,17.75,17.875,18,18.125,18.25,18.375,18.5,18.625,18.75,18.875,19,19.125,19.25,19.375,19.5,19.625,19.75,19.875,20,20.125,20.25,20.375,20.5,20.625,20.75,20.875,21,21.125,21.25,21.375,21.5,21.625,21.75,21.875,22,22.125,22.25,22.375,22.5,22.625,22.75,22.875,23,23.125,23.25,23.375,23.5,23.625,23.75,23.875,24,24.125,24.25,24.375,24.5,24.625,24.75,24.875,25,25.125,25.25,25.375,25.5,25.625,25.75,25.875,26,26.125,26.25,26.375,26.5,26.625,26.75,26.875,27,27.125,27.25,27.375,27.5,27.625,27.75,27.875,28,28.125,28.25,28.375,28.5,28.625,28.75,28.875,29,29.125,29.25,29.375,29.5,29.625,29.75,29.875,30 2 | Characteristic Time,10,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 3 | Heat Loss to Room,11,10.8625,10.7267,10.5926,10.4602,10.3295,10.2004,10.0729,9.94694,9.8226,9.69982,9.57857,9.45884,9.3406,9.22385,9.10855,8.99469,8.88226,8.77123,8.66159,8.55332,8.4464,8.34082,8.23656,8.13361,8.03194,7.93154,7.83239,7.73449,7.63781,7.54233,7.44805,7.35495,7.26302,7.17223,7.08258,6.99404,6.90662,6.82029,6.73503,6.65085,6.56771,6.48561,6.40454,6.32449,6.24543,6.16736,6.09027,6.01414,5.93896,5.86473,5.79142,5.71903,5.64754,5.57694,5.50723,5.43839,5.37041,5.30328,5.23699,5.17153,5.10688,5.04305,4.98001,4.91776,4.85629,4.79558,4.73564,4.67644,4.61799,4.56026,4.50326,4.44697,4.39138,4.33649,4.28228,4.22876,4.1759,4.1237,4.07215,4.02125,3.97098,3.92135,3.87233,3.82393,3.77613,3.72893,3.68231,3.63628,3.59083,3.54595,3.50162,3.45785,3.41463,3.37195,3.3298,3.28817,3.24707,3.20648,3.1664,3.12682,3.08774,3.04914,3.01103,2.97339,2.93622,2.89952,2.86327,2.82748,2.79214,2.75724,2.72277,2.68874,2.65513,2.62194,2.58917,2.5568,2.52484,2.49328,2.46211,2.43134,2.40095,2.37093,2.3413,2.31203,2.28313,2.25459,2.22641,2.19858,2.1711,2.14396,2.11716,2.09069,2.06456,2.03875,2.01327,1.9881,1.96325,1.93871,1.91448,1.89055,1.86691,1.84358,1.82053,1.79778,1.7753,1.75311,1.7312,1.70956,1.68819,1.66709,1.64625,1.62567,1.60535,1.58528,1.56547,1.5459,1.52657,1.50749,1.48865,1.47004,1.45166,1.43352,1.4156,1.3979,1.38043,1.36318,1.34614,1.32931,1.31269,1.29628,1.28008,1.26408,1.24828,1.23268,1.21727,1.20205,1.18703,1.17219,1.15754,1.14307,1.12878,1.11467,1.10073,1.08698,1.07339,1.05997,1.04672,1.03364,1.02072,1.00796,0.99536,0.982918,0.970631,0.958498,0.946517,0.934686,0.923002,0.911465,0.900072,0.888821,0.877711,0.86674,0.855905,0.845206,0.834641,0.824208,0.813905,0.803732,0.793685,0.783764,0.773967,0.764292,0.754739,0.745304,0.735988,0.726788,0.717703,0.708732,0.699873,0.691124,0.682485,0.673954,0.66553,0.657211,0.648995,0.640883,0.632872,0.624961,0.617149,0.609435,0.601817,0.594295,0.586866,0.57953,0.572286,0.565133,0.558069,0.551093,0.544205,0.537402 4 | Room Temperature,70,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,, 5 | Teacup Temperature,180,178.625,177.267,175.926,174.602,173.295,172.004,170.729,169.469,168.226,166.998,165.786,164.588,163.406,162.238,161.085,159.947,158.823,157.712,156.616,155.533,154.464,153.408,152.366,151.336,150.319,149.315,148.324,147.345,146.378,145.423,144.481,143.55,142.63,141.722,140.826,139.94,139.066,138.203,137.35,136.508,135.677,134.856,134.045,133.245,132.454,131.674,130.903,130.141,129.39,128.647,127.914,127.19,126.475,125.769,125.072,124.384,123.704,123.033,122.37,121.715,121.069,120.43,119.8,119.178,118.563,117.956,117.356,116.764,116.18,115.603,115.033,114.47,113.914,113.365,112.823,112.288,111.759,111.237,110.722,110.212,109.71,109.213,108.723,108.239,107.761,107.289,106.823,106.363,105.908,105.459,105.016,104.579,104.146,103.719,103.298,102.882,102.471,102.065,101.664,101.268,100.877,100.491,100.11,99.7339,99.3622,98.9952,98.6327,98.2748,97.9214,97.5724,97.2277,96.8874,96.5513,96.2194,95.8917,95.568,95.2484,94.9328,94.6211,94.3134,94.0095,93.7093,93.413,93.1203,92.8313,92.5459,92.2641,91.9858,91.711,91.4396,91.1716,90.9069,90.6456,90.3875,90.1327,89.881,89.6325,89.3871,89.1448,88.9055,88.6691,88.4358,88.2053,87.9778,87.753,87.5311,87.312,87.0956,86.8819,86.6709,86.4625,86.2567,86.0535,85.8528,85.6547,85.459,85.2657,85.0749,84.8865,84.7004,84.5166,84.3352,84.156,83.979,83.8043,83.6318,83.4614,83.2931,83.1269,82.9628,82.8008,82.6408,82.4828,82.3268,82.1727,82.0205,81.8703,81.7219,81.5754,81.4307,81.2878,81.1467,81.0073,80.8698,80.7339,80.5997,80.4672,80.3364,80.2072,80.0796,79.9536,79.8292,79.7063,79.585,79.4652,79.3469,79.23,79.1146,79.0007,78.8882,78.7771,78.6674,78.5591,78.4521,78.3464,78.2421,78.1391,78.0373,77.9369,77.8376,77.7397,77.6429,77.5474,77.453,77.3599,77.2679,77.177,77.0873,76.9987,76.9112,76.8249,76.7395,76.6553,76.5721,76.49,76.4088,76.3287,76.2496,76.1715,76.0944,76.0182,75.9429,75.8687,75.7953,75.7229,75.6513,75.5807,75.5109,75.442,75.374 6 | -------------------------------------------------------------------------------- /tests/integration_test_factory.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | import glob 3 | 4 | if False: 5 | vensim_test_files = glob.glob("test-models/tests/*/*.mdl") 6 | vensim_test_files.sort() 7 | 8 | tests = [] 9 | for file_path in vensim_test_files: 10 | path, file_name = os.path.split(file_path) 11 | folder = path.split("/")[-1] 12 | 13 | test_func_string = """ 14 | "%(test_name)s": { 15 | "folder": "%(folder)s", 16 | "file": "%(file_name)s" 17 | },""" % { 18 | "folder": folder, 19 | "test_name": folder, 20 | "file_name": file_name, 21 | } 22 | tests.append(test_func_string) 23 | 24 | file_string = """ 25 | vensim_test = {%(tests)s 26 | } 27 | """ % {"tests": "".join(tests)} 28 | 29 | with open("test_factory_result.py", "w", encoding="UTF-8") as ofile: 30 | ofile.write(file_string) 31 | 32 | print("Generated %i integration tests" % len(tests)) 33 | -------------------------------------------------------------------------------- /tests/more-tests/circular_reference/test_circular_reference.py: -------------------------------------------------------------------------------- 1 | from pysd.py_backend.statefuls import Integ, Delay 2 | from pysd import Component 3 | 4 | _subscript_dict = {} 5 | 6 | __pysd_version__ = "3.0.0" 7 | 8 | __data = {'scope': None, 'time': lambda: 0} 9 | 10 | component = Component() 11 | 12 | _control_vars = { 13 | "initial_time": lambda: 0, 14 | "final_time": lambda: 0.5, 15 | "time_step": lambda: 0.5, 16 | "saveper": lambda: time_step(), 17 | } 18 | 19 | 20 | def _init_outer_references(data): 21 | for key in data: 22 | __data[key] = data[key] 23 | 24 | 25 | @component.add(name="Time") 26 | def time(): 27 | return __data["time"]() 28 | 29 | 30 | @component.add(name="Time step") 31 | def time_step(): 32 | return __data["time"].step() 33 | 34 | 35 | @component.add(name="Initial time") 36 | def initial_time(): 37 | return __data["time"].initial() 38 | 39 | 40 | @component.add(name="Final time") 41 | def final_time(): 42 | return __data["time"].final() 43 | 44 | 45 | @component.add(name="Saveper") 46 | def saveper(): 47 | return __data["time"].save() 48 | 49 | 50 | @component.add( 51 | name="Integ", 52 | depends_on={'_integ_integ': 1}, 53 | other_deps={'_integ_integ': {'initial': {'delay': 1}, 'step': {}}} 54 | ) 55 | def integ(): 56 | return _integ_integ() 57 | 58 | 59 | @component.add( 60 | name="Delay", 61 | depends_on={'_delay_delay': 1}, 62 | other_deps={'_delay_delay': {'initial': {'integ': 1}, 'step': {}}} 63 | ) 64 | def delay(): 65 | return _delay_delay() 66 | 67 | 68 | _integ_integ = Integ(lambda: 2, lambda: delay(), '_integ_integ') 69 | 70 | _delay_delay = Delay(lambda: 2, lambda: 1, 71 | lambda: integ(), 1, time_step, '_delay_delay') 72 | -------------------------------------------------------------------------------- /tests/more-tests/constant_pipeline/test_constant_pipeline.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | constant1 = 3 | 4 4 | ~ 5 | ~ | 6 | 7 | constant2 = 8 | 4 * constant1 9 | ~ 10 | ~ | 11 | 12 | constant3 = 13 | constant2 * constant1 + constant1 ^ 2 - constant1 14 | ~ 15 | ~ | 16 | 17 | ******************************************************** 18 | .Control 19 | ********************************************************~ 20 | Simulation Control Parameters 21 | | 22 | 23 | FINAL TIME = 5 24 | ~ Month 25 | ~ The final time for the simulation. 26 | | 27 | 28 | INITIAL TIME = 0 29 | ~ Month 30 | ~ The initial time for the simulation. 31 | | 32 | 33 | SAVEPER = 34 | TIME STEP 35 | ~ Month [0,?] 36 | ~ The frequency with which output is stored. 37 | | 38 | 39 | TIME STEP = 1 40 | ~ Month [0,?] 41 | ~ The time step for the simulation. 42 | | 43 | 44 | \\\---/// Sketch information - do not modify anything except names 45 | V300 Do not put anything below this section - it will be ignored 46 | *View 1 47 | $144-237-53,0,Times New Roman|12||0-0-0|0-0-0|0-0-255|-1--1--1|-1--1--1|96,96,100,0 48 | 10,1,constant1,282,260,31,11,8,3,0,0,-1,0,0,0 49 | 10,2,constant2,471,370,31,11,8,3,0,0,-1,0,0,0 50 | 1,3,1,2,0,0,0,0,0,0,0,-1--1--1,,1|(369,311)| 51 | 10,4,constant3,630,232,31,11,8,3,0,0,-1,0,0,0 52 | 1,5,1,4,0,0,0,0,0,0,0,-1--1--1,,1|(449,246)| 53 | 1,6,2,4,0,0,0,0,0,0,0,-1--1--1,,1|(544,306)| 54 | ///---\\\ 55 | :L<%^E!@ 56 | 9:Current 57 | 15:0,0,0,0,0,0 58 | 19:100,0 59 | 27:0, 60 | 34:0, 61 | 5:constant1 62 | 24:0 63 | 25:0 64 | 26:0 65 | -------------------------------------------------------------------------------- /tests/more-tests/data_model/all_data.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/tests/more-tests/data_model/all_data.nc -------------------------------------------------------------------------------- /tests/more-tests/data_model/data1.nc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/tests/more-tests/data_model/data1.nc -------------------------------------------------------------------------------- /tests/more-tests/data_model/data1.tab: -------------------------------------------------------------------------------- 1 | Time data 1 Data 2 data-3 Time data 1 Data 2 2 | 0 0 0 0 0 0 0 3 | 2 2 4 6 2 2 4 4 | 4 4 8 12 4 4 8 5 | 6 6 12 18 6 6 12 6 | 8 8 16 24 8 8 16 7 | 10 10 20 30 10 10 20 8 | -------------------------------------------------------------------------------- /tests/more-tests/data_model/data2.tab: -------------------------------------------------------------------------------- 1 | Time 0 2 4 6 8 10 2 | data_1 -5 -3 -1 1 3 5 3 | data_2 -5 -1 3 7 11 15 4 | data3 -5 1 7 13 19 25 5 | Time 0 2 4 6 8 10 6 | data_1 -5 -3 -1 1 3 5 7 | data_2 -5 -1 3 7 11 15 8 | -------------------------------------------------------------------------------- /tests/more-tests/data_model/data3.tab: -------------------------------------------------------------------------------- 1 | Time data 1 Data 2 2 | 0 -0 -0 3 | 2 -2 -4 4 | 4 -4 -8 5 | 6 -6 -12 6 | 8 -8 -16 7 | 10 -10 -20 8 | -------------------------------------------------------------------------------- /tests/more-tests/data_model/test_data_model.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | data 1:INTERPOLATE: 3 | ~ 4 | ~ | 5 | 6 | Data 2:INTERPOLATE: 7 | ~ 8 | ~ | 9 | 10 | "data-3":INTERPOLATE: 11 | ~ 12 | ~ | 13 | 14 | var1= 15 | data 1 16 | ~ 17 | ~ | 18 | 19 | var2= 20 | Data 2 21 | ~ 22 | ~ | 23 | 24 | var3= 25 | "data-3" 26 | ~ 27 | ~ | 28 | 29 | ******************************************************** 30 | .Control 31 | ********************************************************~ 32 | Simulation Control Parameters 33 | | 34 | 35 | FINAL TIME = 10 36 | ~ Month 37 | ~ The final time for the simulation. 38 | | 39 | 40 | INITIAL TIME = 0 41 | ~ Month 42 | ~ The initial time for the simulation. 43 | | 44 | 45 | SAVEPER = 1 46 | ~ Month [0,?] 47 | ~ The frequency with which output is stored. 48 | | 49 | 50 | TIME STEP = 1 51 | ~ Month [0,?] 52 | ~ The time step for the simulation. 53 | | 54 | 55 | \\\---/// Sketch information - do not modify anything except names 56 | V300 Do not put anything below this section - it will be ignored 57 | *View 1 58 | $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 59 | 10,1,data 1,429,173,19,11,8,3,0,0,0,0,0,0 60 | 10,2,Data 2,438,249,25,10,8,131,0,0,0,0,0,0 61 | 10,3,"data-3",438,334,22,11,8,3,0,0,0,0,0,0 62 | 10,4,var1,617,171,16,11,8,3,0,0,0,0,0,0 63 | 10,5,var2,618,249,16,11,8,3,0,0,0,0,0,0 64 | 10,6,var3,620,337,16,11,8,3,0,0,0,0,0,0 65 | 1,7,3,6,0,0,0,0,0,128,0,-1--1--1,,1|(525,334)| 66 | 1,8,2,5,0,0,0,0,0,128,0,-1--1--1,,1|(525,249)| 67 | 1,9,1,4,0,0,0,0,0,128,0,-1--1--1,,1|(517,172)| 68 | 10,10,Time,429,203,26,11,8,2,1,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 69 | 1,11,10,1,0,1,0,0,0,64,0,-1--1--1,,1|(429,195)| 70 | 10,12,Time,438,278,26,11,8,2,1,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 71 | 1,13,12,2,0,1,0,0,0,64,0,-1--1--1,,1|(438,270)| 72 | 10,14,Time,438,364,26,11,8,2,1,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 73 | 1,15,14,3,0,1,0,0,0,64,0,-1--1--1,,1|(438,356)| 74 | ///---\\\ 75 | :L<%^E!@ 76 | 1:Current.vdf 77 | 9:Current 78 | 15:0,0,0,0,0,0 79 | 19:100,0 80 | 27:0, 81 | 34:0, 82 | 4:Time 83 | 5:var1 84 | 35:Date 85 | 36:YYYY-MM-DD 86 | 37:2000 87 | 38:1 88 | 39:1 89 | 40:2 90 | 41:0 91 | 42:1 92 | 24:0 93 | 25:10 94 | 26:10 95 | 57:1 96 | 54:0 97 | 55:0 98 | 59:0 99 | 56:0 100 | 58:0 101 | 44:65001 102 | 46:0 103 | 45:1 104 | 49:1 105 | 50:0 106 | 51: 107 | 52: 108 | 53: 109 | 43:out3 110 | 47:Current 111 | 48: 112 | -------------------------------------------------------------------------------- /tests/more-tests/initialization_order/test_initialization_order.py: -------------------------------------------------------------------------------- 1 | """ 2 | Python model 'trying_to_reproduce_bug.py' 3 | Translated using PySD 4 | """ 5 | 6 | 7 | from pysd.py_backend.statefuls import Integ 8 | from pysd import Component 9 | 10 | __pysd_version__ = "3.0.0" 11 | 12 | _subscript_dict = {} 13 | 14 | __data = {"scope": None, "time": lambda: 0} 15 | 16 | component = Component() 17 | 18 | _control_vars = { 19 | "initial_time": lambda: 0, 20 | "final_time": lambda: 20, 21 | "time_step": lambda: 1, 22 | "saveper": lambda: time_step() 23 | } 24 | 25 | 26 | def _init_outer_references(data): 27 | for key in data: 28 | __data[key] = data[key] 29 | 30 | 31 | @component.add(name="Time") 32 | def time(): 33 | return __data["time"]() 34 | 35 | 36 | @component.add(name="Initial time") 37 | def initial_time(): 38 | return __data["time"].initial_time() 39 | 40 | 41 | @component.add(name="Final time") 42 | def final_time(): 43 | return __data["time"].final_time() 44 | 45 | 46 | @component.add(name="Time step") 47 | def time_step(): 48 | return __data["time"].time_step() 49 | 50 | 51 | @component.add(name="Saveper", depends_on={'time_step': 1}) 52 | def saveper(): 53 | return __data["time"].saveper() 54 | 55 | 56 | @component.add(name="Stock B", depends_on={'_integ_stock_b': 1}, 57 | other_deps={ 58 | '_integ_stock_b': { 59 | 'initial': {'stock_a': 1}, 60 | 'step': {} 61 | }}) 62 | def stock_b(): 63 | return _integ_stock_b() 64 | 65 | 66 | @component.add(name="Stock A", depends_on={'_integ_stock_a': 1}, 67 | other_deps={ 68 | '_integ_stock_a': { 69 | 'initial': {'initial_par': 1}, 70 | 'step': {} 71 | }}) 72 | def stock_a(): 73 | return _integ_stock_a() 74 | 75 | 76 | @component.add(name="Initial par") 77 | def initial_par(): 78 | return 42 79 | 80 | 81 | _integ_stock_b = Integ(lambda: 1, lambda: stock_a(), "_integ_stock_b") 82 | 83 | 84 | _integ_stock_a = Integ(lambda: 1, lambda: initial_par(), "_integ_stock_a") 85 | -------------------------------------------------------------------------------- /tests/more-tests/not_implemented_and_incomplete/test_not_implemented_and_incomplete.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | incomplete var = A FUNCTION OF( Time) 3 | ~ 4 | ~ | 5 | 6 | not implemented function= 7 | MY FUNC(Time) 8 | ~ 9 | ~ | 10 | 11 | ******************************************************** 12 | .Control 13 | ********************************************************~ 14 | Simulation Control Parameters 15 | | 16 | 17 | FINAL TIME = 1 18 | ~ Month 19 | ~ The final time for the simulation. 20 | | 21 | 22 | INITIAL TIME = 0 23 | ~ Month 24 | ~ The initial time for the simulation. 25 | | 26 | 27 | SAVEPER = 28 | TIME STEP 29 | ~ Month [0,?] 30 | ~ The frequency with which output is stored. 31 | | 32 | 33 | TIME STEP = 1 34 | ~ Month [0,?] 35 | ~ The time step for the simulation. 36 | | 37 | -------------------------------------------------------------------------------- /tests/more-tests/not_vensim/output.csv: -------------------------------------------------------------------------------- 1 | Time,Var 2 | 0,10 3 | -------------------------------------------------------------------------------- /tests/more-tests/not_vensim/test_not_vensim.txt: -------------------------------------------------------------------------------- 1 | Not a Vensim model -------------------------------------------------------------------------------- /tests/more-tests/old_version/test_old_version.py: -------------------------------------------------------------------------------- 1 | __pysd_version__ = "1.5.0" 2 | 3 | _dependencies = {} 4 | 5 | __data = {'scope': None, 'time': lambda: 0} 6 | 7 | _control_vars = { 8 | "initial_time": lambda: 0, 9 | "final_time": lambda: 20, 10 | "time_step": lambda: 1, 11 | "saveper": lambda: time_step() 12 | } 13 | 14 | 15 | def _init_outer_references(data): 16 | for key in data: 17 | __data[key] = data[key] 18 | 19 | 20 | def time(): 21 | return __data["time"]() 22 | 23 | 24 | def initial_time(): 25 | return __data["time"].initial_time() 26 | 27 | 28 | def final_time(): 29 | return __data["time"].final_time() 30 | 31 | 32 | def time_step(): 33 | return __data["time"].time_step() 34 | 35 | 36 | def saveper(): 37 | return __data["time"].saveper() 38 | -------------------------------------------------------------------------------- /tests/more-tests/random/test_random.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | A B uniform matrix[Dims,Subs]= 3 | RANDOM UNIFORM(10, 11, 1) 4 | ~ 5 | ~ | 6 | 7 | A B uniform matrix 1[Dims,Subs]= 8 | RANDOM UNIFORM(my var[Dims]+100, 200, 1) 9 | ~ 10 | ~ | 11 | 12 | A B uniform matrix 1 0[Dims,Subs]= 13 | RANDOM UNIFORM(my var2[Subs], my var[Dims], 1) 14 | ~ 15 | ~ | 16 | 17 | A B uniform scalar= 18 | RANDOM UNIFORM(-1, 10, 1) 19 | ~ 20 | ~ | 21 | 22 | A B uniform vec[Dims]= 23 | RANDOM UNIFORM(2, 3, 1) 24 | ~ 25 | ~ | 26 | 27 | A B uniform vec 1[Dims]= 28 | RANDOM UNIFORM(my var[Dims], 100, 1) 29 | ~ 30 | ~ | 31 | 32 | Dims: 33 | (Dim1-Dim25) 34 | ~ 35 | ~ | 36 | 37 | my var[Dims]= 38 | Dims-50 39 | ~ 40 | ~ | 41 | 42 | my var2[Subs]= 43 | 2*Subs 44 | ~ 45 | ~ | 46 | 47 | normal A B uniform matrix[Dims,Subs]= 48 | RANDOM NORMAL(-1, 10, 5, 1, 1) 49 | ~ 50 | ~ | 51 | 52 | normal A B uniform matrix 1[Dims,Subs]= 53 | RANDOM NORMAL(my var[Dims], my var[Dims]+5, my var[Dims]+2, 10, 1) 54 | ~ 55 | ~ | 56 | 57 | normal A B uniform matrix 1 0[Dims,Subs]= 58 | RANDOM NORMAL(my var[Dims], my var[Dims]+5, my var[Dims]+2, my var2[Subs], 1) 59 | ~ 60 | ~ | 61 | 62 | normal scalar= 63 | RANDOM NORMAL(2, 4, 0.5, 2, 1) 64 | ~ 65 | ~ | 66 | 67 | normal vec[Dims]= 68 | RANDOM NORMAL(1, 5, 3.5, 10, 1) 69 | ~ 70 | ~ | 71 | 72 | normal vec 1[Dims]= 73 | RANDOM NORMAL(my var[Dims], 400, my var[Dims]+200, 2, 1) 74 | ~ 75 | ~ | 76 | 77 | Subs: 78 | (sub1-sub50) 79 | ~ 80 | ~ | 81 | 82 | uniform matrix[Dims,Subs]= 83 | RANDOM 0 1() 84 | ~ 85 | ~ | 86 | 87 | uniform scalar= 88 | RANDOM 0 1() 89 | ~ 90 | ~ | 91 | 92 | uniform vec[Dims]= 93 | RANDOM 0 1() 94 | ~ 95 | ~ | 96 | 97 | ******************************************************** 98 | .Control 99 | ********************************************************~ 100 | Simulation Control Parameters 101 | | 102 | 103 | FINAL TIME = 10 104 | ~ Month 105 | ~ The final time for the simulation. 106 | | 107 | 108 | INITIAL TIME = 0 109 | ~ Month 110 | ~ The initial time for the simulation. 111 | | 112 | 113 | SAVEPER = 114 | TIME STEP 115 | ~ Month [0,?] 116 | ~ The frequency with which output is stored. 117 | | 118 | 119 | TIME STEP = 1 120 | ~ Month [0,?] 121 | ~ The time step for the simulation. 122 | | 123 | 124 | \\\---/// Sketch information - do not modify anything except names 125 | V300 Do not put anything below this section - it will be ignored 126 | *View 1 127 | $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 128 | 10,1,uniform scalar,275,125,44,11,8,3,0,0,0,0,0,0 129 | 10,2,uniform vec,254,206,37,11,8,3,0,0,0,0,0,0 130 | 10,3,uniform matrix,352,338,45,11,8,3,0,0,0,0,0,0 131 | 10,4,A B uniform scalar,524,129,59,11,8,3,0,0,0,0,0,0 132 | 10,5,A B uniform vec,503,210,52,11,8,3,0,0,0,0,0,0 133 | 10,6,A B uniform matrix,601,342,60,11,8,3,0,0,0,0,0,0 134 | 10,7,A B uniform vec 1,733,205,58,11,8,3,0,0,0,0,0,0 135 | 10,8,A B uniform matrix 1,831,337,40,19,8,3,0,0,0,0,0,0 136 | 10,9,A B uniform matrix 1 0,967,340,40,19,8,3,0,0,0,0,0,0 137 | 10,10,my var,949,87,23,11,8,3,0,0,0,0,0,0 138 | 1,11,10,8,0,0,0,0,0,128,0,-1--1--1,,1|(894,201)| 139 | 1,12,10,7,0,0,0,0,0,128,0,-1--1--1,,1|(847,142)| 140 | 1,13,10,9,0,0,0,0,0,128,0,-1--1--1,,1|(956,202)| 141 | 10,14,my var2,1243,87,27,11,8,3,0,0,0,0,0,0 142 | 1,15,14,9,0,0,0,0,0,128,0,-1--1--1,,1|(1114,204)| 143 | 10,16,normal scalar,1157,311,45,19,8,3,0,0,0,0,0,0 144 | 10,17,normal vec,1162,373,37,19,8,3,0,0,0,0,0,0 145 | 10,18,normal A B uniform matrix,1281,367,46,19,8,3,0,0,0,0,0,0 146 | 10,19,normal vec 1,1188,262,44,19,8,3,0,0,0,0,0,0 147 | 10,20,normal A B uniform matrix 1,1511,362,63,19,8,3,0,0,0,0,0,0 148 | 10,21,normal A B uniform matrix 1 0,1647,365,63,19,8,3,0,0,0,0,0,0 149 | 1,22,10,19,0,0,0,0,0,128,0,-1--1--1,,1|(1057,166)| 150 | 1,23,10,20,0,0,0,0,0,128,0,-1--1--1,,1|(1215,217)| 151 | 1,24,10,21,0,0,0,0,0,128,0,-1--1--1,,1|(1278,218)| 152 | 1,25,14,20,0,0,0,0,0,128,0,-1--1--1,,1|(1367,215)| 153 | 1,26,14,21,0,0,0,0,0,128,0,-1--1--1,,1|(1432,218)| 154 | ///---\\\ 155 | :L<%^E!@ 156 | 1:Current.vdf 157 | 9:Current 158 | 15:0,0,0,0,0,0 159 | 19:100,0 160 | 27:0, 161 | 34:0, 162 | 4:Time 163 | 5:A B uniform matrix[Dims,Subs] 164 | 35:Date 165 | 36:YYYY-MM-DD 166 | 37:2000 167 | 38:1 168 | 39:1 169 | 40:2 170 | 41:0 171 | 42:1 172 | 24:0 173 | 25:100 174 | 26:100 175 | 57:1 176 | 54:0 177 | 55:0 178 | 59:0 179 | 56:0 180 | 58:0 181 | 44:65001 182 | 46:0 183 | 45:1 184 | 49:1 185 | 50:0 186 | 51: 187 | 52: 188 | 53: 189 | 43:output 190 | 47:Current 191 | 48: 192 | 6:Dim1 193 | 6:sub1 194 | -------------------------------------------------------------------------------- /tests/more-tests/random/test_random_scale0.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | 3 | normal1= 4 | RANDOM NORMAL(-1, 10, 5, 0, 1) 5 | ~ 6 | ~ | 7 | 8 | normal2= 9 | RANDOM NORMAL(5, 7, 6, 0, 1) 10 | ~ 11 | ~ | 12 | 13 | exponential1= 14 | RANDOM EXPONENTIAL(1, 3, 0, 0, 1) 15 | ~ 16 | ~ | 17 | 18 | exponential2= 19 | RANDOM EXPONENTIAL(0, 10, 7, 0, 1) 20 | ~ 21 | ~ | 22 | 23 | ******************************************************** 24 | .Control 25 | ********************************************************~ 26 | Simulation Control Parameters 27 | | 28 | 29 | FINAL TIME = 2 30 | ~ Month 31 | ~ The final time for the simulation. 32 | | 33 | 34 | INITIAL TIME = 0 35 | ~ Month 36 | ~ The initial time for the simulation. 37 | | 38 | 39 | SAVEPER = 40 | TIME STEP 41 | ~ Month [0,?] 42 | ~ The frequency with which output is stored. 43 | | 44 | 45 | TIME STEP = 1 46 | ~ Month [0,?] 47 | ~ The time step for the simulation. 48 | | 49 | -------------------------------------------------------------------------------- /tests/more-tests/split_model/input.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SDXorg/pysd/492e3903fd807ac521665b3caeca7d13f7ee3c5e/tests/more-tests/split_model/input.xlsx -------------------------------------------------------------------------------- /tests/more-tests/split_model/test_split_model.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | another var= 3 | 3*Stock 4 | ~ 5 | ~ | 6 | 7 | initial stock= 8 | GET DIRECT CONSTANTS('input.xlsx', 'Sheet1', 'A1') 9 | ~ 10 | ~ | 11 | 12 | initial stock correction= 13 | GET DIRECT CONSTANTS('input.xlsx', 'Sheet1', 'A2') 14 | ~ 15 | ~ | 16 | 17 | lookup table( 18 | (1,0), (10,2), (100,2), (1000,3), (10000,4)) 19 | ~ 20 | ~ | 21 | 22 | other stock= INTEG ( 23 | 6*"var-n", 24 | 3) 25 | ~ 26 | ~ | 27 | 28 | "rate-1"= 29 | "var-n" 30 | ~ 31 | ~ | 32 | 33 | Stock= INTEG ( 34 | "rate-1"+Time*initial stock correction, 35 | initial stock+initial stock correction) 36 | ~ 37 | ~ | 38 | 39 | "var-n"= 40 | 5 41 | ~ 42 | ~ | 43 | 44 | "variable-x"= 45 | lookup table(6*another var) 46 | ~ 47 | ~ | 48 | 49 | ******************************************************** 50 | .Control 51 | ********************************************************~ 52 | Simulation Control Parameters 53 | | 54 | 55 | FINAL TIME = 100 56 | ~ Month 57 | ~ The final time for the simulation. 58 | | 59 | 60 | INITIAL TIME = 0 61 | ~ Month 62 | ~ The initial time for the simulation. 63 | | 64 | 65 | SAVEPER = 66 | TIME STEP 67 | ~ Month [0,?] 68 | ~ The frequency with which output is stored. 69 | | 70 | 71 | TIME STEP = 1 72 | ~ Month [0,?] 73 | ~ The time step for the simulation. 74 | | 75 | 76 | \\\---/// Sketch information - do not modify anything except names 77 | V300 Do not put anything below this section - it will be ignored 78 | *View 1 79 | $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 80 | 10,1,Stock,497,237,40,20,3,3,0,0,0,0,0,0 81 | 12,2,48,297,243,10,8,0,3,0,0,-1,0,0,0 82 | 1,3,5,1,4,0,0,22,0,0,0,-1--1--1,,1|(422,243)| 83 | 1,4,5,2,100,0,0,22,0,0,0,-1--1--1,,1|(341,243)| 84 | 11,5,48,382,243,6,8,34,3,0,0,1,0,0,0 85 | 10,6,"rate-1",382,262,21,11,40,3,0,0,-1,0,0,0 86 | 12,7,0,1141,258,150,150,3,12,0,0,1,0,0,0 87 | Stock 88 | 10,8,"var-n",207,367,18,11,8,3,0,0,0,0,0,0 89 | 1,9,8,6,0,0,0,0,0,128,0,-1--1--1,,1|(288,318)| 90 | 10,10,initial stock,431,347,36,11,8,3,0,0,0,0,0,0 91 | 1,11,10,1,0,0,0,0,0,128,0,-1--1--1,,1|(457,302)| 92 | 10,12,other stock,371,488,40,20,3,3,0,0,0,0,0,0 93 | 12,13,48,95,500,10,8,0,3,0,0,-1,0,0,0 94 | 11,14,48,218,500,6,8,2,3,0,0,1,0,0,0 95 | 1,15,14,12,4,0,0,22,0,0,0,-1--1--1,,1|(277,500)| 96 | 1,16,14,13,100,0,0,22,0,0,0,-1--1--1,,1|(158,500)| 97 | 1,17,8,14,0,0,0,0,0,128,0,-1--1--1,,1|(211,429)| 98 | 10,18,Time,445,175,26,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 99 | 1,19,18,1,0,0,0,0,0,128,0,-1--1--1,,1|(462,196)| 100 | 10,20,initial stock correction,573,348,34,19,8,3,0,0,0,0,0,0 101 | 1,21,20,1,0,0,0,0,0,128,0,-1--1--1,,1|(538,298)| 102 | \\\---/// Sketch information - do not modify anything except names 103 | V300 Do not put anything below this section - it will be ignored 104 | *View-2 105 | $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 106 | 10,1,another var,89,168,36,11,8,3,0,0,0,0,0,0 107 | 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 108 | 1,3,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(221,209)| 109 | 10,4,lookup table,222,122,40,11,8,3,0,0,0,0,0,0 110 | \\\---/// Sketch information - do not modify anything except names 111 | V300 Do not put anything below this section - it will be ignored 112 | *View 3 113 | $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 114 | 10,1,"variable-x",191,176,32,11,8,3,0,0,0,0,0,0 115 | 10,2,another var,199,388,45,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 116 | 12,3,0,461,148,43,11,8,7,0,0,-1,0,0,0 117 | This is view 3 118 | 1,4,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(195,288)| 119 | 10,5,lookup table,334,294,49,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 120 | 1,6,5,1,0,0,0,0,0,128,0,-1--1--1,,1|(267,239)| 121 | \\\---/// Sketch information - do not modify anything except names 122 | V300 Do not put anything below this section - it will be ignored 123 | *View 4 124 | $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 125 | ///---\\\ 126 | :L<%^E!@ 127 | 1:Current.vdf 128 | 9:Current 129 | 15:0,0,0,0,0,0 130 | 19:100,0 131 | 27:0, 132 | 34:0, 133 | 4:Time 134 | 5:another var 135 | 35:Date 136 | 36:YYYY-MM-DD 137 | 37:2000 138 | 38:1 139 | 39:1 140 | 40:2 141 | 41:0 142 | 42:1 143 | 24:0 144 | 25:100 145 | 26:100 146 | -------------------------------------------------------------------------------- /tests/more-tests/split_model/test_split_model_sub_subviews.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | other stock= INTEG ( 3 | var tolo, 4 | 0) 5 | ~ 6 | ~ | 7 | 8 | interesting var 2 looked up= 9 | look up definition(interesting var 2) 10 | ~ 11 | ~ | 12 | 13 | look up definition( 14 | (1,0), (10,1), (50,1.5), (100,4), (1000,5), (10000,3), (100000,4)) 15 | ~ 16 | ~ | 17 | 18 | var tolo= 19 | 55+great var 20 | ~ 21 | ~ | 22 | 23 | great var= 24 | 5 25 | ~ 26 | ~ | 27 | 28 | interesting var 1= 29 | "variable-x"+1 30 | ~ 31 | ~ | 32 | 33 | interesting var 2= 34 | interesting var 1*5 35 | ~ 36 | ~ | 37 | 38 | another var= 39 | 3*Stock 40 | ~ 41 | ~ | 42 | 43 | "rate-1"= 44 | "var-n" 45 | ~ 46 | ~ | 47 | 48 | "var-n"= 49 | 5 50 | ~ 51 | ~ | 52 | 53 | "variable-x"= 54 | ACTIVE INITIAL(6*another var, 1) 55 | ~ 56 | ~ | 57 | 58 | Stock= INTEG ( 59 | "rate-1", 60 | 1) 61 | ~ 62 | ~ | 63 | 64 | ******************************************************** 65 | .Control 66 | ********************************************************~ 67 | Simulation Control Parameters 68 | | 69 | 70 | FINAL TIME = 100 71 | ~ Month 72 | ~ The final time for the simulation. 73 | | 74 | 75 | INITIAL TIME = 0 76 | ~ Month 77 | ~ The initial time for the simulation. 78 | | 79 | 80 | SAVEPER = 81 | TIME STEP 82 | ~ Month [0,?] 83 | ~ The frequency with which output is stored. 84 | | 85 | 86 | TIME STEP = 1 87 | ~ Month [0,?] 88 | ~ The time step for the simulation. 89 | | 90 | 91 | \\\---/// Sketch information - do not modify anything except names 92 | V300 Do not put anything below this section - it will be ignored 93 | *View 1.Submodule 1 94 | $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 95 | 10,1,Stock,497,237,40,20,3,3,0,0,0,0,0,0 96 | 12,2,48,297,243,10,8,0,3,0,0,-1,0,0,0 97 | 1,3,5,1,4,0,0,22,0,0,0,-1--1--1,,1|(422,243)| 98 | 1,4,5,2,100,0,0,22,0,0,0,-1--1--1,,1|(341,243)| 99 | 11,5,48,382,243,6,8,34,3,0,0,1,0,0,0 100 | 10,6,"rate-1",382,262,21,11,40,3,0,0,-1,0,0,0 101 | 12,7,0,1141,258,150,150,3,12,0,0,1,0,0,0 102 | Stock 103 | 10,8,"var-n",207,367,18,11,8,3,0,0,0,0,0,0 104 | 1,9,8,6,0,0,0,0,0,128,0,-1--1--1,,1|(288,318)| 105 | \\\---/// Sketch information - do not modify anything except names 106 | V300 Do not put anything below this section - it will be ignored 107 | *View 1.Submodule 2 108 | $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 109 | 10,1,another var,89,168,36,11,8,3,0,0,0,0,0,0 110 | 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 111 | 1,3,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(221,209)| 112 | \\\---/// Sketch information - do not modify anything except names 113 | V300 Do not put anything below this section - it will be ignored 114 | *View 2 115 | $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 116 | 10,1,"variable-x",191,176,32,11,8,3,0,0,0,0,0,0 117 | 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 118 | 12,3,0,461,148,43,11,8,7,0,0,-1,0,0,0 119 | This is view 2 120 | 1,4,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(208,292)| 121 | 10,5,look up definition,409,316,54,11,8,3,0,0,0,0,0,0 122 | \\\---/// Sketch information - do not modify anything except names 123 | V300 Do not put anything below this section - it will be ignored 124 | *View 3.Subview 1-Sview 1 125 | $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 126 | 10,1,"variable-x",260,232,41,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 127 | 10,2,interesting var 1,490,221,50,11,8,3,0,0,0,0,0,0 128 | 1,3,1,2,1,0,0,0,0,128,0,-1--1--1,,1|(269,233)| 129 | \\\---/// Sketch information - do not modify anything except names 130 | V300 Do not put anything below this section - it will be ignored 131 | *View 3.Subview 1-Sview 2 132 | $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 133 | 10,1,interesting var 1,249,173,59,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 134 | 10,2,interesting var 2,484,153,50,11,8,3,0,0,0,0,0,0 135 | 1,3,1,2,0,0,0,0,0,128,0,-1--1--1,,1|(363,163)| 136 | 10,4,interesting var 2 looked up,501,230,50,19,8,3,0,0,0,0,0,0 137 | 1,5,2,4,0,0,0,0,0,128,0,-1--1--1,,1|(489,180)| 138 | 10,6,look up definition,274,221,34,19,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|0||128-128-128 139 | 1,7,6,4,0,0,0,0,0,128,0,-1--1--1,,1|(372,224)| 140 | \\\---/// Sketch information - do not modify anything except names 141 | V300 Do not put anything below this section - it will be ignored 142 | *View 3.subview 2-sview 3 143 | $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 144 | 10,1,great var,291,291,29,11,8,3,0,0,0,0,0,0 145 | \\\---/// Sketch information - do not modify anything except names 146 | V300 Do not put anything below this section - it will be ignored 147 | *View 3.Subview 2-sview 4 148 | $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 149 | 10,1,var tolo,295,147,25,11,8,3,0,0,0,0,0,0 150 | 10,2,great var,128,103,38,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|12||128-128-128 151 | 1,3,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(211,124)| 152 | 10,6,other stock,414,201,40,20,3,3,0,0,0,0,0,0 153 | 12,7,48,214,216,10,8,0,3,0,0,-1,0,0,0 154 | 11,8,0,299,214,6,8,2,3,0,0,1,0,0,0 155 | 1,9,8,6,4,0,0,22,0,0,0,-1--1--1,,1|(339,214)| 156 | 1,10,8,7,100,0,0,22,0,0,0,-1--1--1,,1|(258,214)| 157 | 1,16,1,8,0,0,0,0,0,128,0,-1--1--1,,1|(296,176)| 158 | ///---\\\ 159 | :L<%^E!@ 160 | 1:Current.vdf 161 | 9:Current 162 | 15:0,0,0,0,0,0 163 | 19:100,6 164 | 27:0, 165 | 34:0, 166 | 4:Time 167 | 5:other stock 168 | 35:Date 169 | 36:YYYY-MM-DD 170 | 37:2000 171 | 38:1 172 | 39:1 173 | 40:2 174 | 41:0 175 | 42:1 176 | 24:0 177 | 25:100 178 | 26:100 179 | -------------------------------------------------------------------------------- /tests/more-tests/split_model/test_split_model_subviews.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | other stock= INTEG ( 3 | "variable-x", 4 | 5) 5 | ~ 6 | ~ | 7 | 8 | another var= 9 | 3*Stock 10 | ~ 11 | ~ | 12 | 13 | "rate-1"= 14 | "var-n" 15 | ~ 16 | ~ | 17 | 18 | "var-n"= 19 | 5 20 | ~ 21 | ~ | 22 | 23 | "variable-x"= 24 | 6*another var 25 | ~ 26 | ~ | 27 | 28 | Stock= INTEG ( 29 | "rate-1", 30 | 1) 31 | ~ 32 | ~ | 33 | 34 | ******************************************************** 35 | .Control 36 | ********************************************************~ 37 | Simulation Control Parameters 38 | | 39 | 40 | FINAL TIME = 100 41 | ~ Month 42 | ~ The final time for the simulation. 43 | | 44 | 45 | INITIAL TIME = 0 46 | ~ Month 47 | ~ The initial time for the simulation. 48 | | 49 | 50 | SAVEPER = 51 | TIME STEP 52 | ~ Month [0,?] 53 | ~ The frequency with which output is stored. 54 | | 55 | 56 | TIME STEP = 1 57 | ~ Month [0,?] 58 | ~ The time step for the simulation. 59 | | 60 | 61 | \\\---/// Sketch information - do not modify anything except names 62 | V300 Do not put anything below this section - it will be ignored 63 | *View 1.Submodule 1 64 | $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 65 | 10,1,Stock,497,237,40,20,3,3,0,0,0,0,0,0 66 | 12,2,48,297,243,10,8,0,3,0,0,-1,0,0,0 67 | 1,3,5,1,4,0,0,22,0,0,0,-1--1--1,,1|(422,243)| 68 | 1,4,5,2,100,0,0,22,0,0,0,-1--1--1,,1|(341,243)| 69 | 11,5,48,382,243,6,8,34,3,0,0,1,0,0,0 70 | 10,6,"rate-1",382,262,21,11,40,3,0,0,-1,0,0,0 71 | 12,7,0,1141,258,150,150,3,12,0,0,1,0,0,0 72 | Stock 73 | 10,8,"var-n",207,367,18,11,8,3,0,0,0,0,0,0 74 | 1,9,8,6,0,0,0,0,0,128,0,-1--1--1,,1|(288,318)| 75 | \\\---/// Sketch information - do not modify anything except names 76 | V300 Do not put anything below this section - it will be ignored 77 | *View 1.Submodule 2 78 | $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 79 | 10,1,another var,89,168,36,11,8,3,0,0,0,0,0,0 80 | 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 81 | 1,3,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(221,209)| 82 | \\\---/// Sketch information - do not modify anything except names 83 | V300 Do not put anything below this section - it will be ignored 84 | *View 2 85 | $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 86 | 10,1,"variable-x",186,173,32,11,8,3,0,0,0,0,0,0 87 | 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 88 | 12,3,0,461,148,43,11,8,7,0,0,-1,0,0,0 89 | This is view 2 90 | 1,4,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(205,290)| 91 | 10,5,other stock,459,338,40,20,3,3,0,0,0,0,0,0 92 | 12,6,48,288,322,10,8,0,3,0,0,-1,0,0,0 93 | 11,7,0,358,324,6,8,2,3,0,0,1,0,0,0 94 | 1,8,7,5,4,0,0,22,0,0,0,-1--1--1,,1|(391,324)| 95 | 1,9,7,6,100,0,0,22,0,0,0,-1--1--1,,1|(325,324)| 96 | 1,10,1,7,1,0,0,0,0,128,0,-1--1--1,,1|(305,221)| 97 | ///---\\\ 98 | :L<%^E!@ 99 | 1:Current.vdf 100 | 9:Current 101 | 15:0,0,0,0,0,0 102 | 19:100,2 103 | 27:0, 104 | 34:0, 105 | 4:Time 106 | 5:other stock 107 | 35:Date 108 | 36:YYYY-MM-DD 109 | 37:2000 110 | 38:1 111 | 39:1 112 | 40:2 113 | 41:0 114 | 42:1 115 | 24:0 116 | 25:100 117 | 26:100 118 | -------------------------------------------------------------------------------- /tests/more-tests/split_model/test_split_multiviews_vars.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | abc= 3 | 2 4 | ~ 5 | ~ | 6 | 7 | lmn= 8 | 4 9 | ~ 10 | ~ | 11 | 12 | xyz= 13 | 3 14 | ~ 15 | ~ | 16 | 17 | opq= 18 | 1 19 | ~ 20 | ~ | 21 | 22 | no view var= 23 | 0 24 | ~ 25 | ~ | 26 | 27 | my var= 28 | INITIAL TIME+10 29 | ~ 30 | ~ | 31 | 32 | ******************************************************** 33 | .Control 34 | ********************************************************~ 35 | Simulation Control Parameters 36 | | 37 | 38 | FINAL TIME = 1 39 | ~ Month 40 | ~ The final time for the simulation. 41 | | 42 | 43 | INITIAL TIME = 0 44 | ~ Month 45 | ~ The initial time for the simulation. 46 | | 47 | 48 | SAVEPER = 49 | TIME STEP 50 | ~ Month [0,?] 51 | ~ The frequency with which output is stored. 52 | | 53 | 54 | TIME STEP = 1 55 | ~ Month [0,?] 56 | ~ The time step for the simulation. 57 | | 58 | 59 | \\\---/// Sketch information - do not modify anything except names 60 | V300 Do not put anything below this section - it will be ignored 61 | *View 1 62 | $-1--1--1,0,|12||-1--1--1|-1--1--1|-1--1--1|-1--1--1|-1--1--1|96,96,100,0 63 | 10,1,my var,230,238,43,23,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 64 | 10,2,INITIAL TIME,230,284,40,18,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|||128-128-128,0,0,0,0,0,0 65 | 1,3,2,1,0,0,0,0,0,0,0,-1--1--1,,1|(0,0)| 66 | \\\---/// Sketch information - do not modify anything except names 67 | V300 Do not put anything below this section - it will be ignored 68 | *View 2 69 | $-1--1--1,0,|12||-1--1--1|-1--1--1|-1--1--1|-1--1--1|-1--1--1|96,96,100,0 70 | 10,1,my var,241,172,31,11,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 71 | 10,2,INITIAL TIME,272,90,58,11,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 72 | 1,3,2,1,0,0,0,0,0,192,0,-1--1--1,,1|(0,0)| 73 | \\\---/// Sketch information - do not modify anything except names 74 | V300 Do not put anything below this section - it will be ignored 75 | *View 3 76 | $-1--1--1,0,|12||-1--1--1|-1--1--1|-1--1--1|-1--1--1|-1--1--1|96,96,100,0 77 | 10,1,my var,255,374,40,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|||128-128-128,0,0,0,0,0,0 78 | 10,2,Time,338,146,22,11,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 79 | 10,3,INITIAL TIME,335,75,56,10,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|||128-128-128,0,0,0,0,0,0 80 | 1,4,3,2,0,0,0,0,0,0,1,-1--1--1,,1|(0,0)| 81 | 10,5,TIME STEP,552,244,44,11,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 82 | \\\---/// Sketch information - do not modify anything except names 83 | V300 Do not put anything below this section - it will be ignored 84 | *View 4 85 | $-1--1--1,0,|12||-1--1--1|-1--1--1|-1--1--1|-1--1--1|-1--1--1|96,96,100,0 86 | 10,1,abc,147,120,43,23,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 87 | 10,2,xyz,292,209,43,23,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 88 | 10,3,lmn,365,140,43,23,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 89 | 10,4,opq,553,224,43,23,8,3,0,0,-1,0,0,0,0,0,0,0,0,0 90 | ///---\\\ 91 | :L<%^E!@ 92 | 5:lmn 93 | 19:100,3 94 | 24:0 95 | 25:0 96 | 26:0 97 | 15:0,0,0,0,0,0 98 | 27:0, 99 | 34:0, 100 | 42:1 101 | 72:0 102 | 73:0 103 | 35:Date 104 | 36:YYYY-MM-DD 105 | 37:2000 106 | 38:1 107 | 39:1 108 | 40:2 109 | 41:0 110 | 95:0 111 | 96:0 112 | 97:0 113 | 77:0 114 | 78:0 115 | 102:1 116 | 93:0 117 | 94:0 118 | 92:0 119 | 91:0 120 | 90:0 121 | 87:0 122 | 75: 123 | 43: 124 | 103:8,8,8,3,8 125 | 105:0,0,0,0,0,0,0,0,0,0 126 | 104:Courier|12||0-0-0|0-0-0|-1--1--1|0-0-255|192-192-192|-1--1--1 127 | -------------------------------------------------------------------------------- /tests/more-tests/split_model_vensim_8_2_1/test_split_model_vensim_8_2_1.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | Heating= 3 | (Cream Temperature - Room Temperature) / Characteristic Time 4 | ~ 5 | ~ | 6 | 7 | Cream Temperature= INTEG ( 8 | -Heating, 9 | 10) 10 | ~ Degrees 11 | ~ | 12 | 13 | Room Temperature= 14 | 70 15 | ~ Degrees 16 | ~ | 17 | 18 | Transfer Coef= 19 | 0.37 20 | ~ 21 | ~ | 22 | 23 | Heat Loss to Room= 24 | (Teacup Temperature - Room Temperature) * Transfer Coef / Characteristic Time 25 | ~ Degrees/Minute 26 | ~ This is the rate at which heat flows from the cup into the room. We can \ 27 | ignore it at this point. 28 | | 29 | 30 | Characteristic Time= 31 | 10 32 | ~ Minutes 33 | ~ | 34 | 35 | Teacup Temperature= INTEG ( 36 | -Heat Loss to Room, 37 | 100) 38 | ~ Degrees 39 | ~ | 40 | 41 | ******************************************************** 42 | .Control 43 | ********************************************************~ 44 | Simulation Control Parameters 45 | | 46 | 47 | FINAL TIME = 30 48 | ~ Minute 49 | ~ The final time for the simulation. 50 | | 51 | 52 | INITIAL TIME = 0 53 | ~ Minute 54 | ~ The initial time for the simulation. 55 | | 56 | 57 | SAVEPER = 58 | TIME STEP 59 | ~ Minute [0,?] 60 | ~ The frequency with which output is stored. 61 | | 62 | 63 | TIME STEP = 0.125 64 | ~ Minute [0,?] 65 | ~ The time step for the simulation. 66 | | 67 | 68 | \\\---/// Sketch information - do not modify anything except names 69 | V300 Do not put anything below this section - it will be ignored 70 | *TeaCup 71 | $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 72 | 10,1,Teacup Temperature,307,224,40,20,3,3,0,0,0,0,0,0,0,0,0,0,0,0 73 | 12,2,48,605,221,8,8,0,3,0,0,-1,0,0,0,0,0,0,0,0,0 74 | 1,3,5,2,4,0,0,22,0,0,0,-1--1--1,,1|(508,220)| 75 | 1,4,5,1,100,0,0,22,0,0,0,-1--1--1,,1|(377,220)| 76 | 11,5,48,413,220,5,8,34,3,0,0,1,0,0,0,0,0,0,0,0,0 77 | 10,6,Heat Loss to Room,413,232,49,8,40,3,0,0,-1,0,0,0,0,0,0,0,0,0 78 | 10,7,Room Temperature,504,373,49,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0 79 | 10,8,Characteristic Time,408,164,49,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0 80 | 1,9,8,5,0,0,0,0,0,64,0,-1--1--1,,1|(412,187)| 81 | 1,10,1,6,1,0,0,0,0,64,0,-1--1--1,,1|(393,308)| 82 | 1,11,7,6,1,0,0,0,0,64,0,-1--1--1,,1|(477,336)| 83 | 10,12,Transfer Coef,541,164,33,8,8,3,0,0,0,0,0,0,0,0,0,0,0,0 84 | 1,13,12,6,0,0,0,0,0,64,0,-1--1--1,,1|(484,195)| 85 | \\\---/// Sketch information - do not modify anything except names 86 | V300 Do not put anything below this section - it will be ignored 87 | *Cream 88 | $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 89 | 10,1,Cream Temperature,363,287,56,15,3,131,0,0,0,0,0,0,0,0,0,0,0,0 90 | 12,2,48,680,284,8,8,0,3,0,0,-1,0,0,0,0,0,0,0,0,0 91 | 1,3,5,2,4,0,0,22,0,0,0,-1--1--1,,1|(611,284)| 92 | 1,4,5,1,100,0,0,22,0,0,0,-1--1--1,,1|(480,284)| 93 | 11,5,0,545,284,5,8,34,3,0,0,1,0,0,0,0,0,0,0,0,0 94 | 10,6,Heating,545,296,19,8,40,3,0,0,-1,0,0,0,0,0,0,0,0,0 95 | 10,7,Room Temperature,532,407,35,16,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|||128-128-128,0,0,0,0,0,0 96 | 1,8,7,6,1,0,0,0,0,64,0,-1--1--1,,1|(608,364)| 97 | 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 98 | 1,10,9,6,1,0,0,0,0,64,0,-1--1--1,,1|(593,241)| 99 | 1,11,1,6,1,0,0,0,0,64,0,-1--1--1,,1|(460,341)| 100 | ///---\\\ 101 | :L<%^E!@ 102 | 4:Time 103 | 5:Teacup Temperature 104 | 9:Current 105 | 19:100,0 106 | 24:0 107 | 25:30 108 | 26:30 109 | 22:$,Dollar,Dollars,$s 110 | 22:Hour,Hours 111 | 22:Month,Months 112 | 22:Person,People,Persons 113 | 22:Unit,Units 114 | 22:Week,Weeks 115 | 22:Year,Years 116 | 22:Day,Days 117 | 23:0 118 | 15:0,0,0,0,0,0 119 | 27:0, 120 | 34:0, 121 | 42:1 122 | 72:0 123 | 73:0 124 | 35:Date 125 | 36:YYYY-MM-DD 126 | 37:2000 127 | 38:1 128 | 39:1 129 | 40:6 130 | 41:0 131 | 95:0 132 | 96:0 133 | 77:0 134 | 78:0 135 | 93:0 136 | 94:0 137 | 92:0 138 | 91:0 139 | 90:0 140 | 87:0 141 | 75: 142 | 43: 143 | 144 | -------------------------------------------------------------------------------- /tests/more-tests/split_model_with_macro/test_split_model_with_macro.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | :MACRO: EXPRESSION MACRO(input, parameter) 3 | EXPRESSION MACRO = INTEG(input, parameter) 4 | ~ input 5 | ~ tests basic macro containing a stock but no output 6 | | 7 | 8 | :END OF MACRO: 9 | new var= 10 | 2 * macro output 11 | ~ 12 | ~ | 13 | 14 | macro input= 15 | 5 16 | ~ 17 | ~ | 18 | 19 | macro output= 20 | EXPRESSION MACRO(macro input,macro parameter) 21 | ~ 22 | ~ | 23 | 24 | macro parameter= 25 | 1.1 26 | ~ 27 | ~ | 28 | 29 | ******************************************************** 30 | .Control 31 | ********************************************************~ 32 | Simulation Control Parameters 33 | | 34 | 35 | FINAL TIME = 10 36 | ~ Month 37 | ~ The final time for the simulation. 38 | | 39 | 40 | INITIAL TIME = 0 41 | ~ Month 42 | ~ The initial time for the simulation. 43 | | 44 | 45 | SAVEPER = 46 | TIME STEP 47 | ~ Month [0,?] 48 | ~ The frequency with which output is stored. 49 | | 50 | 51 | TIME STEP = 1 52 | ~ Month [0,?] 53 | ~ The time step for the simulation. 54 | | 55 | 56 | \\\---/// Sketch information - do not modify anything except names 57 | V300 Do not put anything below this section - it will be ignored 58 | *View 1 59 | $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 60 | 10,1,macro input,340,220,41,11,8,3,0,0,0,0,0,0 61 | 10,2,macro output,511,217,45,11,8,3,0,0,0,0,0,0 62 | 1,3,1,2,0,0,0,0,0,128,0,-1--1--1,,1|(413,219)| 63 | 10,4,macro parameter,468,143,57,11,8,3,0,0,0,0,0,0 64 | 1,5,4,2,0,0,0,0,0,128,0,-1--1--1,,1|(484,171)| 65 | \\\---/// Sketch information - do not modify anything except names 66 | V300 Do not put anything below this section - it will be ignored 67 | *View 2 68 | $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 69 | 10,1,new var,178,172,26,11,8,3,0,0,0,0,0,0 70 | 10,2,macro output,141,92,51,11,8,2,0,3,-1,0,0,0,128-128-128,0-0-0,|0||128-128-128 71 | 1,3,2,1,0,0,0,0,0,128,0,-1--1--1,,1|(156,125)| 72 | ///---\\\ 73 | :L<%^E!@ 74 | 9:Current 75 | 15:0,0,0,0,0,0 76 | 19:100,0 77 | 27:2, 78 | 34:0, 79 | 4:Time 80 | 5:macro output 81 | 35:Date 82 | 36:YYYY-MM-DD 83 | 37:2000 84 | 38:1 85 | 39:1 86 | 40:2 87 | 41:0 88 | 42:0 89 | 24:0 90 | 25:10 91 | 26:10 92 | -------------------------------------------------------------------------------- /tests/more-tests/stepper_cache/stepper_cache.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | bar= 3 | 2*foo 4 | ~ 5 | ~ | 6 | 7 | foo= 8 | 5 9 | ~ 10 | ~ | 11 | 12 | foobar= 13 | 2*bar 14 | ~ 15 | ~ | 16 | 17 | ******************************************************** 18 | .Control 19 | ********************************************************~ 20 | Simulation Control Parameters 21 | | 22 | 23 | FINAL_TIME = 5 24 | ~ Month 25 | ~ The final time for the simulation. 26 | | 27 | 28 | INITIAL_TIME = 0 29 | ~ Month 30 | ~ The initial time for the simulation. 31 | | 32 | 33 | SAVEPER = 34 | TIME_STEP 35 | ~ Month [0,?] 36 | ~ The frequency with which output is stored. 37 | | 38 | 39 | TIME_STEP = 1 40 | ~ Month [0,?] 41 | ~ The time step for the simulation. 42 | | 43 | 44 | \\\---/// Sketch information - do not modify anything except names 45 | V300 Do not put anything below this section - it will be ignored 46 | *View 1 47 | $192-192-192,0,Courier|12||0-0-0|0-0-0|0-0-255|-1--1--1|255-255-255|96,96,100,0 48 | 10,1,foo,224,216,16,9,8,3,0,0,0,0,0,0,0,0,0,0,0,0 49 | 10,2,bar,397,218,16,9,8,3,0,0,0,0,0,0,0,0,0,0,0,0 50 | 10,3,foobar,571,216,29,9,8,3,0,0,0,0,0,0,0,0,0,0,0,0 51 | 1,4,1,2,0,0,0,0,0,128,0,-1--1--1,,1|(0,0)| 52 | 1,5,2,3,0,0,0,0,0,128,0,-1--1--1,,1|(0,0)| 53 | ///---\\\ 54 | :L<%^E!@ 55 | 5:foobar 56 | 19:100,0 57 | 24:0 58 | 25:0 59 | 26:0 60 | 15:0,0,0,0,0,0 61 | 27:0, 62 | 34:0, 63 | 42:0 64 | 72:0 65 | 73:0 66 | 35:Date 67 | 36:YYYY-MM-DD 68 | 37:2000 69 | 38:1 70 | 39:1 71 | 40:2 72 | 41:0 73 | 95:0 74 | 96:0 75 | 97:0 76 | 77:0 77 | 78:0 78 | 102:1 79 | 93:0 80 | 94:0 81 | 92:0 82 | 91:0 83 | 90:0 84 | 87:0 85 | 75: 86 | 43: 87 | 103:8,8,8,3,8 88 | 105:0,0,0,0,0,0,0,0,0,0 89 | 104:Courier|12||0-0-0|0-0-0|-1--1--1|0-0-255|192-192-192|-1--1--1 90 | -------------------------------------------------------------------------------- /tests/more-tests/subscript_individually_defined_stocks2/test_subscript_individually_defined_stocks2.mdl: -------------------------------------------------------------------------------- 1 | {UTF-8} 2 | Stock A[One Dimensional Subscript,Second Dimension Subscript,Depth 1]= INTEG ( 3 | Inflow A[One Dimensional Subscript,Second Dimension Subscript], 4 | Initial Values[One Dimensional Subscript,Second Dimension Subscript,Depth 1]) ~~| 5 | Stock A[One Dimensional Subscript,Second Dimension Subscript,Depth 2]= INTEG ( 6 | Inflow B[One Dimensional Subscript,Second Dimension Subscript], 7 | Initial Values[One Dimensional Subscript,Second Dimension Subscript,Depth 2]) 8 | ~ 9 | ~ | 10 | 11 | Inflow A[One Dimensional Subscript,Second Dimension Subscript]= 12 | Rate A[One Dimensional Subscript,Second Dimension Subscript,Depth 1] 13 | ~ 14 | ~ | 15 | 16 | Inflow B[One Dimensional Subscript,Second Dimension Subscript]= 17 | Rate A[One Dimensional Subscript,Second Dimension Subscript,Depth 1] 18 | ~ 19 | ~ | 20 | Third Dimension Subscript: 21 | Depth 1, Depth 2 22 | ~ 23 | ~ | 24 | 25 | Initial Values[One Dimensional Subscript,Second Dimension Subscript,Depth 1]= 26 | Initial Values A[One Dimensional Subscript,Second Dimension Subscript] ~~| 27 | Initial Values[One Dimensional Subscript,Second Dimension Subscript,Depth 2]= 28 | Initial Values B[One Dimensional Subscript,Second Dimension Subscript] 29 | ~ 30 | ~ | 31 | 32 | Initial Values A[One Dimensional Subscript,Second Dimension Subscript]= 33 | 1, 2; 3, 4; 5, 6; ~~| 34 | Initial Values B[One Dimensional Subscript,Second Dimension Subscript]= 35 | 2, 4; 5, 3; 1, 4; 36 | ~ 37 | ~ | 38 | 39 | Second Dimension Subscript: 40 | Column 1, Column 2 41 | ~ 42 | ~ | 43 | 44 | One Dimensional Subscript: 45 | Entry 1, Entry 2, Entry 3 46 | ~ 47 | ~ | 48 | 49 | Rate A[One Dimensional Subscript,Second Dimension Subscript,Depth 1]= 50 | 0.01, 0.02; 0.03, 0.04; 0.05, 0.06; ~~| 51 | Rate A[One Dimensional Subscript,Second Dimension Subscript,Depth 2]= 52 | 0.02, 0.05; 0.02, 0.04; 0.05, 0.06; 53 | ~ 54 | ~ | 55 | 56 | ******************************************************** 57 | .Control 58 | ********************************************************~ 59 | Simulation Control Parameters 60 | | 61 | 62 | FINAL TIME = 100 63 | ~ Month 64 | ~ The final time for the simulation. 65 | | 66 | 67 | INITIAL TIME = 0 68 | ~ Month 69 | ~ The initial time for the simulation. 70 | | 71 | 72 | SAVEPER = 73 | TIME STEP 74 | ~ Month [0,?] 75 | ~ The frequency with which output is stored. 76 | | 77 | 78 | TIME STEP = 1 79 | ~ Month [0,?] 80 | ~ The time step for the simulation. 81 | | 82 | 83 | \\\---/// Sketch information - do not modify anything except names 84 | V300 Do not put anything below this section - it will be ignored 85 | *View 1 86 | $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 87 | 10,1,Stock A,420,222,40,20,3,3,0,0,0,0,0,0 88 | 12,2,48,257,223,10,8,0,3,0,0,-1,0,0,0 89 | 1,3,5,1,4,0,0,22,0,0,0,-1--1--1,,1|(354,223)| 90 | 1,4,5,2,100,0,0,22,0,0,0,-1--1--1,,1|(292,223)| 91 | 11,5,48,323,223,6,8,34,3,0,0,1,0,0,0 92 | 10,6,Inflow A,323,239,24,8,40,3,0,0,-1,0,0,0 93 | 10,7,Rate A,318,308,19,8,8,3,0,0,0,0,0,0 94 | 1,8,7,6,0,0,0,0,0,64,0,-1--1--1,,1|(319,280)| 95 | 10,9,Initial Values,415,180,34,8,8,3,0,0,0,0,0,0 96 | 1,10,9,1,0,0,0,0,0,64,1,-1--1--1,,1|(415,188)| 97 | ///---\\\ 98 | :L<%^E!@ 99 | 1:/Users/houghton/Google_Drive/Academic Projects/PYSD/test-models/tests/subscript_1d_arrays/Current.vdf 100 | 1:Current.vdf 101 | 9:Current 102 | 22:$,Dollar,Dollars,$s 103 | 22:Hour,Hours 104 | 22:Month,Months 105 | 22:Person,People,Persons 106 | 22:Unit,Units 107 | 22:Week,Weeks 108 | 22:Year,Years 109 | 22:Day,Days 110 | 15:0,0,0,0,0,0 111 | 19:100,0 112 | 27:2, 113 | 34:0, 114 | 4:Time 115 | 5:Stock A[One Dimensional Subscript,Second Dimension Subscript,Third Dimension Subscript] 116 | 35:Date 117 | 36:YYYY-MM-DD 118 | 37:2000 119 | 38:1 120 | 39:1 121 | 40:2 122 | 41:0 123 | 42:1 124 | 24:0 125 | 25:100 126 | 26:100 127 | 6:Column 1 128 | 6:Column 2 129 | 6:Depth 1 130 | 6:Entry 1 131 | 6:Entry 2 132 | 6:Entry 3 133 | -------------------------------------------------------------------------------- /tests/more-tests/type_error/test_type_error.py: -------------------------------------------------------------------------------- 1 | from pysd import external, Component 2 | 3 | __pysd_version__ = "3.0.0" 4 | _root = './' 5 | 6 | component = Component() 7 | 8 | external.ExtData('input.xlsx', 'Sheet1', '5', 'B6') 9 | -------------------------------------------------------------------------------- /tests/pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | python_files = pytest_*/**/*.py pytest_*/*.py 3 | filterwarnings = 4 | error 5 | always:numpy.ndarray size changed, may indicate binary incompatibility.:RuntimeWarning 6 | always::DeprecationWarning 7 | always::PendingDeprecationWarning 8 | -------------------------------------------------------------------------------- /tests/pytest_pysd/pytest_errors.py: -------------------------------------------------------------------------------- 1 | import re 2 | import shutil 3 | 4 | import pytest 5 | 6 | from pysd import read_vensim, read_xmile, load 7 | 8 | 9 | @pytest.fixture 10 | def original_path(_root, name, suffix): 11 | return _root / "more-tests" / name / f"test_{name}.{suffix}" 12 | 13 | 14 | @pytest.fixture 15 | def model_path(original_path, shared_tmpdir, _root): 16 | """ 17 | Copy test folder to a temporary folder therefore we avoid creating 18 | PySD model files in the original folder 19 | """ 20 | new_file = shared_tmpdir / original_path.name 21 | shutil.copy( 22 | original_path, 23 | new_file 24 | ) 25 | return new_file 26 | 27 | 28 | @pytest.mark.parametrize( 29 | "name,suffix,loader,raise_type,error_message", 30 | [ 31 | ( # load_old_version 32 | "old_version", 33 | "py", 34 | load, 35 | ImportError, 36 | r"Not able to import the model\. The model was translated " 37 | r"with a not compatible version of PySD:\n\tPySD 1\.5\.0" 38 | ), 39 | ( # load_type 40 | "type_error", 41 | "py", 42 | load, 43 | ImportError, 44 | r".*Not able to import the model\. This may be because the " 45 | "model was compiled with an earlier version of PySD, you can " 46 | r"check on the top of the model file you are trying to load\..*" 47 | ), 48 | ( # not_vensim_model 49 | "not_vensim", 50 | "txt", 51 | read_vensim, 52 | ValueError, 53 | "The file to translate, " 54 | "'.*test_not_vensim.txt' is not a " 55 | r"Vensim model\. It must end with \.mdl extension\." 56 | ), 57 | ( # not_xmile_model 58 | "not_vensim", 59 | "txt", 60 | read_xmile, 61 | ValueError, 62 | "The file to translate, " 63 | "'.*test_not_vensim.txt' is not a " 64 | r"Xmile model\. It must end with any of \.xmile, \.xml, " 65 | r"\.stmx extensions\." 66 | ), 67 | ( # circular_reference 68 | "circular_reference", 69 | "py", 70 | load, 71 | ValueError, 72 | r"Circular initialization\.\.\.\nNot able to initialize the " 73 | "following objects:\n\t_integ_integ\n\t_delay_delay" 74 | ), 75 | ], 76 | ids=[ 77 | "old_version", "load_type", 78 | "not_vensim_model", "not_xmile_model", 79 | "circular_reference" 80 | ] 81 | ) 82 | def test_loading_error(loader, model_path, raise_type, error_message): 83 | with pytest.raises(raise_type, match=error_message): 84 | loader(model_path) 85 | 86 | 87 | @pytest.mark.parametrize( 88 | "name,suffix", 89 | [ 90 | ( # load_old_version 91 | "not_implemented_and_incomplete", 92 | "mdl", 93 | ), 94 | ] 95 | ) 96 | def test_not_implemented_and_incomplete(model_path): 97 | with pytest.warns() as record: 98 | model = read_vensim(model_path) 99 | 100 | warn_message = "'incomplete var' has no equation specified" 101 | assert any([ 102 | re.match(warn_message, str(warn.message)) 103 | for warn in record 104 | ]), f"Couldn't match warning:\n{warn_message}" 105 | 106 | warn_message = "Trying to translate 'MY FUNC' which it is "\ 107 | "not implemented on PySD. The translated model will crash..." 108 | assert any([ 109 | re.match(warn_message, str(warn.message)) 110 | for warn in record 111 | ]), f"Couldn't match warning:\n{warn_message}" 112 | 113 | with pytest.warns(RuntimeWarning, 114 | match="Call to undefined function, calling dependencies " 115 | "and returning NaN"): 116 | model["incomplete var"] 117 | 118 | with pytest.raises(NotImplementedError, 119 | match="Not implemented function 'my_func'"): 120 | model["not implemented function"] 121 | -------------------------------------------------------------------------------- /tests/pytest_pysd/pytest_model_attributes.py: -------------------------------------------------------------------------------- 1 | import shutil 2 | import pytest 3 | from pathlib import Path 4 | 5 | import pysd 6 | 7 | 8 | @pytest.fixture(scope="class") 9 | def input_file(shared_tmpdir, _root): 10 | input_path = Path("more-tests/split_model/input.xlsx") 11 | shutil.copy( 12 | _root.joinpath(input_path), 13 | shared_tmpdir.joinpath(input_path.name) 14 | ) 15 | 16 | 17 | @pytest.mark.parametrize( 18 | "model_path,subview_sep", 19 | [ 20 | ( # teacup 21 | Path("test-models/samples/teacup/teacup.mdl"), 22 | None 23 | ), 24 | ( # split_views 25 | Path("more-tests/split_model/test_split_model.mdl"), 26 | [] 27 | ), 28 | ( # split_subviews 29 | Path("more-tests/split_model/test_split_model_subviews.mdl"), 30 | ["."] 31 | ), 32 | ( # split_sub_subviews 33 | Path("more-tests/split_model/test_split_model_sub_subviews.mdl"), 34 | [".", "-"] 35 | ) 36 | ], 37 | ids=["teacup", "split_views", "split_subviews", "split_sub_subviews"] 38 | ) 39 | class TestModelProperties(): 40 | 41 | @pytest.fixture 42 | def model(self, shared_tmpdir, model_path, subview_sep, _root, input_file): 43 | """ 44 | Translate the model or read a translated version. 45 | This way each file is only translated once. 46 | """ 47 | # expected file 48 | file = shared_tmpdir.joinpath(model_path.with_suffix(".py").name) 49 | if file.is_file(): 50 | # load already translated file 51 | return pysd.load(file) 52 | else: 53 | # copy mdl file to tmp_dir and translate it 54 | file = shared_tmpdir.joinpath(model_path.name) 55 | shutil.copy(_root.joinpath(model_path), file) 56 | return pysd.read_vensim( 57 | file, 58 | split_views=(subview_sep is not None), subview_sep=subview_sep) 59 | 60 | def test_propierties(self, model): 61 | # test are equal to model attributes they are copying 62 | assert model.namespace == model._namespace 63 | assert model.subscripts == model._subscript_dict 64 | assert model.dependencies == model._dependencies 65 | if model._modules: 66 | assert model.modules == model._modules 67 | else: 68 | assert model.modules is None 69 | 70 | # test thatwhen modifying a propierty by the user the model 71 | # attribute remains the same 72 | ns = model.namespace 73 | ns["Time"] = "my_new_time" 74 | assert ns != model._namespace 75 | assert ns != model.namespace 76 | sd = model.subscripts 77 | sd["my_new_subs"] = ["s1", "s2", "s3"] 78 | assert sd != model._subscript_dict 79 | assert sd != model.subscripts 80 | ds = model.dependencies 81 | ds["my_var"] = {"time": 1} 82 | assert ds != model._dependencies 83 | assert ds != model.dependencies 84 | if model._modules: 85 | ms = model.modules 86 | del ms[list(ms)[0]] 87 | assert ms != model._modules 88 | assert ms != model.modules 89 | -------------------------------------------------------------------------------- /tests/pytest_tools/pytest_benchmarking.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import pandas as pd 4 | 5 | from pysd.tools.benchmarking import runner, load_outputs, assert_frames_close 6 | 7 | # most of the features of this script are already tested indirectly when 8 | # running vensim and xmile integration tests 9 | 10 | 11 | class TestErrors(): 12 | 13 | def test_canonical_file_not_found(self, _root): 14 | error_message = 'Canonical output file not found.' 15 | with pytest.raises(FileNotFoundError, match=error_message): 16 | runner(_root.joinpath("more-tests/not_existent.mdl")) 17 | 18 | def test_non_valid_model(self, _root): 19 | error_message = r"The model file name must be a Vensim \(\.mdl\),"\ 20 | r" a Xmile \(\.xmile, \.xml, \.stmx\) or a PySD \(\.py\) "\ 21 | r"model file\.\.\.$" 22 | with pytest.raises(ValueError, match=error_message): 23 | runner(_root.joinpath("more-tests/not_vensim/test_not_vensim.txt")) 24 | 25 | def test_different_frames_error(self, _root): 26 | 27 | error_message = r"\nFollowing columns are not close:\n\t"\ 28 | r"Teacup Temperature\n\nFirst false values \(30\.0\):\n\t"\ 29 | r"Teacup Temperature$" 30 | 31 | with pytest.raises(AssertionError, match=error_message): 32 | assert_frames_close( 33 | load_outputs(_root.joinpath("data/out_teacup.csv")), 34 | load_outputs(_root.joinpath("data/out_teacup_modified.csv"))) 35 | 36 | error_message = r"\nFollowing columns are not close:\n\tTeacup "\ 37 | r"Temperature\n\nFirst false values \(30\.0\):\n\tTeacup "\ 38 | r"Temperature\n\nColumn 'Teacup Temperature' is not close\."\ 39 | r"\n\nExpected values:\n\t\[[\-0-9\s\n,\.]*\]"\ 40 | r"\n\nActual values:\n\t\[[\-0-9\s\n,\.]*\]"\ 41 | r"\n\nDifference:\n\t\[[\-0-9\s\n,\.]*\]$" 42 | 43 | with pytest.raises(AssertionError, match=error_message): 44 | assert_frames_close( 45 | load_outputs(_root.joinpath("data/out_teacup.csv")), 46 | load_outputs(_root.joinpath("data/out_teacup_modified.csv")), 47 | verbose=True) 48 | 49 | def test_different_frames_warning(self, _root): 50 | 51 | warn_message = r"\nFollowing columns are not close:\n\t"\ 52 | r"Teacup Temperature\n\nFirst false values \(30\.0\):\n\t"\ 53 | r"Teacup Temperature$" 54 | with pytest.warns(UserWarning, match=warn_message): 55 | assert_frames_close( 56 | load_outputs(_root.joinpath("data/out_teacup.csv")), 57 | load_outputs(_root.joinpath("data/out_teacup_modified.csv")), 58 | assertion="warn") 59 | 60 | warn_message = r"\nFollowing columns are not close:\n\tTeacup "\ 61 | r"Temperature\n\nFirst false values \(30\.0\):\n\tTeacup "\ 62 | r"Temperature\n\nColumn 'Teacup Temperature' is not close\."\ 63 | r"\n\nExpected values:\n\t\[[\-0-9\s\n,\.]*\]"\ 64 | r"\n\nActual values:\n\t\[[\-0-9\s\n,\.]*\]"\ 65 | r"\n\nDifference:\n\t\[[\-0-9\s\n,\.]*\]$" 66 | 67 | with pytest.warns(UserWarning, match=warn_message): 68 | assert_frames_close( 69 | load_outputs(_root.joinpath("data/out_teacup.csv")), 70 | load_outputs(_root.joinpath("data/out_teacup_modified.csv")), 71 | assertion="warn", verbose=True) 72 | 73 | def test_different_frames_return(self, _root): 74 | 75 | cols, first_false_time, first_false_cols = assert_frames_close( 76 | load_outputs(_root.joinpath("data/out_teacup.csv")), 77 | load_outputs(_root.joinpath("data/out_teacup_modified.csv")), 78 | assertion="return") 79 | 80 | assert cols == {"Teacup Temperature"} 81 | assert first_false_time == 30. 82 | assert first_false_cols == {"Teacup Temperature"} 83 | 84 | def test_different_cols(self): 85 | d1 = pd.DataFrame({'a': [1, 2], 'b': [3, 4], 'd': [6, 7]}) 86 | d2 = pd.DataFrame({'a': [1, 2]}) 87 | d3 = pd.DataFrame({'a': [1, 2], 'c': [3, 4]}) 88 | 89 | error_message = r"Columns from actual and expected values must "\ 90 | r"be equal\.\nColumns 'b', 'd' from actual values not found"\ 91 | r" in expected values\.$" 92 | with pytest.raises(ValueError, match=error_message): 93 | assert_frames_close( 94 | actual=d1, 95 | expected=d2) 96 | 97 | warn_message = r"Columns 'b', 'd' from actual values not found "\ 98 | r"in expected values\.$" 99 | with pytest.warns(UserWarning, match=warn_message): 100 | assert_frames_close( 101 | actual=d1, 102 | expected=d2, 103 | assertion="warn") 104 | 105 | warn_message = r"Columns 'b', 'd' from expected values not found "\ 106 | r"in actual values\.$" 107 | with pytest.warns(UserWarning, match=warn_message): 108 | assert_frames_close( 109 | expected=d1, 110 | actual=d2, 111 | assertion="warn") 112 | 113 | warn_message = r"Columns 'b', 'd' from actual values not found "\ 114 | r"in expected values\.\nColumns 'c' from expected values not"\ 115 | r" found in actual values\.$" 116 | with pytest.warns(UserWarning, match=warn_message): 117 | assert_frames_close( 118 | actual=d1, 119 | expected=d3, 120 | assertion="warn") 121 | 122 | def test_invalid_input(self): 123 | 124 | error_message = r"Inputs must both be pandas DataFrames\." 125 | with pytest.raises(TypeError, match=error_message): 126 | assert_frames_close( 127 | actual=[1, 2], 128 | expected=[1, 2]) 129 | 130 | def test_run_python(self, _root): 131 | test_model = _root.joinpath("test-models/samples/teacup/teacup.mdl") 132 | assert ( 133 | runner(str(test_model))[0] 134 | == runner(test_model.with_suffix(".py"))[0] 135 | ).all().all() 136 | -------------------------------------------------------------------------------- /tests/pytest_types/data/pytest_columns.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import pytest 3 | import itertools 4 | 5 | from pysd.py_backend.data import Columns 6 | 7 | 8 | class TestColumns: 9 | @pytest.fixture(scope="class") 10 | def out_teacup(self, _root): 11 | return _root.joinpath("data/out_teacup.csv") 12 | 13 | @pytest.fixture(scope="class") 14 | def out_teacup_transposed(self, _root): 15 | return _root.joinpath("data/out_teacup_transposed.csv") 16 | 17 | def test_clean_columns(self, out_teacup): 18 | # test the singleton works well for laizy loading 19 | Columns.clean() 20 | assert Columns._files == {} 21 | Columns.read(out_teacup) 22 | assert Columns._files != {} 23 | assert out_teacup in Columns._files 24 | Columns.clean() 25 | assert Columns._files == {} 26 | 27 | def test_transposed_frame(self, out_teacup, out_teacup_transposed): 28 | # test loading transposed frames 29 | cols1, trans1 = Columns.get_columns(out_teacup) 30 | cols2, trans2 = Columns.get_columns(out_teacup_transposed) 31 | Columns.clean() 32 | 33 | assert cols1 == cols2 34 | assert not trans1 35 | assert trans2 36 | 37 | def test_get_columns(self, out_teacup, out_teacup_transposed): 38 | # test getting specific columns by name 39 | cols0, trans0 = Columns.get_columns(out_teacup) 40 | 41 | cols1, trans1 = Columns.get_columns( 42 | out_teacup, 43 | vars=["Room Temperature", "Teacup Temperature"]) 44 | 45 | cols2, trans2 = Columns.get_columns( 46 | out_teacup_transposed, 47 | vars=["Heat Loss to Room"]) 48 | 49 | cols3 = Columns.get_columns( 50 | out_teacup_transposed, 51 | vars=["No column"])[0] 52 | 53 | Columns.clean() 54 | 55 | assert cols1.issubset(cols0) 56 | assert cols1 == set(["Room Temperature", "Teacup Temperature"]) 57 | 58 | assert cols2.issubset(cols0) 59 | assert cols2 == set(["Heat Loss to Room"]) 60 | 61 | assert cols3 == set() 62 | 63 | assert not trans0 64 | assert not trans1 65 | assert trans2 66 | 67 | def test_get_columns_subscripted(self, _root): 68 | # test get subscripted columns 69 | data_file = _root.joinpath( 70 | "test-models/tests/subscript_3d_arrays_widthwise/output.tab" 71 | ) 72 | 73 | data_file2 = _root.joinpath( 74 | "test-models/tests/subscript_2d_arrays/output.tab" 75 | ) 76 | 77 | subsd = { 78 | "d3": ["Depth 1", "Depth 2"], 79 | "d2": ["Column 1", "Column 2"], 80 | "d1": ["Entry 1", "Entry 2", "Entry 3"] 81 | } 82 | 83 | cols1 = Columns.get_columns( 84 | data_file, 85 | vars=["Three Dimensional Constant"])[0] 86 | 87 | expected = { 88 | "Three Dimensional Constant[" + ",".join(el) + "]" 89 | for el in itertools.product(subsd["d1"], subsd["d2"], subsd["d3"]) 90 | } 91 | 92 | assert cols1 == expected 93 | 94 | cols2 = Columns.get_columns( 95 | data_file2, 96 | vars=["Rate A", "Stock A"])[0] 97 | 98 | subs = list(itertools.product(subsd["d1"], subsd["d2"])) 99 | expected = { 100 | "Rate A[" + ",".join(el) + "]" 101 | for el in subs 102 | } 103 | 104 | expected.update({ 105 | "Stock A[" + ",".join(el) + "]" 106 | for el in subs 107 | }) 108 | 109 | assert cols2 == expected 110 | 111 | 112 | @pytest.mark.parametrize( 113 | "file,raise_type,error_message", 114 | [ 115 | ( # invalid_file_type 116 | "more-tests/not_vensim/test_not_vensim.txt", 117 | ValueError, 118 | "Not able to read '%s'" 119 | ), 120 | ( # invalid_file_format 121 | "data/out_teacup_no_head.csv", 122 | ValueError, 123 | "Invalid file format '%s'... varible names should appear" 124 | + " in the first row or in the first column..." 125 | ) 126 | ], 127 | ids=["invalid_file_type", "invalid_file_format"] 128 | ) 129 | @pytest.mark.skipif( 130 | sys.platform.startswith("win"), 131 | reason=r"bad scape \e") 132 | class TestColumnsErrors: 133 | # Test errors associated with Columns class 134 | 135 | @pytest.fixture 136 | def file_path(self, _root, file): 137 | return _root.joinpath(file) 138 | 139 | def test_columns_errors(self, file_path, raise_type, error_message): 140 | with pytest.raises(raise_type, match=error_message % str(file_path)): 141 | Columns.read_file(file_path) 142 | -------------------------------------------------------------------------------- /tests/pytest_types/data/pytest_data.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import xarray as xr 4 | import pandas as pd 5 | 6 | from pysd.py_backend.data import Data, TabData 7 | 8 | 9 | @pytest.mark.filterwarnings("ignore") 10 | class TestDataErrors(): 11 | # Test errors associated with Data class 12 | # Several Data cases are tested in pytest_external while some other 13 | # are tested indirectly in pytest_pysd and integration_test_vensim 14 | 15 | @pytest.fixture 16 | def data(self, value, interp): 17 | obj = Data() 18 | obj.data = value 19 | obj.interp = interp 20 | obj.py_name = "data" 21 | return obj 22 | 23 | @pytest.mark.parametrize( 24 | "value,interp,raise_type,error_message", 25 | [ 26 | ( # not_loaded_data 27 | None, 28 | "interpolate", 29 | ValueError, 30 | "Trying to interpolate data variable before loading " 31 | "the data..." 32 | ), 33 | # test that try/except block on call doesn't catch errors 34 | # differents than data = None 35 | ( # try_except 36 | xr.DataArray([10, 20], {'time': [0, 1]}, ['time']), 37 | None, 38 | AttributeError, 39 | "'Data' object has no attribute 'is_float'" 40 | ) 41 | ], 42 | ids=["not_loaded_data", "try_except"] 43 | ) 44 | def test_data_errors(self, data, raise_type, error_message): 45 | with pytest.raises(raise_type, match=error_message): 46 | data(1.5) 47 | 48 | def test_invalid_interp_method(self): 49 | error_message = r"\nThe interpolation method \(interp\) must be"\ 50 | r" 'raw', 'interpolate', 'look_forward' or 'hold_backward'" 51 | with pytest.raises(ValueError, match=error_message): 52 | TabData("", "", {}, interp="invalid") 53 | 54 | 55 | @pytest.mark.parametrize( 56 | "value,new_value,expected", 57 | [ 58 | ( # float-constant 59 | xr.DataArray([10, 20], {'time': [0, 1]}, ['time']), 60 | 26, 61 | 26 62 | ), 63 | ( # float-series 64 | xr.DataArray([10, 20], {'time': [0, 1]}, ['time']), 65 | pd.Series(index=[1, 20, 40], data=[2, 10, 2]), 66 | xr.DataArray([2, 10, 2], {"time": [1, 20, 40]}, ["time"]) 67 | ), 68 | ( # array-constantfloat 69 | xr.DataArray( 70 | [[10, 20], [30, 40]], 71 | {"time": [0, 1], "dim":["A", "B"]}, 72 | ["time", "dim"]), 73 | 26, 74 | xr.DataArray(26, {"dim": ["A", "B"]}, ["dim"]), 75 | ), 76 | ( # array-seriesfloat 77 | xr.DataArray( 78 | [[10, 20], [30, 40]], 79 | {"time": [0, 1], "dim":["A", "B"]}, 80 | ["time", "dim"]), 81 | pd.Series(index=[1, 20, 40], data=[2, 10, 2]), 82 | xr.DataArray( 83 | [[2, 2], [10, 10], [2, 2]], 84 | {"time": [1, 20, 40], "dim":["A", "B"]}, 85 | ["time", "dim"]) 86 | ), 87 | ( # array-constantarray 88 | xr.DataArray( 89 | [[[10, 20], [30, 40]], [[15, 25], [35, 45]]], 90 | {"time": [0, 1], "dim":["A", "B"], "dim2": ["C", "D"]}, 91 | ["time", "dim", "dim2"]), 92 | xr.DataArray( 93 | [1, 2], 94 | {"dim": ["A", "B"]}, 95 | ["dim"]), 96 | xr.DataArray( 97 | [[1, 2], [1, 2]], 98 | {"dim": ["A", "B"], "dim2": ["C", "D"]}, 99 | ["dim", "dim2"]) 100 | ), 101 | ( # array-seriesarray 102 | xr.DataArray( 103 | [[[10, 20], [30, 40]], [[15, 25], [35, 45]]], 104 | {"time": [0, 1], "dim":["A", "B"], "dim2": ["C", "D"]}, 105 | ["time", "dim", "dim2"]), 106 | pd.Series(index=[1, 20, 40], data=[ 107 | xr.DataArray([1, 2], {"dim": ["A", "B"]}, ["dim"]), 108 | xr.DataArray([10, 20], {"dim": ["A", "B"]}, ["dim"]), 109 | xr.DataArray([1, 2], {"dim": ["A", "B"]}, ["dim"]) 110 | ]), 111 | xr.DataArray( 112 | [[[1, 1], [2, 2]], [[10, 10], [20, 20]], [[1, 1], [2, 2]]], 113 | {"time": [1, 20, 40], "dim":["A", "B"], "dim2": ["C", "D"]}, 114 | ["time", "dim", "dim2"]) 115 | ) 116 | ], 117 | ids=[ 118 | "float-constant", "float-series", 119 | "array-constantfloat", "array-seriesfloat", 120 | "array-constantarray", "array-seriesarray" 121 | ] 122 | ) 123 | class TestDataSetValues(): 124 | 125 | @pytest.fixture 126 | def data(self, value): 127 | obj = Data() 128 | obj.data = value 129 | obj.interp = "interp" 130 | obj.is_float = len(value.shape) < 2 131 | obj.final_coords = { 132 | dim: value.coords[dim] for dim in value.dims if dim != "time" 133 | } 134 | obj.py_name = "data" 135 | return obj 136 | 137 | def test_data_set_value(self, data, new_value, expected): 138 | data.set_values(new_value) 139 | if isinstance(expected, (float, int)): 140 | assert data.data == expected 141 | else: 142 | assert data.data.equals(expected) 143 | -------------------------------------------------------------------------------- /tests/pytest_types/lookup/pytest_lookups.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | 3 | import xarray as xr 4 | import pandas as pd 5 | 6 | from pysd.py_backend.lookups import Lookups, HardcodedLookups 7 | 8 | 9 | @pytest.mark.parametrize( 10 | "value,new_value,expected", 11 | [ 12 | ( # float-constant 13 | xr.DataArray([10, 20], {'lookup_dim': [0, 1]}, ['lookup_dim']), 14 | 26, 15 | 26 16 | ), 17 | ( # float-series 18 | xr.DataArray([10, 20], {'lookup_dim': [0, 1]}, ['lookup_dim']), 19 | pd.Series(index=[1, 20, 40], data=[2, 10, 2]), 20 | xr.DataArray( 21 | [2, 10, 2], 22 | {"lookup_dim": [1, 20, 40]}, 23 | ["lookup_dim"] 24 | ) 25 | 26 | ), 27 | ( # array-constantfloat 28 | xr.DataArray( 29 | [[10, 20], [30, 40]], 30 | {"lookup_dim": [0, 1], "dim":["A", "B"]}, 31 | ["lookup_dim", "dim"]), 32 | 26, 33 | xr.DataArray(26, {"dim": ["A", "B"]}, ["dim"]), 34 | ), 35 | ( # array-seriesfloat 36 | xr.DataArray( 37 | [[10, 20], [30, 40]], 38 | {"lookup_dim": [0, 1], "dim":["A", "B"]}, 39 | ["lookup_dim", "dim"]), 40 | pd.Series(index=[1, 20, 40], data=[2, 10, 2]), 41 | xr.DataArray( 42 | [[2, 2], [10, 10], [2, 2]], 43 | {"lookup_dim": [1, 20, 40], "dim":["A", "B"]}, 44 | ["lookup_dim", "dim"]) 45 | ), 46 | ( # array-constantarray 47 | xr.DataArray( 48 | [[[10, 20], [30, 40]], [[15, 25], [35, 45]]], 49 | {"lookup_dim": [0, 1], "dim":["A", "B"], "dim2": ["C", "D"]}, 50 | ["lookup_dim", "dim", "dim2"]), 51 | xr.DataArray( 52 | [1, 2], 53 | {"dim": ["A", "B"]}, 54 | ["dim"]), 55 | xr.DataArray( 56 | [[1, 2], [1, 2]], 57 | {"dim": ["A", "B"], "dim2": ["C", "D"]}, 58 | ["dim", "dim2"]) 59 | ), 60 | ( # array-seriesarray 61 | xr.DataArray( 62 | [[[10, 20], [30, 40]], [[15, 25], [35, 45]]], 63 | {"lookup_dim": [0, 1], "dim":["A", "B"], "dim2": ["C", "D"]}, 64 | ["lookup_dim", "dim", "dim2"]), 65 | pd.Series(index=[1, 20, 40], data=[ 66 | xr.DataArray([1, 2], {"dim": ["A", "B"]}, ["dim"]), 67 | xr.DataArray([10, 20], {"dim": ["A", "B"]}, ["dim"]), 68 | xr.DataArray([1, 2], {"dim": ["A", "B"]}, ["dim"]) 69 | ]), 70 | xr.DataArray( 71 | [[[1, 1], [2, 2]], [[10, 10], [20, 20]], [[1, 1], [2, 2]]], 72 | { 73 | "lookup_dim": [1, 20, 40], 74 | "dim":["A", "B"], 75 | "dim2": ["C", "D"] 76 | }, 77 | ["lookup_dim", "dim", "dim2"]) 78 | ) 79 | ], 80 | ids=[ 81 | "float-constant", "float-series", 82 | "array-constantfloat", "array-seriesfloat", 83 | "array-constantarray", "array-seriesarray" 84 | ] 85 | ) 86 | class TestLookupsSetValues(): 87 | 88 | @pytest.fixture 89 | def lookups(self, value): 90 | obj = Lookups() 91 | obj.data = value 92 | obj.interp = "interp" 93 | obj.is_float = len(value.shape) < 2 94 | obj.final_coords = { 95 | dim: value.coords[dim] for dim in value.dims if dim != "lookup_dim" 96 | } 97 | obj.py_name = "lookup" 98 | return obj 99 | 100 | def test_lookups_set_value(self, lookups, new_value, expected): 101 | lookups.set_values(new_value) 102 | if isinstance(expected, (float, int)): 103 | assert lookups.data == expected 104 | else: 105 | assert lookups.data.equals(expected) 106 | 107 | 108 | @pytest.mark.parametrize( 109 | "x,y,error_type,error_message", 110 | [ 111 | ( # repeated 112 | [1, 2, 2, 3, 4], 113 | [10, 20, 20, 30, 40], 114 | ValueError, 115 | r"my_look\nx dimension has repeated values..." 116 | ), 117 | ( # diff-len1 118 | [1, 2, 3, 4], 119 | [10, 20, 20, 30, 40], 120 | ValueError, 121 | r"my_look\nx and y dimensions have different length..." 122 | ), 123 | ( # diff-len2 124 | [1, 2, 3, 4], 125 | [10, 30, 40], 126 | ValueError, 127 | r"my_look\nx and y dimensions have different length..." 128 | ), 129 | ], 130 | ids=[ 131 | "repeated", "diff-len1", "diff-len2" 132 | ] 133 | ) 134 | class TestLookupsError(): 135 | 136 | def test_hardcoded_lookups_error(self, x, y, error_type, error_message): 137 | hl = HardcodedLookups(x, y, {}, None, {}, "my_look") 138 | 139 | with pytest.raises(error_type, match=error_message): 140 | hl.initialize() 141 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | --only-binary=netCDF4 2 | 3 | pytest 4 | pytest-cov 5 | pytest-mock 6 | pytest-xdist 7 | coverage 8 | coveralls 9 | psutil 10 | netCDF4>=1.6 11 | dask[array] 12 | dask[diagnostics] 13 | dask[distributed] 14 | --------------------------------------------------------------------------------