├── .gitattributes ├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ └── build.yml ├── .gitignore ├── CHANGELOG ├── LICENSE ├── MANIFEST.in ├── README.md ├── doc └── sphinx │ ├── Makefile │ ├── make.bat │ └── source │ ├── _static │ └── sphinxdoc.css │ ├── _templates │ ├── index.html │ └── layout.html │ ├── changelog.rst │ ├── code.rst │ ├── conf.py │ ├── content.rst │ ├── examples.rst │ ├── installation.rst │ ├── markup.py │ ├── pyfmi.common.plotting.rst │ ├── pyfmi.common.rst │ ├── pyfmi.examples.rst │ ├── pyfmi.rst │ ├── pyfmi.simulation.rst │ └── tutorial.rst ├── pytest.ini ├── setup.cfg ├── setup.py ├── src ├── common │ ├── __init__.py │ ├── algorithm_drivers.py │ ├── core.py │ ├── diagnostics.py │ ├── io.py │ ├── log │ │ ├── __init__.py │ │ ├── parser.py │ │ ├── prettyprinter.py │ │ └── tree.py │ └── plotting │ │ ├── __init__.py │ │ └── plot_gui.py └── pyfmi │ ├── __init__.py │ ├── debug.py │ ├── examples │ ├── __init__.py │ ├── files │ │ └── FMUs │ │ │ ├── CS1.0 │ │ │ └── bouncingBall.fmu │ │ │ ├── CS2.0 │ │ │ ├── README.txt │ │ │ └── bouncingBall.fmu │ │ │ ├── ME1.0 │ │ │ ├── README.txt │ │ │ ├── SecondOrder.fmu │ │ │ └── bouncingBall.fmu │ │ │ └── ME2.0 │ │ │ ├── README.txt │ │ │ └── bouncingBall.fmu │ ├── fmi20_bouncing_ball_native.py │ ├── fmi_bouncing_ball.py │ ├── fmi_bouncing_ball_cs.py │ ├── fmi_bouncing_ball_native.py │ ├── fmu_with_input.py │ └── fmu_with_input_function.py │ ├── exceptions.py │ ├── fmi.pxd │ ├── fmi.pyx │ ├── fmi1.pxd │ ├── fmi1.pyx │ ├── fmi2.pxd │ ├── fmi2.pyx │ ├── fmi3.pxd │ ├── fmi3.pyx │ ├── fmi_algorithm_drivers.py │ ├── fmi_base.pxd │ ├── fmi_base.pyx │ ├── fmi_coupled.pyx │ ├── fmi_extended.pyx │ ├── fmi_util.pxd │ ├── fmi_util.pyx │ ├── fmil1_import.pxd │ ├── fmil2_import.pxd │ ├── fmil3_import.pxd │ ├── fmil_import.pxd │ ├── master.pyx │ ├── simulation │ ├── __init__.py │ ├── assimulo_interface.pxd │ ├── assimulo_interface.pyx │ ├── assimulo_interface_fmi1.pxd │ ├── assimulo_interface_fmi1.pyx │ ├── assimulo_interface_fmi2.pxd │ ├── assimulo_interface_fmi2.pyx │ ├── assimulo_interface_fmi3.pxd │ └── assimulo_interface_fmi3.pyx │ ├── test_util.pxd │ ├── test_util.pyx │ ├── util.pxd │ └── util.pyx └── tests ├── __init__.py ├── conftest.py ├── files ├── FMUs │ └── XML │ │ ├── CS1.0 │ │ ├── CoupledClutches.fmu │ │ ├── NegatedAlias.fmu │ │ └── bouncingBall.fmu │ │ ├── CS2.0 │ │ ├── CoupledClutches.fmu │ │ ├── GainTestInteger.fmu │ │ ├── GainTestReal.fmu │ │ ├── IntegerStep.fmu │ │ ├── LinearCoSimulation_LinearSubSystem1.fmu │ │ ├── LinearCoSimulation_LinearSubSystem2.fmu │ │ ├── LinearStability.SubSystem1.fmu │ │ ├── LinearStability.SubSystem2.fmu │ │ ├── LinearStability_LinearSubSystemNoFeed1.fmu │ │ ├── LinearStability_LinearSubSystemNoFeed2.fmu │ │ ├── NegatedAlias.fmu │ │ └── bouncingBall.fmu │ │ ├── ME1.0 │ │ ├── Alias1.fmu │ │ ├── CoupledClutches.fmu │ │ ├── Description.fmu │ │ ├── NegatedAlias.fmu │ │ ├── NoState.Example1.fmu │ │ ├── NominalTest4.fmu │ │ ├── RLC_Circuit.fmu │ │ ├── bouncingBall.fmu │ │ └── dq.fmu │ │ ├── ME2.0 │ │ ├── Alias.fmu │ │ ├── BasicSens1.fmu │ │ ├── BasicSens2.fmu │ │ ├── Bouncing_Ball.fmu │ │ ├── CoupledClutches.fmu │ │ ├── CoupledClutchesModified.fmu │ │ ├── Description.fmu │ │ ├── Enumerations.Enumeration3.fmu │ │ ├── Friction2.fmu │ │ ├── Large.fmu │ │ ├── LinearStability.FullSystem.fmu │ │ ├── LinearStability.SubSystem1.fmu │ │ ├── LinearStability.SubSystem2.fmu │ │ ├── LinearStateSpace.fmu │ │ ├── MalFormed.fmu │ │ ├── NegatedAlias.fmu │ │ ├── NoState.Example1.fmu │ │ ├── NominalTests.NominalTest4.fmu │ │ ├── OutputTest2.fmu │ │ ├── ParameterAlias.fmu │ │ ├── QuadTankPack_Sim_QuadTank.fmu │ │ ├── bouncingBall.fmu │ │ └── test_type_definitions.fmu │ │ └── ME3.0 │ │ └── alias │ │ └── modelDescription.xml ├── Logs │ ├── CoupledClutches_CS_log.txt │ ├── CoupledClutches_log.txt │ ├── CoupledClutches_log.xml │ └── boolean_log.xml └── Results │ ├── DoublePendulum.mat │ ├── TestCSV.csv │ └── qt_par_est_data.mat ├── test_common.py ├── test_fmi.py ├── test_fmi3.py ├── test_fmi3_sim.py ├── test_fmi_coupled.py ├── test_fmi_estimate.py ├── test_fmi_extended.py ├── test_fmi_master.py ├── test_fmi_util.py ├── test_io.py ├── test_log.py └── test_stream.py /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please make sure to check the issue tracker for existing issues! 11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **To Reproduce** 16 | Steps to reproduce the behavior: Please provide a complete [minimal reproducible example](https://en.wikipedia.org/wiki/Minimal_reproducible_example). 17 | You need not share your FMUs, but please share the relevant Python code. 18 | 19 | If your issue is related to specific variables in your FMU, please add the relevant entries from your *modelDescription.xml* in cleartext 20 | 21 | **Observed behavior** 22 | Please describe observed issues and provide error messages and log output in _cleartext_. 23 | 24 | **Expected behavior** 25 | A clear and concise description of what you expected to happen and why. 26 | 27 | Please make sure that the expected behavior corresponds to the FMI specification, see [specification documents](https://fmi-standard.org/). 28 | 29 | **Versions** 30 | - Version, e.g., _PyFMI 2.13.0_, current master or a particular commit 31 | - FMU generation tool 32 | - any other relevant information about your environment, e.g., _Python version_, _OS_, ... 33 | 34 | **Additional context** 35 | Add any other context about the problem here. 36 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | Please make sure to check the issue tracker for existing issues! 11 | 12 | **Is your feature request related to a problem? Please describe.** 13 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 14 | 15 | **Describe the solution you'd like** 16 | A clear and concise description of what you want to happen. 17 | 18 | **Describe alternatives you've considered** 19 | A clear and concise description of any alternative solutions or features you've considered. 20 | 21 | **Additional context** 22 | Add any other context or screenshots about the feature request here. 23 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | pull_request: 4 | 5 | permissions: 6 | contents: read 7 | 8 | jobs: 9 | linux: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 30 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-python@v5 15 | with: 16 | python-version: '3.11.x' 17 | - name: Setup Python 18 | run: | 19 | python3 -m pip install Cython numpy scipy matplotlib pytest setuptools==69.1.0 20 | - name: Install system 21 | run: | 22 | sudo apt-get -y install cmake liblapack-dev libsuitesparse-dev libhypre-dev 23 | sudo cp -v /usr/lib/x86_64-linux-gnu/libblas.so /usr/lib/x86_64-linux-gnu/libblas_OPENMP.so 24 | - name: Install superlu 25 | run: | 26 | cd /tmp 27 | curl -fSsL https://github.com/xiaoyeli/superlu_mt/archive/refs/tags/v4.0.1.tar.gz | tar xz 28 | cd superlu_mt-4.0.1 29 | cmake -Denable_examples=OFF -Denable_tests=OFF -DPLAT="_OPENMP" -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_INSTALL_LIBDIR=lib -DSUPERLUMT_INSTALL_INCLUDEDIR=include . 30 | make -j4 31 | sudo make install 32 | - name: Install sundials 33 | run: | 34 | git clone --depth 1 -b v2.7.0 https://github.com/LLNL/sundials /tmp/sundials 35 | cd /tmp/sundials 36 | echo "target_link_libraries(sundials_idas_shared lapack blas superlu_mt_OPENMP)" >> src/idas/CMakeLists.txt 37 | echo "target_link_libraries(sundials_kinsol_shared lapack blas superlu_mt_OPENMP)" >> src/kinsol/CMakeLists.txt 38 | cmake -LAH -DSUPERLUMT_BLAS_LIBRARIES=blas -DSUPERLUMT_INCLUDE_DIR=/usr/include -DSUPERLUMT_LIBRARY=/usr/lib/libsuperlu_mt_OPENMP.a -DSUPERLUMT_THREAD_TYPE=OpenMP -DCMAKE_INSTALL_PREFIX=/usr -DSUPERLUMT_ENABLE=ON -DLAPACK_ENABLE=ON -DEXAMPLES_ENABLE=OFF -B build . 39 | cd build 40 | make -j4 41 | sudo make install 42 | - name: Install assimulo 43 | run: | 44 | git clone --depth 1 -b Assimulo-3.7.0 https://github.com/modelon-community/Assimulo /tmp/Assimulo 45 | cd /tmp/Assimulo 46 | rm setup.cfg 47 | python3 setup.py install --user --sundials-home=/usr --blas-home=/usr/lib/x86_64-linux-gnu/ --lapack-home=/usr/lib/x86_64-linux-gnu/ --superlu-home=/usr 48 | - name: Install fmilib 49 | run: | 50 | cd /tmp 51 | curl -fSsL https://github.com/modelon-community/fmi-library/archive/3.0.3.tar.gz | tar xz 52 | cd fmi-library-3.0.3 53 | cmake -DCMAKE_INSTALL_PREFIX=/usr -DFMILIB_BUILD_TESTS=OFF -B build . 54 | cd build 55 | make -j4 56 | sudo make install 57 | - name: Build 58 | run: python3 setup.py install --user --fmil-home=/usr 59 | - name: Test 60 | run: pytest 61 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | build/ 3 | 4 | dist/ 5 | 6 | PyFMI.egg-info/ 7 | 8 | build.sh 9 | 10 | src/pyfmi/fmi_coupled.c 11 | 12 | src/pyfmi/fmi_util.c 13 | 14 | src/pyfmi/fmi_extended.c 15 | 16 | src/pyfmi/master.c 17 | 18 | src/pyfmi/simulation/assimulo_interface.c 19 | src/pyfmi/simulation/assimulo_interface_fmi1.c 20 | src/pyfmi/simulation/assimulo_interface_fmi2.c 21 | 22 | src/pyfmi/fmi.c 23 | 24 | src/pyfmi/test_util.c 25 | 26 | src/pyfmi/LICENSE 27 | 28 | src/pyfmi/CHANGELOG 29 | 30 | src/pyfmi/version.txt 31 | 32 | tests/files/reference_fmus/ 33 | 34 | __pycache__ 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include CHANGELOG 2 | include LICENSE 3 | include src/pyfmi/*.pyx 4 | include src/pyfmi/*.pxd 5 | exclude src/pyfmi/*.dll 6 | -------------------------------------------------------------------------------- /doc/sphinx/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = build 9 | 10 | # Internal variables. 11 | PAPEROPT_a4 = -D latex_paper_size=a4 12 | PAPEROPT_letter = -D latex_paper_size=letter 13 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 14 | # the i18n builder cannot share the environment and doctrees with the others 15 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source 16 | 17 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext 18 | 19 | help: 20 | @echo "Please use \`make ' where is one of" 21 | @echo " html to make standalone HTML files" 22 | @echo " dirhtml to make HTML files named index.html in directories" 23 | @echo " singlehtml to make a single large HTML file" 24 | @echo " pickle to make pickle files" 25 | @echo " json to make JSON files" 26 | @echo " htmlhelp to make HTML files and a HTML help project" 27 | @echo " qthelp to make HTML files and a qthelp project" 28 | @echo " devhelp to make HTML files and a Devhelp project" 29 | @echo " epub to make an epub" 30 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 31 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 32 | @echo " text to make text files" 33 | @echo " man to make manual pages" 34 | @echo " texinfo to make Texinfo files" 35 | @echo " info to make Texinfo files and run them through makeinfo" 36 | @echo " gettext to make PO message catalogs" 37 | @echo " changes to make an overview of all changed/added/deprecated items" 38 | @echo " linkcheck to check all external links for integrity" 39 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 40 | 41 | clean: 42 | -rm -rf $(BUILDDIR)/* 43 | 44 | html: 45 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 46 | @echo 47 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 48 | 49 | dirhtml: 50 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 51 | @echo 52 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 53 | 54 | singlehtml: 55 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 56 | @echo 57 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 58 | 59 | pickle: 60 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 61 | @echo 62 | @echo "Build finished; now you can process the pickle files." 63 | 64 | json: 65 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 66 | @echo 67 | @echo "Build finished; now you can process the JSON files." 68 | 69 | htmlhelp: 70 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 71 | @echo 72 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 73 | ".hhp project file in $(BUILDDIR)/htmlhelp." 74 | 75 | qthelp: 76 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 77 | @echo 78 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 79 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 80 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/PyFMI.qhcp" 81 | @echo "To view the help file:" 82 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/PyFMI.qhc" 83 | 84 | devhelp: 85 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 86 | @echo 87 | @echo "Build finished." 88 | @echo "To view the help file:" 89 | @echo "# mkdir -p $$HOME/.local/share/devhelp/PyFMI" 90 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/PyFMI" 91 | @echo "# devhelp" 92 | 93 | epub: 94 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 95 | @echo 96 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 97 | 98 | latex: 99 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 100 | @echo 101 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 102 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 103 | "(use \`make latexpdf' here to do that automatically)." 104 | 105 | latexpdf: 106 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 107 | @echo "Running LaTeX files through pdflatex..." 108 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 109 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 110 | 111 | text: 112 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 113 | @echo 114 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 115 | 116 | man: 117 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 118 | @echo 119 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 120 | 121 | texinfo: 122 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 123 | @echo 124 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 125 | @echo "Run \`make' in that directory to run these through makeinfo" \ 126 | "(use \`make info' here to do that automatically)." 127 | 128 | info: 129 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 130 | @echo "Running Texinfo files through makeinfo..." 131 | make -C $(BUILDDIR)/texinfo info 132 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 133 | 134 | gettext: 135 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 136 | @echo 137 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 138 | 139 | changes: 140 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 141 | @echo 142 | @echo "The overview file is in $(BUILDDIR)/changes." 143 | 144 | linkcheck: 145 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 146 | @echo 147 | @echo "Link check complete; look for any errors in the above output " \ 148 | "or in $(BUILDDIR)/linkcheck/output.txt." 149 | 150 | doctest: 151 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 152 | @echo "Testing of doctests in the sources finished, look at the " \ 153 | "results in $(BUILDDIR)/doctest/output.txt." 154 | -------------------------------------------------------------------------------- /doc/sphinx/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | REM Command file for Sphinx documentation 4 | 5 | if "%SPHINXBUILD%" == "" ( 6 | set SPHINXBUILD=sphinx-build 7 | ) 8 | set BUILDDIR=build 9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% source 10 | set I18NSPHINXOPTS=%SPHINXOPTS% source 11 | if NOT "%PAPER%" == "" ( 12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% 13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% 14 | ) 15 | 16 | if "%1" == "" goto help 17 | 18 | if "%1" == "help" ( 19 | :help 20 | echo.Please use `make ^` where ^ is one of 21 | echo. html to make standalone HTML files 22 | echo. dirhtml to make HTML files named index.html in directories 23 | echo. singlehtml to make a single large HTML file 24 | echo. pickle to make pickle files 25 | echo. json to make JSON files 26 | echo. htmlhelp to make HTML files and a HTML help project 27 | echo. qthelp to make HTML files and a qthelp project 28 | echo. devhelp to make HTML files and a Devhelp project 29 | echo. epub to make an epub 30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter 31 | echo. text to make text files 32 | echo. man to make manual pages 33 | echo. texinfo to make Texinfo files 34 | echo. gettext to make PO message catalogs 35 | echo. changes to make an overview over all changed/added/deprecated items 36 | echo. linkcheck to check all external links for integrity 37 | echo. doctest to run all doctests embedded in the documentation if enabled 38 | goto end 39 | ) 40 | 41 | if "%1" == "clean" ( 42 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i 43 | del /q /s %BUILDDIR%\* 44 | goto end 45 | ) 46 | 47 | if "%1" == "html" ( 48 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html 49 | if errorlevel 1 exit /b 1 50 | echo. 51 | echo.Build finished. The HTML pages are in %BUILDDIR%/html. 52 | goto end 53 | ) 54 | 55 | if "%1" == "dirhtml" ( 56 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml 57 | if errorlevel 1 exit /b 1 58 | echo. 59 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. 60 | goto end 61 | ) 62 | 63 | if "%1" == "singlehtml" ( 64 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml 65 | if errorlevel 1 exit /b 1 66 | echo. 67 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. 68 | goto end 69 | ) 70 | 71 | if "%1" == "pickle" ( 72 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle 73 | if errorlevel 1 exit /b 1 74 | echo. 75 | echo.Build finished; now you can process the pickle files. 76 | goto end 77 | ) 78 | 79 | if "%1" == "json" ( 80 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json 81 | if errorlevel 1 exit /b 1 82 | echo. 83 | echo.Build finished; now you can process the JSON files. 84 | goto end 85 | ) 86 | 87 | if "%1" == "htmlhelp" ( 88 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp 89 | if errorlevel 1 exit /b 1 90 | echo. 91 | echo.Build finished; now you can run HTML Help Workshop with the ^ 92 | .hhp project file in %BUILDDIR%/htmlhelp. 93 | goto end 94 | ) 95 | 96 | if "%1" == "qthelp" ( 97 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp 98 | if errorlevel 1 exit /b 1 99 | echo. 100 | echo.Build finished; now you can run "qcollectiongenerator" with the ^ 101 | .qhcp project file in %BUILDDIR%/qthelp, like this: 102 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\PyFMI.qhcp 103 | echo.To view the help file: 104 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\PyFMI.ghc 105 | goto end 106 | ) 107 | 108 | if "%1" == "devhelp" ( 109 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp 110 | if errorlevel 1 exit /b 1 111 | echo. 112 | echo.Build finished. 113 | goto end 114 | ) 115 | 116 | if "%1" == "epub" ( 117 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub 118 | if errorlevel 1 exit /b 1 119 | echo. 120 | echo.Build finished. The epub file is in %BUILDDIR%/epub. 121 | goto end 122 | ) 123 | 124 | if "%1" == "latex" ( 125 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex 126 | if errorlevel 1 exit /b 1 127 | echo. 128 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. 129 | goto end 130 | ) 131 | 132 | if "%1" == "text" ( 133 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text 134 | if errorlevel 1 exit /b 1 135 | echo. 136 | echo.Build finished. The text files are in %BUILDDIR%/text. 137 | goto end 138 | ) 139 | 140 | if "%1" == "man" ( 141 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man 142 | if errorlevel 1 exit /b 1 143 | echo. 144 | echo.Build finished. The manual pages are in %BUILDDIR%/man. 145 | goto end 146 | ) 147 | 148 | if "%1" == "texinfo" ( 149 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo 150 | if errorlevel 1 exit /b 1 151 | echo. 152 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. 153 | goto end 154 | ) 155 | 156 | if "%1" == "gettext" ( 157 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale 158 | if errorlevel 1 exit /b 1 159 | echo. 160 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale. 161 | goto end 162 | ) 163 | 164 | if "%1" == "changes" ( 165 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes 166 | if errorlevel 1 exit /b 1 167 | echo. 168 | echo.The overview file is in %BUILDDIR%/changes. 169 | goto end 170 | ) 171 | 172 | if "%1" == "linkcheck" ( 173 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck 174 | if errorlevel 1 exit /b 1 175 | echo. 176 | echo.Link check complete; look for any errors in the above output ^ 177 | or in %BUILDDIR%/linkcheck/output.txt. 178 | goto end 179 | ) 180 | 181 | if "%1" == "doctest" ( 182 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest 183 | if errorlevel 1 exit /b 1 184 | echo. 185 | echo.Testing of doctests in the sources finished, look at the ^ 186 | results in %BUILDDIR%/doctest/output.txt. 187 | goto end 188 | ) 189 | 190 | :end 191 | -------------------------------------------------------------------------------- /doc/sphinx/source/_static/sphinxdoc.css: -------------------------------------------------------------------------------- 1 | /* 2 | * sphinxdoc.css_t 3 | * ~~~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- sphinxdoc theme. Originally created by 6 | * Armin Ronacher for Werkzeug. 7 | * 8 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 9 | * :license: BSD, see LICENSE for details. 10 | * 11 | */ 12 | 13 | @import url("basic.css"); 14 | 15 | /* -- page layout ----------------------------------------------------------- */ 16 | 17 | body { 18 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 19 | 'Verdana', sans-serif; 20 | font-size: 14px; 21 | letter-spacing: -0.01em; 22 | line-height: 150%; 23 | text-align: center; 24 | background-color: #BFD1D4; 25 | color: black; 26 | padding: 0; 27 | border: 1px solid #aaa; 28 | 29 | margin: 0px 0px 0px 0px; 30 | min-width: 740px; 31 | } 32 | 33 | div.document { 34 | background-color: white; 35 | text-align: left; 36 | background-image: url(contents.png); 37 | background-repeat: repeat-x; 38 | } 39 | 40 | div.bodywrapper { 41 | margin: 0 {{ theme_sidebarwidth|toint + 10 }}px 0 0; 42 | border-right: 1px solid #ccc; 43 | } 44 | 45 | div.body { 46 | margin: 0; 47 | padding: 0.5em 20px 20px 20px; 48 | } 49 | 50 | div.related { 51 | font-size: 1em; 52 | } 53 | 54 | div.related ul { 55 | background-image: url(navigation.png); 56 | height: 2em; 57 | border-top: 1px solid #ddd; 58 | border-bottom: 1px solid #ddd; 59 | } 60 | 61 | div.related ul li { 62 | margin: 0; 63 | padding: 0; 64 | height: 2em; 65 | float: left; 66 | } 67 | 68 | div.related ul li.right { 69 | float: right; 70 | margin-right: 5px; 71 | } 72 | 73 | div.related ul li a { 74 | margin: 0; 75 | padding: 0 5px 0 5px; 76 | line-height: 1.75em; 77 | color: #EE9816; 78 | } 79 | 80 | div.related ul li a:hover { 81 | color: #3CA8E7; 82 | } 83 | 84 | div.sphinxsidebarwrapper { 85 | padding: 0; 86 | } 87 | 88 | div.sphinxsidebar { 89 | margin: 0; 90 | padding: 0.5em 15px 15px 0; 91 | width: {{ theme_sidebarwidth|toint - 20 }}px; 92 | float: right; 93 | font-size: 1em; 94 | text-align: left; 95 | } 96 | 97 | div.sphinxsidebar h3, div.sphinxsidebar h4 { 98 | margin: 1em 0 0.5em 0; 99 | font-size: 1em; 100 | padding: 0.1em 0 0.1em 0.5em; 101 | color: white; 102 | border: 1px solid #86989B; 103 | background-color: #AFC1C4; 104 | } 105 | 106 | div.sphinxsidebar h3 a { 107 | color: white; 108 | } 109 | 110 | div.sphinxsidebar ul { 111 | padding-left: 1.5em; 112 | margin-top: 7px; 113 | padding: 0; 114 | line-height: 130%; 115 | } 116 | 117 | div.sphinxsidebar ul ul { 118 | margin-left: 20px; 119 | } 120 | 121 | div.footer { 122 | background-color: #E3EFF1; 123 | color: #86989B; 124 | padding: 3px 8px 3px 0; 125 | clear: both; 126 | font-size: 0.8em; 127 | text-align: right; 128 | } 129 | 130 | div.footer a { 131 | color: #86989B; 132 | text-decoration: underline; 133 | } 134 | 135 | /* -- body styles ----------------------------------------------------------- */ 136 | 137 | p { 138 | margin: 0.8em 0 0.5em 0; 139 | } 140 | 141 | a { 142 | color: #CA7900; 143 | text-decoration: none; 144 | } 145 | 146 | a:hover { 147 | color: #2491CF; 148 | } 149 | 150 | div.body a { 151 | text-decoration: underline; 152 | } 153 | 154 | h1 { 155 | margin: 0; 156 | padding: 0.7em 0 0.3em 0; 157 | font-size: 1.5em; 158 | color: #11557C; 159 | } 160 | 161 | h2 { 162 | margin: 1.3em 0 0.2em 0; 163 | font-size: 1.35em; 164 | padding: 0; 165 | } 166 | 167 | h3 { 168 | margin: 1em 0 -0.3em 0; 169 | font-size: 1.2em; 170 | } 171 | 172 | div.body h1 a, div.body h2 a, div.body h3 a, div.body h4 a, div.body h5 a, div.body h6 a { 173 | color: black!important; 174 | } 175 | 176 | h1 a.anchor, h2 a.anchor, h3 a.anchor, h4 a.anchor, h5 a.anchor, h6 a.anchor { 177 | display: none; 178 | margin: 0 0 0 0.3em; 179 | padding: 0 0.2em 0 0.2em; 180 | color: #aaa!important; 181 | } 182 | 183 | h1:hover a.anchor, h2:hover a.anchor, h3:hover a.anchor, h4:hover a.anchor, 184 | h5:hover a.anchor, h6:hover a.anchor { 185 | display: inline; 186 | } 187 | 188 | h1 a.anchor:hover, h2 a.anchor:hover, h3 a.anchor:hover, h4 a.anchor:hover, 189 | h5 a.anchor:hover, h6 a.anchor:hover { 190 | color: #777; 191 | background-color: #eee; 192 | } 193 | 194 | a.headerlink { 195 | color: #c60f0f!important; 196 | font-size: 1em; 197 | margin-left: 6px; 198 | padding: 0 4px 0 4px; 199 | text-decoration: none!important; 200 | } 201 | 202 | a.headerlink:hover { 203 | background-color: #ccc; 204 | color: white!important; 205 | } 206 | 207 | cite, code, tt { 208 | font-family: 'Consolas', 'Deja Vu Sans Mono', 209 | 'Bitstream Vera Sans Mono', monospace; 210 | font-size: 0.95em; 211 | letter-spacing: 0.01em; 212 | } 213 | 214 | tt { 215 | background-color: #f2f2f2; 216 | border-bottom: 1px solid #ddd; 217 | color: #333; 218 | } 219 | 220 | tt.descname, tt.descclassname, tt.xref { 221 | border: 0; 222 | } 223 | 224 | hr { 225 | border: 1px solid #abc; 226 | margin: 2em; 227 | } 228 | 229 | a tt { 230 | border: 0; 231 | color: #CA7900; 232 | } 233 | 234 | a tt:hover { 235 | color: #2491CF; 236 | } 237 | 238 | pre { 239 | font-family: 'Consolas', 'Deja Vu Sans Mono', 240 | 'Bitstream Vera Sans Mono', monospace; 241 | font-size: 0.95em; 242 | letter-spacing: 0.015em; 243 | line-height: 120%; 244 | padding: 0.5em; 245 | border: 1px solid #ccc; 246 | background-color: #f8f8f8; 247 | } 248 | 249 | pre a { 250 | color: inherit; 251 | text-decoration: underline; 252 | } 253 | 254 | td.linenos pre { 255 | padding: 0.5em 0; 256 | } 257 | 258 | div.quotebar { 259 | background-color: #f8f8f8; 260 | max-width: 250px; 261 | float: right; 262 | padding: 2px 7px; 263 | border: 1px solid #ccc; 264 | } 265 | 266 | div.topic { 267 | background-color: #f8f8f8; 268 | } 269 | 270 | table { 271 | border-collapse: collapse; 272 | margin: 0 -0.5em 0 -0.5em; 273 | } 274 | 275 | table td, table th { 276 | padding: 0.2em 0.5em 0.2em 0.5em; 277 | } 278 | 279 | div.admonition, div.warning { 280 | font-size: 0.9em; 281 | margin: 1em 0 1em 0; 282 | border: 1px solid #86989B; 283 | background-color: #f7f7f7; 284 | padding: 0; 285 | } 286 | 287 | div.admonition p, div.warning p { 288 | margin: 0.5em 1em 0.5em 1em; 289 | padding: 0; 290 | } 291 | 292 | div.admonition pre, div.warning pre { 293 | margin: 0.4em 1em 0.4em 1em; 294 | } 295 | 296 | div.admonition p.admonition-title, 297 | div.warning p.admonition-title { 298 | margin: 0; 299 | padding: 0.1em 0 0.1em 0.5em; 300 | color: white; 301 | border-bottom: 1px solid #86989B; 302 | font-weight: bold; 303 | background-color: #AFC1C4; 304 | } 305 | 306 | div.warning { 307 | border: 1px solid #940000; 308 | } 309 | 310 | div.warning p.admonition-title { 311 | background-color: #CF0000; 312 | border-bottom-color: #940000; 313 | } 314 | 315 | div.admonition ul, div.admonition ol, 316 | div.warning ul, div.warning ol { 317 | margin: 0.1em 0.5em 0.5em 3em; 318 | padding: 0; 319 | } 320 | 321 | div.versioninfo { 322 | margin: 1em 0 0 0; 323 | border: 1px solid #ccc; 324 | background-color: #DDEAF0; 325 | padding: 8px; 326 | line-height: 1.3em; 327 | font-size: 0.9em; 328 | } 329 | 330 | .viewcode-back { 331 | font-family: 'Lucida Grande', 'Lucida Sans Unicode', 'Geneva', 332 | 'Verdana', sans-serif; 333 | } 334 | 335 | div.viewcode-block:target { 336 | background-color: #f4debf; 337 | border-top: 1px solid #ac9; 338 | border-bottom: 1px solid #ac9; 339 | } 340 | -------------------------------------------------------------------------------- /doc/sphinx/source/_templates/index.html: -------------------------------------------------------------------------------- 1 | {% extends "layout.html" %} 2 | {% set title = 'Overview' %} 3 | {% block body %} 4 |

Welcome

5 | 6 | 7 |

8 | 9 | PyFMI is a package for loading and interacting with Functional Mock-Up Units (FMUs) both for Model Exchange and Co-Simulation, which are compiled dynamic models compliant with the Functional Mock-Up Interface (FMI), see here for more information.

10 |

11 | FMI is a standard that enables tool independent exchange of dynamic models on binary format. Several industrial simulation platforms supports export of FMUs, including, Dymola, JModelica.org, OpenModelica and SimulationX, see here for a complete list. PyFMI offers a Python interface for interacting with FMUs and enables for example loading of FMU models, setting of model parameters and evaluation of model equations. 12 |

13 | PyFMI is available as a stand-alone package or as part of the JModelica.org distribution. Using PyFMI together with the Python simulation package Assimulo adds industrial grade simulation capabilities of FMUs to Python. 14 |

15 | The latest version is available for download here 16 |

17 | 18 | 19 |

Documentation

20 | 21 | 22 | 26 | 39 |
23 | 25 | 27 | 29 | 30 | 32 | 33 | 35 | 36 | 38 |
40 | 41 | 42 | {% endblock %} 43 | -------------------------------------------------------------------------------- /doc/sphinx/source/_templates/layout.html: -------------------------------------------------------------------------------- 1 | {% extends "!layout.html" %} 2 | 3 | {%- macro relbar1() %} 4 | 22 | {%- endmacro %} 23 | 24 | {%- block relbar1 %}{{ relbar1() }}{% endblock %} 25 | {%- block relbar2 %}{% endblock %} 26 | -------------------------------------------------------------------------------- /doc/sphinx/source/code.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | =================== 4 | Code Documentation 5 | =================== 6 | 7 | 8 | .. toctree:: 9 | :maxdepth: 4 10 | :glob: 11 | 12 | pyfmi 13 | -------------------------------------------------------------------------------- /doc/sphinx/source/content.rst: -------------------------------------------------------------------------------- 1 | .. PyFMI documentation master file, created by 2 | sphinx-quickstart on Tue Jan 03 16:18:33 2012. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | ==================================== 7 | Documentation contents 8 | ==================================== 9 | 10 | Overview of the contents included in the documentation. 11 | 12 | Contents 13 | ============== 14 | 15 | .. toctree:: 16 | :maxdepth: 5 17 | 18 | tutorial 19 | examples 20 | installation 21 | code 22 | changelog 23 | 24 | 25 | 26 | Indices and tables 27 | ================== 28 | 29 | * :ref:`genindex` 30 | * :ref:`modindex` 31 | * :ref:`search` 32 | 33 | -------------------------------------------------------------------------------- /doc/sphinx/source/examples.rst: -------------------------------------------------------------------------------- 1 | 2 | 3 | ============= 4 | Examples 5 | ============= 6 | 7 | PyFMI comes with a range of different examples which can be a very useful source of information when implementing and solving your own problems. The examples are located in the examples folder of your PyFMI installation and can be imported and run with the following Python commands:: 8 | 9 | from pyfmi.examples import * 10 | 11 | fmi_bouncing_ball.run_demo() 12 | 13 | Below is a full list of the available examples, 14 | 15 | .. toctree:: 16 | :maxdepth: 1 17 | :glob: 18 | 19 | EXAMPLE_* 20 | 21 | .. note:: 22 | 23 | A recommended first starting point would be the :doc:`tutorial`. 24 | -------------------------------------------------------------------------------- /doc/sphinx/source/installation.rst: -------------------------------------------------------------------------------- 1 | .. highlight:: rest 2 | 3 | ============= 4 | Installation 5 | ============= 6 | 7 | This file has been deprecated, please see README.md instead. 8 | -------------------------------------------------------------------------------- /doc/sphinx/source/markup.py: -------------------------------------------------------------------------------- 1 | 2 | def mark_examples(): 3 | import pyfmi.examples as examples 4 | 5 | exclude = ["log_analysis"] 6 | 7 | for ex in examples.__all__: 8 | 9 | if ex in exclude: 10 | continue 11 | 12 | file = open("EXAMPLE_"+ex+".rst",'w') 13 | 14 | file.write(ex + '.py\n') 15 | file.write('===================================\n\n') 16 | file.write('.. autofunction:: pyfmi.examples.'+ex+'.run_demo\n') 17 | file.write(' :noindex:\n\n') 18 | file.write('.. note::\n\n') 19 | file.write(' Press [source] (to the right) to view the example.\n') 20 | file.close() 21 | 22 | 23 | mark_examples() 24 | -------------------------------------------------------------------------------- /doc/sphinx/source/pyfmi.common.plotting.rst: -------------------------------------------------------------------------------- 1 | Plotting Folder 2 | ================ 3 | 4 | Base :mod:`plotting` 5 | ----------------------- 6 | 7 | .. automodule:: pyfmi.common.plotting 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Module :mod:`plot_gui` 13 | ---------------------- 14 | 15 | .. automodule:: pyfmi.common.plotting.plot_gui 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /doc/sphinx/source/pyfmi.common.rst: -------------------------------------------------------------------------------- 1 | Common Folder 2 | ============== 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | pyfmi.common.plotting 10 | 11 | Base :mod:`common` 12 | --------------------- 13 | 14 | .. automodule:: pyfmi.common 15 | :members: 16 | :undoc-members: 17 | :show-inheritance: 18 | 19 | Module :mod:`algorithm_drivers` 20 | ------------------------------- 21 | 22 | .. automodule:: pyfmi.common.algorithm_drivers 23 | :members: 24 | :undoc-members: 25 | :show-inheritance: 26 | 27 | Module :mod:`core` 28 | ------------------ 29 | 30 | .. automodule:: pyfmi.common.core 31 | :members: 32 | :undoc-members: 33 | :show-inheritance: 34 | 35 | Module :mod:`diagnostics` 36 | ------------------ 37 | 38 | .. automodule:: pyfmi.common.diagnostics 39 | :members: 40 | :undoc-members: 41 | :show-inheritance: 42 | 43 | Module :mod:`io` 44 | ---------------- 45 | 46 | .. automodule:: pyfmi.common.io 47 | :members: 48 | :undoc-members: 49 | :show-inheritance: 50 | -------------------------------------------------------------------------------- /doc/sphinx/source/pyfmi.examples.rst: -------------------------------------------------------------------------------- 1 | Examples Folder 2 | ================ 3 | 4 | Base :mod:`examples` 5 | ----------------------- 6 | 7 | .. automodule:: pyfmi.examples 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Module :mod:`fmi_bouncing_ball_cs` 13 | ---------------------------------- 14 | 15 | .. automodule:: pyfmi.examples.fmi_bouncing_ball_cs 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | 21 | Module :mod:`fmi_bouncing_ball` 22 | ------------------------------- 23 | 24 | .. automodule:: pyfmi.examples.fmi_bouncing_ball 25 | :members: 26 | :undoc-members: 27 | :show-inheritance: 28 | 29 | Module :mod:`fmi_bouncing_ball_native` 30 | -------------------------------------- 31 | 32 | .. automodule:: pyfmi.examples.fmi_bouncing_ball_native 33 | :members: 34 | :undoc-members: 35 | :show-inheritance: 36 | 37 | Module :mod:`fmi20_bouncing_ball_native` 38 | ----------------------------------------- 39 | 40 | .. automodule:: pyfmi.examples.fmi20_bouncing_ball_native 41 | :members: 42 | :undoc-members: 43 | :show-inheritance: 44 | 45 | Module :mod:`fmu_with_input` 46 | ---------------------------- 47 | 48 | .. automodule:: pyfmi.examples.fmu_with_input 49 | :members: 50 | :undoc-members: 51 | :show-inheritance: 52 | 53 | Module :mod:`fmu_with_input_function` 54 | ------------------------------------- 55 | 56 | .. automodule:: pyfmi.examples.fmu_with_input_function 57 | :members: 58 | :undoc-members: 59 | :show-inheritance: 60 | -------------------------------------------------------------------------------- /doc/sphinx/source/pyfmi.rst: -------------------------------------------------------------------------------- 1 | PyFMI Folder 2 | ============= 3 | 4 | Subpackages 5 | ----------- 6 | 7 | .. toctree:: 8 | 9 | pyfmi.common 10 | pyfmi.examples 11 | pyfmi.simulation 12 | 13 | 14 | Base :mod:`pyfmi` 15 | -------------------- 16 | 17 | .. automodule:: pyfmi.__init__ 18 | :members: 19 | :undoc-members: 20 | :show-inheritance: 21 | 22 | Module :mod:`fmi` 23 | ----------------- 24 | 25 | .. automodule:: pyfmi.fmi 26 | :members: 27 | :undoc-members: 28 | :show-inheritance: 29 | 30 | Module :mod:`fmi_coupled` 31 | ---------------------------- 32 | 33 | .. automodule:: pyfmi.fmi_coupled 34 | :members: 35 | :undoc-members: 36 | :show-inheritance: 37 | 38 | Module :mod:`fmi_extended` 39 | --------------------------- 40 | 41 | .. automodule:: pyfmi.fmi_extended 42 | :members: 43 | :undoc-members: 44 | :show-inheritance: 45 | 46 | Module :mod:`master` 47 | --------------------- 48 | 49 | .. automodule:: pyfmi.master 50 | :members: 51 | :undoc-members: 52 | :show-inheritance: 53 | 54 | Module :mod:`fmi_algorithm_drivers` 55 | ----------------------------------- 56 | 57 | .. automodule:: pyfmi.fmi_algorithm_drivers 58 | :members: 59 | :undoc-members: 60 | :show-inheritance: 61 | 62 | Module :mod:`debug` 63 | ----------------------------------- 64 | 65 | .. automodule:: pyfmi.debug 66 | :members: 67 | :undoc-members: 68 | :show-inheritance: 69 | 70 | Module :mod:`fmi_util` 71 | ----------------------------------- 72 | 73 | .. automodule:: pyfmi.fmi_util 74 | :members: 75 | :undoc-members: 76 | :show-inheritance: 77 | 78 | -------------------------------------------------------------------------------- /doc/sphinx/source/pyfmi.simulation.rst: -------------------------------------------------------------------------------- 1 | Simulation Folder 2 | ================== 3 | 4 | Base :mod:`simulation` 5 | ------------------------- 6 | 7 | .. automodule:: pyfmi.simulation 8 | :members: 9 | :undoc-members: 10 | :show-inheritance: 11 | 12 | Module :mod:`assimulo_interface` 13 | -------------------------------- 14 | 15 | .. automodule:: pyfmi.simulation.assimulo_interface 16 | :members: 17 | :undoc-members: 18 | :show-inheritance: 19 | 20 | -------------------------------------------------------------------------------- /doc/sphinx/source/tutorial.rst: -------------------------------------------------------------------------------- 1 | ########### 2 | Tutorial 3 | ########### 4 | 5 | This tutorial is intended to give a short introduction on how to use the PyFMI package to load an FMU into Python and to simulate the given model. 6 | 7 | For a more detailed description on how to use PyFMI, see the user's documentation in `JModelica.org `_ 8 | 9 | Loading an FMU into Python 10 | ============================ 11 | 12 | Loading of an FMU is performed by simply importing the necessary object (*load_fmu*) from PyFMI. The object takes care of unzipping the model, loading the XML description and connecting the binary for use from Python. 13 | 14 | .. code-block:: python 15 | 16 | # Import the load function (load_fmu) 17 | from pyfmi import load_fmu 18 | 19 | #Load the FMU 20 | model = load_fmu('myFMU.fmu') 21 | 22 | Note that this will either return an instance of a class consistent with the FMI for Model Exchange definition or for the Co-Simulation definition (and of the correct FMI version). 23 | 24 | Simulating an FMU 25 | ======================== 26 | 27 | Simulation of an FMU exported as Model Exchange requires that the additional package `Assimulo `_ is available. For Co-Simulation FMUs, no additional package is required as the solver is contained inside the FMU. A simulation is performed simply by using the *simulate* method: 28 | 29 | .. code-block:: python 30 | 31 | #Simulate an FMU 32 | res = model.simulate(final_time=10) 33 | 34 | This will simulate the model from its default starting time 0.0 to 10.0 using default options. The result is returned and stored in *res*. 35 | 36 | Information about the arguments to *simulate* is best viewed interactively from for example an IPython shell: 37 | 38 | .. code-block:: python 39 | 40 | #View the documentation for simulate 41 | model.simulate? 42 | 43 | Options (for Model Exchange) 44 | ------------------------------ 45 | 46 | The options for an algorithm, which in our case is Assimulo, can be retrieved by calling the method *simulate_options*: 47 | 48 | .. code-block:: python 49 | 50 | #Get the default options 51 | opts = model.simulate_options() 52 | 53 | This will return the default options for a simulation as a dictionary, for example the default solver is *CVode* from the Assimulo package. Changing the options specifically related to the solver is done by: 54 | 55 | .. code-block:: python 56 | 57 | opts["CVode_options"]["atol"] = 1e-6 #Change the absolute tolerance 58 | opts["CVode_options"]["discr"] = "Adams" #Change the discretization from BDF to Adams 59 | 60 | All options related to the *CVode* solver can be found in the Assimulo documentation. 61 | 62 | For changing general options such as the number of output points (number of communication points, ncp), they are accessed directly: 63 | 64 | .. code-block:: python 65 | 66 | opts["ncp"] = 100 #One hundred output points 67 | 68 | To use the options in an simulation, pass them in the call to the *simulate* method: 69 | 70 | .. code-block:: python 71 | 72 | res = model.simulate(final_time=10, options=opts) 73 | 74 | Options (for Co-Simulation) 75 | ----------------------------- 76 | 77 | The simulation options for a Co-Simulation FMU is retrieved and set consistent as for a Model Exchange FMU. The only difference is the actual options. 78 | 79 | 80 | Result Object 81 | --------------- 82 | 83 | The result object returned from a simulation contains all trajectories related to the variables in the model and are accessed as a dictionary. 84 | 85 | .. code-block:: python 86 | 87 | res = model.simulate() 88 | 89 | y = res['y'] #Return the result for the variable/parameter/constant y 90 | dery = res['der(y)'] #Return the result for the variable/parameter/constant der(y) 91 | 92 | This can be done for all the variables, parameters and constants defined in the model and is the preferred way of retrieving the result. 93 | 94 | 95 | 96 | 97 | Additional information 98 | ======================== 99 | 100 | The PyFMI package comes with a number of examples, showing how to simulate different problems. These examples can be found :doc:`here `. 101 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | testpaths = 3 | tests 4 | filterwarnings = 5 | ignore:.*does not support directional derivatives.*:UserWarning 6 | markers = 7 | assimulo -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [options] 2 | setup_requires = 3 | setuptools 4 | numpy >= 1.19.5 5 | cython >= 3.0 6 | 7 | python_requires = >=3.9 8 | 9 | install_requires = 10 | numpy >= 1.19.5 11 | scipy >= 1.10.1 12 | cython >= 3.0.7 13 | matplotlib > 3 14 | assimulo >= 3.5.0 15 | 16 | tests_require = 17 | pytest >= 7.4.4 18 | -------------------------------------------------------------------------------- /src/common/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2010 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | """ 18 | The JModelica.org Python package for common classes 19 | and functions. 20 | """ 21 | 22 | __all__ = ['algorithm_drivers', 'core', 'diagnostics', 'io', 'plotting'] 23 | 24 | ## for backwards compatibility, to not break 'from pyfmi.common import diagnostics_prefix' 25 | ## TODO: Future: remove this 26 | from .diagnostics import DIAGNOSTICS_PREFIX as diagnostics_prefix 27 | 28 | def encode(x): 29 | if isinstance(x, str): 30 | return x.encode() 31 | else: 32 | return x 33 | def decode(x): 34 | if isinstance(x, bytes): 35 | return x.decode() 36 | else: 37 | return x 38 | -------------------------------------------------------------------------------- /src/common/diagnostics.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2023 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | ## This file contains various components for the 'dynamics_diagnostics' options 20 | 21 | from pyfmi.fmi2 import FMUModelME2 22 | import numpy as np 23 | import numbers 24 | 25 | DIAGNOSTICS_PREFIX = '@Diagnostics.' 26 | 27 | class DiagnosticsBase: 28 | """ Class serves as a template 29 | to keep track of diagnostics variables not part of the generated result file. 30 | """ 31 | calculated_diagnostics = { 32 | 'nbr_events': {'name': f'{DIAGNOSTICS_PREFIX}nbr_events', 'description': 'Cumulative number of events'}, 33 | 'nbr_time_events': {'name': f'{DIAGNOSTICS_PREFIX}nbr_time_events', 'description': 'Cumulative number of time events'}, 34 | 'nbr_state_events': {'name': f'{DIAGNOSTICS_PREFIX}nbr_state_events', 'description': 'Cumulative number of state events'}, 35 | 'nbr_steps': {'name': f'{DIAGNOSTICS_PREFIX}nbr_steps', 'description': 'Cumulative number of steps'}, 36 | 'nbr_state_limits_step': {'name': f'{DIAGNOSTICS_PREFIX}nbr_state_limits_step', 'description': 'Cumulative number of times states limit the step'}, 37 | } 38 | 39 | def setup_diagnostics_variables(model, start_time, options, solver_options): 40 | """ Sets up initial diagnostics data. This function is called before a simulation is initiated. """ 41 | _diagnostics_params = {} 42 | _diagnostics_vars = {} 43 | 44 | if options.get("logging", False): 45 | solver_name = options["solver"] 46 | 47 | _diagnostics_params[f"{DIAGNOSTICS_PREFIX}solver.solver_name.{solver_name}"] = (1.0, "Chosen solver.") 48 | 49 | support_state_errors = (solver_name=="CVode" or solver_name=="Radau5ODE") 50 | support_solver_order = solver_name=="CVode" 51 | support_elapsed_time = solver_options.get("clock_step", False) 52 | 53 | support_event_indicators = (solver_name=="CVode" or 54 | solver_name=="Radau5ODE" or 55 | solver_name=="LSODAR" or 56 | solver_name=="ImplicitEuler" or 57 | solver_name=="ExplicitEuler" 58 | ) 59 | 60 | states_list = model.get_states_list() if isinstance(model, FMUModelME2) else [] 61 | 62 | if solver_name != "ExplicitEuler": 63 | rtol = solver_options.get('rtol', None) 64 | atol = solver_options.get('atol', None) 65 | if (rtol is None) or (atol is None): 66 | rtol, atol = model.get_tolerances() 67 | 68 | # is atol is scalar, convert to list 69 | if isinstance(atol, numbers.Number): 70 | atol = [atol]*len(states_list) 71 | # atol is "pseudoscalar", array/list with single entry; 72 | if np.size(atol) == 1: 73 | # distinction here is need since e.g., np.array(1.) does not support indexing 74 | if isinstance(atol, np.ndarray): 75 | atol = [atol.item()]*len(states_list) 76 | else: # general iterable, e.g., list 77 | atol = [atol[0]]*len(states_list) 78 | 79 | _diagnostics_params[f"{DIAGNOSTICS_PREFIX}solver.relative_tolerance"] = (rtol, "Relative solver tolerance.") 80 | 81 | for idx, state in enumerate(states_list): 82 | _diagnostics_params[f"{DIAGNOSTICS_PREFIX}solver.absolute_tolerance."+state] = (atol[idx], "Absolute solver tolerance for "+state+".") 83 | 84 | _diagnostics_vars[f"{DIAGNOSTICS_PREFIX}step_time"] = (start_time, "Step time") 85 | if support_elapsed_time: 86 | _diagnostics_vars[f"{DIAGNOSTICS_PREFIX}cpu_time_per_step"] = (0, "CPU time per step.") 87 | if support_solver_order: 88 | _diagnostics_vars[f"{DIAGNOSTICS_PREFIX}solver.solver_order"] = (0.0, "Solver order for CVode used in each time step") 89 | if support_state_errors: 90 | for state in states_list: 91 | _diagnostics_vars[f"{DIAGNOSTICS_PREFIX}state_errors."+state] = (0.0, "State error for "+state+".") 92 | if support_event_indicators: 93 | _, nof_ei = model.get_ode_sizes() 94 | ei_values = model.get_event_indicators() if nof_ei > 0 else [] 95 | for i in range(nof_ei): 96 | _diagnostics_vars[f"{DIAGNOSTICS_PREFIX}event_data.event_info.indicator_"+str(i+1)] = (ei_values[i], "Value for event indicator {}.".format(i+1)) 97 | for i in range(nof_ei): 98 | _diagnostics_vars[f"{DIAGNOSTICS_PREFIX}event_data.event_info.state_event_info.index_"+str(i+1)] = (0.0, "Zero crossing indicator for event indicator {}".format(i+1)) 99 | _diagnostics_vars[f"{DIAGNOSTICS_PREFIX}event_data.event_info.event_type"] = (-1, "No event=-1, state event=0, time event=1") 100 | 101 | return _diagnostics_params, _diagnostics_vars 102 | -------------------------------------------------------------------------------- /src/common/log/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2020 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | """ 19 | The log analysis toolkit. 20 | """ 21 | 22 | from pyfmi.common.log.parser import parse_xml_log, parse_xml_log, extract_xml_log, parse_fmu_xml_log 23 | from pyfmi.common.log.prettyprinter import prettyprint_to_file 24 | 25 | __all__=['parser', 'tree', 'prettyprinter'] 26 | -------------------------------------------------------------------------------- /src/common/log/prettyprinter.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2020 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | """ 18 | Prettyprinter for in memory log trees (as parsed by parser.py) 19 | """ 20 | 21 | from numpy import ndarray 22 | from pyfmi.common.log.tree import Node, Comment 23 | 24 | def prettyprint(write, node): 25 | """Prettyprint a log node to the write callback write.""" 26 | if isinstance(node, Node): 27 | write('<' + node.type + '>') 28 | for (key, child) in zip(node.keys, node.nodes): 29 | if key is not None: 30 | write('<' + key + ' = ') 31 | prettyprint(write, child) 32 | write('>') 33 | else: 34 | prettyprint(write, child) 35 | write('') 36 | elif isinstance(node, Comment): 37 | write(node.text) 38 | elif isinstance(node, str): 39 | write(repr(node)) 40 | elif isinstance(node, ndarray) and node.ndim > 1: 41 | # Multiline array literals won't be indented properly; since we're 42 | # not keeping track of indentation, just put them on their own lines. 43 | write('\n') 44 | write(str(node)) 45 | else: 46 | write(str(node)) 47 | 48 | def prettyprint_to_file(destfilename, node): 49 | with open(destfilename, 'w') as f: 50 | prettyprint(f.write, node) 51 | -------------------------------------------------------------------------------- /src/common/log/tree.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2020 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | """ 18 | Tree representation for an FMU XML log format 19 | 20 | Each node is represented as a Node, Comment, or leaf (other types) 21 | """ 22 | 23 | class Comment(object): 24 | """Log comment node. 25 | 26 | Attributes: 27 | text -- the comment text without enclosing braces {} 28 | """ 29 | def __init__(self, text): 30 | self.text = text 31 | 32 | def __repr__(self): 33 | return '' 34 | 35 | class Node(object): 36 | """Log node. 37 | 38 | Attributes: 39 | type -- a string 40 | nodes -- a list of child nodes, in order 41 | """ 42 | 43 | def __init__(self, node_type): 44 | self.type = node_type 45 | self.nodes = [] 46 | self.keys = [] 47 | self.dict = {} 48 | 49 | def add(self, node, key=None): 50 | self.keys.append(key) 51 | self.nodes.append(node) 52 | if key is not None: 53 | if key in self.dict: 54 | # Duplicate attribute value ==> record no value. (should not happen) 55 | # consider: Is None the best to use for this? 56 | self.dict[key] = None 57 | else: 58 | self.dict[key] = node 59 | 60 | def __repr__(self): 61 | return ('<' + self.type + ' node with ' + repr(len(self.nodes)) 62 | + ' subnodes, and named subnodes ' + repr(list(self.dict.keys())) + '>') 63 | 64 | 65 | def __iter__(self): 66 | return iter(self.nodes) 67 | 68 | def __contains__(self, key): 69 | return key in self.dict 70 | 71 | def __getitem__(self, key): 72 | return self.dict[key] 73 | 74 | def __getattr__(self, name): 75 | try: 76 | return self[name] 77 | except KeyError: 78 | classname = type(self).__name__ 79 | msg = "%s object has no attribute %s"%(classname, name) 80 | raise AttributeError(msg) 81 | 82 | def __setitem__(self, key, value): 83 | self.dict[key] = value 84 | 85 | def find(self, types): 86 | """ 87 | Return a list of children with the given type(s), in order. 88 | 89 | types may be a string or list of strings. 90 | """ 91 | if isinstance(types, str): 92 | types = [types] 93 | 94 | nodes = [] 95 | for node in self.nodes: 96 | if isinstance(node, Node): 97 | if node.type in types: 98 | nodes.append(node) 99 | else: 100 | nodes.extend(node.find(types)) 101 | return nodes 102 | -------------------------------------------------------------------------------- /src/common/plotting/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2010 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | """ 19 | This package contains the interface to different simulation packages. 20 | """ 21 | __all__ = ['plot_gui'] 22 | -------------------------------------------------------------------------------- /src/pyfmi/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | """ 18 | The JModelica.org Python package for working with FMI 19 | """ 20 | 21 | __all__ = ['fmi_algorithm_drivers', 'examples', 'fmi', 'common'] 22 | 23 | #Import the model class allowing for users to type e.g.,: from pyfmi import FMUModelME1 24 | from pyfmi.fmi import load_fmu 25 | from pyfmi.fmi1 import FMUModelME1, FMUModelCS1 26 | from pyfmi.fmi2 import FMUModelME2, FMUModelCS2 27 | from pyfmi.fmi_coupled import CoupledFMUModelME2 28 | from pyfmi.master import Master 29 | from pyfmi.fmi_extended import FMUModelME1Extended 30 | import os.path 31 | import sys 32 | import time 33 | 34 | try: 35 | curr_dir = os.path.dirname(os.path.abspath(__file__)) 36 | _fpath=os.path.join(curr_dir,'version.txt') 37 | with open(_fpath, 'r') as f: 38 | __version__=f.readline().strip() 39 | __revision__=f.readline().strip() 40 | except Exception: 41 | __version__ = "unknown" 42 | __revision__= "unknown" 43 | 44 | 45 | def check_packages(): 46 | le=30 47 | le_short=15 48 | startstr = "Performing pyfmi package check" 49 | sys.stdout.write("\n") 50 | sys.stdout.write(startstr+" \n") 51 | sys.stdout.write("="*len(startstr)) 52 | sys.stdout.write("\n\n") 53 | sys.stdout.flush() 54 | time.sleep(0.25) 55 | 56 | # print pyfmi version 57 | sys.stdout.write( 58 | "%s %s" %("PyFMI version ".ljust(le,'.'),(__version__).ljust(le)+"\n\n")) 59 | sys.stdout.flush() 60 | time.sleep(0.25) 61 | 62 | # check os 63 | platform = sys.platform 64 | sys.stdout.write( 65 | "%s %s" %("Platform ".ljust(le,'.'),(str(platform)).ljust(le)+"\n\n")) 66 | sys.stdout.flush() 67 | time.sleep(0.25) 68 | 69 | #check python version 70 | pyversion = sys.version.partition(" ")[0] 71 | sys.stdout.write( 72 | "%s %s" % ("Python version ".ljust(le,'.'),pyversion.ljust(le))) 73 | sys.stdout.write("\n\n") 74 | sys.stdout.flush() 75 | time.sleep(0.25) 76 | 77 | import imp 78 | # Test dependencies 79 | sys.stdout.write("\n\n") 80 | sys.stdout.write("Dependencies: \n\n".rjust(0)) 81 | modstr="Package" 82 | verstr="Version" 83 | sys.stdout.write("%s %s" % (modstr.ljust(le), verstr.ljust(le))) 84 | sys.stdout.write("\n") 85 | sys.stdout.write( 86 | "%s %s" % (("-"*len(modstr)).ljust(le), ("-"*len(verstr)).ljust(le))) 87 | sys.stdout.write("\n") 88 | 89 | packages=["assimulo", "Cython", "matplotlib", "numpy", "scipy", "wxPython"] 90 | 91 | if platform == "win32": 92 | packages.append("pyreadline") 93 | packages.append("setuptools") 94 | 95 | error_packages=[] 96 | warning_packages=[] 97 | fp = None 98 | for package in packages: 99 | try: 100 | vers="--" 101 | fp, path, desc = imp.find_module(package) 102 | mod = imp.load_module(package, fp, path, desc) 103 | 104 | try: 105 | if package == "pyreadline": 106 | vers = mod.release.version 107 | else: 108 | vers = mod.__version__ 109 | except AttributeError: 110 | pass 111 | sys.stdout.write("%s %s" %(package.ljust(le,'.'), vers.ljust(le))) 112 | except ImportError: 113 | if package == "assimulo" or package == "wxPython": 114 | sys.stdout.write("%s %s %s" % (package.ljust(le,'.'), vers.ljust(le_short), "Package missing - Warning issued, see details below".ljust(le_short))) 115 | warning_packages.append(package) 116 | else: 117 | sys.stdout.write("%s %s %s " % (package.ljust(le,'.'), vers.ljust(le_short), "Package missing - Error issued, see details below.".ljust(le_short))) 118 | error_packages.append(package) 119 | pass 120 | finally: 121 | if fp: 122 | fp.close() 123 | sys.stdout.write("\n") 124 | sys.stdout.flush() 125 | time.sleep(0.25) 126 | 127 | 128 | # Write errors and warnings 129 | # are there any errors? 130 | if len(error_packages) > 0: 131 | sys.stdout.write("\n") 132 | errtitle = "Errors" 133 | sys.stdout.write("\n") 134 | sys.stdout.write(errtitle+" \n") 135 | sys.stdout.write("-"*len(errtitle)) 136 | sys.stdout.write("\n\n") 137 | sys.stdout.write("The package(s): \n\n") 138 | 139 | for er in error_packages: 140 | sys.stdout.write(" - "+str(er)) 141 | sys.stdout.write("\n") 142 | sys.stdout.write("\n") 143 | sys.stdout.write("could not be found. It is not possible to run the pyfmi \ 144 | package without it/them.\n") 145 | 146 | if len(warning_packages) > 0: 147 | sys.stdout.write("\n") 148 | wartitle = "Warnings" 149 | sys.stdout.write("\n") 150 | sys.stdout.write(wartitle+" \n") 151 | sys.stdout.write("-"*len(wartitle)) 152 | sys.stdout.write("\n\n") 153 | 154 | for w in warning_packages: 155 | if w == 'assimulo': 156 | sys.stdout.write("-- The package assimulo could not be found. \ 157 | This package is needed to be able to simulate FMUs. Also, some of the examples \ 158 | in pyfmi.examples will not work.") 159 | elif w == 'wxPython': 160 | sys.stdout.write("-- The package wxPython could not be found. \ 161 | This package is needed to be able to use the plot-GUI.") 162 | 163 | sys.stdout.write("\n\n") 164 | -------------------------------------------------------------------------------- /src/pyfmi/debug.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | """ 19 | This file contains methods for helping with debugging a simulation. 20 | """ 21 | 22 | import numpy as np 23 | import pylab as pl 24 | 25 | class DebugInformation: 26 | 27 | def __init__(self, file_name): 28 | self.simulated_time = [] 29 | self.real_time = [] 30 | self.events = [] 31 | self.event_indicators = [] 32 | self.state_variables = [] 33 | self.file_name = file_name 34 | 35 | self._load_data() 36 | 37 | def _load_data(self): 38 | file_name = self.file_name 39 | 40 | with open(file_name) as file: 41 | while True: 42 | row_data = file.readline() 43 | if row_data.startswith("Time"): 44 | row_data = file.readline() 45 | while row_data != "\n" and row_data != "": 46 | row_data = row_data.split("|") 47 | self.simulated_time.append(float(row_data[0].strip())) 48 | self.real_time.append(float(row_data[1].strip())) 49 | if len(row_data) > 2: 50 | event_indicators = row_data[2].strip().replace("\n","") 51 | event_indicators = event_indicators.split(" ") 52 | self.event_indicators.append(np.array([abs(float(i)) for i in event_indicators])) 53 | row_data = file.readline() 54 | elif row_data.startswith("Solver"): 55 | self.solver = row_data.split(" ")[-1] 56 | elif row_data.startswith("State variables:"): 57 | self.state_variables = row_data.replace(",","").split(" ")[2:-1] 58 | elif row_data.startswith("Detected"): 59 | self.events.append(float(row_data.split(" ")[-2])) 60 | if not row_data: 61 | break 62 | 63 | def plot_time_distribution(self, normalized=False): 64 | 65 | if normalized: 66 | total_time = np.sum(self.real_time) 67 | pl.plot(self.simulated_time,self.real_time/total_time) 68 | pl.ylabel("Real Time (normalized)") 69 | else: 70 | pl.plot(self.simulated_time,self.real_time) 71 | pl.ylabel("Real Time [s]") 72 | pl.xlabel("Time [s]") 73 | 74 | self._plot_events() 75 | pl.legend(("Time","Events")) 76 | 77 | pl.grid() 78 | pl.show() 79 | 80 | def plot_cumulative_time_elapsed(self, log_scale=False): 81 | cumulative_sum = np.cumsum(self.real_time) 82 | 83 | if log_scale: 84 | pl.semilogy(self.simulated_time, cumulative_sum) 85 | else: 86 | pl.plot(self.simulated_time, cumulative_sum) 87 | pl.xlabel("Time [s]") 88 | pl.ylabel("Real Time [s]") 89 | 90 | self._plot_events() 91 | pl.legend(("Time","Events")) 92 | 93 | pl.grid() 94 | pl.show() 95 | 96 | def plot_step_size(self): 97 | pl.semilogy(self.simulated_time,np.diff([0.0]+self.simulated_time),drawstyle="steps-pre") 98 | pl.ylabel("Step-size") 99 | pl.xlabel("Time [s]") 100 | pl.title("Step-size history") 101 | pl.grid() 102 | pl.show() 103 | 104 | def _plot_events(self): 105 | for ev in self.events: 106 | pl.axvline(x=ev,color='r') 107 | 108 | def plot_event_indicators(self, mask=None, region=None): 109 | ev_ind = np.array(self.event_indicators) 110 | time = np.array(self.simulated_time) 111 | ev_ind_name = np.array(["event_ind_%d"%i for i in range(len(ev_ind[0,:]))]) 112 | 113 | if region: 114 | lw = time > region[0] 115 | up = time < region[1] 116 | time = time[np.logical_and(lw,up)] 117 | ev_ind = ev_ind[np.logical_and(lw,up), :] 118 | 119 | if mask: 120 | pl.plot(time, ev_ind[:,mask]) 121 | pl.legend(ev_ind_name[mask]) 122 | else: 123 | pl.plot(time, ev_ind) 124 | pl.legend(ev_ind_name) 125 | pl.grid() 126 | pl.xlabel("Time [s]") 127 | pl.title("Event Indicators") 128 | pl.show() 129 | 130 | class ImplicitEulerDebugInformation(DebugInformation): 131 | pass 132 | 133 | class ExplicitEulerDebugInformation(DebugInformation): 134 | pass 135 | 136 | class CVodeDebugInformation(DebugInformation): 137 | 138 | def __init__(self, file_name): 139 | self.order = [] 140 | self.weighted_error = [] 141 | 142 | #Call the super class 143 | DebugInformation.__init__(self, file_name) 144 | 145 | def _load_data(self): 146 | file_name = self.file_name 147 | 148 | with open(file_name) as file: 149 | while True: 150 | row_data = file.readline() 151 | if row_data.startswith("Time"): 152 | row_data = file.readline() 153 | while row_data != "\n" and row_data != "": 154 | row_data = row_data.split("|") 155 | self.simulated_time.append(float(row_data[0].strip())) 156 | self.real_time.append(float(row_data[1].strip())) 157 | self.order.append(int(row_data[2].strip())) 158 | error_data = row_data[3].strip().replace("\n","") 159 | error_data = error_data.split(" ") 160 | self.weighted_error.append(np.array([abs(float(i)) for i in error_data])) 161 | if len(row_data) > 4: 162 | event_indicators = row_data[4].strip().replace("\n","") 163 | event_indicators = event_indicators.split(" ") 164 | self.event_indicators.append(np.array([abs(float(i)) for i in event_indicators])) 165 | row_data = file.readline() 166 | elif row_data.startswith("Solver"): 167 | self.solver = row_data.split(" ")[-1] 168 | elif row_data.startswith("State variables:"): 169 | self.state_variables = row_data.replace(",","").split(" ")[2:-1] 170 | elif row_data.startswith("Detected"): 171 | self.events.append(float(row_data.split(" ")[-2])) 172 | if not row_data: 173 | break 174 | 175 | 176 | def plot_order(self): 177 | pl.plot(self.simulated_time, self.order,drawstyle="steps-pre") 178 | pl.grid() 179 | pl.xlabel("Time [s]") 180 | pl.ylabel("Order") 181 | pl.title("Order evolution") 182 | 183 | self._plot_events() 184 | 185 | pl.legend(("Order","Events")) 186 | 187 | pl.show() 188 | 189 | def plot_error(self, threshold=None, region=None, legend=True): 190 | err = np.array(self.weighted_error) 191 | time = np.array(self.simulated_time) 192 | if region: 193 | lw = time > region[0] 194 | up = time < region[1] 195 | time = time[np.logical_and(lw,up)] 196 | err = err[np.logical_and(lw,up), :] 197 | if threshold: 198 | time_points, nbr_vars = err.shape 199 | mask = np.ones(nbr_vars,dtype=bool) 200 | for i in range(nbr_vars): 201 | if np.max(err[:,i]) < threshold: 202 | mask[i] = False 203 | pl.semilogy(time, err[:,mask]) 204 | if legend: 205 | pl.legend(np.array(self.state_variables)[mask],loc="lower right") 206 | else: 207 | pl.semilogy(time, err) 208 | if legend: 209 | pl.legend(self.state_variables, loc="lower right") 210 | pl.xlabel("Time [s]") 211 | pl.ylabel("Error") 212 | pl.title("Error evolution") 213 | pl.grid() 214 | pl.show() 215 | 216 | class Radau5ODEDebugInformation(CVodeDebugInformation): 217 | pass 218 | -------------------------------------------------------------------------------- /src/pyfmi/examples/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | """ 19 | The pyfmi example package. 20 | """ 21 | __all__ = ['fmi_bouncing_ball_native', 'fmi20_bouncing_ball_native', 'fmi_bouncing_ball', 'fmu_with_input', 22 | 'fmu_with_input_function','fmi_bouncing_ball_cs'] 23 | -------------------------------------------------------------------------------- /src/pyfmi/examples/files/FMUs/CS1.0/bouncingBall.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/src/pyfmi/examples/files/FMUs/CS1.0/bouncingBall.fmu -------------------------------------------------------------------------------- /src/pyfmi/examples/files/FMUs/CS2.0/README.txt: -------------------------------------------------------------------------------- 1 | Regarding, bouncingBall.fmu 2 | 3 | These files have been generated using the FMU SDK by Qtronic 4 | (http://www.qtronic.de/en/fmusdk.html). 5 | 6 | Below is a copy of the license of the FMU SDK: 7 | 8 | FMU SDK license 9 | 10 | Copyright © 2008-2014, QTronic GmbH. All rights reserved. 11 | The FmuSdk is licensed by the copyright holder under the BSD License 12 | (http://www.opensource.org/licenses/bsd-license.html): 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | - Redistributions of source code must retain the above copyright notice, 16 | this list of conditions and the following disclaimer. 17 | - Redistributions in binary form must reproduce the above copyright notice, 18 | this list of conditions and the following disclaimer in the documentation 19 | and/or other materials provided with the distribution. 20 | 21 | THIS SOFTWARE IS PROVIDED BY QTRONIC GMBH "AS IS" AND ANY EXPRESS OR 22 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | IN NO EVENT SHALL QTRONIC GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /src/pyfmi/examples/files/FMUs/CS2.0/bouncingBall.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/src/pyfmi/examples/files/FMUs/CS2.0/bouncingBall.fmu -------------------------------------------------------------------------------- /src/pyfmi/examples/files/FMUs/ME1.0/README.txt: -------------------------------------------------------------------------------- 1 | Regarding, bouncingBall.fmu 2 | 3 | 4 | This file have been generated using the FMU SDK by Qtronic 5 | (http://www.qtronic.de/en/fmusdk.html) together with the Linux 6 | port by Michael Tiller (http://github.com/mtiller/fmusdk). 7 | 8 | Below is a copy of the license of the FMU SDK: 9 | 10 | FMU SDK license 11 | 12 | Copyright © 2008-2010, QTronic GmbH. All rights reserved. 13 | The FmuSdk is licensed by the copyright holder under the BSD License 14 | (http://www.opensource.org/licenses/bsd-license.html): 15 | Redistribution and use in source and binary forms, with or without 16 | modification, are permitted provided that the following conditions are met: 17 | - Redistributions of source code must retain the above copyright notice, 18 | this list of conditions and the following disclaimer. 19 | - Redistributions in binary form must reproduce the above copyright notice, 20 | this list of conditions and the following disclaimer in the documentation 21 | and/or other materials provided with the distribution. 22 | 23 | THIS SOFTWARE IS PROVIDED BY QTRONIC GMBH "AS IS" AND ANY EXPRESS OR 24 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 25 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 26 | IN NO EVENT SHALL QTRONIC GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, 27 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 28 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 29 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 30 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 31 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 32 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 33 | 34 | -------------------------------------------------------------------------------- /src/pyfmi/examples/files/FMUs/ME1.0/SecondOrder.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/src/pyfmi/examples/files/FMUs/ME1.0/SecondOrder.fmu -------------------------------------------------------------------------------- /src/pyfmi/examples/files/FMUs/ME1.0/bouncingBall.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/src/pyfmi/examples/files/FMUs/ME1.0/bouncingBall.fmu -------------------------------------------------------------------------------- /src/pyfmi/examples/files/FMUs/ME2.0/README.txt: -------------------------------------------------------------------------------- 1 | Regarding, bouncingBall.fmu 2 | 3 | These files have been generated using the FMU SDK by Qtronic 4 | (http://www.qtronic.de/en/fmusdk.html). 5 | 6 | Below is a copy of the license of the FMU SDK: 7 | 8 | FMU SDK license 9 | 10 | Copyright © 2008-2014, QTronic GmbH. All rights reserved. 11 | The FmuSdk is licensed by the copyright holder under the BSD License 12 | (http://www.opensource.org/licenses/bsd-license.html): 13 | Redistribution and use in source and binary forms, with or without 14 | modification, are permitted provided that the following conditions are met: 15 | - Redistributions of source code must retain the above copyright notice, 16 | this list of conditions and the following disclaimer. 17 | - Redistributions in binary form must reproduce the above copyright notice, 18 | this list of conditions and the following disclaimer in the documentation 19 | and/or other materials provided with the distribution. 20 | 21 | THIS SOFTWARE IS PROVIDED BY QTRONIC GMBH "AS IS" AND ANY EXPRESS OR 22 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 23 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 24 | IN NO EVENT SHALL QTRONIC GMBH BE LIABLE FOR ANY DIRECT, INDIRECT, 25 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 26 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 30 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /src/pyfmi/examples/files/FMUs/ME2.0/bouncingBall.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/src/pyfmi/examples/files/FMUs/ME2.0/bouncingBall.fmu -------------------------------------------------------------------------------- /src/pyfmi/examples/fmi20_bouncing_ball_native.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2018 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import pylab as pl 20 | import numpy as np 21 | 22 | from pyfmi import load_fmu 23 | 24 | curr_dir = os.path.dirname(os.path.abspath(__file__)) 25 | path_to_fmus = os.path.join(curr_dir, 'files', 'FMUs') 26 | path_to_fmus_me2 = os.path.join(path_to_fmus,"ME2.0") 27 | 28 | def run_demo(with_plots=True): 29 | """ 30 | This example shows how to use the raw (JModelica.org) FMI interface for 31 | simulation of an FMU. 32 | 33 | FMU = bouncingBall.fmu 34 | (Generated using Qtronic FMU SDK (http://www.qtronic.de/en/fmusdk.html) ) 35 | 36 | This example is written similar to the example in the documentation of the 37 | 'Functional Mock-up Interface for Model Exchange' version 2.0 38 | (http://www.functional-mockup-interface.org/) 39 | """ 40 | 41 | #Load the FMU by specifying the fmu and the directory 42 | bouncing_fmu = load_fmu(os.path.join(path_to_fmus_me2, 'bouncingBall.fmu')) 43 | 44 | Tstart = 0.5 #The start time. 45 | Tend = 3.0 #The final simulation time. 46 | rtol = 1e-6 ## relative tolerance 47 | 48 | # Initialize the model. Also sets all the start attributes defined in the 49 | # XML file. 50 | bouncing_fmu.setup_experiment(start_time = Tstart) 51 | bouncing_fmu.enter_initialization_mode() 52 | bouncing_fmu.exit_initialization_mode() 53 | 54 | eInfo = bouncing_fmu.get_event_info() 55 | eInfo.newDiscreteStatesNeeded = True 56 | #Event iteration 57 | while eInfo.newDiscreteStatesNeeded: 58 | bouncing_fmu.enter_event_mode() 59 | bouncing_fmu.event_update() 60 | eInfo = bouncing_fmu.get_event_info() 61 | 62 | bouncing_fmu.enter_continuous_time_mode() 63 | #Get Continuous States 64 | x = bouncing_fmu.continuous_states 65 | #Get the Nominal Values 66 | x_nominal = bouncing_fmu.nominal_continuous_states 67 | #Get the Event Indicators 68 | event_ind = bouncing_fmu.get_event_indicators() 69 | 70 | #For retrieving the solutions use, 71 | #bouncing_fmu.get_real,get_integer,get_boolean,get_string (valueref) 72 | 73 | #Values for the solution 74 | #Retrieve the valureferences for the values 'h' and 'v' 75 | vref = [bouncing_fmu.get_variable_valueref('h')] + [bouncing_fmu.get_variable_valueref('v')] 76 | 77 | t_sol = [Tstart] 78 | sol = [bouncing_fmu.get_real(vref)] 79 | 80 | #Main integration loop. 81 | time = Tstart 82 | Tnext = Tend #Used for time events 83 | dt = 0.01 #Step-size 84 | 85 | while time < Tend and not bouncing_fmu.get_event_info().terminateSimulation: 86 | #Compute the derivative 87 | dx = bouncing_fmu.get_derivatives() 88 | 89 | #Advance 90 | h = min(dt, Tnext-time) 91 | time = time + h 92 | 93 | #Set the time 94 | bouncing_fmu.time = time 95 | 96 | #Set the inputs at the current time (if any) 97 | #bouncing_fmu.set_real,set_integer,set_boolean,set_string (valueref, values) 98 | 99 | #Set the states at t = time (Perform the step) 100 | x = x + h*dx 101 | bouncing_fmu.continuous_states = x 102 | 103 | #Get the event indicators at t = time 104 | event_ind_new = bouncing_fmu.get_event_indicators() 105 | 106 | #Inform the model about an accepted step and check for step events 107 | step_event, terminate = bouncing_fmu.completed_integrator_step() 108 | if terminate: 109 | return 110 | 111 | #Check for time and state events 112 | time_event = abs(time-Tnext) <= 1.e-10 113 | state_event = True in ((event_ind_new>0.0) != (event_ind>0.0)) 114 | 115 | #Event handling 116 | if step_event or time_event or state_event: 117 | bouncing_fmu.enter_event_mode() 118 | eInfo = bouncing_fmu.get_event_info() 119 | eInfo.newDiscreteStatesNeeded = True 120 | #Event iteration 121 | while eInfo.newDiscreteStatesNeeded: 122 | bouncing_fmu.event_update(intermediateResult=True) #Stops after each event iteration 123 | eInfo = bouncing_fmu.get_event_info() 124 | 125 | #Retrieve solutions (if needed) 126 | if eInfo.newDiscreteStatesNeeded: 127 | #bouncing_fmu.get_real, get_integer, get_boolean, 128 | # get_string(valueref) 129 | pass 130 | 131 | #Check if the event affected the state values and if so sets them 132 | if eInfo.valuesOfContinuousStatesChanged: 133 | x = bouncing_fmu.continuous_states 134 | 135 | #Get new nominal values. 136 | if eInfo.nominalsOfContinuousStatesChanged: 137 | atol = 0.01*rtol*bouncing_fmu.nominal_continuous_states 138 | 139 | #Check for new time event 140 | if eInfo.nextEventTimeDefined: 141 | Tnext = min(eInfo.nextEventTime, Tend) 142 | else: 143 | Tnext = Tend 144 | bouncing_fmu.enter_continuous_time_mode() 145 | 146 | event_ind = event_ind_new 147 | 148 | #Retrieve solutions at t=time for outputs 149 | #bouncing_fmu.get_real,get_integer,get_boolean,get_string (valueref) 150 | 151 | t_sol += [time] 152 | sol += [bouncing_fmu.get_real(vref)] 153 | 154 | 155 | #Plot the solution 156 | if with_plots: 157 | #Plot the height 158 | pl.figure(1) 159 | pl.plot(t_sol,np.array(sol)[:,0]) 160 | pl.title(bouncing_fmu.get_name()) 161 | pl.ylabel('Height (m)') 162 | pl.xlabel('Time (s)') 163 | #Plot the velocity 164 | pl.figure(2) 165 | pl.plot(t_sol,np.array(sol)[:,1]) 166 | pl.title(bouncing_fmu.get_name()) 167 | pl.ylabel('Velocity (m/s)') 168 | pl.xlabel('Time (s)') 169 | pl.show() 170 | 171 | if __name__ == "__main__": 172 | run_demo() 173 | -------------------------------------------------------------------------------- /src/pyfmi/examples/fmi_bouncing_ball.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import pylab as pl 20 | import numpy as np 21 | 22 | from pyfmi import load_fmu 23 | 24 | curr_dir = os.path.dirname(os.path.abspath(__file__)); 25 | path_to_fmus = os.path.join(curr_dir, 'files', 'FMUs','ME1.0') 26 | path_to_fmus2 = os.path.join(curr_dir, 'files', 'FMUs','ME2.0') 27 | 28 | def run_demo(with_plots=True, version="2.0"): 29 | """ 30 | Demonstrates how to use JModelica.org for simulation of 31 | ME FMUs version 1.0 and 2.0. 32 | """ 33 | if version == '1.0': 34 | fmu_name = os.path.join(path_to_fmus,'bouncingBall.fmu') 35 | else: 36 | fmu_name = os.path.join(path_to_fmus2,'bouncingBall.fmu') 37 | 38 | model = load_fmu(fmu_name) 39 | 40 | res = model.simulate(final_time=2.) 41 | 42 | # Retrieve the result for the variables 43 | h_res = res['h'] 44 | v_res = res['v'] 45 | t = res['time'] 46 | 47 | assert np.abs(res.final('h') - (0.0424044)) < 1e-4 48 | 49 | # Plot the solution 50 | if with_plots: 51 | # Plot the height 52 | pl.figure() 53 | pl.clf() 54 | pl.subplot(2,1,1) 55 | pl.plot(t, h_res) 56 | pl.ylabel('Height (m)') 57 | pl.xlabel('Time (s)') 58 | # Plot the velocity 59 | pl.subplot(2,1,2) 60 | pl.plot(t, v_res) 61 | pl.ylabel('Velocity (m/s)') 62 | pl.xlabel('Time (s)') 63 | pl.suptitle('FMI Bouncing Ball') 64 | pl.show() 65 | 66 | 67 | if __name__ == "__main__": 68 | run_demo() 69 | -------------------------------------------------------------------------------- /src/pyfmi/examples/fmi_bouncing_ball_cs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import pylab as pl 20 | import numpy as np 21 | 22 | from pyfmi import load_fmu 23 | 24 | curr_dir = os.path.dirname(os.path.abspath(__file__)); 25 | path_to_fmus = os.path.join(curr_dir, 'files', 'FMUs', 'CS1.0') 26 | path_to_fmus2 = os.path.join(curr_dir, 'files', 'FMUs', 'CS2.0') 27 | 28 | def run_demo(with_plots=True, version='2.0'): 29 | """ 30 | Demonstrates how to use PyFMI for simulation of 31 | Co-Simulation FMUs (version 1.0 or 2.0). 32 | """ 33 | 34 | if version == '1.0': 35 | fmu_name = os.path.join(path_to_fmus,'bouncingBall.fmu') 36 | else: 37 | fmu_name = os.path.join(path_to_fmus2,'bouncingBall.fmu') 38 | model = load_fmu(fmu_name) 39 | 40 | res = model.simulate(final_time=2.) 41 | 42 | # Retrieve the result for the variables 43 | h_res = res['h'] 44 | v_res = res['v'] 45 | t = res['time'] 46 | 47 | assert np.abs(res.final('h') - (0.0424044)) < 1e-2 48 | 49 | # Plot the solution 50 | if with_plots: 51 | # Plot the height 52 | pl.figure() 53 | pl.clf() 54 | pl.subplot(2,1,1) 55 | pl.plot(t, h_res) 56 | pl.ylabel('Height (m)') 57 | pl.xlabel('Time (s)') 58 | # Plot the velocity 59 | pl.subplot(2,1,2) 60 | pl.plot(t, v_res) 61 | pl.ylabel('Velocity (m/s)') 62 | pl.xlabel('Time (s)') 63 | pl.suptitle('FMI Bouncing Ball') 64 | pl.show() 65 | 66 | 67 | if __name__ == "__main__": 68 | run_demo() 69 | -------------------------------------------------------------------------------- /src/pyfmi/examples/fmi_bouncing_ball_native.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | import os 18 | 19 | import pylab as pl 20 | import numpy as np 21 | 22 | from pyfmi import load_fmu 23 | 24 | curr_dir = os.path.dirname(os.path.abspath(__file__)) 25 | path_to_fmus = os.path.join(curr_dir, 'files', 'FMUs') 26 | path_to_fmus_me1 = os.path.join(path_to_fmus,"ME1.0") 27 | path_to_fmus_cs1 = os.path.join(path_to_fmus,"CS1.0") 28 | 29 | def run_demo(with_plots=True): 30 | """ 31 | This example shows how to use the raw (JModelica.org) FMI interface for 32 | simulation of an FMU. 33 | 34 | FMU = bouncingBall.fmu 35 | (Generated using Qtronic FMU SDK (http://www.qtronic.de/en/fmusdk.html) ) 36 | 37 | This example is written similair to the example in the documentation of the 38 | 'Functional Mock-up Interface for Model Exchange' version 1.0 39 | (http://www.functional-mockup-interface.org/) 40 | """ 41 | 42 | #Load the FMU by specifying the fmu and the directory 43 | bouncing_fmu = load_fmu(os.path.join(path_to_fmus_me1, 'bouncingBall.fmu')) 44 | 45 | Tstart = 0.5 #The start time. 46 | Tend = 3.0 #The final simulation time. 47 | 48 | bouncing_fmu.time = Tstart #Set the start time before the initialization. 49 | 50 | #Initialize the model. Also sets all the start attributes defined in the 51 | # XML file. 52 | bouncing_fmu.initialize() 53 | 54 | #Get Continuous States 55 | x = bouncing_fmu.continuous_states 56 | #Get the Nominal Values 57 | x_nominal = bouncing_fmu.nominal_continuous_states 58 | #Get the Event Indicators 59 | event_ind = bouncing_fmu.get_event_indicators() 60 | 61 | #For retrieving the solutions use, 62 | #bouncing_fmu.get_real,get_integer,get_boolean,get_string (valueref) 63 | 64 | #Values for the solution 65 | #Retrieve the valureferences for the values 'h' and 'v' 66 | vref = [bouncing_fmu.get_variable_valueref('h')] + [bouncing_fmu.get_variable_valueref('v')] 67 | 68 | t_sol = [Tstart] 69 | sol = [bouncing_fmu.get_real(vref)] 70 | 71 | #Main integration loop. 72 | time = Tstart 73 | Tnext = Tend #Used for time events 74 | dt = 0.01 #Step-size 75 | 76 | while time < Tend and not bouncing_fmu.get_event_info().terminateSimulation: 77 | #Compute the derivative 78 | dx = bouncing_fmu.get_derivatives() 79 | 80 | #Advance 81 | h = min(dt, Tnext-time) 82 | time = time + h 83 | 84 | #Set the time 85 | bouncing_fmu.time = time 86 | 87 | #Set the inputs at the current time (if any) 88 | #bouncing_fmu.set_real,set_integer,set_boolean,set_string (valueref, values) 89 | 90 | #Set the states at t = time (Perform the step) 91 | x = x + h*dx 92 | bouncing_fmu.continuous_states = x 93 | 94 | #Get the event indicators at t = time 95 | event_ind_new = bouncing_fmu.get_event_indicators() 96 | 97 | #Inform the model about an accepted step and check for step events 98 | step_event = bouncing_fmu.completed_integrator_step() 99 | 100 | #Check for time and state events 101 | time_event = abs(time-Tnext) <= 1.e-10 102 | state_event = True if True in ((event_ind_new>0.0) != (event_ind>0.0)) else False 103 | 104 | #Event handling 105 | if step_event or time_event or state_event: 106 | 107 | eInfo = bouncing_fmu.get_event_info() 108 | eInfo.iterationConverged = False 109 | 110 | #Event iteration 111 | while not eInfo.iterationConverged: 112 | bouncing_fmu.event_update(intermediateResult=True) #Stops after each event iteration 113 | eInfo = bouncing_fmu.get_event_info() 114 | 115 | #Retrieve solutions (if needed) 116 | if not eInfo.iterationConverged: 117 | #bouncing_fmu.get_real, get_integer, get_boolean, 118 | # get_string(valueref) 119 | pass 120 | 121 | #Check if the event affected the state values and if so sets them 122 | if eInfo.stateValuesChanged: 123 | x = bouncing_fmu.continuous_states 124 | 125 | #Get new nominal values. 126 | if eInfo.stateValueReferencesChanged: 127 | atol = 0.01*rtol*bouncing_fmu.nominal_continuous_states 128 | 129 | #Check for new time event 130 | if eInfo.upcomingTimeEvent: 131 | Tnext = min(eInfo.nextEventTime, Tend) 132 | else: 133 | Tnext = Tend 134 | 135 | event_ind = event_ind_new 136 | 137 | #Retrieve solutions at t=time for outputs 138 | #bouncing_fmu.get_real,get_integer,get_boolean,get_string (valueref) 139 | 140 | t_sol += [time] 141 | sol += [bouncing_fmu.get_real(vref)] 142 | 143 | 144 | #Plot the solution 145 | if with_plots: 146 | #Plot the height 147 | pl.figure(1) 148 | pl.plot(t_sol,np.array(sol)[:,0]) 149 | pl.title(bouncing_fmu.get_name()) 150 | pl.ylabel('Height (m)') 151 | pl.xlabel('Time (s)') 152 | #Plot the velocity 153 | pl.figure(2) 154 | pl.plot(t_sol,np.array(sol)[:,1]) 155 | pl.title(bouncing_fmu.get_name()) 156 | pl.ylabel('Velocity (m/s)') 157 | pl.xlabel('Time (s)') 158 | pl.show() 159 | 160 | if __name__ == "__main__": 161 | run_demo() 162 | -------------------------------------------------------------------------------- /src/pyfmi/examples/fmu_with_input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import numpy as np 20 | import pylab as pl 21 | 22 | from pyfmi import load_fmu 23 | 24 | curr_dir = os.path.dirname(os.path.abspath(__file__)) 25 | path_to_fmus = os.path.join(curr_dir, 'files', 'FMUs') 26 | path_to_fmus_me1 = os.path.join(path_to_fmus,"ME1.0") 27 | path_to_fmus_cs1 = os.path.join(path_to_fmus,"CS1.0") 28 | 29 | def run_demo(with_plots=True): 30 | """ 31 | Demonstrates how to simulate an FMU with inputs. 32 | 33 | See also simulation_with_input.py 34 | """ 35 | fmu_name = os.path.join(path_to_fmus_me1,'SecondOrder.fmu') 36 | 37 | # Generate input 38 | t = np.linspace(0.,10.,100) 39 | u = np.cos(t) 40 | u_traj = np.transpose(np.vstack((t,u))) 41 | 42 | # Create input object 43 | input_object = ('u', u_traj) 44 | 45 | # Load the dynamic library and XML data 46 | model = load_fmu(fmu_name) 47 | 48 | # Set the first input value to the model 49 | model.set('u',u[0]) 50 | 51 | # Simulate 52 | res = model.simulate(final_time=30, input=input_object, options={'ncp':3000}) 53 | 54 | x1_sim = res['x1'] 55 | x2_sim = res['x2'] 56 | u_sim = res['u'] 57 | time_sim = res['time'] 58 | 59 | assert np.abs(res.final('x1')*1.e1 - (-8.3999640)) < 1e-3 60 | assert np.abs(res.final('x2')*1.e1 - (-5.0691179)) < 1e-3 61 | assert np.abs(res.final('u')*1.e1 - (-8.3907153)) < 1e-3 62 | 63 | if with_plots: 64 | pl.figure() 65 | pl.subplot(2,1,1) 66 | pl.plot(time_sim, x1_sim, time_sim, x2_sim) 67 | pl.subplot(2,1,2) 68 | pl.plot(time_sim, u_sim,'x-',t, u[:],'x-') 69 | pl.show() 70 | 71 | if __name__=="__main__": 72 | run_demo() 73 | 74 | -------------------------------------------------------------------------------- /src/pyfmi/examples/fmu_with_input_function.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import numpy as np 20 | import pylab as pl 21 | 22 | from pyfmi import load_fmu 23 | 24 | curr_dir = os.path.dirname(os.path.abspath(__file__)) 25 | path_to_fmus = os.path.join(curr_dir, 'files', 'FMUs') 26 | path_to_fmus_me1 = os.path.join(path_to_fmus,"ME1.0") 27 | path_to_fmus_cs1 = os.path.join(path_to_fmus,"CS1.0") 28 | 29 | def run_demo(with_plots=True): 30 | """ 31 | Demonstrates how to simulate an FMU with an input function. 32 | 33 | See also simulation_with_input.py 34 | """ 35 | fmu_name = os.path.join(path_to_fmus_me1,'SecondOrder.fmu') 36 | 37 | # Create input object 38 | input_object = ('u', np.cos) 39 | 40 | # Load the dynamic library and XML data 41 | model = load_fmu(fmu_name) 42 | 43 | # Simulate 44 | res = model.simulate(final_time=30, input=input_object, options={'ncp':3000}) 45 | 46 | x1_sim = res['x1'] 47 | x2_sim = res['x2'] 48 | u_sim = res['u'] 49 | time_sim = res['time'] 50 | 51 | assert np.abs(res.final('x1') - (-1.646485144)) < 1e-3 52 | assert np.abs(res.final('x2')*1.e1 - (-7.30591626709)) < 1e-3 53 | assert np.abs(res.final('u')*1.e1 - (1.54251449888)) < 1e-3 54 | 55 | if with_plots: 56 | pl.figure() 57 | pl.subplot(2,1,1) 58 | pl.plot(time_sim, x1_sim, time_sim, x2_sim) 59 | pl.subplot(2,1,2) 60 | pl.plot(time_sim, u_sim) 61 | pl.show() 62 | 63 | if __name__=="__main__": 64 | run_demo() 65 | -------------------------------------------------------------------------------- /src/pyfmi/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | # This file contains the various exceptions classes used in PyFMI 19 | 20 | class FMUException(Exception): 21 | """ 22 | An FMU exception. 23 | """ 24 | pass 25 | 26 | class FMIModel_Exception(Exception): 27 | """ 28 | A FMIModel Exception. 29 | """ 30 | # TODO Future; remove 31 | pass 32 | 33 | class FMIModelException(FMIModel_Exception): 34 | """ 35 | A FMIModel Exception. 36 | """ 37 | pass 38 | 39 | class IOException(FMUException): 40 | """ 41 | Exception covering issues related to writing/reading data. 42 | """ 43 | pass 44 | 45 | class InvalidOptionException(FMUException): 46 | """ 47 | Exception covering issues related to invalid choices of options. 48 | """ 49 | pass 50 | 51 | class TimeLimitExceeded(FMUException): 52 | pass 53 | 54 | class InvalidFMUException(FMUException): 55 | """ 56 | Exception covering problems with the imported FMU. 57 | """ 58 | pass 59 | 60 | class InvalidXMLException(InvalidFMUException): 61 | """ 62 | Exception covering problem with the XML-file in the imported FMU. 63 | """ 64 | pass 65 | 66 | class InvalidBinaryException(InvalidFMUException): 67 | """ 68 | Exception covering problem with the binary in the imported FMU. 69 | """ 70 | pass 71 | 72 | class InvalidVersionException(InvalidFMUException): 73 | """ 74 | Exception covering problem with the version of the imported FMU. 75 | """ 76 | pass 77 | -------------------------------------------------------------------------------- /src/pyfmi/fmi.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014-2021 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | -------------------------------------------------------------------------------- /src/pyfmi/fmi1.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | # Module containing the FMI1 interface Python wrappers. 19 | 20 | cimport pyfmi.fmil_import as FMIL 21 | cimport pyfmi.fmil1_import as FMIL1 22 | 23 | cimport pyfmi.fmi_base as FMI_BASE 24 | 25 | cdef class ScalarVariable: 26 | """ 27 | Class defining data structure based on the XML element ScalarVariable. 28 | """ 29 | cdef object _name 30 | cdef FMIL1.fmi1_value_reference_t _value_reference 31 | cdef object _description #A character pointer but we need an own reference and this is sufficient 32 | cdef FMIL1.fmi1_base_type_enu_t _type 33 | cdef FMIL1.fmi1_variability_enu_t _variability 34 | cdef FMIL1.fmi1_causality_enu_t _causality 35 | cdef FMIL1.fmi1_variable_alias_kind_enu_t _alias 36 | 37 | cdef class FMUModelBase(FMI_BASE.ModelBase): 38 | """ 39 | An FMI Model loaded from a DLL. 40 | """ 41 | #FMIL related variables 42 | cdef FMIL1.fmi1_callback_functions_t callBackFunctions 43 | cdef FMIL.fmi_import_context_t* context 44 | cdef FMIL1.fmi1_import_t* _fmu 45 | cdef FMIL1.fmi1_event_info_t _eventInfo 46 | cdef FMIL1.fmi1_import_variable_list_t *variable_list 47 | cdef FMIL1.fmi1_fmu_kind_enu_t fmu_kind 48 | cdef FMIL.jm_status_enu_t jm_status 49 | cdef FMIL.jm_callbacks callbacks_defaults 50 | cdef FMIL.jm_callbacks* callbacks_standard 51 | 52 | #Internal values 53 | cdef public object _t 54 | cdef public object _file_open 55 | cdef public object _npoints 56 | cdef public object _enable_logging 57 | cdef public object _pyEventInfo 58 | cdef object _fmu_full_path 59 | cdef int _version 60 | cdef int _instantiated_fmu 61 | cdef int _allow_unzipped_fmu 62 | cdef int _allocated_dll, _allocated_context, _allocated_xml, _allocated_fmu 63 | cdef object _allocated_list 64 | cdef object _modelname 65 | cdef unsigned int _nEventIndicators 66 | cdef unsigned int _nContinuousStates 67 | cdef public list _save_real_variables_val 68 | cdef public list _save_int_variables_val 69 | cdef public list _save_bool_variables_val 70 | cdef int _fmu_kind 71 | 72 | cpdef _internal_set_fmu_null(self) 73 | cpdef get_variable_description(self, variablename) 74 | cpdef FMIL1.fmi1_base_type_enu_t get_variable_data_type(self, variablename) except * 75 | cpdef FMIL1.fmi1_value_reference_t get_variable_valueref(self, variablename) except * 76 | cpdef get_variable_fixed(self, variablename) 77 | cpdef get_variable_start(self, variablename) 78 | cpdef get_variable_max(self, variablename) 79 | cpdef get_variable_min(self, variablename) 80 | cpdef FMIL1.fmi1_variability_enu_t get_variable_variability(self, variablename) except * 81 | cpdef FMIL1.fmi1_causality_enu_t get_variable_causality(self, variablename) except * 82 | cdef _add_scalar_variable(self, FMIL1.fmi1_import_variable_t* variable) 83 | 84 | cdef class FMUModelCS1(FMUModelBase): 85 | 86 | cpdef _get_time(self) 87 | cpdef _set_time(self, FMIL1.fmi1_real_t t) 88 | 89 | cdef class FMUModelME1(FMUModelBase): 90 | 91 | cpdef _get_time(self) 92 | cpdef _set_time(self, FMIL1.fmi1_real_t t) 93 | cpdef get_derivatives(self) 94 | cdef int _get_nominal_continuous_states_fmil(self, FMIL1.fmi1_real_t* xnominal, size_t nx) 95 | cdef public object _preinit_nominal_continuous_states 96 | 97 | cdef object _load_fmi1_fmu( 98 | fmu, 99 | object log_file_name, 100 | str kind, 101 | int log_level, 102 | int allow_unzipped_fmu, 103 | FMIL.fmi_import_context_t* context, 104 | bytes fmu_temp_dir, 105 | FMIL.jm_callbacks callbacks, 106 | list log_data 107 | ) 108 | -------------------------------------------------------------------------------- /src/pyfmi/fmi2.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | # Module containing the FMI2 interface Python wrappers. 19 | 20 | import numpy as np 21 | cimport numpy as np 22 | 23 | cimport pyfmi.fmil_import as FMIL 24 | cimport pyfmi.fmil2_import as FMIL2 25 | cimport pyfmi.fmi_base as FMI_BASE 26 | 27 | cdef class ScalarVariable2: 28 | """ 29 | Class defining data structure based on the XML element ScalarVariable. 30 | """ 31 | cdef object _value_reference 32 | cdef FMIL2.fmi2_base_type_enu_t _type 33 | cdef FMIL2.fmi2_variability_enu_t _variability 34 | cdef FMIL2.fmi2_causality_enu_t _causality 35 | cdef FMIL2.fmi2_variable_alias_kind_enu_t _alias 36 | cdef FMIL2.fmi2_initial_enu_t _initial 37 | cdef object _name 38 | cdef object _description #A character pointer but we need an own reference and this is sufficient 39 | 40 | cdef class DeclaredType2: 41 | cdef object _name 42 | cdef object _description 43 | cdef object _quantity 44 | 45 | cdef class EnumerationType2(DeclaredType2): 46 | cdef object _items 47 | 48 | cdef class IntegerType2(DeclaredType2): 49 | cdef int _min, _max 50 | 51 | cdef class RealType2(DeclaredType2): 52 | cdef float _min, _max, _nominal 53 | cdef object _unbounded, _relativeQuantity, _unit, _display_unit 54 | 55 | cdef class FMUState2: 56 | """ 57 | Class containing a pointer to a FMU-state. 58 | """ 59 | cdef FMIL2.fmi2_FMU_state_t fmu_state 60 | cdef dict _internal_state_variables 61 | 62 | cdef class FMUModelBase2(FMI_BASE.ModelBase): 63 | """ 64 | FMI Model loaded from a dll. 65 | """ 66 | 67 | # FMIL related variables 68 | cdef FMIL.fmi_import_context_t* _context 69 | cdef FMIL2.fmi2_callback_functions_t callBackFunctions 70 | cdef FMIL2.fmi2_import_t* _fmu 71 | cdef FMIL2.fmi2_fmu_kind_enu_t _fmu_kind 72 | cdef FMIL.fmi_version_enu_t _version 73 | cdef FMIL.jm_string last_error 74 | cdef FMIL.size_t _nEventIndicators 75 | cdef FMIL.size_t _nContinuousStates 76 | cdef FMIL2.fmi2_event_info_t _eventInfo 77 | 78 | #Internal values 79 | cdef public float _last_accepted_time, _relative_tolerance 80 | cdef object _fmu_full_path 81 | cdef public object _enable_logging 82 | cdef int _allow_unzipped_fmu 83 | cdef int _allocated_dll, _allocated_context, _allocated_xml, _allocated_fmu, _initialized_fmu 84 | cdef object _modelName 85 | cdef public list _save_real_variables_val 86 | cdef public list _save_int_variables_val 87 | cdef public list _save_bool_variables_val 88 | cdef object _t 89 | cdef public object _pyEventInfo 90 | cdef object _states_references 91 | cdef object _inputs_references 92 | cdef object _outputs_references 93 | cdef object _derivatives_references 94 | cdef object _derivatives_states_dependencies 95 | cdef object _derivatives_inputs_dependencies 96 | cdef object _derivatives_states_dependencies_kind 97 | cdef object _derivatives_inputs_dependencies_kind 98 | cdef object _outputs_states_dependencies 99 | cdef object _outputs_inputs_dependencies 100 | cdef object _outputs_states_dependencies_kind 101 | cdef object _outputs_inputs_dependencies_kind 102 | cdef object _A, _B, _C, _D 103 | cdef public object _group_A, _group_B, _group_C, _group_D 104 | cdef object _mask_A 105 | cdef object _A_row_ind, _A_col_ind 106 | cdef public object _has_entered_init_mode 107 | cdef WorkerClass2 _worker_object 108 | 109 | cpdef FMIL2.fmi2_value_reference_t get_variable_valueref(self, variablename) except * 110 | cpdef FMIL2.fmi2_base_type_enu_t get_variable_data_type(self, variablename) except * 111 | cpdef get_variable_description(self, variablename) 112 | cpdef FMIL2.fmi2_variability_enu_t get_variable_variability(self, variablename) except * 113 | cpdef FMIL2.fmi2_causality_enu_t get_variable_causality(self, variablename) except * 114 | cpdef get_output_dependencies(self) 115 | cpdef get_output_dependencies_kind(self) 116 | cpdef get_derivatives_dependencies(self) 117 | cpdef get_derivatives_dependencies_kind(self) 118 | cpdef get_variable_start(self, variablename) 119 | cpdef get_variable_max(self, variablename) 120 | cpdef get_variable_min(self, variablename) 121 | cpdef get_variable_unbounded(self, variablename) 122 | cpdef FMIL2.fmi2_initial_enu_t get_variable_initial(self, variable_name) except * 123 | cpdef serialize_fmu_state(self, state) 124 | cpdef deserialize_fmu_state(self, serialized_fmu) 125 | cpdef serialized_fmu_state_size(self, state) 126 | cdef _add_scalar_variables(self, FMIL2.fmi2_import_variable_list_t* variable_list) 127 | cdef _add_scalar_variable(self, FMIL2.fmi2_import_variable_t* variable) 128 | cdef int _get_directional_derivative(self, np.ndarray v_ref, np.ndarray z_ref, np.ndarray dv, np.ndarray dz) except -1 129 | cpdef set_real(self, valueref, values) 130 | cpdef np.ndarray get_real(self, valueref) 131 | cdef int _set_real(self, FMIL2.fmi2_value_reference_t* vrefs, FMIL2.fmi2_real_t* values, size_t _size) 132 | cdef int _get_real_by_ptr(self, FMIL2.fmi2_value_reference_t* vrefs, size_t _size, FMIL2.fmi2_real_t* values) 133 | cdef int _get_real_by_list(self, FMIL2.fmi2_value_reference_t[:] valueref, size_t _size, FMIL2.fmi2_real_t[:] values) 134 | cdef int _get_integer(self, FMIL2.fmi2_value_reference_t[:] valueref, size_t _size, FMIL2.fmi2_integer_t[:] values) 135 | cdef int _get_boolean(self, FMIL2.fmi2_value_reference_t[:] valueref, size_t _size, FMIL2.fmi2_real_t[:] values) 136 | 137 | cdef class FMUModelCS2(FMUModelBase2): 138 | 139 | cpdef _get_time(self) 140 | cpdef _set_time(self, FMIL2.fmi2_real_t t) 141 | cpdef int do_step(self, FMIL2.fmi2_real_t current_t, FMIL2.fmi2_real_t step_size, new_step=*) 142 | cdef int _set_input_derivatives(self, np.ndarray value_refs, np.ndarray values, np.ndarray orders) 143 | cdef int _get_output_derivatives(self, np.ndarray value_refs, np.ndarray values, np.ndarray orders) 144 | 145 | cdef class FMUModelME2(FMUModelBase2): 146 | 147 | cpdef _get_time(self) 148 | cpdef _set_time(self, FMIL2.fmi2_real_t t) 149 | cpdef get_derivatives(self) 150 | cdef public object force_finite_differences 151 | cdef int _get_derivatives(self, FMIL2.fmi2_real_t[:] values) 152 | cdef int _get_continuous_states_fmil(self, FMIL2.fmi2_real_t[:] ndx) 153 | cdef int _set_continuous_states_fmil(self, FMIL2.fmi2_real_t[:] ndx) 154 | cdef int _get_event_indicators(self, FMIL2.fmi2_real_t[:] values) 155 | cdef int _completed_integrator_step(self, int* enter_event_mode, int* terminate_simulation) 156 | cdef int _get_nominal_continuous_states_fmil(self, FMIL2.fmi2_real_t* xnominal, size_t nx) 157 | cdef public object _preinit_nominal_continuous_states 158 | 159 | cdef class WorkerClass2: 160 | cdef int _dim 161 | 162 | cdef np.ndarray _tmp1_val, _tmp2_val, _tmp3_val, _tmp4_val 163 | cdef np.ndarray _tmp1_ref, _tmp2_ref, _tmp3_ref, _tmp4_ref 164 | 165 | cdef FMIL2.fmi2_real_t* get_real_vector(self, int index) 166 | cdef FMIL2.fmi2_value_reference_t* get_value_reference_vector(self, int index) 167 | cdef np.ndarray get_value_reference_numpy_vector(self, int index) 168 | cdef np.ndarray get_real_numpy_vector(self, int index) 169 | cpdef verify_dimensions(self, int dim) 170 | 171 | cdef object _load_fmi2_fmu( 172 | fmu, 173 | object log_file_name, 174 | str kind, 175 | int log_level, 176 | int allow_unzipped_fmu, 177 | FMIL.fmi_import_context_t* context, 178 | bytes fmu_temp_dir, 179 | FMIL.jm_callbacks callbacks, 180 | list log_data 181 | ) 182 | -------------------------------------------------------------------------------- /src/pyfmi/fmi3.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | # Module containing the FMI3 interface Python wrappers. 19 | 20 | import numpy as np 21 | cimport numpy as np 22 | 23 | cimport pyfmi.fmil_import as FMIL 24 | cimport pyfmi.fmil3_import as FMIL3 25 | cimport pyfmi.fmi_base as FMI_BASE 26 | 27 | cdef class FMI3ModelVariable: 28 | """ Class defining data structure based on the XML elements for ModelVariables. """ 29 | cdef FMIL3.fmi3_value_reference_t _value_reference 30 | cdef FMIL3.fmi3_base_type_enu_t _type 31 | cdef FMIL3.fmi3_variability_enu_t _variability 32 | cdef FMIL3.fmi3_causality_enu_t _causality 33 | cdef FMIL3.fmi3_initial_enu_t _initial 34 | cdef object _name 35 | cdef object _description 36 | cdef FMIL3.fmi3_boolean_t _alias 37 | 38 | cdef class FMI3EventInfo: 39 | cdef public FMIL3.fmi3_boolean_t newDiscreteDtatesNeeded 40 | cdef public FMIL3.fmi3_boolean_t terminateSimulation 41 | cdef public FMIL3.fmi3_boolean_t nominalsOfContinuousStatesChanged 42 | cdef public FMIL3.fmi3_boolean_t valuesOfContinuousStatesChanged 43 | cdef public FMIL3.fmi3_boolean_t nextEventTimeDefined 44 | cdef public FMIL3.fmi3_float64_t nextEventTime 45 | # This will be populated further once we add support for CS and Clocks in particular. 46 | 47 | cdef class FMUModelBase3(FMI_BASE.ModelBase): 48 | # FMIL related variables 49 | cdef FMIL.fmi_import_context_t* _context 50 | cdef FMIL3.fmi3_import_t* _fmu 51 | cdef FMIL3.fmi3_fmu_kind_enu_t _fmu_kind 52 | cdef FMIL.fmi_version_enu_t _version 53 | cdef FMIL.size_t _nEventIndicators # format with snake case? 54 | cdef FMIL.size_t _nContinuousStates # format with snake case? 55 | cdef FMIL3.fmi3_boolean_t _event_info_new_discrete_states_needed 56 | cdef FMIL3.fmi3_boolean_t _event_info_terminate_simulation 57 | cdef FMIL3.fmi3_boolean_t _event_info_nominals_of_continuous_states_changed 58 | cdef FMIL3.fmi3_boolean_t _event_info_values_of_continuous_states_changed 59 | cdef FMIL3.fmi3_boolean_t _event_info_next_event_time_defined 60 | cdef FMIL3.fmi3_float64_t _event_info_next_event_time 61 | cdef FMI3EventInfo _eventInfo 62 | 63 | # Internal values 64 | cdef public float _last_accepted_time 65 | cdef public object _enable_logging 66 | cdef public object _event_info 67 | cdef object _fmu_full_path 68 | cdef object _modelName 69 | cdef object _t 70 | cdef int _allow_unzipped_fmu 71 | cdef int _allocated_context, _allocated_dll, _allocated_fmu, _allocated_xml 72 | 73 | cdef int _initialized_fmu 74 | cdef object _has_entered_init_mode # this is public in FMI2 but I don't see why 75 | 76 | cpdef set_float64(self, valueref, values) 77 | cpdef set_float32(self, valueref, values) 78 | cpdef set_int64 (self, valueref, values) 79 | cpdef set_int32 (self, valueref, values) 80 | cpdef set_int16 (self, valueref, values) 81 | cpdef set_int8 (self, valueref, values) 82 | cpdef set_uint64 (self, valueref, values) 83 | cpdef set_uint32 (self, valueref, values) 84 | cpdef set_uint16 (self, valueref, values) 85 | cpdef set_uint8 (self, valueref, values) 86 | cpdef set_boolean(self, valueref, values) 87 | cpdef set_string (self, valueref, values) 88 | cpdef set_enum (self, valueref, values) 89 | 90 | cpdef np.ndarray get_float64(self, valueref) 91 | cpdef np.ndarray get_float32(self, valueref) 92 | cpdef np.ndarray get_int64 (self, valueref) 93 | cpdef np.ndarray get_int32 (self, valueref) 94 | cpdef np.ndarray get_int16 (self, valueref) 95 | cpdef np.ndarray get_int8 (self, valueref) 96 | cpdef np.ndarray get_uint64 (self, valueref) 97 | cpdef np.ndarray get_uint32 (self, valueref) 98 | cpdef np.ndarray get_uint16 (self, valueref) 99 | cpdef np.ndarray get_uint8 (self, valueref) 100 | cpdef np.ndarray get_boolean(self, valueref) 101 | cpdef list get_string (self, valueref) 102 | cpdef np.ndarray get_enum (self, valueref) 103 | 104 | cpdef _get_time(self) 105 | cpdef _set_time(self, FMIL3.fmi3_float64_t t) 106 | 107 | cpdef FMIL3.fmi3_value_reference_t get_variable_valueref(self, variable_name) except * 108 | cdef FMIL3.fmi3_base_type_enu_t _get_variable_data_type(self, variable_name) except * 109 | cdef FMIL3.fmi3_causality_enu_t _get_variable_causality(self, variable_name) except * 110 | cpdef get_variable_description(self, variable_name) 111 | cdef _add_variable(self, FMIL3.fmi3_import_variable_t* variable) 112 | cpdef get_variable_unbounded(self, variablename) 113 | cdef _get_variable_description(self, FMIL3.fmi3_import_variable_t*) 114 | cdef _get_alias_description(self, FMIL3.fmi3_import_alias_variable_t*) 115 | 116 | 117 | cdef class FMUModelME3(FMUModelBase3): 118 | cpdef get_derivatives(self) 119 | cdef FMIL3.fmi3_status_t _get_derivatives(self, FMIL3.fmi3_float64_t[:] values) 120 | cdef FMIL3.fmi3_status_t _get_continuous_states_fmil(self, FMIL3.fmi3_float64_t[:] ndx) 121 | cdef FMIL3.fmi3_status_t _set_continuous_states_fmil(self, FMIL3.fmi3_float64_t[:] ndx) 122 | cdef FMIL3.fmi3_status_t _completed_integrator_step(self, 123 | FMIL3.fmi3_boolean_t no_set_FMU_state_prior_to_current_point, 124 | FMIL3.fmi3_boolean_t* enter_event_mode, 125 | FMIL3.fmi3_boolean_t* terminate_simulation 126 | ) 127 | cdef FMIL3.fmi3_status_t _get_nominal_continuous_states_fmil(self, FMIL3.fmi3_float64_t* xnominal, size_t nx) 128 | 129 | cdef void _cleanup_on_load_error( 130 | FMIL3.fmi3_import_t* fmu_3, 131 | FMIL.fmi_import_context_t* context, 132 | int allow_unzipped_fmu, 133 | FMIL.jm_callbacks callbacks, 134 | bytes fmu_temp_dir, 135 | list log_data 136 | ) 137 | 138 | cdef object _load_fmi3_fmu( 139 | fmu, 140 | object log_file_name, 141 | str kind, 142 | int log_level, 143 | int allow_unzipped_fmu, 144 | FMIL.fmi_import_context_t* context, 145 | bytes fmu_temp_dir, 146 | FMIL.jm_callbacks callbacks, 147 | list log_data 148 | ) 149 | -------------------------------------------------------------------------------- /src/pyfmi/fmi_base.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | # Module containing the abstract base class for FMI interface Python wrappers. 19 | # Plus some auxiliary functions 20 | 21 | cimport pyfmi.fmil_import as FMIL 22 | 23 | cdef FMIL.fmi_version_enu_t import_and_get_version(FMIL.fmi_import_context_t*, char*, char*, int) 24 | 25 | cdef class ModelBase: 26 | """ 27 | Abstract Model class containing base functionality. 28 | """ 29 | cdef list _log 30 | cdef char* _fmu_log_name 31 | cdef FMIL.jm_callbacks callbacks 32 | cdef public dict cache 33 | cdef public object _log_stream 34 | cdef public object file_object 35 | cdef public object _additional_logger 36 | cdef public object _max_log_size_msg_sent 37 | cdef public object _result_file 38 | cdef public object _log_handler 39 | cdef object _modelId 40 | cdef public int _log_is_stream, _invoked_dealloc 41 | cdef public unsigned long long int _current_log_size, _max_log_size 42 | cdef char* _fmu_temp_dir 43 | 44 | cdef _logger(self, FMIL.jm_string module, int log_level, FMIL.jm_string message) with gil 45 | 46 | cdef class LogHandler: 47 | cdef unsigned long _max_log_size 48 | 49 | cpdef void set_max_log_size(self, unsigned long val) 50 | cpdef void capi_start_callback(self, int limit_reached, unsigned long current_log_size) 51 | cpdef void capi_end_callback (self, int limit_reached, unsigned long current_log_size) 52 | 53 | cdef class LogHandlerDefault(LogHandler): 54 | cdef unsigned long _log_checkpoint 55 | 56 | cdef void _update_checkpoint (self, int limit_reached, unsigned long current_log_size) 57 | cpdef unsigned long get_log_checkpoint(self) 58 | cpdef void capi_start_callback(self, int limit_reached, unsigned long current_log_size) 59 | cpdef void capi_end_callback (self, int limit_reached, unsigned long current_log_size) 60 | -------------------------------------------------------------------------------- /src/pyfmi/fmi_util.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2019-2022 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | from libc.stdio cimport FILE 19 | 20 | import numpy as np 21 | cimport numpy as np 22 | cimport pyfmi.fmi2 as FMI2 23 | cimport pyfmi.fmi3 as FMI3 24 | 25 | # TODO: Should this be split further into e.g., fmi_io_util & fmi_coupled_util? 26 | 27 | """ 28 | Below we define a 'modification' to fseek that is OS specific in order to handle very large files. 29 | This is because fseek/ftell is not sufficient as soon as the number of bytes in a result file 30 | exceed the maximum value for long int. 31 | """ 32 | IF UNAME_SYSNAME == "Windows": 33 | cdef extern from "stdio.h" nogil: 34 | ctypedef struct FILE: 35 | pass 36 | long long _ftelli64(FILE *stream) 37 | int _fseeki64(FILE *stream, long long offset, int whence) 38 | cdef inline int os_specific_fseek(FILE *stream, long long offset, int whence): 39 | return _fseeki64(stream, offset, whence) 40 | cdef inline long long os_specific_ftell(FILE *stream): 41 | return _ftelli64(stream) 42 | ELSE: 43 | cdef extern from "stdio.h" nogil: 44 | ctypedef struct FILE: 45 | pass 46 | long long ftello(FILE *stream) 47 | int fseeko(FILE *stream, long long offset, int whence) 48 | cdef inline int os_specific_fseek(FILE *stream, long long offset, int whence): 49 | return fseeko(stream, offset, whence) 50 | cdef inline long long os_specific_ftell(FILE *stream): 51 | return ftello(stream) 52 | 53 | cdef class DumpDataFMI3: 54 | cdef np.ndarray time_tmp 55 | # TODO: Investigate if there is a difference in performance by declaring 'model' 56 | # as an object instead of FMI3.FMUModelME3 57 | cdef public object model 58 | cdef public dict value_references, type_getters 59 | cdef public object _file 60 | cdef int _with_diagnostics 61 | cdef dump_data(self, np.ndarray data) 62 | 63 | cdef class DumpData: 64 | cdef np.ndarray real_var_ref, int_var_ref, bool_var_ref 65 | cdef np.ndarray real_var_tmp, int_var_tmp, bool_var_tmp 66 | cdef np.ndarray time_tmp 67 | cdef public FMI2.FMUModelME2 model_me2 68 | cdef public int model_me2_instance 69 | cdef public object _file, model 70 | cdef size_t real_size, int_size, bool_size 71 | cdef int _with_diagnostics 72 | cdef dump_data(self, np.ndarray data) 73 | -------------------------------------------------------------------------------- /src/pyfmi/fmil_import.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | #============================================== 19 | # C headers 20 | #============================================== 21 | 22 | # This file contains FMIL header content not specific to a given FMI version 23 | 24 | cdef extern from "stdlib.h": 25 | ctypedef long unsigned int size_t 26 | 27 | void *malloc(size_t) 28 | void free(void *ptr) 29 | void *calloc(size_t, size_t) 30 | void *realloc(void *, size_t) 31 | 32 | cdef extern from "string.h": 33 | int memcmp(void *s1, void *s2, size_t n); 34 | 35 | cdef extern from "stdio.h": 36 | ctypedef struct FILE: 37 | pass 38 | int fprintf(FILE *restrict, const char *restrict, ...) 39 | FILE *fopen(const char *path, const char *mode) 40 | int fclose(FILE *file_pointer) 41 | 42 | #SEE http://wiki.cython.org/FAQ#HowdoIusevariableargs. 43 | cdef extern from "stdarg.h": 44 | ctypedef struct va_list: 45 | pass 46 | ctypedef struct fake_type: 47 | pass 48 | void va_start(va_list, void* arg) 49 | void va_end(va_list) 50 | int vsnprintf(char *str, size_t size, char *format, va_list ap) 51 | 52 | cdef extern from 'fmilib.h': 53 | # ctypedef long unsigned int size_t # same as via stdlib.h 54 | ctypedef void* jm_voidp 55 | ctypedef char* jm_string 56 | 57 | #STRUCTS 58 | ctypedef enum jm_log_level_enu_t: 59 | jm_log_level_nothing = 0 60 | jm_log_level_fatal = 1 61 | jm_log_level_error = 2 62 | jm_log_level_warning = 3 63 | jm_log_level_info = 4 64 | jm_log_level_verbose = 5 65 | jm_log_level_debug = 6 66 | jm_log_level_all = 7 67 | 68 | ctypedef enum jm_status_enu_t: 69 | jm_status_error = -1 70 | jm_status_success = 0 71 | jm_status_warning = 1 72 | 73 | ctypedef enum fmi_version_enu_t: 74 | fmi_version_unknown_enu = 0 75 | fmi_version_1_enu = 1 76 | fmi_version_2_0_enu = 2 77 | fmi_version_3_0_enu = 3 78 | fmi_version_unsupported_enu = 4 79 | 80 | ctypedef int(*jm_compare_ft)(void *, void *) 81 | ctypedef jm_voidp(*jm_malloc_f)(size_t) 82 | ctypedef jm_voidp(*jm_calloc_f)(size_t, size_t) 83 | ctypedef jm_voidp(*jm_realloc_f)(void *, size_t) 84 | ctypedef void(*jm_free_f)(jm_voidp) 85 | ctypedef void (*jm_logger_f)(jm_callbacks* c, jm_string module, jm_log_level_enu_t log_level, jm_string message) except * 86 | 87 | cdef struct jm_callbacks: 88 | jm_malloc_f malloc 89 | jm_calloc_f calloc 90 | jm_realloc_f realloc 91 | jm_free_f free 92 | jm_logger_f logger 93 | jm_log_level_enu_t log_level 94 | jm_voidp context 95 | char * errMessageBuffer 96 | 97 | cdef struct jm_named_ptr: 98 | jm_voidp ptr 99 | jm_string name 100 | 101 | cdef struct fmi_xml_context_t: 102 | pass 103 | 104 | ctypedef fmi_xml_context_t fmi_import_context_t 105 | 106 | cdef struct __va_list_tag: 107 | pass 108 | 109 | #FMI HELPER METHODS 110 | char * fmi_import_get_dll_path(char *, char *, jm_callbacks *) 111 | char * fmi_import_get_model_description_path(char *, jm_callbacks *) 112 | fmi_import_context_t * fmi_import_allocate_context(jm_callbacks *) 113 | void fmi_import_free_context(fmi_import_context_t *) 114 | fmi_version_enu_t fmi_import_get_fmi_version(fmi_import_context_t*, char*, char*) 115 | int fmi_import_rmdir(jm_callbacks*, char *) 116 | 117 | #OTHER HELPER METHODS 118 | void jm_set_default_callbacks(jm_callbacks *) 119 | jm_string jm_get_last_error(jm_callbacks *) 120 | # void jm_clear_last_error(jm_callbacks *) 121 | void jm_log(jm_callbacks *, char *, int, char *) 122 | void * mempcpy(void *, void *, size_t) 123 | void * memcpy(void *, void *, size_t) 124 | void * memset(void *, int, size_t) 125 | char * strcat(char *, char *) 126 | char * strcpy(char *, char *) 127 | size_t strlen(char *) 128 | jm_callbacks * jm_get_default_callbacks() 129 | void jm_log_v(jm_callbacks *, char *, int, char *, __va_list_tag *) 130 | -------------------------------------------------------------------------------- /src/pyfmi/simulation/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | """ 19 | This package contains the interface to different simulation packages. 20 | """ 21 | __all__ = ['assimulo_interface'] 22 | -------------------------------------------------------------------------------- /src/pyfmi/simulation/assimulo_interface.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014-2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | -------------------------------------------------------------------------------- /src/pyfmi/simulation/assimulo_interface.pyx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014-2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | """ 19 | This file contains code for mapping FMUs to the fmu_problem specifications 20 | required by Assimulo. 21 | """ 22 | 23 | # Imports for convenience/backwards compatibility 24 | from pyfmi.simulation.assimulo_interface_fmi1 import ( 25 | FMIODE, 26 | FMIODESENS 27 | ) 28 | from pyfmi.simulation.assimulo_interface_fmi2 import ( 29 | FMIODE2, 30 | FMIODESENS2 31 | ) 32 | from pyfmi.simulation.assimulo_interface_fmi3 import ( 33 | FMIODE3, 34 | ) 35 | from pyfmi.exceptions import ( 36 | FMIModel_Exception, 37 | FMUException, 38 | FMIModelException 39 | ) 40 | 41 | from pyfmi.fmi1 import FMUModelME1 42 | from pyfmi.fmi2 import FMUModelME2 43 | from pyfmi.fmi3 import FMUModelME3 44 | from pyfmi.fmi_coupled import CoupledFMUModelME2 45 | 46 | def get_fmi_ode_problem( 47 | model, 48 | result_file_name, 49 | with_jacobian: bool, 50 | start_time: double, 51 | logging: bool, 52 | result_handler, 53 | input_traj = None, 54 | number_of_diagnostics_variables: int = 0, 55 | sensitivities = None, 56 | extra_equations = None, 57 | synchronize_simulation = False 58 | ): 59 | """Convenience function for getting the correct FMIODEX class instance.""" 60 | if isinstance(model, FMUModelME3): 61 | if sensitivities: 62 | raise FMUException("Sensitivities are not yet supported for FMI3") 63 | else: 64 | fmu_prob = FMIODE3( 65 | model = model, 66 | input = input_traj, 67 | result_file_name = result_file_name, 68 | with_jacobian = with_jacobian, 69 | start_time = start_time, 70 | logging = logging, 71 | result_handler = result_handler, 72 | extra_equations = extra_equations, 73 | synchronize_simulation = synchronize_simulation, 74 | number_of_diagnostics_variables = number_of_diagnostics_variables 75 | ) 76 | elif isinstance(model, (FMUModelME2, CoupledFMUModelME2)): 77 | if sensitivities: 78 | fmu_prob = FMIODESENS2( 79 | model = model, 80 | input = input_traj, 81 | result_file_name = result_file_name, 82 | with_jacobian = with_jacobian, 83 | start_time = start_time, 84 | parameters = sensitivities, 85 | logging = logging, 86 | result_handler = result_handler, 87 | number_of_diagnostics_variables = number_of_diagnostics_variables 88 | ) 89 | else: 90 | fmu_prob = FMIODE2( 91 | model = model, 92 | input = input_traj, 93 | result_file_name = result_file_name, 94 | with_jacobian = with_jacobian, 95 | start_time = start_time, 96 | logging = logging, 97 | result_handler = result_handler, 98 | extra_equations = extra_equations, 99 | synchronize_simulation = synchronize_simulation, 100 | number_of_diagnostics_variables = number_of_diagnostics_variables 101 | ) 102 | elif isinstance(model, FMUModelME1): 103 | if sensitivities: 104 | fmu_prob = FMIODESENS( 105 | model = model, 106 | input = input_traj, 107 | result_file_name = result_file_name, 108 | with_jacobian = with_jacobian, 109 | start_time = start_time, 110 | parameters = sensitivities, 111 | logging = logging, 112 | result_handler = result_handler 113 | ) 114 | else: 115 | fmu_prob = FMIODE( 116 | model = model, 117 | input = input_traj, 118 | result_file_name = result_file_name, 119 | with_jacobian = with_jacobian, 120 | start_time = start_time, 121 | logging = logging, 122 | result_handler = result_handler 123 | ) 124 | else: 125 | raise FMUException("Unknown model.") 126 | return fmu_prob 127 | -------------------------------------------------------------------------------- /src/pyfmi/simulation/assimulo_interface_fmi1.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014-2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | -------------------------------------------------------------------------------- /src/pyfmi/simulation/assimulo_interface_fmi2.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014-2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | import numpy as np 20 | cimport numpy as np 21 | import logging 22 | cimport pyfmi.fmi2 as FMI2 23 | 24 | from assimulo.problem cimport cExplicit_Problem 25 | 26 | cdef class FMIODE2(cExplicit_Problem): 27 | """ 28 | An Assimulo Explicit Model extended to FMI2 interface. 29 | """ 30 | cdef public int _f_nbr, _g_nbr, _input_activated, _extra_f_nbr, jac_nnz, input_len_names 31 | cdef public object _model, problem_name, result_file_name, __input, _A, debug_file_name, debug_file_object 32 | cdef public object export, _sparse_representation, _with_jacobian, _logging, _write_header, _start_time 33 | cdef public dict timings 34 | cdef public np.ndarray y0, input_real_mask, input_other_mask 35 | cdef public list input_names, input_real_value_refs, input_other, _logg_step_event 36 | cdef public double t0, _synchronize_factor 37 | cdef public jac_use, state_events_use, time_events_use 38 | cdef public FMI2.FMUModelME2 model_me2 39 | cdef public int model_me2_instance 40 | cdef public np.ndarray _state_temp_1, _event_temp_1 41 | 42 | cdef int _logging_as_dynamic_diagnostics 43 | cdef int _number_of_diagnostics_variables 44 | cpdef _set_input_values(self, double t) 45 | cdef _update_model(self, double t, np.ndarray[double, ndim=1, mode="c"] y) 46 | cdef int _compare(self, double t, np.ndarray[double, ndim=1, mode="c"] y) 47 | -------------------------------------------------------------------------------- /src/pyfmi/simulation/assimulo_interface_fmi3.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | 19 | import numpy as np 20 | cimport numpy as np 21 | import logging 22 | cimport pyfmi.fmi3 as FMI3 23 | 24 | from assimulo.problem cimport cExplicit_Problem 25 | 26 | cdef class FMIODE3(cExplicit_Problem): 27 | """ An Assimulo Explicit Model extended to FMI3 interface. """ 28 | cdef public int _f_nbr, _g_nbr, _input_activated, _extra_f_nbr, jac_nnz, input_len_names 29 | cdef public object _model, problem_name, result_file_name, __input, _A 30 | cdef public object export, _sparse_representation, _with_jacobian, _logging, _write_header, _start_time 31 | cdef public dict timings 32 | cdef public np.ndarray y0, input_float64_mask, input_other_mask 33 | cdef public list input_names, input_float64_value_refs, input_other, _logg_step_event 34 | cdef public double t0, _synchronize_factor 35 | cdef public jac_use, state_events_use, time_events_use # Flags for Assimulo 36 | cdef public FMI3.FMUModelME3 model_me3 37 | cdef public int model_me3_instance 38 | cdef public np.ndarray _state_temp_1, _event_temp_1 39 | 40 | cdef int _logging_as_dynamic_diagnostics 41 | cdef int _number_of_diagnostics_variables 42 | cpdef _set_input_values(self, double t) 43 | cdef _update_model(self, double t, np.ndarray[double, ndim=1, mode="c"] y) 44 | cdef int _compare(self, double t, np.ndarray[double, ndim=1, mode="c"] y) 45 | -------------------------------------------------------------------------------- /src/pyfmi/test_util.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2014-2024 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | """ Collection of classes used for testing purposes.""" 19 | cimport pyfmi.fmil1_import as FMIL1 20 | cimport pyfmi.fmil2_import as FMIL2 21 | cimport pyfmi.fmi1 as FMI1 22 | cimport pyfmi.fmi2 as FMI2 23 | 24 | cdef class _ForTestingFMUModelME1(FMI1.FMUModelME1): 25 | cdef int _get_nominal_continuous_states_fmil(self, FMIL1.fmi1_real_t* xnominal, size_t nx) 26 | cpdef set_allocated_fmu(self, int value) 27 | 28 | cdef class _ForTestingFMUModelME2(FMI2.FMUModelME2): 29 | cdef int _get_real_by_ptr(self, FMIL2.fmi2_value_reference_t* vrefs, size_t _size, FMIL2.fmi2_real_t* values) 30 | cdef int _set_real(self, FMIL2.fmi2_value_reference_t* vrefs, FMIL2.fmi2_real_t* values, size_t _size) 31 | cdef int _get_real_by_list(self, FMIL2.fmi2_value_reference_t[:] valueref, size_t _size, FMIL2.fmi2_real_t[:] values) 32 | cdef int _get_integer(self, FMIL2.fmi2_value_reference_t[:] valueref, size_t _size, FMIL2.fmi2_integer_t[:] values) 33 | cdef int _get_boolean(self, FMIL2.fmi2_value_reference_t[:] valueref, size_t _size, FMIL2.fmi2_real_t[:] values) 34 | cdef int _get_nominal_continuous_states_fmil(self, FMIL2.fmi2_real_t* xnominal, size_t nx) 35 | cpdef set_initialized_fmu(self, int value) 36 | -------------------------------------------------------------------------------- /src/pyfmi/util.pxd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | # Module containing general utility functions independent of FMI 19 | 20 | cpdef decode(x) 21 | cpdef encode(x) 22 | 23 | cpdef cpr_seed(dependencies, list column_keys, dict interested_columns = *) 24 | -------------------------------------------------------------------------------- /src/pyfmi/util.pyx: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU Lesser General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU Lesser General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU Lesser General Public License 16 | # along with this program. If not, see . 17 | 18 | # distutils: define_macros=NPY_NO_DEPRECATED_API=NPY_1_7_API_VERSION 19 | 20 | # Module containing general utility functions independent of FMI 21 | # This is split from fmi_util.pyx to avoid circular dependencies 22 | 23 | import numpy as np 24 | cimport numpy as np 25 | 26 | import functools 27 | import marshal 28 | 29 | cpdef decode(x): 30 | if isinstance(x, bytes): 31 | return x.decode(errors="replace") 32 | else: 33 | return x 34 | 35 | cpdef encode(x): 36 | if isinstance(x, str): 37 | return x.encode() 38 | else: 39 | return x 40 | 41 | def enable_caching(obj): 42 | @functools.wraps(obj, ('__name__', '__doc__')) 43 | def memoizer(*args, **kwargs): 44 | cache = args[0].cache #First argument is the self object 45 | key = (obj, marshal.dumps(args[1:]), marshal.dumps(kwargs)) 46 | 47 | if len(cache) > 1000: #Remove items from cache in case it grows large 48 | cache.popitem() 49 | 50 | if key not in cache: 51 | cache[key] = obj(*args, **kwargs) 52 | return cache[key] 53 | 54 | return memoizer 55 | 56 | cpdef cpr_seed(dependencies, list column_keys, dict interested_columns = None): 57 | cdef int i=0,j=0,k=0 58 | cdef int n_col = len(column_keys)#len(dependencies.keys()) 59 | cdef dict columns_taken# = {key: 1 for key in dependencies.keys() if len(dependencies[key]) == 0} 60 | cdef dict groups = {} 61 | cdef dict column_dict = {} 62 | cdef dict column_keys_dict = {} 63 | cdef dict data_index = {} 64 | 65 | row_keys_dict = {s:i for i,s in enumerate(dependencies.keys())} 66 | column_keys_dict = {s:i for i,s in enumerate(column_keys)} 67 | column_dict = {i:[] for i,s in enumerate(column_keys)} 68 | for i,dx in enumerate(dependencies.keys()): 69 | for x in dependencies[dx]: 70 | column_dict[column_keys_dict[x]].append(dx) 71 | columns_taken = {key: 1 for key in column_dict.keys() if len(column_dict[key]) == 0} 72 | 73 | k = 0 74 | kd = 0 75 | data_index = {} 76 | data_index_with_diag = {} 77 | for i in range(n_col): 78 | data_index[i] = list(range(k, k + len(column_dict[i]))) 79 | k = k + len(column_dict[i]) 80 | 81 | data_index_with_diag[i] = [] 82 | diag_added = False 83 | for x in column_dict[i]: 84 | ind = row_keys_dict[x] 85 | if ind < i: 86 | data_index_with_diag[i].append(kd) 87 | kd = kd + 1 88 | else: 89 | if ind == i: 90 | diag_added = True 91 | if not diag_added: 92 | kd = kd + 1 93 | diag_added = True 94 | data_index_with_diag[i].append(kd) 95 | kd = kd + 1 96 | if not diag_added: 97 | kd = kd + 1 98 | 99 | nnz = k 100 | nnz_with_diag = kd 101 | 102 | k = 0 103 | for i in range(n_col): 104 | if (i in columns_taken) or (interested_columns is not None and not (i in interested_columns)): 105 | continue 106 | 107 | # New group 108 | groups[k] = [[i], column_dict[i][:], [row_keys_dict[x] for x in column_dict[i]], [i]*len(column_dict[i]), data_index[i], data_index_with_diag[i]] 109 | 110 | for j in range(i+1, n_col): 111 | if (j in columns_taken) or (interested_columns is not None and not (j in interested_columns)): 112 | continue 113 | 114 | intersect = frozenset(groups[k][1]).intersection(column_dict[j]) 115 | if not intersect: 116 | 117 | #structure 118 | # - [0] - variable indexes 119 | # - [1] - variable names 120 | # - [2] - matrix rows 121 | # - [3] - matrix columns 122 | # - [4] - position in data vector (CSC format) 123 | # - [5] - position in data vector (with diag) (CSC format) 124 | 125 | groups[k][0].append(j) 126 | groups[k][1].extend(column_dict[j]) 127 | groups[k][2].extend([row_keys_dict[x] for x in column_dict[j]]) 128 | groups[k][3].extend([j]*len(column_dict[j])) 129 | groups[k][4].extend(data_index[j]) 130 | groups[k][5].extend(data_index_with_diag[j]) 131 | columns_taken[j] = 1 132 | 133 | groups[k][0] = np.array(groups[k][0],dtype=np.int32) 134 | groups[k][2] = np.array(groups[k][2],dtype=np.int32) 135 | groups[k][3] = np.array(groups[k][3],dtype=np.int32) 136 | groups[k][4] = np.array(groups[k][4],dtype=np.int32) 137 | groups[k][5] = np.array(groups[k][5],dtype=np.int32) 138 | k = k + 1 139 | 140 | groups["groups"] = list(groups.keys()) 141 | groups["nnz"] = nnz 142 | groups["nnz_with_diag"] = nnz_with_diag 143 | 144 | return groups 145 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/__init__.py -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import urllib.request 2 | import hashlib 3 | from tempfile import TemporaryDirectory 4 | from zipfile import ZipFile 5 | from pathlib import Path 6 | import pytest 7 | 8 | files_directory = Path(__file__).parent / 'files' 9 | 10 | @pytest.fixture(autouse=True, scope="session") 11 | def setup_reference_fmus(): 12 | """ 13 | This function downloads reference FMUs from the Modelica group and unpacks 14 | them into the test files directory. 15 | The MD5sum of the URL is checked in order to avoid unnecessary downloading. 16 | Note that this requires an internet connection to work. 17 | """ 18 | 19 | def download_url(url, save_file_to): 20 | """ Download file from URL to 'save_file_to' in chunks. """ 21 | try: 22 | with urllib.request.urlopen(url) as file_to_download: 23 | with open(save_file_to, 'wb') as file_handle: 24 | file_handle.write(file_to_download.read()) 25 | except urllib.request.URLError as e: 26 | raise Exception( 27 | "Unable to download reference FMUs, please verify your internet connection is working and" + \ 28 | f" that the URL {url} exists." 29 | ) from e 30 | zip_file_url = "https://github.com/modelica/Reference-FMUs/releases/download/v0.0.37/Reference-FMUs-0.0.37.zip" 31 | zip_file_name = 'reference_fmus.zip' 32 | zip_unzip_to = files_directory / 'reference_fmus' 33 | 34 | # Simple version of 'cache' 35 | # Note that we generate the MD5 sum for the URL and not the contents of the zip-file. 36 | # This is intended and is sufficient for our needs. 37 | md5 = hashlib.md5(zip_file_url.encode("utf-8")).hexdigest() 38 | md5_file = Path(zip_unzip_to) / 'metadata' # Expected file 39 | use_already_existing = False 40 | if md5_file.exists(): 41 | with open(md5_file, 'r') as f: 42 | use_already_existing = md5 == f.read() 43 | 44 | if not use_already_existing: 45 | with TemporaryDirectory() as tmpdirname: 46 | zip_path = Path(tmpdirname) / zip_file_name 47 | download_url(zip_file_url, zip_path) 48 | 49 | with ZipFile(zip_path, 'r') as zf: 50 | for fobj in zf.filelist: 51 | # For now, only unpack FMI 3.0 FMUs 52 | if fobj.filename.startswith('3.0') and fobj.filename.endswith('.fmu'): 53 | zf.extract(fobj, zip_unzip_to) 54 | with open(md5_file, 'w') as f: 55 | f.write(md5) 56 | -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS1.0/CoupledClutches.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS1.0/CoupledClutches.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS1.0/NegatedAlias.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS1.0/NegatedAlias.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS1.0/bouncingBall.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS1.0/bouncingBall.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/CoupledClutches.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/CoupledClutches.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/GainTestInteger.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/GainTestInteger.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/GainTestReal.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/GainTestReal.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/IntegerStep.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/IntegerStep.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/LinearCoSimulation_LinearSubSystem1.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/LinearCoSimulation_LinearSubSystem1.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/LinearCoSimulation_LinearSubSystem2.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/LinearCoSimulation_LinearSubSystem2.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/LinearStability.SubSystem1.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/LinearStability.SubSystem1.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/LinearStability.SubSystem2.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/LinearStability.SubSystem2.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/LinearStability_LinearSubSystemNoFeed1.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/LinearStability_LinearSubSystemNoFeed1.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/LinearStability_LinearSubSystemNoFeed2.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/LinearStability_LinearSubSystemNoFeed2.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/NegatedAlias.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/NegatedAlias.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/CS2.0/bouncingBall.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/CS2.0/bouncingBall.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/Alias1.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/Alias1.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/CoupledClutches.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/CoupledClutches.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/Description.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/Description.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/NegatedAlias.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/NegatedAlias.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/NoState.Example1.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/NoState.Example1.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/NominalTest4.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/NominalTest4.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/RLC_Circuit.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/RLC_Circuit.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/bouncingBall.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/bouncingBall.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME1.0/dq.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME1.0/dq.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/Alias.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/Alias.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/BasicSens1.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/BasicSens1.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/BasicSens2.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/BasicSens2.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/Bouncing_Ball.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/Bouncing_Ball.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/CoupledClutches.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/CoupledClutches.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/CoupledClutchesModified.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/CoupledClutchesModified.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/Description.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/Description.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/Enumerations.Enumeration3.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/Enumerations.Enumeration3.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/Friction2.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/Friction2.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/Large.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/Large.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/LinearStability.FullSystem.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/LinearStability.FullSystem.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/LinearStability.SubSystem1.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/LinearStability.SubSystem1.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/LinearStability.SubSystem2.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/LinearStability.SubSystem2.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/LinearStateSpace.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/LinearStateSpace.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/MalFormed.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/MalFormed.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/NegatedAlias.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/NegatedAlias.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/NoState.Example1.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/NoState.Example1.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/NominalTests.NominalTest4.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/NominalTests.NominalTest4.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/OutputTest2.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/OutputTest2.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/ParameterAlias.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/ParameterAlias.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/QuadTankPack_Sim_QuadTank.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/QuadTankPack_Sim_QuadTank.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/bouncingBall.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/bouncingBall.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME2.0/test_type_definitions.fmu: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/FMUs/XML/ME2.0/test_type_definitions.fmu -------------------------------------------------------------------------------- /tests/files/FMUs/XML/ME3.0/alias/modelDescription.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /tests/files/Logs/boolean_log.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Successful solver step at 4.00000000000000E-01. 4 | 2.22199999999617E-04 5 | 3 6 | 4.73586919441991E-06, 9.51952684588326E-04, 1.30761683086889E-07, 1.80958489026530E-08, 5.68591603144660E-13, 1.35313236136660E-13, 4.06514549116529E-17, 3.07946109899289E-13 7 | 1.75261336008773E+01, 9.99999990000000E-01, 9.99999990000000E-01, -1.00000000023326E+10, 9.99999990000000E-01, 1.00000001000000E+00, 1.00000001000000E+00, 1.00000001000000E+00, 1.00000000000000E+00, 9.99999990000000E-01, -2.33264105137300E+00, -1.00000000000000E-08, 9.99999990000000E-01, 9.99999990000000E-01, 9.99999990000000E-01, 9.99999990000000E-01, 1.00000000000000E+00, 1.00000000000000E+00, 1.00000001000000E+00, 1.00000001000000E+00, 9.99999990000000E-01, 1.00000001000000E+00, -1.00000000000000E-08, 9.99999990000000E-01, 9.99999990000000E-01, 9.99999990000000E-01, 9.99999990000000E-01, 1.00000001000000E+00, 1.00000001000000E+00, 1.00000001000000E+00, 1.00000001000000E+00, 9.99999990000000E-01, 1.00000001000000E+00 8 | 9 | Detected event at 4.00000000000000E-01. 10 | 11 | True 12 | 13 | Detected event at 4.00000000000000E-01. 14 | 15 | False 16 | 17 | Detected event at 4.00000000000000E-01. 18 | 19 | true 20 | 21 | Detected event at 4.00000000000000E-01. 22 | 23 | false 24 | 25 | 26 | -------------------------------------------------------------------------------- /tests/files/Results/DoublePendulum.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/Results/DoublePendulum.mat -------------------------------------------------------------------------------- /tests/files/Results/TestCSV.csv: -------------------------------------------------------------------------------- 1 | "time","fd.d","fd.noevent","fd.u","fd.y" 2 | 0.00000000000000E+00,1.00000000000000E+00,1.00000000000000E+00,1.00000000000000E+00,1.00000000000000E+00 3 | 1.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,9.84292036732051E-01,1.00000000000000E+00 4 | 2.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,9.68584073464102E-01,1.00000000000000E+00 5 | 3.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,9.52876110196153E-01,1.00000000000000E+00 6 | 4.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,9.37168146928204E-01,1.00000000000000E+00 7 | 5.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,9.21460183660255E-01,1.00000000000000E+00 8 | 6.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,9.05752220392306E-01,1.00000000000000E+00 9 | 7.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,8.90044257124357E-01,1.00000000000000E+00 10 | 8.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,8.74336293856408E-01,1.00000000000000E+00 11 | 9.00000000000000E-02,1.00000000000000E+00,1.00000000000000E+00,8.58628330588459E-01,1.00000000000000E+00 12 | 1.00000000000000E-01,1.00000000000000E+00,1.00000000000000E+00,8.42920367320510E-01,1.00000000000000E+00 13 | -------------------------------------------------------------------------------- /tests/files/Results/qt_par_est_data.mat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modelon-community/PyFMI/03ffe234e7b0814b9bd6b626ca1b58c9d407d0bd/tests/files/Results/qt_par_est_data.mat -------------------------------------------------------------------------------- /tests/test_common.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2024 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | # Testing common functionality 19 | 20 | import numpy as np 21 | from pyfmi.common.core import TrajectoryLinearInterpolation, TrajectoryUserFunction 22 | 23 | 24 | class TestTrajectoryLinearInterpolation: 25 | def test_shape(self): 26 | """Test returned shape of TrajectoryLinearInterpolation.""" 27 | t = np.linspace(0, 1, 11) 28 | x = np.random.rand(11, 3) 29 | traj = TrajectoryLinearInterpolation(t, x) 30 | assert traj.eval(0.5).shape == (1, 3) 31 | 32 | 33 | class TestTrajectoryUserFunction: 34 | def test_shape_1_dim(self): 35 | """Testing shape of TrajectoryUserFunction; 1 dim output""" 36 | traj = TrajectoryUserFunction(lambda x: 5) 37 | 38 | assert traj.eval(1).shape == (1, 1) 39 | assert traj.eval(np.array([1])).shape == (1, 1) 40 | 41 | assert traj.eval([1, 2]).shape == (2, 1) 42 | assert traj.eval(np.array([1, 2])).shape == (2, 1) 43 | 44 | def test_shape_multi_dim(self): 45 | """Testing shape of TrajectoryUserFunction; multi dim output; array""" 46 | traj = TrajectoryUserFunction(lambda x: np.array([1, 2, 3])) 47 | 48 | assert traj.eval(1).shape == (1, 3) 49 | assert traj.eval(np.array([1])).shape == (1, 3) 50 | 51 | assert traj.eval([1, 2]).shape == (2, 3) 52 | assert traj.eval(np.array([1, 2])).shape == (2, 3) 53 | 54 | def test_shape_multi_dim_list(self): 55 | """Testing shape of TrajectoryUserFunction; multi dim output, list""" 56 | traj = TrajectoryUserFunction(lambda x: list(range(3))) 57 | 58 | assert traj.eval(1).shape == (1, 3) 59 | assert traj.eval(np.array([1])).shape == (1, 3) 60 | 61 | assert traj.eval([1, 2]).shape == (2, 3) 62 | assert traj.eval(np.array([1, 2])).shape == (2, 3) 63 | -------------------------------------------------------------------------------- /tests/test_fmi3_sim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2025 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | from pathlib import Path 19 | 20 | import pytest 21 | import numpy as np 22 | from scipy.interpolate import interp1d 23 | 24 | from pyfmi import load_fmu 25 | 26 | this_dir = Path(__file__).parent.absolute() 27 | FMI3_REF_FMU_PATH = Path(this_dir) / 'files' / 'reference_fmus' / '3.0' 28 | 29 | class TestSimulation: 30 | """Tests involving simulation of FMUs for FMI 3.""" 31 | 32 | def test_simulate(self): 33 | """Test simulate VDP model and verify the integrity of the results. """ 34 | fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") 35 | results = fmu.simulate() 36 | 37 | assert results['x0'][0] == 2.0 38 | assert results['x1'][0] == 0.0 39 | assert results['x0'][-1] == pytest.approx( 2.00814337) 40 | assert results['x1'][-1] == pytest.approx(-0.04277047) 41 | np.testing.assert_equal(results['mu'], np.ones(len(results['x0']))) 42 | 43 | def test_simulate_check_result_members(self): 44 | """Test simulate VDP model and check accessible data. """ 45 | fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") 46 | results = fmu.simulate(options = {"ncp": 1}) 47 | 48 | expected_variables = ['time', 'x0', 'der(x0)', 'x1', 'der(x1)', 'mu'] 49 | 50 | assert results.keys() == expected_variables 51 | 52 | def test_simulate_change_ncp(self): 53 | """Test simulate VDP model and change ncp. """ 54 | fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") 55 | opts = fmu.simulate_options() 56 | 57 | opts['ncp'] = 600 58 | results = fmu.simulate(options = opts) 59 | 60 | expected_variables = ['time', 'x0', 'der(x0)', 'x1', 'der(x1)', 'mu'] 61 | 62 | assert results.keys() == expected_variables 63 | 64 | assert all(len(results[v]) == 601 for v in expected_variables) 65 | 66 | @pytest.mark.parametrize("rh", ["file", "memory", "csv"]) 67 | def test_simulate_unsupported_result_handler(self, rh): 68 | """Verify unsupported result handlers raises an exception""" 69 | fmu = load_fmu(FMI3_REF_FMU_PATH / "VanDerPol.fmu") 70 | opts = fmu.simulate_options() 71 | 72 | opts['result_handling'] = rh 73 | opts["ncp"] = 1 74 | msg = f"For FMI3: 'result_handling' set to '{rh}' is not supported. " + \ 75 | "Consider setting this option to 'binary', 'custom' or None to continue." 76 | with pytest.raises(NotImplementedError, match = msg): 77 | fmu.simulate(options = opts) 78 | 79 | def test_result_variable_types(self): 80 | """Test which FMI variable types are supported in default result handler.""" 81 | fmu = load_fmu(FMI3_REF_FMU_PATH / "Feedthrough.fmu") 82 | res = fmu.simulate(options = {"ncp": 1}) 83 | 84 | expected = set([ 85 | 'time', 86 | 'Float64_fixed_parameter', 87 | 'Float64_tunable_parameter', 88 | 'Float64_continuous_input', 89 | 'Float64_continuous_output', 90 | 'Float64_discrete_input', 91 | 'Float64_discrete_output', 92 | 'Float32_continuous_input', 93 | 'Float32_continuous_output', 94 | 'Float32_discrete_input', 95 | 'Float32_discrete_output', 96 | 'Int64_input', 97 | 'Int64_output', 98 | 'Int32_input', 99 | 'Int32_output', 100 | 'Int16_input', 101 | 'Int16_output', 102 | 'Int8_input', 103 | 'Int8_output', 104 | 'UInt64_input', 105 | 'UInt64_output', 106 | 'UInt32_input', 107 | 'UInt32_output', 108 | 'UInt16_input', 109 | 'UInt16_output', 110 | 'UInt8_input', 111 | 'UInt8_output', 112 | 'Boolean_input', 113 | 'Boolean_output', 114 | 'Enumeration_input', 115 | 'Enumeration_output' 116 | ]) 117 | assert set(res.keys()) == expected 118 | 119 | @pytest.mark.parametrize("variable_base_name, value", 120 | [ 121 | ("Float64_continuous", 3.14), 122 | ("Float32_continuous", np.float32(3.14)), 123 | ("Int64", 10), 124 | ("Int32", 10), 125 | ("Int16", 10), 126 | ("Int8", 10), 127 | ("UInt64", 10), 128 | ("UInt32", 10), 129 | ("UInt16", 10), 130 | ("UInt8", 10), 131 | ("Boolean", True), 132 | ("Enumeration", 2), 133 | ] 134 | ) 135 | def test_result_handling_sanity_check(self, variable_base_name, value): 136 | """Sanity check for result handling of the various supported variable types.""" 137 | fmu = load_fmu(FMI3_REF_FMU_PATH / "Feedthrough.fmu") 138 | fmu.set(f"{variable_base_name}_input", value) 139 | res = fmu.simulate(options = {"ncp": 0}) 140 | assert all(v for v in res[f"{variable_base_name}_output"] == value) 141 | 142 | def test_result_handler_int64_limitations(self): 143 | """Test precision limitations for (u)int64 variables in result handling. """ 144 | fmu = load_fmu(FMI3_REF_FMU_PATH / "Feedthrough.fmu") 145 | val = 9223372036854775807 146 | assert float(val) != val # cannot be exactly represented 147 | fmu.set("Int64_input", val) 148 | fmu.set("UInt64_input", val) 149 | res = fmu.simulate(options = {"ncp": 0}) 150 | 151 | assert res["Int64_output"][-1] == float(val) 152 | assert res["UInt64_output"][-1] == float(val) 153 | 154 | @pytest.mark.xfail(strict = True, reason = "Requires support for state-events.") 155 | def test_result_handling_with_alias(self): 156 | """Test that result handling works with aliases.""" 157 | fmu = load_fmu(FMI3_REF_FMU_PATH / "BouncingBall.fmu") 158 | res = fmu.simulate(0, 0.001, options = {"ncp": 1}) 159 | assert "h_ft" in res.keys() 160 | np.testing.assert_equal(res["h"], res["h_ft"]) 161 | 162 | def test_time_events(self): 163 | """Test simulation with time events.""" 164 | fmu = load_fmu(FMI3_REF_FMU_PATH / "Stair.fmu") 165 | res = fmu.simulate(0, 10, options = {"ncp": 1}) 166 | assert res["counter"][-1] == 9 167 | assert res.solver.get_statistics()["ntimeevents"] == 9 168 | 169 | @pytest.mark.parametrize("var_name", [ 170 | "Float32_continuous", 171 | "Float64_continuous" 172 | ] 173 | ) 174 | def test_continuous_input(self, var_name): 175 | """Test setting continuous inputs to float values.""" 176 | fmu = load_fmu(FMI3_REF_FMU_PATH / "Feedthrough.fmu") 177 | input_var = f"{var_name}_input" 178 | output_var = f"{var_name}_output" 179 | 180 | # Generate input 181 | t = np.linspace(0., 10., 100) 182 | real_y = np.cos(t) 183 | real_input_traj = np.transpose(np.vstack((t, real_y))) 184 | input_object = (input_var, real_input_traj) 185 | 186 | ncp = 500 187 | fmu.set(input_var, real_y[0]) 188 | res = fmu.simulate(final_time = 10, input = input_object, options = {"ncp": ncp}) 189 | 190 | np.testing.assert_array_equal(res[input_var], res[output_var]) 191 | output_interp = interp1d(res["time"], res[output_var])(t) 192 | np.testing.assert_array_almost_equal(output_interp, real_y, decimal = 3) 193 | -------------------------------------------------------------------------------- /tests/test_fmi_estimate.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2019 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import pytest 20 | import numpy as np 21 | 22 | from pyfmi.test_util import Dummy_FMUModelME2 23 | from scipy.io.matlab import loadmat 24 | 25 | file_path = os.path.dirname(os.path.abspath(__file__)) 26 | 27 | @pytest.mark.assimulo 28 | class Test_FMUModelME2_Estimate: 29 | def test_quadtank_estimate(self): 30 | model = Dummy_FMUModelME2([], os.path.join(file_path, "files", "FMUs", "XML", "ME2.0", "QuadTankPack_Sim_QuadTank.fmu"), _connect_dll=False) 31 | 32 | g = model.get_real([model.get_variable_valueref("qt.g")], evaluate = False) 33 | g1_nmp = model.get_real([model.get_variable_valueref("qt.g1_nmp")], evaluate = False) 34 | g2_nmp = model.get_real([model.get_variable_valueref("qt.g2_nmp")], evaluate = False) 35 | k1_nmp = model.get_real([model.get_variable_valueref("qt.k1_nmp")], evaluate = False) 36 | k2_nmp = model.get_real([model.get_variable_valueref("qt.k2_nmp")], evaluate = False) 37 | A1 = model.get_real([model.get_variable_valueref("qt.A1")], evaluate = False) 38 | A2 = model.get_real([model.get_variable_valueref("qt.A2")], evaluate = False) 39 | A3 = model.get_real([model.get_variable_valueref("qt.A3")], evaluate = False) 40 | A4 = model.get_real([model.get_variable_valueref("qt.A4")], evaluate = False) 41 | a3 = model.get_real([model.get_variable_valueref("qt.a3")], evaluate = False) 42 | a4 = model.get_real([model.get_variable_valueref("qt.a4")], evaluate = False) 43 | u1_vref = model.get_variable_valueref("u1") 44 | u2_vref = model.get_variable_valueref("u2") 45 | a1_vref = model.get_variable_valueref("qt.a1") 46 | a2_vref = model.get_variable_valueref("qt.a2") 47 | 48 | def f(*args, **kwargs): 49 | x1 = model.continuous_states[0] 50 | x2 = model.continuous_states[1] 51 | x3 = model.continuous_states[2] 52 | x4 = model.continuous_states[3] 53 | 54 | u1 = model.get_real([u1_vref], evaluate = False) 55 | u2 = model.get_real([u2_vref], evaluate = False) 56 | a1 = model.get_real([a1_vref], evaluate = False) 57 | a2 = model.get_real([a2_vref], evaluate = False) 58 | 59 | der_x1 = -a1/A1*np.sqrt(2.*g*x1) + a3/A1*np.sqrt(2*g*x3) + g1_nmp*k1_nmp/A1*u1 60 | der_x2 = -a2/A2*np.sqrt(2.*g*x2) + a4/A2*np.sqrt(2*g*x4) + g2_nmp*k2_nmp/A2*u2 61 | der_x3 = -a3/A3*np.sqrt(2.*g*x3) + (1.-g2_nmp)*k2_nmp/A3*u2 62 | der_x4 = -a4/A4*np.sqrt(2.*g*x4) + (1.-g1_nmp)*k1_nmp/A4*u1 63 | return np.concatenate([der_x1, der_x2, der_x3, der_x4]) 64 | 65 | model.get_derivatives = f 66 | 67 | # Load measurement data from file 68 | data = loadmat(os.path.join(file_path, "files", "Results", "qt_par_est_data.mat"), appendmat=False) 69 | 70 | # Extract data series 71 | t_meas = data['t'][6000::100,0]-60 72 | y1_meas = data['y1_f'][6000::100,0]/100 73 | y2_meas = data['y2_f'][6000::100,0]/100 74 | y3_meas = data['y3_d'][6000::100,0]/100 75 | y4_meas = data['y4_d'][6000::100,0]/100 76 | u1 = data['u1_d'][6000::100,0] 77 | u2 = data['u2_d'][6000::100,0] 78 | 79 | # Build input trajectory matrix for use in simulation 80 | u = np.transpose(np.vstack((t_meas,u1,u2))) 81 | 82 | # Estimation of 2 parameters 83 | data = np.vstack((t_meas, y1_meas, y2_meas)).transpose() 84 | 85 | res = model.estimate(parameters=["qt.a1", "qt.a2"], 86 | measurements = (['qt.x1', 'qt.x2'], data), input=(['u1','u2'],u)) 87 | 88 | 89 | model.reset() 90 | 91 | # Set optimal values for a1 and a2 into the model 92 | model.set(['qt.a1'], res["qt.a1"]) 93 | model.set(['qt.a2'], res["qt.a2"]) 94 | 95 | # Simulate model response with optimal parameters a1 and a2 96 | res = model.simulate(input=(['u1','u2'], u), start_time=0., final_time=60) 97 | 98 | assert np.abs(res.final('qt.x1') - 0.07060188) < 1e-3, "Was: " + str(res.final('qt.x1')) + ", expected: 0.07060188" 99 | assert np.abs(res.final('qt.x2') - 0.06654621) < 1e-3 100 | assert np.abs(res.final('qt.x3') - 0.02736549) < 1e-3 101 | assert np.abs(res.final('qt.x4') - 0.02789857) < 1e-3 102 | assert np.abs(res.final('u1') - 6.0) < 1e-3 103 | assert np.abs(res.final('u2') - 5.0) < 1e-3 104 | -------------------------------------------------------------------------------- /tests/test_fmi_extended.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2019 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import os 19 | import numpy as np 20 | 21 | from pyfmi.fmi_extended import FMUModelME1Extended 22 | 23 | file_path = os.path.dirname(os.path.abspath(__file__)) 24 | me1_xml_path = os.path.join(file_path, "files", "FMUs", "XML", "ME1.0") 25 | 26 | class Test_FMUModelME1Extended: 27 | 28 | def test_log_file_name(self): 29 | model = FMUModelME1Extended(os.path.join(me1_xml_path, "bouncingBall.fmu"), _connect_dll=False) 30 | assert os.path.exists("bouncingBall_log.txt") 31 | model = FMUModelME1Extended(os.path.join(me1_xml_path, "bouncingBall.fmu"), log_file_name="Test_log.txt", _connect_dll=False) 32 | assert os.path.exists("Test_log.txt") 33 | 34 | def test_default_experiment(self): 35 | model = FMUModelME1Extended(os.path.join(me1_xml_path, "CoupledClutches.fmu"), _connect_dll=False) 36 | 37 | assert np.abs(model.get_default_experiment_start_time()) < 1e-4 38 | assert np.abs(model.get_default_experiment_stop_time()-1.5) < 1e-4 39 | assert np.abs(model.get_default_experiment_tolerance()-0.0001) < 1e-4 40 | -------------------------------------------------------------------------------- /tests/test_fmi_util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2010 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | """ 19 | Module containing the tests for the FMI interface. 20 | """ 21 | 22 | import numpy as np 23 | from collections import OrderedDict 24 | 25 | import pyfmi.util as pyfmi_util 26 | 27 | class Test_FMIUtil: 28 | def test_cpr_seed(self): 29 | structure = OrderedDict([('der(inertia3.phi)', ['inertia3.w']), 30 | ('der(inertia3.w)', ['damper.phi_rel', 'inertia3.phi']), 31 | ('der(damper.phi_rel)', ['damper.w_rel']), 32 | ('der(damper.w_rel)', 33 | ['damper.phi_rel', 'damper.w_rel', 'inertia3.phi'])]) 34 | 35 | states = ['inertia3.phi', 'inertia3.w', 'damper.phi_rel', 'damper.w_rel'] 36 | 37 | groups = pyfmi_util.cpr_seed(structure, states) 38 | 39 | assert np.array(groups[0][5] == [1,2,3]).all() 40 | assert np.array(groups[1][5] == [5,7]).all() 41 | assert np.array(groups[2][5] == [8,9]).all() 42 | assert np.array(groups[0][4] == [0,1,2]).all() 43 | assert np.array(groups[1][4] == [3,4]).all() 44 | assert np.array(groups[2][4] == [5,6]).all() 45 | 46 | def test_cpr_seed_interested_columns(self): 47 | structure = OrderedDict([('der(inertia3.phi)', ['inertia3.w']), 48 | ('der(inertia3.w)', ['damper.phi_rel', 'inertia3.phi']), 49 | ('der(damper.phi_rel)', ['damper.w_rel']), 50 | ('der(damper.w_rel)', 51 | ['damper.phi_rel', 'damper.w_rel', 'inertia3.phi'])]) 52 | 53 | states = ['inertia3.phi', 'inertia3.w', 'damper.phi_rel', 'damper.w_rel'] 54 | 55 | interested_columns = {0:1, 1:0, 2:0} 56 | groups = pyfmi_util.cpr_seed(structure, states, interested_columns) 57 | 58 | assert np.array(groups[0][5] == [1,2,3]).all() 59 | assert np.array(groups[1][5] == [5,7]).all() 60 | assert np.array(groups[0][4] == [0,1,2]).all() 61 | assert np.array(groups[1][4] == [3,4]).all() 62 | assert len(groups) == 5 63 | 64 | interested_columns = {0:1, 1:0, 3:0} 65 | groups = pyfmi_util.cpr_seed(structure, states, interested_columns) 66 | 67 | assert np.array(groups[0][5] == [1,2,3]).all() 68 | assert np.array(groups[1][5] == [8,9]).all() 69 | assert np.array(groups[0][4] == [0,1,2]).all() 70 | assert np.array(groups[1][4] == [5,6]).all() 71 | assert len(groups) == 5 72 | 73 | interested_columns = {1:1, 2:0, 3:0} 74 | groups = pyfmi_util.cpr_seed(structure, states, interested_columns) 75 | 76 | assert np.array(groups[0][5] == [3,5,7]).all() 77 | assert np.array(groups[1][5] == [8,9]).all() 78 | assert np.array(groups[0][4] == [2,3,4]).all() 79 | assert np.array(groups[1][4] == [5,6]).all() 80 | assert len(groups) == 5 81 | -------------------------------------------------------------------------------- /tests/test_stream.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | # Copyright (C) 2021 Modelon AB 5 | # 6 | # This program is free software: you can redistribute it and/or modify 7 | # it under the terms of the GNU General Public License as published by 8 | # the Free Software Foundation, version 3 of the License. 9 | # 10 | # This program is distributed in the hope that it will be useful, 11 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 | # GNU General Public License for more details. 14 | # 15 | # You should have received a copy of the GNU General Public License 16 | # along with this program. If not, see . 17 | 18 | import pytest 19 | import os 20 | from io import StringIO 21 | import tempfile 22 | from shutil import rmtree 23 | from filecmp import cmp as compare_files 24 | 25 | from pyfmi.fmi import FMUException, load_fmu, FMUModelCS2, FMUModelME2 26 | from pyfmi.test_util import get_examples_folder 27 | 28 | file_path = os.path.dirname(os.path.abspath(__file__)) 29 | 30 | class TestIO(StringIO): 31 | """ Test class used to verify that a custom class can be used as a logger 32 | if it inherits specific properties. 33 | """ 34 | __test__ = False 35 | def __init__(self, arg): 36 | StringIO.__init__(self, arg) 37 | 38 | 39 | def write_stream_to_file(stream, output_file): 40 | """ Writes contents of 'stream' to 'output_file'. 41 | The stream needs to support the two functions 'seek' and 'readlines'. 42 | """ 43 | stream.seek(0) 44 | with open(output_file, 'w') as f: 45 | f.writelines(stream.readlines()) 46 | 47 | def simulate_and_verify_stream_contents(compiled_fmu, fmu_loader, stream, open_to_read = False): 48 | """ 49 | Loads a compiled fmu with specified class 'fmu_loader', logs into stream and 50 | check the contents of the stream and verifies it with a reference result. 51 | 52 | The boolean parameter open_to_read is set in order to reopen the stream in 53 | mode 'r' in order to read from it, since it is assumed the specified stream 54 | is given in mode 'w'. This is solely used for testing to reduce duplicated code. 55 | 56 | """ 57 | fmu = fmu_loader(compiled_fmu, log_file_name = stream, log_level = 3) 58 | results = fmu.simulate() 59 | 60 | contents = [] 61 | if open_to_read: 62 | stream.close() 63 | with open(stream.name, 'r') as f: 64 | contents = f.readlines() 65 | else: 66 | stream.seek(0) 67 | contents = stream.readlines() 68 | 69 | # Is enough to check substrings 70 | expected = [ 71 | 'FMIL: module = FMI2XML, log level = 3: fmi2_xml_get_default_experiment_start:', 72 | 'FMIL: module = FMI2XML, log level = 3: fmi2_xml_get_default_experiment_stop:', 73 | 'FMIL: module = FMI2XML, log level = 3: fmi2_xml_get_default_experiment_tolerance:' 74 | ] 75 | for i, line in enumerate(expected): 76 | err_msg = "Unable to find substring {} in list {}".format(line, "".join(contents)) 77 | assert line in contents[i], err_msg 78 | 79 | 80 | @pytest.mark.parametrize("fmu_path, test_class", 81 | [ 82 | pytest.param(os.path.join(get_examples_folder(), 'files', 'FMUs', 'ME2.0', 'bouncingBall.fmu'), FMUModelME2, marks = pytest.mark.assimulo), 83 | pytest.param(os.path.join(get_examples_folder(), 'files', 'FMUs', 'ME2.0', 'bouncingBall.fmu'), load_fmu, marks = pytest.mark.assimulo), 84 | pytest.param(os.path.join(get_examples_folder(), 'files', 'FMUs', 'CS2.0', 'bouncingBall.fmu'), FMUModelCS2), 85 | ]) 86 | class Test_Stream: 87 | def test_testio(self, fmu_path, test_class): 88 | """ Test custom IO class. """ 89 | stream = TestIO("") 90 | simulate_and_verify_stream_contents(fmu_path, test_class, stream) 91 | 92 | def test_stringio(self, fmu_path, test_class): 93 | """ Test StringIO. """ 94 | stream = StringIO() 95 | simulate_and_verify_stream_contents(fmu_path, test_class, stream) 96 | 97 | def test_textiowrapper(self, fmu_path, test_class): 98 | """ Test TextIOWrapper. """ 99 | p = tempfile.mkdtemp() 100 | output_file = os.path.join(p, 'test.txt') 101 | stream = open(output_file, 'w') 102 | simulate_and_verify_stream_contents(fmu_path, test_class, stream, True) 103 | if not stream.closed: 104 | stream.close() 105 | rmtree(p) 106 | 107 | @pytest.mark.assimulo 108 | class TestXML: 109 | """ Test other log related functions together with streams. """ 110 | @pytest.fixture(autouse=True) 111 | @classmethod 112 | def setup_class(cls): 113 | cls.example_fmu = os.path.join(get_examples_folder(), 'files', 'FMUs', 'ME2.0', 'bouncingBall.fmu') 114 | 115 | # Verify the installation is not corrupt while setting up the class. 116 | assert os.path.isfile(cls.example_fmu) 117 | 118 | def test_extract_xml_log(self): 119 | """ Compare contents of XML log when using stream and normal logfile. """ 120 | stream = TestIO("") 121 | fmu_s = load_fmu(self.example_fmu, log_file_name = stream, log_level = 4) 122 | xml_file1 = fmu_s.get_log_filename() +'.xml' 123 | if os.path.isfile(xml_file1): 124 | os.remove(xml_file1) 125 | res_s = fmu_s.simulate() 126 | xml_log_s = fmu_s.extract_xml_log() 127 | 128 | log_file_name = 'test_cmp_xml_files.txt' 129 | if os.path.isfile(log_file_name): 130 | os.remove(log_file_name) 131 | fmu = load_fmu(self.example_fmu, log_file_name = log_file_name, log_level = 4) 132 | xml_file2 = 'test_cmp_xml_files.xml' 133 | if os.path.isfile(xml_file2): 134 | os.remove(xml_file2) 135 | res = fmu.simulate() 136 | xml_log = fmu.extract_xml_log() 137 | 138 | err_msg = "Unequal xml files, please compare the contents of:\n{}\nand\n{}".format(xml_log_s, xml_log) 139 | assert compare_files(xml_log_s, xml_log), err_msg 140 | 141 | def test_get_log(self): 142 | """ Test get_log throws exception if stream doesnt support getvalue. """ 143 | stream = StringIO("") 144 | fmu_s = load_fmu(self.example_fmu, log_file_name = stream, log_level = 3) 145 | res_s = fmu_s.simulate() 146 | log = fmu_s.get_log() 147 | expected_substr = [ 148 | 'FMIL: module = FMI2XML, log level = 3: fmi2_xml_get_default_experiment_start', 149 | 'FMIL: module = FMI2XML, log level = 3: fmi2_xml_get_default_experiment_stop', 150 | 'FMIL: module = FMI2XML, log level = 3: fmi2_xml_get_default_experiment_tolerance' 151 | ] 152 | for i, line in enumerate(expected_substr): 153 | assert line in log[i] 154 | 155 | 156 | def test_get_log_exception1(self): 157 | """ Test get_log throws exception if stream doesnt allow reading (it is set for writing). """ 158 | try: 159 | p = tempfile.mkdtemp() 160 | output_file = os.path.join(p, 'test.txt') 161 | stream = open(output_file, 'w') 162 | fmu_s = load_fmu(self.example_fmu, log_file_name = stream, log_level = 3) 163 | res_s = fmu_s.simulate() 164 | err_msg = "Unable to read from given stream, make sure the stream is readable." 165 | with pytest.raises(FMUException, match = err_msg): 166 | log = fmu_s.get_log() 167 | finally: 168 | if not stream.closed: 169 | stream.close() 170 | rmtree(p) 171 | 172 | 173 | def test_get_nbr_of_lines_in_log(self): 174 | """ Test get_number_of_lines_log when using a stream. """ 175 | stream = StringIO("") 176 | fmu = load_fmu(self.example_fmu, log_file_name = stream, log_level = 3) 177 | assert fmu.get_number_of_lines_log() == 0 178 | res = fmu.simulate() 179 | assert fmu.get_number_of_lines_log() == 0 180 | 181 | def test_extract_xml_log_into_stream(self): 182 | """ Compare contents of XML log when extract XML into a stream. """ 183 | stream = TestIO("") 184 | extracted_xml_stream = StringIO("") 185 | fmu_s = load_fmu(self.example_fmu, log_file_name = stream, log_level = 4) 186 | res_s = fmu_s.simulate() 187 | fmu_s.extract_xml_log(extracted_xml_stream) 188 | 189 | # write the contents of extract_xml_stream to a file for test 190 | xml_file1 = "my_new_file.xml" 191 | if os.path.isfile(xml_file1): 192 | os.remove(xml_file1) 193 | write_stream_to_file(extracted_xml_stream, xml_file1) 194 | 195 | log_file_name = 'test_cmp_xml_files.txt' 196 | if os.path.isfile(log_file_name): 197 | os.remove(log_file_name) 198 | fmu = load_fmu(self.example_fmu, log_file_name = log_file_name, log_level = 4) 199 | xml_file2 = 'test_cmp_xml_files.xml' 200 | if os.path.isfile(xml_file2): 201 | os.remove(xml_file2) 202 | res = fmu.simulate() 203 | xml_log = fmu.extract_xml_log() 204 | 205 | err_msg = "Unequal xml files, please compare the contents of:\n{}\nand\n{}".format(xml_file1, xml_log) 206 | assert compare_files(xml_file1, xml_log), err_msg 207 | --------------------------------------------------------------------------------