From fe1874c231657f5227a553cae87be529d959a82c Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 12 May 2023 11:17:33 +0200 Subject: [PATCH] Run pre-commit run --all-files (#2082) see #2042 --- .coveragerc | 1 - .github/CODEOWNERS | 1 - .../test_benchmark_collection_models.yml | 2 +- CHANGELOG.md | 76 +- CITATION.cff | 32 +- README.md | 6 +- binder/Dockerfile | 1 - container/README.md | 4 +- documentation/CI.md | 18 +- documentation/MATLAB_.md | 8 +- documentation/README.md | 6 +- .../_templates/autosummary/class.rst | 2 - documentation/code_review_guide.md | 22 +- documentation/conf.py | 492 +-- documentation/gfx/logo_template.svg | 20 +- .../implementation_discontinuities.rst | 18 +- documentation/recreate_reference_list.py | 53 +- matlab/@amidata/amidata.m | 53 +- matlab/@amievent/amievent.m | 13 +- matlab/@amifun/amifun.m | 25 +- matlab/@amifun/gccode.m | 32 +- matlab/@amifun/getArgs.m | 6 +- matlab/@amifun/getCVar.m | 3 +- matlab/@amifun/getDeps.m | 133 +- matlab/@amifun/getNVecs.m | 10 +- matlab/@amifun/getSensiFlag.m | 45 +- matlab/@amifun/getSyms.m | 184 +- matlab/@amifun/writeCcode.m | 2 +- matlab/@amifun/writeCcode_sensi.m | 8 +- matlab/@amifun/writeMcode.m | 4 +- matlab/@amimodel/amimodel.m | 57 +- matlab/@amimodel/augmento2.m | 24 +- matlab/@amimodel/augmento2vec.m | 26 +- matlab/@amimodel/checkDeps.m | 6 +- matlab/@amimodel/compileAndLinkModel.m | 2 - matlab/@amimodel/compileC.m | 4 +- matlab/@amimodel/generateM.m | 1 - matlab/@amimodel/generateMatlabWrapper.m | 1 - matlab/@amimodel/generateRebuildM.m | 1 - matlab/@amimodel/getFun.m | 14 +- matlab/@amimodel/loadOldHashes.m | 8 +- matlab/@amimodel/makeEvents.m | 39 +- matlab/@amimodel/makeSyms.m | 3 +- matlab/@amimodel/parseModel.m | 10 +- matlab/@amised/amised.m | 19 +- matlab/@optsym/optsym.m | 8 +- matlab/AMICI2D2D.m | 8 +- matlab/SBML2AMICI.m | 1 - matlab/SBMLimporter/@SBMLode/SBMLode.m | 15 +- matlab/SBMLimporter/@SBMLode/checkODE.m | 2 +- matlab/SBMLimporter/@SBMLode/importSBML.m | 55 +- matlab/SBMLimporter/@SBMLode/writeAMICI.m | 16 +- matlab/SBMLimporter/computeBracketLevel.m | 5 +- matlab/amiwrap.m | 1 - matlab/auxiliary/CalcMD5/CalcMD5.c | 92 +- matlab/auxiliary/CalcMD5/TestCalcMD5.m | 18 +- matlab/auxiliary/am_setdefault.m | 2 +- matlab/auxiliary/betterSym.m | 2 +- matlab/auxiliary/getCommitHash.m | 7 +- matlab/auxiliary/struct2xml/struct2xml.m | 34 +- matlab/auxiliary/structToHDF5Attribute.m | 2 +- matlab/auxiliary/template.m | 15 +- matlab/auxiliary/xml2struct/xml2struct.m | 48 +- .../example_adjoint/example_adjoint.m | 22 +- .../example_adjoint/model_adjoint_syms.m | 6 +- .../example_adjoint_hessian.m | 2 +- .../model_adjoint_hessian_syms.m | 6 +- .../example_calvetti/example_calvetti.m | 6 +- .../example_calvetti/model_calvetti_syms.m | 13 +- matlab/examples/example_dirac/example_dirac.m | 16 +- .../examples/example_dirac/model_dirac_syms.m | 6 +- .../example_dirac_adjoint.m | 4 +- .../example_model_5_paper.m | 8 +- .../model_dirac_adjoint_syms.m | 4 +- .../example_dirac_adjoint_hessVecProd.m | 1 - .../model_dirac_adjoint_hessVecProd_syms.m | 4 +- .../example_dirac_secondorder.m | 2 +- .../model_dirac_secondorder_syms.m | 6 +- .../example_dirac_secondorder_vectmult.m | 10 +- .../model_dirac_secondorder_vectmult_syms.m | 8 +- .../examples/example_events/example_events.m | 20 +- .../example_events/model_events_syms.m | 8 +- .../example_jakstat_adjoint.m | 28 +- .../model_jakstat_adjoint_syms.m | 44 +- .../example_jakstat_adjoint_hvp.m | 24 +- .../model_jakstat_adjoint_hvp_syms.m | 44 +- .../example_nested_events.m | 8 +- .../model_nested_events_syms.m | 4 +- .../examples/example_neuron/example_neuron.m | 30 +- .../example_neuron/model_neuron_syms.m | 36 +- .../example_robertson/example_robertson.m | 10 +- .../example_robertson/model_robertson_syms.m | 8 +- .../example_steadystate/example_steadystate.m | 58 +- .../model_steadystate_syms.m | 8 +- matlab/installAMICI.m | 2 +- matlab/mtoc/MatlabDocMaker.m | 104 +- matlab/mtoc/config/customdoxygen.css | 56 +- matlab/mtoc/config/latexextras.template | 4 +- matlab/mtoc/config/mtocpp.conf | 6 +- matlab/symbolic/am_and.m | 2 +- matlab/symbolic/am_eq.m | 4 +- matlab/symbolic/am_ge.m | 2 +- matlab/symbolic/am_gt.m | 2 +- matlab/symbolic/am_if.m | 2 +- matlab/symbolic/am_le.m | 2 +- matlab/symbolic/am_lt.m | 2 +- matlab/symbolic/am_min.m | 2 +- matlab/symbolic/am_or.m | 2 +- matlab/symbolic/am_piecewise.m | 1 - matlab/symbolic/am_spline_pos.m | 2 +- matlab/symbolic/am_stepfun.m | 4 +- matlab/symbolic/am_xor.m | 2 +- python/benchmark/benchmark_pysb.py | 67 +- .../ExampleEquilibrationLogic.ipynb | 2 +- python/examples/example_petab/petab.ipynb | 2 +- .../createModelPresimulation.py | 105 +- .../swameye2003_conditions.tsv | 2 +- .../ExampleSteadystate.ipynb | 2 +- .../model_steadystate_scaled.xml | 1 - ...steadystate_scaled_without_observables.xml | 1 - python/examples/example_units/model_units.xml | 2 +- python/sdist/amici/__init__.py | 49 +- python/sdist/amici/__init__.template.py | 16 +- python/sdist/amici/__main__.py | 4 +- python/sdist/amici/bngl_import.py | 4 +- .../amici/conserved_quantities_demartino.py | 278 +- .../sdist/amici/conserved_quantities_rref.py | 8 +- python/sdist/amici/constants.py | 29 +- python/sdist/amici/custom_commands.py | 56 +- python/sdist/amici/cxxcodeprinter.py | 123 +- python/sdist/amici/de_export.py | 2815 +++++++++-------- python/sdist/amici/de_model.py | 201 +- python/sdist/amici/gradient_check.py | 146 +- python/sdist/amici/import_utils.py | 283 +- python/sdist/amici/logging.py | 104 +- python/sdist/amici/numpy.py | 276 +- python/sdist/amici/pandas.py | 406 +-- python/sdist/amici/parameter_mapping.py | 149 +- python/sdist/amici/petab_import.py | 543 ++-- python/sdist/amici/petab_import_pysb.py | 214 +- python/sdist/amici/petab_objective.py | 538 ++-- python/sdist/amici/petab_simulate.py | 40 +- python/sdist/amici/plotting.py | 45 +- python/sdist/amici/pysb_import.py | 668 ++-- python/sdist/amici/sbml_import.py | 1388 ++++---- python/sdist/amici/sbml_utils.py | 98 +- python/sdist/amici/setup.template.py | 44 +- python/sdist/amici/splines.py | 737 ++--- python/sdist/amici/swig.py | 95 +- python/sdist/amici/swig_wrappers.py | 167 +- python/sdist/amici/testing.py | 14 +- python/sdist/setup.py | 95 +- python/tests/conftest.py | 50 +- .../lotka_volterra/model/lotka_volterra.yaml | 10 +- .../lotka_volterra/model/writer.py | 12 +- .../bngwiki_egfr_simple_deletemolecules.py | 122 +- python/tests/splines_utils.py | 469 +-- python/tests/test_bngl.py | 57 +- .../test_compare_conservation_laws_sbml.py | 210 +- .../test_conserved_quantities_demartino.py | 853 ++++- .../tests/test_conserved_quantities_rref.py | 7 +- python/tests/test_edata.py | 9 +- python/tests/test_events.py | 182 +- python/tests/test_hdf5.py | 35 +- python/tests/test_heavisides.py | 182 +- python/tests/test_misc.py | 68 +- python/tests/test_observable_events.py | 154 +- python/tests/test_ode_export.py | 70 +- python/tests/test_pandas.py | 40 +- python/tests/test_parameter_mapping.py | 47 +- python/tests/test_petab_import.py | 84 +- python/tests/test_petab_objective.py | 49 +- python/tests/test_petab_simulate.py | 11 +- python/tests/test_preequilibration.py | 293 +- python/tests/test_pregenerated_models.py | 207 +- python/tests/test_pysb.py | 271 +- python/tests/test_rdata.py | 33 +- python/tests/test_sbml_import.py | 363 ++- .../test_sbml_import_special_functions.py | 82 +- python/tests/test_splines.py | 49 +- python/tests/test_splines_python.py | 185 +- python/tests/test_splines_short.py | 59 +- python/tests/test_swig_interface.py | 244 +- python/tests/util.py | 69 +- scripts/README.md | 18 +- scripts/run-cppcheck.sh | 1 - scripts/run-python-tests.sh | 1 - swig/model_ode.i | 2 - tests/benchmark-models/benchmark_models.yaml | 1 - tests/benchmark-models/evaluate_benchmark.py | 49 +- .../benchmark-models/test_petab_benchmark.py | 98 +- tests/benchmark-models/test_petab_model.py | 165 +- tests/conftest.py | 61 +- tests/cpp/wrapTestModels.m | 37 +- tests/generateTestConfig/example.py | 82 +- tests/generateTestConfig/example_calvetti.py | 23 +- tests/generateTestConfig/example_dirac.py | 33 +- tests/generateTestConfig/example_events.py | 52 +- tests/generateTestConfig/example_jakstat.py | 153 +- .../example_nested_events.py | 47 +- tests/generateTestConfig/example_neuron.py | 198 +- tests/generateTestConfig/example_robertson.py | 106 +- .../generateTestConfig/example_steadystate.py | 610 ++-- tests/performance/test.py | 105 +- tests/petab_test_suite/conftest.py | 30 +- tests/petab_test_suite/test_petab_suite.py | 120 +- tests/testSBMLSuite.py | 169 +- 207 files changed, 10365 insertions(+), 8670 deletions(-) diff --git a/.coveragerc b/.coveragerc index 5c8d382e6f..d5f1748d1b 100644 --- a/.coveragerc +++ b/.coveragerc @@ -16,4 +16,3 @@ exclude_lines = raise except: import - diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index dbda62fd09..6b2aece452 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -2,4 +2,3 @@ # default owners * @AMICI-dev/amici-maintainers - diff --git a/.github/workflows/test_benchmark_collection_models.yml b/.github/workflows/test_benchmark_collection_models.yml index 5dc3658ba1..9cfa50ea2a 100644 --- a/.github/workflows/test_benchmark_collection_models.yml +++ b/.github/workflows/test_benchmark_collection_models.yml @@ -61,7 +61,7 @@ jobs: git clone --depth 1 https://github.com/benchmarking-initiative/Benchmark-Models-PEtab.git \ && export BENCHMARK_COLLECTION="$(pwd)/Benchmark-Models-PEtab/Benchmark-Models/" \ && AMICI_PARALLEL_COMPILE=2 tests/benchmark-models/test_benchmark_collection.sh - + # run gradient checks - name: Run Gradient Checks run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index ff722cd5d3..bded8fee35 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# Changelog +# Changelog ## v0.X Series @@ -244,7 +244,7 @@ Fixes * Added equality operator for ExpData by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1881 - + * Updated base image for Dockerfile to Ubuntu 22.04/Python 3.10 by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1896 @@ -272,7 +272,7 @@ Fixes #### Documentation: -* Update reference list +* Update reference list by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1874, https://github.com/AMICI-dev/AMICI/pull/1884 **Full Changelog**: @@ -492,7 +492,7 @@ https://github.com/AMICI-dev/AMICI/compare/v0.11.26...v0.11.27 ### v0.11.26 (2022-03-14) New features: -* Import of BioNetGenLanguage (BNGL) models by @FFroehlich in +* Import of BioNetGenLanguage (BNGL) models by @FFroehlich in https://github.com/AMICI-dev/AMICI/pull/1709 * Added support for observable-dependent sigmas by @dweindl, @FFroehlich in https://github.com/AMICI-dev/AMICI/pull/1692 @@ -572,26 +572,26 @@ Fixes: Features: * Added overload for Model::setParameterScale with vector by @dilpath in https://github.com/AMICI-dev/AMICI/pull/1614 -* Removed assert_fun argument from gradient checking, improve output +* Removed assert_fun argument from gradient checking, improve output by @dweindl, @FFroehlich in https://github.com/AMICI-dev/AMICI/pull/1609 * Added get_expressions_as_dataframe by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1621 -* Added `id` field to ExpData and ReturnData by @dweindl in +* Added `id` field to ExpData and ReturnData by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1622 -* Included condition id in dataframes by @dweindl in +* Included condition id in dataframes by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1623 Fixes: -* C++: Fixed SUNMatrixWrapper ctor for size 0 matrices by @dweindl in +* C++: Fixed SUNMatrixWrapper ctor for size 0 matrices by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1608 -* Python: Handle TemporaryDirectory cleanup failures on Windows by @dweindl in +* Python: Handle TemporaryDirectory cleanup failures on Windows by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1617 -* Python: pysb.Model.initial_conditions throws a DeprecationWarning by +* Python: pysb.Model.initial_conditions throws a DeprecationWarning by @PaulJonasJost in https://github.com/AMICI-dev/AMICI/pull/1620 * Fixed wrong array size in warnings by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1624 -NOTE: AMICI 0.11.23 requires numpy<1.22.0 +NOTE: AMICI 0.11.23 requires numpy<1.22.0 **Full Changelog**: https://github.com/AMICI-dev/AMICI/compare/v0.11.22...v0.11.23 @@ -626,7 +626,7 @@ New: ### v0.11.20 (2021-11-12) -New: +New: * Changed parameter mappings such that unassigned values have non-nan default values. This fixes erroneous evaluation of `llh` as `NaN` in some situations (#1574) * Added support for Python 3.10 (#1555) @@ -760,7 +760,7 @@ Misc: Breaking changes: * AMICI requires Python>=3.7 -* Updated package installation (PEP517/518): +* Updated package installation (PEP517/518): Creating source distributions requires https://github.com/pypa/build (#1384) (but now handles all package building dependencies properly) @@ -794,7 +794,7 @@ Other: ### v0.11.12 (2021-01-26) -Features: +Features: * Add expression IDs and names to generated models (#1374) Fixes: @@ -882,7 +882,7 @@ Bugfix release that restores compatibility with sympy 1.7 * Overload python interface functions for amici.{Model,Solver,ExpData} and amici.{Model,Solver,ExpData}Ptr (#1271) #### C++ -* Fix and extend use of sparse matrix operations (#1230, #1240, #1244, #1247, #1271) +* Fix and extend use of sparse matrix operations (#1230, #1240, #1244, #1247, #1271) * **Fix application of maximal number of steps**, MaxNumStep parameter now limit total number of steps, not number of steps between output times. (#1267) #### Doc @@ -913,7 +913,7 @@ Bugfix release that restores compatibility with sympy 1.7 * Create sdist on GHA using swig4.0.1 (#1204) (Fixing broken pypi package) * Fix links after repository move * Speed-up swig build: disable all languages except python (#1211) -* Fix doc generation on readthedocs (#1196) +* Fix doc generation on readthedocs (#1196) ### v0.11.5 (2020-08-07) @@ -977,7 +977,7 @@ Bugfix release that restores compatibility with sympy 1.7 #### Python * Upgrade to sympy 1.6.0, which is now required minimum version (#1098, #1103) -* Speed up model import +* Speed up model import * Speed-up computation of sx0, reduce file size (#1109) * Replace terribly slow sympy.MutableDenseMatrix.is_zero_matrix by custom implementation (#1104) * speedup dataframe creation in `get*AsDataFrame` (#1088) @@ -1103,11 +1103,11 @@ CI: ### v0.10.17 (2020-01-15) -- **added python 3.8 support, dropped python 3.6 support** (#898) +- **added python 3.8 support, dropped python 3.6 support** (#898) - Added logging functionality (#900) - Fixes PySB import (#879, #902) - Fixes symbolic processing (#899) -- Improved build scripts (#894, +- Improved build scripts (#894, - Improved petab support (#886, #888, #891) - CI related fixes (#865, #896) @@ -1130,15 +1130,15 @@ No other changes. **NOTE: For Python-imported SBML-models this release may compute incorrect sensitivities w.r.t. sigma. Bug introduced in 0.10.14, fixed in 0.10.15.** -Python: +Python: * Don't require use of ModelPtr.get to call ExpData(Model) * Fix import in generated model Python package * Setup AMICI standalone scripts as setuptools entrypoints * Simplify symbolic sensitivity expressions during Python SBML import Fixes Infs in the Jacobian when using Hill-functions with states of 0.0. -* Extended Newton solver #848 - The changes that allow performing Newton tests from the paper: +* Extended Newton solver #848 + The changes that allow performing Newton tests from the paper: G. T. Lines, Ł. Paszkowski, L. Schmiester, D. Weindl, P. Stapor, and J. Hasenauer. Efficient computation of steady states in large-scale ODE models of biochemical reaction networks. accepted for Proceedings of the 8th IFAC Conference on Foundations of Systems Biology in Engineering (FOSBE), Valencia, Spain, October 2019. * Use SWIG>=4.0 on travis to include PyDoc in sdist / pypi package (#841) * **Fix choice of likelihood formula; failed if observable names were not equal to observable IDs** @@ -1183,8 +1183,8 @@ Misc: ### v0.10.11 (2019-08-31) -* Fixed setting initial conditions for preequilibration (#784) -* Fixed species->parameter conversion during PEtab import (#782) +* Fixed setting initial conditions for preequilibration (#784) +* Fixed species->parameter conversion during PEtab import (#782) * Set correct Matlab include directories in CMake (#793) * Extended and updated documentation (#785, #787) * Fix various SBML import issues @@ -1216,7 +1216,7 @@ Detaills: * feature(python) Use MKL from environment modules to provide cblas * fix(python) Fix define_macros not being passed to setuptools for Extension * fix(python) Fix define_macros not being passed to setuptools for clibs - * Do not always add 'cblas' library since users may want to override that by a cblas-compatible library with a different name (closes #736) + * Do not always add 'cblas' library since users may want to override that by a cblas-compatible library with a different name (closes #736) * Update HDF5 path hints; use shared library if static is not available. * Check for HDF5_BASE from environment module * Fix system-dependent sundials library directory (Fixes #749) (#750) @@ -1240,7 +1240,7 @@ All: - Fix reuse of `Solver` instances (#541) C++: -- Check for correct AMICI version for model in CMake +- Check for correct AMICI version for model in CMake - Add reporting of computation times (#699) Python: @@ -1277,12 +1277,12 @@ Doc C++ - Fix missing source files in CMakeLists.txt (#658) - Set CMake policies to prevent warnings (Closes #676) (#677) -- Start using gsl::span instead of raw pointers (#393) (#678) +- Start using gsl::span instead of raw pointers (#393) (#678) Python - PySB parsing fix (#669) -- Fix failure to propagate BLAS_LIBS contents (#665) -- Require setuptools at setup (#673) +- Fix failure to propagate BLAS_LIBS contents (#665) +- Require setuptools at setup (#673) - Updated PEtab import to allow for different noise models @@ -1355,7 +1355,7 @@ Bugfixes: Maintenance: -- use newer CI images +- use newer CI images ### v0.9.4 (2019-02-11) @@ -1387,9 +1387,9 @@ Bugfixes: - fixes a critical bug in the newton solver - fixes multiple bugs in sbml import for degenerate models, empty stoichiometry assignments and conversion factors - improved error messages for sbml import -- #560 -- #557 -- #559 +- #560 +- #557 +- #559 ### v0.9.1 (2019-01-21) @@ -1418,7 +1418,7 @@ Features / improvements: - Allow more detailed finiteness checks (#514) Bugfixes: - - #491 + - #491 Maintenance: - Several improvements to travis log sizes and folding @@ -1475,7 +1475,7 @@ Maintenance: ### v0.7.11 (2018-10-15) - [python] Added numpy and python wrappers that provide a more user friendly python API -- [python] Enable import of SBML models with non-float assignment rules +- [python] Enable import of SBML models with non-float assignment rules - [python] Enable handling of exceptions in python - [python] Enable nativ python access to std::vector data-structures - [core] Provide an API for more fine-grained control over sensitivity tolerances and steady-state tolerances @@ -1555,7 +1555,7 @@ Features: Major bugfixes: - Fix python sbml model import / compilation error (undefined function) -- Fix model preequilibration +- Fix model preequilibration Minor fixes: - Various fixes for mingw compilation of python source distribution @@ -1585,8 +1585,8 @@ WARNING: Implement experimental support for python via swig. Python interface is now usable, but API will still receive some updates in the future. -WARNING: -- There is a bug in sensitivity computation for Python-generated models +WARNING: +- There is a bug in sensitivity computation for Python-generated models - Matlab C++ compilation will fail due to undefined M_PI -> Please use v0.7.0 diff --git a/CITATION.cff b/CITATION.cff index c5e6d8d6ff..d251658032 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -1,32 +1,32 @@ authors: - - + - family-names: "Fröhlich" given-names: "Fabian" orcid: "https://orcid.org/0000-0002-5360-4292" - - + - family-names: "Weindl" given-names: "Daniel" orcid: "https://orcid.org/0000-0001-9963-6057" - - + - family-names: "Schälte" given-names: "Yannik" orcid: "https://orcid.org/0000-0003-1293-820X" - - + - family-names: "Pathirana" given-names: "Dilan" orcid: "https://orcid.org/0000-0001-7000-2659" - - + - family-names: "Paszkowski" given-names: "Lukasz" - - + - family-names: "Lines" given-names: "Glenn Terje" orcid: "https://orcid.org/0000-0002-6294-1805" - - + - family-names: "Stapor" given-names: "Paul" orcid: "https://orcid.org/0000-0002-7567-3985" - - + - family-names: "Hasenauer" given-names: "Jan" orcid: "https://orcid.org/0000-0002-4935-3312" @@ -42,34 +42,34 @@ preferred-citation: start: 1 end: 1 authors: - - + - family-names: "Fröhlich" given-names: "Fabian" orcid: "https://orcid.org/0000-0002-5360-4292" - - + - family-names: "Weindl" given-names: "Daniel" orcid: "https://orcid.org/0000-0001-9963-6057" - - + - family-names: "Schälte" given-names: "Yannik" orcid: "https://orcid.org/0000-0003-1293-820X" - - + - family-names: "Pathirana" given-names: "Dilan" orcid: "https://orcid.org/0000-0001-7000-2659" - - + - family-names: "Paszkowski" given-names: "Lukasz" - - + - family-names: "Lines" given-names: "Glenn Terje" orcid: "https://orcid.org/0000-0002-6294-1805" - - + - family-names: "Stapor" given-names: "Paul" orcid: "https://orcid.org/0000-0002-7567-3985" - - + - family-names: "Hasenauer" given-names: "Jan" orcid: "https://orcid.org/0000-0002-4935-3312" diff --git a/README.md b/README.md index 7531627e94..006ae200dc 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ## Advanced Multilanguage Interface for CVODES and IDAS -## About +## About AMICI provides a multi-language (Python, C++, Matlab) interface for the [SUNDIALS](https://computing.llnl.gov/projects/sundials/) solvers @@ -143,8 +143,8 @@ When using AMICI in your project, please cite eprint = {https://academic.oup.com/bioinformatics/advance-article-pdf/doi/10.1093/bioinformatics/btab227/36866220/btab227.pdf}, } ``` - -When presenting work that employs AMICI, feel free to use one of the icons in + +When presenting work that employs AMICI, feel free to use one of the icons in [documentation/gfx/](https://github.com/AMICI-dev/AMICI/tree/master/documentation/gfx), which are available under a [CC0](https://github.com/AMICI-dev/AMICI/tree/master/documentation/gfx/LICENSE.md) diff --git a/binder/Dockerfile b/binder/Dockerfile index daa51b99d1..bda8f7e1af 100644 --- a/binder/Dockerfile +++ b/binder/Dockerfile @@ -48,4 +48,3 @@ RUN . ./.profile && python3 -m build --sdist python/sdist && \ scripts/buildBNGL.sh ENV BNGPATH="${HOME}/ThirdParty/BioNetGen-2.7.0" - diff --git a/container/README.md b/container/README.md index 5396681df4..f6b51719af 100644 --- a/container/README.md +++ b/container/README.md @@ -17,7 +17,7 @@ git archive -o container/amici.tar.gz --format=tar.gz HEAD cd container && docker build -t $USER/amici:latest . ``` Note that this will include files from the last commit, but no uncommitted -changes. +changes. ### Pull published image @@ -34,7 +34,7 @@ In the AMICI base directory run: ```bash # prepare amici files to be copied to the image # Note that this will include files from the last commit, but no uncommitted -# changes. +# changes. git archive -o container/amici.tar.gz --format=tar.gz HEAD # install spython if necessary test -x "$(command -v spython)" || pip install spython diff --git a/documentation/CI.md b/documentation/CI.md index 3fd72d99f9..34b08355dc 100644 --- a/documentation/CI.md +++ b/documentation/CI.md @@ -12,7 +12,7 @@ This includes the following steps: More details are provided in the sections below. The CI scripts and tests can be found in `tests/` and `scripts/`. Some of the -tests are integrated with CMake, see `make help` in the build directory. +tests are integrated with CMake, see `make help` in the build directory. ## C++ unit and integration tests @@ -61,7 +61,7 @@ obtained from the Python and C++ are compared to results saved in an HDF5 file (`tests/cpp/expectedResults.h5`). Settings and data for the test simulations are also specified in this file. -**Note:** The C++ code for the models is included in the repository under +**Note:** The C++ code for the models is included in the repository under `models/`. This code is to be updated whenever `amici::Model` changes. @@ -72,23 +72,23 @@ Regeneration of the model code has to be done whenever `amici::Model` or the Matlab model import routines change. This is done with - + tests/cpp/wrapTestModels.m **Note:** This is currently only possible from Matlab < R2018a. This should change as soon as 1) all second-order sensitivity code is ported to C++/Python, 2) a non-SBML import exists for Python and 3) support for events has been added for Python. - - + + ### Regenerating expected results To update test results, run `make test` in the build directory, -replace `tests/cpp/expectedResults.h5` by -`tests/cpp/writeResults.h5.bak` +replace `tests/cpp/expectedResults.h5` by +`tests/cpp/writeResults.h5.bak` [ONLY DO THIS AFTER TRIPLE CHECKING CORRECTNESS OF RESULTS] Before replacing the test results, confirm that only expected datasets have -changed, e.g. using +changed, e.g. using h5diff -v --relative 1e-8 tests/cpp/expectedResults.h5 tests/cpp/writeResults.h5.bak | less @@ -96,6 +96,6 @@ changed, e.g. using ## Adding/Updating tests To add new tests add a new corresponding python script (see, e.g., -`./tests/generateTestConfig/example_dirac.py`) and add it to and run +`./tests/generateTestConfig/example_dirac.py`) and add it to and run `tests/generateTestConfigurationForExamples.sh`. Then regenerate the expected test results (see above). diff --git a/documentation/MATLAB_.md b/documentation/MATLAB_.md index 40ecc87c31..760a228df9 100644 --- a/documentation/MATLAB_.md +++ b/documentation/MATLAB_.md @@ -2,7 +2,7 @@ In the following we will give a detailed overview how to specify models in MATLAB and how to call the generated simulation files. -## Model Definition +## Model Definition This guide will guide the user on how to specify models in MATLAB. For example implementations see the examples in the matlab/examples directory. @@ -120,7 +120,7 @@ Specifying events is optional. Events are specified in terms of a trigger functi Events may depend on states, parameters and constants but __not__ on observables. -For more details about event support see https://doi.org/10.1093/bioinformatics/btw764 +For more details about event support see https://doi.org/10.1093/bioinformatics/btw764 ### Standard Deviation @@ -139,7 +139,7 @@ They can depend on time and parameters but must not depend on the states or obse ### Objective Function -By default, AMICI assumes a normal noise model and uses the corresponding negative log-likelihood +By default, AMICI assumes a normal noise model and uses the corresponding negative log-likelihood J = 1/2*sum(((y_i(t)-my_ti)/sigma_y_i)^2 + log(2*pi*sigma_y^2) @@ -193,7 +193,7 @@ Here for proof of concept: * Install the python package as described in the documentation * Ensure `pyversion` shows the correct python version (3.6 or 3.7) * Then, from within the AMICI `matlab/` directory: - + ``` sbml_importer = py.amici.SbmlImporter('../python/examples/example_steadystate/model_steadystate_scaled.xml') sbml_importer.sbml2amici('steadystate', 'steadystate_example_from_python') diff --git a/documentation/README.md b/documentation/README.md index ac5d1b12b7..af9f33320e 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -78,12 +78,12 @@ Graphics for documentation are kept in `documentation/gfx/`. for offline use. * Please stick to the limit of 80 characters per line for readability of raw - Markdown files where possible. + Markdown files where possible. However, note that some Markdown interpreters can handle line breaks within links and headings, whereas others cannot. Here, compatibility is preferred - over linebreaks. - + over linebreaks. + * Avoid trailing whitespace ## Maintaining the list of publications diff --git a/documentation/_templates/autosummary/class.rst b/documentation/_templates/autosummary/class.rst index 030e2a4c16..ed9e3761ef 100644 --- a/documentation/_templates/autosummary/class.rst +++ b/documentation/_templates/autosummary/class.rst @@ -36,5 +36,3 @@ {%- endfor %} {% endif %} {% endblock %} - - diff --git a/documentation/code_review_guide.md b/documentation/code_review_guide.md index 456b78709e..43d6e815c0 100644 --- a/documentation/code_review_guide.md +++ b/documentation/code_review_guide.md @@ -5,7 +5,7 @@ A guide for reviewing code and having your code reviewed by others. ## Everyone * Don't be too protective of your code -* Accept that, to a large extent, coding decisions are a matter of personal +* Accept that, to a large extent, coding decisions are a matter of personal preference * Don't get personal * Ask for clarification @@ -13,25 +13,25 @@ A guide for reviewing code and having your code reviewed by others. * Try to understand your counterpart's perspective * Clarify how strong you feel about each discussion point -## Reviewing code +## Reviewing code * If there are no objective advantages, don't force your style on others * Ask questions instead of making demands * Assume the author gave his best -* Mind the scope (many things are nice to have, but might be out of scope - of the current change - open a new issue) -* The goal is "good enough", not "perfect" +* Mind the scope (many things are nice to have, but might be out of scope + of the current change - open a new issue) +* The goal is "good enough", not "perfect" * Be constructive -* You do not always have to request changes +* You do not always have to request changes -## Having your code reviewed +## Having your code reviewed * Don't take it personal - the review is on the code, not on you * Code reviews take time, appreciate the reviewer's comments * Assume the reviewer did his best (but might still be wrong) -* Keep code changes small (e.g. separate wide reformatting from actual code +* Keep code changes small (e.g. separate wide reformatting from actual code changes to facility review) -* If the reviewer does not understand your code, probably many others won't +* If the reviewer does not understand your code, probably many others won't either ## Checklist @@ -42,10 +42,10 @@ A guide for reviewing code and having your code reviewed by others. * [ ] Meaningful identifiers are used * [ ] Corner-cases are covered, cases not covered fail loudly * [ ] The code can be expected to scale well (enough) -* [ ] The code is well documented (e.g., input, operation, output), but +* [ ] The code is well documented (e.g., input, operation, output), but without trivial comments * [ ] The code is [SOLID](https://en.wikipedia.org/wiki/SOLID) -* [ ] New code is added in the most meaningful place (i.e. matches the +* [ ] New code is added in the most meaningful place (i.e. matches the current architecture) * [ ] No magic numbers * [ ] No hard-coded values that should be user inputs diff --git a/documentation/conf.py b/documentation/conf.py index ec2460d38d..85b6766ff5 100644 --- a/documentation/conf.py +++ b/documentation/conf.py @@ -33,10 +33,11 @@ def my_exhale_generate_doxygen(doxygen_input): # run mtocpp_post doxy_xml_dir = exhale_configs._doxygen_xml_output_directory - if 'matlab' in doxy_xml_dir: - print('Running mtocpp_post on ', doxy_xml_dir) - mtocpp_post = os.path.join(amici_dir, 'ThirdParty', 'mtocpp-master', - 'build', 'mtocpp_post') + if "matlab" in doxy_xml_dir: + print("Running mtocpp_post on ", doxy_xml_dir) + mtocpp_post = os.path.join( + amici_dir, "ThirdParty", "mtocpp-master", "build", "mtocpp_post" + ) subprocess.run([mtocpp_post, doxy_xml_dir]) # let exhale do its job @@ -48,27 +49,26 @@ def my_exhale_generate_doxygen(doxygen_input): # BEGIN Monkeypatch breathe -from breathe.renderer.sphinxrenderer import \ - DomainDirectiveFactory as breathe_DomainDirectiveFactory +from breathe.renderer.sphinxrenderer import ( + DomainDirectiveFactory as breathe_DomainDirectiveFactory, +) -old_breathe_DomainDirectiveFactory_create = \ - breathe_DomainDirectiveFactory.create +old_breathe_DomainDirectiveFactory_create = breathe_DomainDirectiveFactory.create def my_breathe_DomainDirectiveFactory_create(domain: str, args): - if domain != 'mat': + if domain != "mat": return old_breathe_DomainDirectiveFactory_create(domain, args) from sphinxcontrib.matlab import MATLABDomain, MatClassmember matlab_classes = {k: (v, k) for k, v in MATLABDomain.directives.items()} - matlab_classes['variable'] = (MatClassmember, 'attribute') + matlab_classes["variable"] = (MatClassmember, "attribute") cls, name = matlab_classes[args[0]] - return cls(domain + ':' + name, *args[1:]) + return cls(domain + ":" + name, *args[1:]) -breathe_DomainDirectiveFactory.create = \ - my_breathe_DomainDirectiveFactory_create +breathe_DomainDirectiveFactory.create = my_breathe_DomainDirectiveFactory_create # END Monkeypatch breathe @@ -76,21 +76,23 @@ def my_breathe_DomainDirectiveFactory_create(domain: str, args): def install_mtocpp(): """Install mtocpp (Matlab doxygen filter)""" - cmd = os.path.join(amici_dir, 'scripts', 'downloadAndBuildMtocpp.sh') - ret = subprocess.run(cmd, shell=True, - stdout=subprocess.PIPE, stderr=subprocess.STDOUT) + cmd = os.path.join(amici_dir, "scripts", "downloadAndBuildMtocpp.sh") + ret = subprocess.run( + cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT + ) if ret.returncode != 0: - print(ret.stdout.decode('utf-8')) - raise RuntimeError('downloadAndBuildMtocpp.sh failed') + print(ret.stdout.decode("utf-8")) + raise RuntimeError("downloadAndBuildMtocpp.sh failed") def install_doxygen(): """Get a more recent doxygen""" - version = '1.9.6' - doxygen_exe = os.path.join(amici_dir, 'ThirdParty', - f'doxygen-{version}', 'bin', 'doxygen') + version = "1.9.6" + doxygen_exe = os.path.join( + amici_dir, "ThirdParty", f"doxygen-{version}", "bin", "doxygen" + ) # to create a symlink to doxygen in a location that is already on PATH - some_dir_on_path = os.environ['PATH'].split(os.pathsep)[0] + some_dir_on_path = os.environ["PATH"].split(os.pathsep)[0] cmd = ( f"cd '{os.path.join(amici_dir, 'ThirdParty')}' " f"&& wget 'https://www.doxygen.nl/files/" @@ -99,10 +101,9 @@ def install_doxygen(): f"&& ln -sf '{doxygen_exe}' '{some_dir_on_path}'" ) subprocess.run(cmd, shell=True, check=True) - assert os.path.islink(os.path.join(some_dir_on_path, 'doxygen')) + assert os.path.islink(os.path.join(some_dir_on_path, "doxygen")) # verify it's available - res = subprocess.run(['doxygen', '--version'], - check=False, capture_output=True) + res = subprocess.run(["doxygen", "--version"], check=False, capture_output=True) print(res.stdout.decode(), res.stderr.decode()) assert version in res.stdout.decode() @@ -118,7 +119,7 @@ def install_doxygen(): # -- RTD custom build -------------------------------------------------------- # only execute those commands when running from RTD -if 'READTHEDOCS' in os.environ and os.environ['READTHEDOCS']: +if "READTHEDOCS" in os.environ and os.environ["READTHEDOCS"]: install_doxygen() # Required for matlab doxygen processing @@ -127,6 +128,7 @@ def install_doxygen(): # Install AMICI if not already present typing.TYPE_CHECKING = True import amici + typing.TYPE_CHECKING = False @@ -136,15 +138,15 @@ def install_doxygen(): # The full version, including alpha/beta/rc tags release = version -project = 'AMICI' -copyright = '2020, The AMICI developers' -author = 'The AMICI developers' -title = 'AMICI Documentation' +project = "AMICI" +copyright = "2020, The AMICI developers" +author = "The AMICI developers" +title = "AMICI Documentation" # -- Mock out some problematic modules------------------------------------- # Note that for sub-modules, all parent modules must be listed explicitly. -autodoc_mock_imports = ['_amici', 'amici._amici'] +autodoc_mock_imports = ["_amici", "amici._amici"] for mod_name in autodoc_mock_imports: sys.modules[mod_name] = mock.MagicMock() @@ -158,45 +160,39 @@ def install_doxygen(): # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ - 'readthedocs_ext.readthedocs', + "readthedocs_ext.readthedocs", # Required, e.g. for PEtab-derived classes where the base class has non-rst # docstrings - 'sphinx.ext.napoleon', - 'sphinx.ext.autodoc', - 'sphinx.ext.doctest', - 'sphinx.ext.coverage', - 'sphinx.ext.intersphinx', - 'sphinx.ext.autosummary', - 'sphinx.ext.viewcode', - 'sphinx.ext.mathjax', - 'sphinxcontrib.matlab', - 'nbsphinx', - 'IPython.sphinxext.ipython_console_highlighting', - 'recommonmark', - 'sphinx_autodoc_typehints', - 'hoverxref.extension', - 'breathe', - 'exhale', + "sphinx.ext.napoleon", + "sphinx.ext.autodoc", + "sphinx.ext.doctest", + "sphinx.ext.coverage", + "sphinx.ext.intersphinx", + "sphinx.ext.autosummary", + "sphinx.ext.viewcode", + "sphinx.ext.mathjax", + "sphinxcontrib.matlab", + "nbsphinx", + "IPython.sphinxext.ipython_console_highlighting", + "recommonmark", + "sphinx_autodoc_typehints", + "hoverxref.extension", + "breathe", + "exhale", ] intersphinx_mapping = { - 'pysb': ('https://pysb.readthedocs.io/en/stable/', None), - 'petab': ( - 'https://petab.readthedocs.io/projects/libpetab-python/en/latest/', - None - ), - 'pandas': ('https://pandas.pydata.org/docs/', None), - 'numpy': ('https://numpy.org/devdocs/', None), - 'sympy': ('https://docs.sympy.org/latest/', None), - 'python': ('https://docs.python.org/3', None), + "pysb": ("https://pysb.readthedocs.io/en/stable/", None), + "petab": ("https://petab.readthedocs.io/projects/libpetab-python/en/latest/", None), + "pandas": ("https://pandas.pydata.org/docs/", None), + "numpy": ("https://numpy.org/devdocs/", None), + "sympy": ("https://docs.sympy.org/latest/", None), + "python": ("https://docs.python.org/3", None), } # Add notebooks prolog with binder links # get current git reference -ret = subprocess.run( - "git rev-parse HEAD".split(" "), - capture_output=True -) +ret = subprocess.run("git rev-parse HEAD".split(" "), capture_output=True) ref = ret.stdout.rstrip().decode() nbsphinx_prolog = ( f"{{% set {ref=} %}}" @@ -213,16 +209,16 @@ def install_doxygen(): ) # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = ['.rst', '.md'] +source_suffix = [".rst", ".md"] # The master toctree document. -master_doc = 'index' +master_doc = "index" # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. @@ -235,27 +231,27 @@ def install_doxygen(): # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path . exclude_patterns = [ - '_build', - 'Thumbs.db', - '.DS_Store', - '**.ipynb_checkpoints', - 'numpy.py', - 'INSTALL.md', - 'MATLAB_.md', - 'CPP_.md', - 'gfx' + "_build", + "Thumbs.db", + ".DS_Store", + "**.ipynb_checkpoints", + "numpy.py", + "INSTALL.md", + "MATLAB_.md", + "CPP_.md", + "gfx", ] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = False # autodoc autodoc_default_options = { - 'special-members': '__init__', - 'inherited-members': True, + "special-members": "__init__", + "inherited-members": True, } # sphinx-autodoc-typehints @@ -265,17 +261,17 @@ def install_doxygen(): # hoverxref hoverxref_auto_ref = True -hoverxref_roles = ['term'] -hoverxref_domains = ['py'] +hoverxref_roles = ["term"] +hoverxref_domains = ["py"] hoverxref_role_types = { - 'hoverxref': 'tooltip', - 'ref': 'tooltip', - 'term': 'tooltip', - 'obj': 'tooltip', - 'func': 'tooltip', - 'mod': 'tooltip', - 'meth': 'tooltip', - 'class': 'tooltip', + "hoverxref": "tooltip", + "ref": "tooltip", + "term": "tooltip", + "obj": "tooltip", + "func": "tooltip", + "mod": "tooltip", + "meth": "tooltip", + "class": "tooltip", } # breathe settings @@ -302,47 +298,47 @@ def install_doxygen(): "verboseBuild": True, } -mtocpp_filter = os.path.join(amici_dir, 'matlab', 'mtoc', - 'config', 'mtocpp_filter.sh') +mtocpp_filter = os.path.join(amici_dir, "matlab", "mtoc", "config", "mtocpp_filter.sh") exhale_projects_args = { "AMICI_CPP": { - "exhaleDoxygenStdin": "\n".join([ - "INPUT = ../include/amici", - "BUILTIN_STL_SUPPORT = YES", - "PREDEFINED += EXHALE_DOXYGEN_SHOULD_SKIP_THIS", - "EXCLUDE += ../include/amici/interface_matlab.h", - "EXCLUDE += ../include/amici/returndata_matlab.h", - "EXCLUDE += ../include/amici/spline.h", - # amici::log collides with amici::${some_enum}::log - # potentially fixed in - # https://github.com/svenevs/exhale/commit/c924df2e139a09fbacd07587779c55fd0ee4e00b - # and can be un-excluded after the next exhale release - "EXCLUDE += ../include/amici/symbolic_functions.h", - ]), + "exhaleDoxygenStdin": "\n".join( + [ + "INPUT = ../include/amici", + "BUILTIN_STL_SUPPORT = YES", + "PREDEFINED += EXHALE_DOXYGEN_SHOULD_SKIP_THIS", + "EXCLUDE += ../include/amici/interface_matlab.h", + "EXCLUDE += ../include/amici/returndata_matlab.h", + "EXCLUDE += ../include/amici/spline.h", + # amici::log collides with amici::${some_enum}::log + # potentially fixed in + # https://github.com/svenevs/exhale/commit/c924df2e139a09fbacd07587779c55fd0ee4e00b + # and can be un-excluded after the next exhale release + "EXCLUDE += ../include/amici/symbolic_functions.h", + ] + ), "containmentFolder": "_exhale_cpp_api", "rootFileTitle": "AMICI C++ API", - "afterTitleDescription": - "AMICI C++ library functions", + "afterTitleDescription": "AMICI C++ library functions", }, # Third Party Project Includes "AMICI_Matlab": { - "exhaleDoxygenStdin": "\n".join([ - "INPUT = ../matlab", - "EXTENSION_MAPPING = .m=C++", - "FILTER_PATTERNS = " - f"*.m={mtocpp_filter}", - "EXCLUDE += ../matlab/examples", - "EXCLUDE += ../matlab/mtoc", - "EXCLUDE += ../matlab/SBMLimporter", - "EXCLUDE += ../matlab/auxiliary", - "EXCLUDE += ../matlab/tests", - "PREDEFINED += EXHALE_DOXYGEN_SHOULD_SKIP_THIS", - ]), + "exhaleDoxygenStdin": "\n".join( + [ + "INPUT = ../matlab", + "EXTENSION_MAPPING = .m=C++", + "FILTER_PATTERNS = " f"*.m={mtocpp_filter}", + "EXCLUDE += ../matlab/examples", + "EXCLUDE += ../matlab/mtoc", + "EXCLUDE += ../matlab/SBMLimporter", + "EXCLUDE += ../matlab/auxiliary", + "EXCLUDE += ../matlab/tests", + "PREDEFINED += EXHALE_DOXYGEN_SHOULD_SKIP_THIS", + ] + ), "containmentFolder": "_exhale_matlab_api", "rootFileTitle": "AMICI Matlab API", - "afterTitleDescription": - "AMICI Matlab library functions", - "lexerMapping": {r'.*\.m$': 'matlab'} + "afterTitleDescription": "AMICI Matlab library functions", + "lexerMapping": {r".*\.m$": "matlab"}, }, } # -- Options for HTML output ------------------------------------------------- @@ -350,7 +346,7 @@ def install_doxygen(): # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' +html_theme = "sphinx_rtd_theme" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the @@ -378,7 +374,7 @@ def install_doxygen(): # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'AMICIdoc' +htmlhelp_basename = "AMICIdoc" # -- Options for LaTeX output ------------------------------------------------ @@ -386,15 +382,12 @@ def install_doxygen(): # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -404,18 +397,14 @@ def install_doxygen(): # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'AMICI.tex', title, - author, 'manual'), + (master_doc, "AMICI.tex", title, author, "manual"), ] # -- Options for manual page output ------------------------------------------ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'amici', title, - [author], 1) -] +man_pages = [(master_doc, "amici", title, [author], 1)] # -- Options for Texinfo output ---------------------------------------------- @@ -423,78 +412,80 @@ def install_doxygen(): # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'AMICI', title, - author, 'AMICI', 'Advanced Multilanguage Interface for CVODES and IDAS.', - 'Miscellaneous'), + ( + master_doc, + "AMICI", + title, + author, + "AMICI", + "Advanced Multilanguage Interface for CVODES and IDAS.", + "Miscellaneous", + ), ] # Custom processing routines for docstrings and signatures typemaps = { - 'std::vector< amici::realtype,std::allocator< amici::realtype > >': - 'DoubleVector', - 'std::vector< double,std::allocator< double > >': - 'DoubleVector', - 'std::vector< int,std::allocator< int > >': - 'IntVector', - 'std::vector< amici::ParameterScaling,std::allocator< ' - 'amici::ParameterScaling >': 'ParameterScalingVector', - 'std::vector< std::string,std::allocator< std::string > >': - 'StringVector', - 'std::vector< bool,std::allocator< bool > >': - 'BoolVector', - 'std::map< std::string,amici::realtype,std::less< std::string >,' - 'std::allocator< std::pair< std::string const,amici::realtype > > >': - 'StringDoubleMap', - 'std::vector< amici::ExpData *,std::allocator< amici::ExpData * > >': - 'ExpDataPtrVector', - 'std::vector< std::unique_ptr< amici::ReturnData >,std::allocator< ' - 'std::unique_ptr< amici::ReturnData > > >': - 'Iterable[ReturnData]', - 'std::unique_ptr< amici::ExpData >': - 'ExpData', - 'std::unique_ptr< amici::ReturnData >': - 'ReturnData', - 'std::unique_ptr< amici::Solver >': - 'Solver', - 'amici::realtype': - 'float', + "std::vector< amici::realtype,std::allocator< amici::realtype > >": "DoubleVector", + "std::vector< double,std::allocator< double > >": "DoubleVector", + "std::vector< int,std::allocator< int > >": "IntVector", + "std::vector< amici::ParameterScaling,std::allocator< " + "amici::ParameterScaling >": "ParameterScalingVector", + "std::vector< std::string,std::allocator< std::string > >": "StringVector", + "std::vector< bool,std::allocator< bool > >": "BoolVector", + "std::map< std::string,amici::realtype,std::less< std::string >," + "std::allocator< std::pair< std::string const,amici::realtype > > >": "StringDoubleMap", + "std::vector< amici::ExpData *,std::allocator< amici::ExpData * > >": "ExpDataPtrVector", + "std::vector< std::unique_ptr< amici::ReturnData >,std::allocator< " + "std::unique_ptr< amici::ReturnData > > >": "Iterable[ReturnData]", + "std::unique_ptr< amici::ExpData >": "ExpData", + "std::unique_ptr< amici::ReturnData >": "ReturnData", + "std::unique_ptr< amici::Solver >": "Solver", + "amici::realtype": "float", } vector_types = { - 'IntVector': ':class:`int`', - 'BoolVector': ':class:`bool`', - 'DoubleVector': ':class:`float`', - 'StringVector': ':class:`str`', - 'ExpDataPtrVector': ':class:`amici.amici.ExpData`', + "IntVector": ":class:`int`", + "BoolVector": ":class:`bool`", + "DoubleVector": ":class:`float`", + "StringVector": ":class:`str`", + "ExpDataPtrVector": ":class:`amici.amici.ExpData`", } def process_docstring(app, what, name, obj, options, lines): # only apply in the amici.amici module - if len(name.split('.')) < 2 or name.split('.')[1] != 'amici': + if len(name.split(".")) < 2 or name.split(".")[1] != "amici": return # add custom doc to swig generated classes - if len(name.split('.')) == 3 and name.split('.')[2] in \ - ['IntVector', 'BoolVector', 'DoubleVector', 'StringVector', - 'ExpDataPtrVector']: - cname = name.split('.')[2] + if len(name.split(".")) == 3 and name.split(".")[2] in [ + "IntVector", + "BoolVector", + "DoubleVector", + "StringVector", + "ExpDataPtrVector", + ]: + cname = name.split(".")[2] lines.append( - f'Swig-Generated class templating common python ' - f'types including :class:`Iterable` ' - f'[{vector_types[cname]}] ' - f'and ' - f':class:`numpy.array` [{vector_types[cname]}] to facilitate' - ' interfacing with C++ bindings.' + f"Swig-Generated class templating common python " + f"types including :class:`Iterable` " + f"[{vector_types[cname]}] " + f"and " + f":class:`numpy.array` [{vector_types[cname]}] to facilitate" + " interfacing with C++ bindings." ) return - if len(name.split('.')) == 3 and name.split('.')[2] in \ - ['ExpDataPtr', 'ReturnDataPtr', 'ModelPtr', 'SolverPtr']: - cname = name.split('.')[2] + if len(name.split(".")) == 3 and name.split(".")[2] in [ + "ExpDataPtr", + "ReturnDataPtr", + "ModelPtr", + "SolverPtr", + ]: + cname = name.split(".")[2] lines.append( - f'Swig-Generated class that implements smart pointers to ' + f"Swig-Generated class that implements smart pointers to " f'{cname.replace("Ptr", "")} as objects.' ) return @@ -505,9 +496,12 @@ def process_docstring(app, what, name, obj, options, lines): while len(lines): line = lines.pop(0) - if re.match(r':(type|rtype|param|return)', line) and \ - len(lines_clean) and lines_clean[-1] != '': - lines_clean.append('') + if ( + re.match(r":(type|rtype|param|return)", line) + and len(lines_clean) + and lines_clean[-1] != "" + ): + lines_clean.append("") lines_clean.append(line) lines.extend(lines_clean) @@ -517,14 +511,10 @@ def process_docstring(app, what, name, obj, options, lines): for old, new in typemaps.items(): lines[i] = lines[i].replace(old, new) lines[i] = re.sub( - r'amici::(Model|Solver|ExpData) ', - r':class:`amici\.amici\.\1\`', - lines[i] + r"amici::(Model|Solver|ExpData) ", r":class:`amici\.amici\.\1\`", lines[i] ) lines[i] = re.sub( - r'amici::(runAmiciSimulation[s]?)', - r':func:`amici\.amici\.\1`', - lines[i] + r"amici::(runAmiciSimulation[s]?)", r":func:`amici\.amici\.\1`", lines[i] ) @@ -535,44 +525,45 @@ def fix_typehints(sig: str) -> str: for old, new in typemaps.items(): sig = sig.replace(old, new) - sig = sig.replace('void', 'None') - sig = sig.replace('amici::realtype', 'float') - sig = sig.replace('std::string', 'str') - sig = sig.replace('double', 'float') - sig = sig.replace('long', 'int') - sig = sig.replace('char const *', 'str') - sig = sig.replace('amici::', '') - sig = sig.replace('sunindextype', 'int') - sig = sig.replace('H5::H5File', 'object') + sig = sig.replace("void", "None") + sig = sig.replace("amici::realtype", "float") + sig = sig.replace("std::string", "str") + sig = sig.replace("double", "float") + sig = sig.replace("long", "int") + sig = sig.replace("char const *", "str") + sig = sig.replace("amici::", "") + sig = sig.replace("sunindextype", "int") + sig = sig.replace("H5::H5File", "object") # remove const - sig = sig.replace(' const ', r' ') - sig = re.sub(r' const$', r'', sig) + sig = sig.replace(" const ", r" ") + sig = re.sub(r" const$", r"", sig) # remove pass by reference - sig = re.sub(r' &(,|\))', r'\1', sig) - sig = re.sub(r' &$', r'', sig) + sig = re.sub(r" &(,|\))", r"\1", sig) + sig = re.sub(r" &$", r"", sig) # turn gsl_spans and pointers int Iterables - sig = re.sub(r'([\w.]+) \*', r'Iterable[\1]', sig) - sig = re.sub(r'gsl::span< ([\w.]+) >', r'Iterable[\1]', sig) + sig = re.sub(r"([\w.]+) \*", r"Iterable[\1]", sig) + sig = re.sub(r"gsl::span< ([\w.]+) >", r"Iterable[\1]", sig) # fix garbled output - sig = sig.replace(' >', '') + sig = sig.replace(" >", "") return sig -def process_signature(app, what: str, name: str, obj, options, signature, - return_annotation): +def process_signature( + app, what: str, name: str, obj, options, signature, return_annotation +): if signature is None: return # only apply in the amici.amici module - if name.split('.')[1] != 'amici': + if name.split(".")[1] != "amici": return signature = fix_typehints(signature) - if hasattr(obj, '__annotations__'): + if hasattr(obj, "__annotations__"): for ann in obj.__annotations__: obj.__annotations__[ann] = fix_typehints(obj.__annotations__[ann]) @@ -582,71 +573,92 @@ def process_signature(app, what: str, name: str, obj, options, signature, # this code fixes references in symlinked md files in documentation folder # link replacements must be in env.domains['std'].labels doclinks = { - 'documentation/development': '/development.md', - 'documentation/CI': '/ci.md', - 'documentation/code_review_guide': '/code_review_guide.md', + "documentation/development": "/development.md", + "documentation/CI": "/ci.md", + "documentation/code_review_guide": "/code_review_guide.md", } def process_missing_ref(app, env, node, contnode): - if not any(link in node['reftarget'] for link in doclinks): + if not any(link in node["reftarget"] for link in doclinks): return # speedup futile processing for old, new in doclinks.items(): - node['reftarget'] = node['reftarget'].replace(old, new) + node["reftarget"] = node["reftarget"].replace(old, new) cnode = node[0] - if 'refuri' in cnode: + if "refuri" in cnode: for old, new in doclinks.items(): - cnode['refuri'] = cnode['refuri'].replace(old, new) + cnode["refuri"] = cnode["refuri"].replace(old, new) - refdoc = node.get('refdoc', env.docname) + refdoc = node.get("refdoc", env.docname) resolver = ReferencesResolver(env.get_doctree(refdoc)) result = resolver.resolve_anyref(refdoc, node, cnode) return result def skip_member(app, what, name, obj, skip, options): - ignored = ['AbstractModel', 'CVodeSolver', 'IDASolver', 'Model_ODE', - 'Model_DAE', 'ConditionContext', 'checkSigmaPositivity', - 'createGroup', 'createGroup', 'equals', 'printErrMsgIdAndTxt', - 'wrapErrHandlerFn', 'printWarnMsgIdAndTxt', - 'AmiciApplication', 'writeReturnData', - 'writeReturnDataDiagnosis', 'attributeExists', 'locationExists', - 'createAndWriteDouble1DDataset', - 'createAndWriteDouble2DDataset', - 'createAndWriteDouble3DDataset', - 'createAndWriteInt1DDataset', 'createAndWriteInt2DDataset', - 'createAndWriteInt3DDataset', 'getDoubleDataset1D', - 'getDoubleDataset2D', 'getDoubleDataset3D', 'getIntDataset1D', - 'getIntScalarAttribute', 'getDoubleScalarAttribute', - 'stdVec2ndarray', 'SwigPyIterator', 'thisown'] + ignored = [ + "AbstractModel", + "CVodeSolver", + "IDASolver", + "Model_ODE", + "Model_DAE", + "ConditionContext", + "checkSigmaPositivity", + "createGroup", + "createGroup", + "equals", + "printErrMsgIdAndTxt", + "wrapErrHandlerFn", + "printWarnMsgIdAndTxt", + "AmiciApplication", + "writeReturnData", + "writeReturnDataDiagnosis", + "attributeExists", + "locationExists", + "createAndWriteDouble1DDataset", + "createAndWriteDouble2DDataset", + "createAndWriteDouble3DDataset", + "createAndWriteInt1DDataset", + "createAndWriteInt2DDataset", + "createAndWriteInt3DDataset", + "getDoubleDataset1D", + "getDoubleDataset2D", + "getDoubleDataset3D", + "getIntDataset1D", + "getIntScalarAttribute", + "getDoubleScalarAttribute", + "stdVec2ndarray", + "SwigPyIterator", + "thisown", + ] if name in ignored: return True - if name.startswith('_') and name != '__init__': + if name.startswith("_") and name != "__init__": return True # ignore various functions for std::vector<> types - if re.match(r'^ - + - + - + - + - + - + - + - + - + - + diff --git a/documentation/implementation_discontinuities.rst b/documentation/implementation_discontinuities.rst index fc04bacce4..45e2d78aba 100644 --- a/documentation/implementation_discontinuities.rst +++ b/documentation/implementation_discontinuities.rst @@ -72,15 +72,15 @@ respective root function as argument. These will be automatically updated during events and take either 0 or 1 values as appropriate pre/post event limits. -In order to fully support SBML events and Piecewise functions, AMICI uses -the SUNDIALS functionality to only track zero crossings from negative to -positive. Accordingly, two root functions are necessary to keep track of -Heaviside functions and two Heaviside function helper variables will be -created, where one corresponds to the value of `Heaviside(...)` and one -to the value of `1-Heaviside(...)`. To ensure that Heaviside functions are -correctly evaluated at the beginning of the simulation, Heaviside functions -are implement as unit steps that evaluate to `1` at `0`. The arguments of -Heaviside functions are normalized such that respective properties of +In order to fully support SBML events and Piecewise functions, AMICI uses +the SUNDIALS functionality to only track zero crossings from negative to +positive. Accordingly, two root functions are necessary to keep track of +Heaviside functions and two Heaviside function helper variables will be +created, where one corresponds to the value of `Heaviside(...)` and one +to the value of `1-Heaviside(...)`. To ensure that Heaviside functions are +correctly evaluated at the beginning of the simulation, Heaviside functions +are implement as unit steps that evaluate to `1` at `0`. The arguments of +Heaviside functions are normalized such that respective properties of Piecewise functions are conserved for the first Heaviside function variable. Accordingly, the value of of the second helper variable is incorrect when simulation starts when the respective Heaviside function evaluates to zero diff --git a/documentation/recreate_reference_list.py b/documentation/recreate_reference_list.py index 71cbdaa5a4..152ab4e576 100755 --- a/documentation/recreate_reference_list.py +++ b/documentation/recreate_reference_list.py @@ -19,17 +19,17 @@ def get_keys_by_year(bibfile): """Get bibtex entry keys as dict by year""" - with open(bibfile, 'r') as f: + with open(bibfile, "r") as f: db = biblib.bib.Parser().parse(f, log_fp=sys.stderr).get_entries() recoverer = biblib.messages.InputErrorRecoverer() by_year = {} for ent in db.values(): with recoverer: - if 'year' in ent: + if "year" in ent: try: - by_year[ent['year']].append(ent.key) + by_year[ent["year"]].append(ent.key) except KeyError: - by_year[ent['year']] = [ent.key] + by_year[ent["year"]] = [ent.key] else: print("Missing year for entry", ent.key) recoverer.reraise() @@ -39,15 +39,17 @@ def get_keys_by_year(bibfile): def get_sub_bibliography(year, by_year, bibfile): """Get HTML bibliography for the given year""" - entries = ','.join(['@' + x for x in by_year[year]]) - stdin_input = '---\n' \ - f'bibliography: {bibfile}\n' \ - f'nocite: "{entries}"\n...\n' \ - f'# {year}' - - out = subprocess.run(['pandoc', '--citeproc', '-f', 'markdown'], - input=stdin_input, capture_output=True, - encoding='utf-8') + entries = ",".join(["@" + x for x in by_year[year]]) + stdin_input = ( + "---\n" f"bibliography: {bibfile}\n" f'nocite: "{entries}"\n...\n' f"# {year}" + ) + + out = subprocess.run( + ["pandoc", "--citeproc", "-f", "markdown"], + input=stdin_input, + capture_output=True, + encoding="utf-8", + ) if out.returncode != 0: raise AssertionError(out.stderr) @@ -56,30 +58,33 @@ def get_sub_bibliography(year, by_year, bibfile): def main(): script_path = os.path.dirname(os.path.realpath(__file__)) - bibfile = os.path.join(script_path, 'amici_refs.bib') - outfile = os.path.join(script_path, 'references.md') + bibfile = os.path.join(script_path, "amici_refs.bib") + outfile = os.path.join(script_path, "references.md") by_year = get_keys_by_year(bibfile) num_total = sum(map(len, by_year.values())) - with open(outfile, 'w') as f: - f.write('# References\n\n') - f.write('List of publications using AMICI. ' - f'Total number is {num_total}.\n\n') - f.write('If you applied AMICI in your work and your publication is ' - 'missing, please let us know via a new GitHub issue.\n\n') + with open(outfile, "w") as f: + f.write("# References\n\n") f.write( -""" + "List of publications using AMICI. " f"Total number is {num_total}.\n\n" + ) + f.write( + "If you applied AMICI in your work and your publication is " + "missing, please let us know via a new GitHub issue.\n\n" + ) + f.write( + """ \n """ - ) + ) for year in reversed(sorted(by_year.keys())): cur_bib = get_sub_bibliography(year, by_year, bibfile) f.write(cur_bib) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/matlab/@amidata/amidata.m b/matlab/@amidata/amidata.m index b9be294d27..85ff897fed 100644 --- a/matlab/@amidata/amidata.m +++ b/matlab/@amidata/amidata.m @@ -4,11 +4,11 @@ % classdef amidata < handle % AMIDATA provides a data container to pass experimental data to the - % simulation routine for likelihood computation. - % when any of the properties are updated, the class automatically - % checks consistency of dimension and updates related properties and + % simulation routine for likelihood computation. + % when any of the properties are updated, the class automatically + % checks consistency of dimension and updates related properties and % initialises them with NaNs - + properties % number of timepoints nt=0; @@ -37,12 +37,12 @@ % reinitialize states based on fixed parameters after preeq.? reinitializeStates = false; end - + methods function D = amidata(varargin) - % amidata creates an amidata container for experimental data + % amidata creates an amidata container for experimental data % with specified dimensions amidata. - % + % % AMIDATA(amidata) creates a copy of the input container % % AMIDATA(struct) tries to creates an amidata container from the @@ -62,15 +62,15 @@ % AMIDATA(nt,ny,nz,ne,nk) constructs an empty data container with % in the provided dimensions intialised with NaNs % - % + % % % Parameters: % varargin: % % Return values: % - - + + % initialisation via struct if isa(varargin{1},'amidata') if strcmp(class(varargin{1}),class(D)) @@ -112,7 +112,7 @@ end if(isfield(varargin{1},'Sigma_Z')) D.Sigma_Z = varargin{1}.Sigma_Z; - end + end if(isfield(varargin{1},'condition')) D.nk = numel(varargin{1}.condition); D.condition = varargin{1}.condition; @@ -131,7 +131,7 @@ else error('Assignment error: Value for field reinitializeStates must be logical.'); end - end + end elseif(nargin == 5) D.nt = varargin{1}; D.ny = varargin{2}; @@ -141,62 +141,62 @@ end end - + function set.nt(this,nt) this.nt = nt; this.t = 1:nt; this.Y = NaN; this.Sigma_Y = NaN; end - + function set.ny(this,ny) this.ny = ny; this.Y = NaN; this.Sigma_Y = NaN; end - + function set.nz(this,nz) this.nz = nz; this.Z = NaN; this.Sigma_Z = NaN; end - + function set.ne(this,ne) this.ne = ne; this.Z = NaN; this.Sigma_Z = NaN; end - + function set.nk(this,nk) this.nk = nk; this.condition = NaN(nk,1); end - + function set.t(this,value) assert(isnumeric(value),'AMICI:amimodel:t:numeric','t must have a numeric value!') assert(ismatrix(value),'AMICI:amimodel:t:ndims','t must be a two dimensional matrix!') assert(numel(value)==this.nt,'AMICI:amimodel:t:ndims',['t must have ' num2str(this.nt) ' (D.nt) elements!']) this.t = double(value(:)); end - + function set.condition(this,value) assert(isnumeric(value),'AMICI:amimodel:condition:numeric','condition must have a numeric value!') assert(ismatrix(value),'AMICI:amimodel:condition:ndims','condition must be a two dimensional matrix!') assert(numel(value)==this.nk,'AMICI:amimodel:condition:ndims',['condition must have ' num2str(this.nk) ' (D.nk) elements!']) this.condition = double(value(:)); end - + function set.conditionPreequilibration(this,value) assert(isnumeric(value),'AMICI:amimodel:condition:numeric','condition must have a numeric value!') assert(ismatrix(value),'AMICI:amimodel:condition:ndims','condition must be a two dimensional matrix!') assert(numel(value)==this.nk,'AMICI:amimodel:condition:ndims',['condition must have ' num2str(this.nk) ' (D.nk) elements!']) this.conditionPreequilibration = double(value(:)); end - + function set.Y(this,value) assert(ismatrix(value),'AMICI:amimodel:Y:ndims','Y must be a two dimensional matrix!') assert(all(all(or(isnumeric(value),isnan(value)))),'AMICI:amimodel:Y:numeric','Y must have a numeric value!') - + if(all(size(value)==[this.nt this.ny])) this.Y = double(value); elseif(all(size(value)==[this.nt 1])) @@ -209,7 +209,7 @@ error('AMICI:amimodel:Y:size',['Y must have size [' num2str(this.nt) ',' num2str(this.ny) '] ([D.nt,D.ny])!']) end end - + function set.Sigma_Y(this,value) assert(ismatrix(value),'AMICI:amimodel:Sigma_Y:ndims','Sigma_Y must be a two dimensional matrix!') assert(all(all(or(isnumeric(value),isnan(value)))),'AMICI:amimodel:Sigma_Y:numeric','Sigma_Y must have a numeric value!') @@ -225,7 +225,7 @@ error('AMICI:amimodel:Sigma_Y:size',['Sigma_Y must have size [' num2str(this.nt) ',' num2str(this.ny) '] ([D.nt,D.ny])!']) end end - + function set.Z(this,value) assert(ismatrix(value),'AMICI:amimodel:Z:ndims','Z must be a two dimensional matrix!') assert(all(all(or(isnumeric(value),isnan(value)))),'AMICI:amimodel:Z:numeric','Z must have a numeric value!') @@ -241,7 +241,7 @@ error('AMICI:amimodel:Z:size',['Z must have size [' num2str(this.ne) ',' num2str(this.nz) '] ([D.ne,D.nz])!']) end end - + function set.Sigma_Z(this,value) assert(ismatrix(value),'AMICI:amimodel:Sigma_Z:ndims','Sigma_Z must be a two dimensional matrix!') assert(all(all(or(isnumeric(value),isnan(value)))),'AMICI:amimodel:Sigma_Z:numeric','Sigma_Z must have a numeric value!') @@ -258,6 +258,5 @@ end end end - -end +end diff --git a/matlab/@amievent/amievent.m b/matlab/@amievent/amievent.m index 098a4e4372..1ce5d7f2cd 100644 --- a/matlab/@amievent/amievent.m +++ b/matlab/@amievent/amievent.m @@ -5,7 +5,7 @@ classdef amievent % AMIEVENT defines events which later on will be transformed into appropriate % C code - + properties ( GetAccess = 'public', SetAccess = 'private' ) % the trigger function activates the event on every zero crossing @type symbolic trigger = sym.empty(); @@ -17,7 +17,7 @@ % to speed up symbolic computations hflag = logical.empty(); end - + methods function AE = amievent(trigger,bolus,z) % amievent constructs an amievent object from the provided input. @@ -46,7 +46,7 @@ if(numel(AE.trigger)>1) error('The trigger function must be scalar.') end - + if(~isa(bolus,'sym')) if(isa(bolus,'double')) AE.bolus = sym(bolus(:)); @@ -56,7 +56,7 @@ else AE.bolus = bolus; end - + if(~isa(z,'sym')) if(isa(z,'double')) if(~isempty(z)) @@ -67,13 +67,12 @@ end else error('output function must be a symbolic expression') - end + end else AE.z = z; end end - + this = setHflag(this,hflag); end end - diff --git a/matlab/@amifun/amifun.m b/matlab/@amifun/amifun.m index 6743434cb8..e51c43b311 100644 --- a/matlab/@amifun/amifun.m +++ b/matlab/@amifun/amifun.m @@ -5,7 +5,7 @@ classdef amifun % AMIFUN defines functions which later on will be transformed into % appropriate C code - + properties ( GetAccess = 'public', SetAccess = 'public' ) % symbolic definition struct @type symbolic sym = sym([]); @@ -29,7 +29,7 @@ % with respect to parameters sensiflag = logical.empty(); end - + methods function AF = amifun(funstr,model) % amievent constructs an amifun object from the provided input. @@ -38,7 +38,7 @@ % funstr: name of the requested function % model: amimodel object which carries all symbolic % definitions to construct the function - % + % % % Return values: % AF: amifun object @@ -50,26 +50,25 @@ AF = AF.getCVar(); AF = AF.getSensiFlag(); end - + writeCcode_sensi(this,model,fid) - + writeCcode(this,model,fid) - + writeMcode(this,model) - + gccode(this,model,fid) - + [ this ] = getDeps(this,model) - + [ this ] = getArgs(this,model) - + [ this ] = getNVecs(this) - + [ this ] = getCVar(this) - + [ this ] = getSensiFlag(this) [ this, model ] = getSyms(this,model) end end - diff --git a/matlab/@amifun/gccode.m b/matlab/@amifun/gccode.m index a00724b3ba..5670f26886 100644 --- a/matlab/@amifun/gccode.m +++ b/matlab/@amifun/gccode.m @@ -8,17 +8,17 @@ % % Return values: % this: function definition object @type amifun - - + + if(any(any(any(this.sym~=0)))) - + % replace unknown partial derivatives if(model.maxflag) this.sym = subs(this.sym,sym('D([1], am_max)'),sym('D1max')); this.sym = subs(this.sym,sym('D([2], am_max)'),sym('D2max')); this.sym = subs(this.sym,sym('am_max'),sym('max')); end - + % If we have spline, we need to parse them to get derivatives if (model.splineflag) symstr = char(this.sym); @@ -31,7 +31,7 @@ else isDSpline = false; end - + if (isDSpline) [~, nCol] = size(this.sym); for iCol = 1 : nCol @@ -48,7 +48,7 @@ end end end - + cstr = ccode(this.sym); if(~strcmp(cstr(3:4),'t0')) if(any(strcmp(this.funstr,{'J','JB','JDiag','dJydsigma','dJydy','dJzdsigma','dJzdz','dJrzdsigma','dJrzdz','dydx','dzdx','drzdx','M','dfdx'}) )) @@ -60,13 +60,13 @@ else cstr = strrep(cstr,'t0',[this.cvar '_0']); end - + cstr = strrep(cstr,'log','amici::log'); % fix derivatives again (we cant do this before as this would yield % incorrect symbolic expressions cstr = regexprep(regexprep(cstr,'D([0-9]*)([\w]*)\(','D$2\($1,'),'DD([0-9]*)([\w]*)\(','DD$2\($1,'); cstr = strrep(strrep(cstr, 'DDspline', 'DDspline'), 'Dspline', 'Dspline'); - + if (model.splineflag) if (strfind(symstr, 'spline')) % The floating numbers after 't' must be converted to integers @@ -75,11 +75,11 @@ cstr = regexprep(cstr, '([D]*(spline|spline_pos))\((\w+)\,(\w+)\,t\,\w+\.\w+\,', ['amici::$1\($2\,$3\,t\,', num2str(nNodes), '\,']); end end - + if(numel(cstr)>1) - + % fix various function specific variable names/indexes - + cstr = regexprep(cstr,'var_x_([0-9]+)','x[$1]'); cstr = regexprep(cstr,'var_dx_([0-9]+)','dx[$1]'); cstr = regexprep(cstr,'var_sx_([0-9]+)','sx[$1]'); @@ -101,7 +101,7 @@ cstr = regexprep(cstr,'var_sx0_([0-9]+)','sx0[$1]'); cstr = regexprep(cstr,'var_sdx0_([0-9]+)','sdx0[$1]'); cstr = regexprep(cstr,'var_root_([0-9]+)', 'root[$1]'); - + cstr = regexprep(cstr,'var_p_([0-9]+)','p[$1]'); cstr = regexprep(cstr,'var_k_([0-9]+)','k[$1]'); cstr = regexprep(cstr,'h_([0-9]+)','h[$1]'); @@ -114,7 +114,7 @@ cstr = regexprep(cstr,'var_dwdp_([0-9]+)','dwdp[$1]'); cstr = regexprep(cstr,'tmp_J_([0-9]+)','J->data[$1]'); cstr = regexprep(cstr,'tmp_dxdotdp_([0-9]+)','dxdotdp[$1]'); - + cstr = regexprep(cstr,'var_y_([0-9]+)','y[$1]'); cstr = regexprep(cstr,'my_([0-9]+)','my[$1]'); cstr = regexprep(cstr,'var_z_([0-9]+)','z[$1]'); @@ -123,7 +123,7 @@ cstr = regexprep(cstr,'var_srz_([0-9]+)','srz[$1]'); cstr = regexprep(cstr,'var_sy_([0-9]+)','sy[$1]'); cstr = regexprep(cstr,'var_sz_([0-9]+)','sz[$1]'); - + cstr = regexprep(cstr,'var_dydx[_\[]*([0-9\+\*]+)[\]]*','dydx[$1]'); % matches both _... and [...] cstr = regexprep(cstr,'var_dzdx[_\[]*([0-9\+\*]+)[\]]*','dzdx[$1]'); cstr = regexprep(cstr,'var_drzdx[_\[]*([0-9\+\*]+)[\]]*','drzdx[$1]'); @@ -139,7 +139,7 @@ cstr = regexprep(cstr,'var_sigma_z_([0-9]+)','sigmaz[$1]'); cstr = regexprep(cstr,'var_dsigma_zdp_([0-9]+)',['dsigmazdp[$1]']); cstr = regexprep(cstr,'var_dsigma_ydp_([0-9]+)',['dsigmaydp[$1]']); - + cstr = regexprep(cstr,'var_dsdydp_([0-9]+)',['dsigmaydp[ip*' num2str(model.ny) ' + $1]']); cstr = regexprep(cstr,'var_dsdzdp_([0-9]+)',['dsigmazdp[ip*' num2str(model.nz) ' + $1]']); cstr = regexprep(cstr,'var_Jy_([0-9]+)','nllh[$1]'); @@ -153,7 +153,7 @@ cstr = regexprep(cstr,'var_dJrzdsigma[_\[]*([0-9\+\*]+)[\]]*','dJrzdsigma[$1]'); cstr = regexprep(cstr,'var_JDiag[_\[]*([0-9\+\*]+)[\]]*','JDiag[$1]'); end - + %% % print to file fprintf(fid,[cstr '\n']); diff --git a/matlab/@amifun/getArgs.m b/matlab/@amifun/getArgs.m index 03dd91c096..afb50802e0 100644 --- a/matlab/@amifun/getArgs.m +++ b/matlab/@amifun/getArgs.m @@ -10,7 +10,7 @@ % Return values: % this: updated function definition object @type amifun % - + if(strcmp(model.wtype,'iw')) dx = ', const realtype *dx'; sdx = ', const realtype *sdx'; @@ -24,7 +24,7 @@ M = ''; cj = ''; end - + switch(this.funstr) case 'xdot' this.argstr = ['(realtype *xdot, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h' dx ', const realtype *w)']; @@ -118,5 +118,5 @@ otherwise %nothing end - + end diff --git a/matlab/@amifun/getCVar.m b/matlab/@amifun/getCVar.m index 323303925d..43d5ecc2ed 100644 --- a/matlab/@amifun/getCVar.m +++ b/matlab/@amifun/getCVar.m @@ -5,7 +5,7 @@ % % Return values: % this: updated function definition object @type amifun - + switch(this.funstr) case 'JSparse' this.cvar = 'var_JSparse'; @@ -17,4 +17,3 @@ this.cvar = ['var_' this.funstr]; end end - diff --git a/matlab/@amifun/getDeps.m b/matlab/@amifun/getDeps.m index 800f4ccb98..5249fde319 100644 --- a/matlab/@amifun/getDeps.m +++ b/matlab/@amifun/getDeps.m @@ -6,7 +6,7 @@ % % Return values: % this: updated function definition object @type amifun - + switch(this.funstr) case 'xdot' if(strcmp(model.wtype,'iw')) @@ -14,205 +14,205 @@ else this.deps = {'p','x','k'}; end - + case 'dfdx' this.deps = {'xdot','x','dwdx'}; - + case 'J' if(strcmp(model.wtype,'iw')) this.deps = {'dfdx','M','x','xdot'}; else this.deps = {'xdot','x','dwdx'}; end - + case 'dxdotdp' this.deps = {'xdot','p','dwdp'}; - + case 'sx0' - this.deps = {'x0','p'}; - + this.deps = {'x0','p'}; + case 'sdx0' - this.deps = {'dx0','p'}; - + this.deps = {'dx0','p'}; + case 'sxdot' if(strcmp(model.wtype,'iw')) this.deps = {'dfdx','M','dxdotdp','sdx','sx'}; else this.deps = {'J','dxdotdp','sx'}; end - + case 'dydx' this.deps = {'y','x'}; - + case 'dydp' this.deps = {'y','p'}; - + case 'sy' this.deps = {'dydp','dydx','sx'}; - + case 'Jv' this.deps = {'J'}; - + case 'JvB' this.deps = {'J'}; - + case 'xBdot' if(strcmp(model.wtype,'iw')) this.deps = {'J','M','xB','dxB'}; else this.deps = {'J','xB'}; end - + case 'qBdot' this.deps = {'dxdotdp','xB'}; - + case 'dsigma_ydp' this.deps = {'sigma_y','p'}; - + case 'dsigma_zdp' this.deps = {'sigma_z','p'}; - + case 'root' this.deps = {'x','k','p'}; - + case 'drootdp' this.deps = {'root','p','drootdx','sx'}; - + case 'drzdp' this.deps = {'rz','p',}; - + case 'drootdx' this.deps = {'root','x'}; - + case 'drzdx' this.deps = {'rz','x',}; - + case 'drootdt' % w is necessary for xdot_noopt this.deps = {'root','drootdx','xdot','w'}; - + case 'deltax' this.deps = {'x','k','p'}; - + case 'ddeltaxdp' this.deps = {'deltax','p'}; - + case 'ddeltaxdx' this.deps = {'deltax','x'}; - + case 'ddeltaxdt' this.deps = {'deltax'}; - + case 'deltasx' this.deps = {'deltax','deltaxdot','ddeltaxdx','ddeltaxdp','ddeltaxdt','dtaudp','xdot','sx','stau'}; - + case 'deltaqB' this.deps = {'ddeltaxdp','xB'}; - + case 'deltaxB' this.deps = {'deltax','dtaudp','xdot','xB','ddeltaxdx'}; - + case 'z' this.deps = {'x','k','p'}; - + case 'rz' this.deps = {'z','root'}; - + case 'srz' this.deps = {'rz','root','drootdx','drootdp','sx'}; - + case 'dzdp' this.deps = {'z','p','dtaudp'}; - + case 'dzdx' this.deps = {'z','x','dtaudx'}; - + case 'dzdt' % w is necessary for xdot_noopt this.deps = {'z','x','xdot','w'}; - + case 'sz' this.deps = {'dzdp','dzdx','dzdt','sx','dtaudp','stau'}; - + case 'sz_tf' this.deps = {'dzdp','dzdx','sx'}; - + case 'dtaudp' this.deps = {'drootdp','drootdt'}; - + case 'dtaudx' this.deps = {'drootdx','drootdt'}; - + case 'stau' this.deps = {'sroot','drootdt'}; - + case 'sroot' this.deps = {'drootdp','drootdx','sx'}; - + case 'x0' this.deps = {'p','k','x'}; - + case 'JBand' this.deps = {'J'}; - + case 'JBandB' this.deps = {'JB'}; - + case 'JSparse' this.deps = {'J'}; - + case 'y' this.deps = {'x','p','k'}; - + case 'sigma_y' this.deps = {'p','k'}; - + case 'sigma_z' this.deps = {'p','k'}; - + case 'rhs' this.deps = {'xdot'}; - + case 'dx0' this.deps = {'x','p','k'}; - + case 'M' this.deps = {'x','p','k'}; - + case 'x' this.deps = {}; - + case 'dx' this.deps = {}; - + case 'xB' this.deps = {}; - + case 'dxB' this.deps = {}; - + case 'k' this.deps = {}; - + case 'p' this.deps = {}; - + case 'sx' this.deps = {}; - + case 'sdx' this.deps = {}; - + case 'deltaxdot' this.deps = {'xdot'}; - + case 'Jy' this.deps = {'y','sigma_y'}; case 'dJydy' this.deps = {'Jy','y'}; case 'dJydsigma' this.deps = {'Jy','sigma_y'}; - + case 'Jz' this.deps = {'z','sigma_z'}; case 'dJzdz' @@ -225,14 +225,14 @@ this.deps = {'Jrz','x'}; case 'dJrzdsigma' this.deps = {'Jrz','sigma_z'}; - + case 'w' this.deps = {'xdot'}; case 'dwdp' this.deps = {'w','p'}; case 'dwdx' this.deps = {'w','x'}; - + case 's2root' this.deps = {'sroot'}; @@ -240,4 +240,3 @@ error(['unknown function string: ' this.funstr ]) end end - diff --git a/matlab/@amifun/getNVecs.m b/matlab/@amifun/getNVecs.m index 4b75133a0e..1eb6670c57 100644 --- a/matlab/@amifun/getNVecs.m +++ b/matlab/@amifun/getNVecs.m @@ -1,6 +1,6 @@ function this = getNVecs(this) % getfunargs populates the nvecs property with the names of the - % N_Vector elements which are required in the execution of the function + % N_Vector elements which are required in the execution of the function % (if applicable). the information is directly extracted from the % argument string % @@ -9,18 +9,18 @@ % Return values: % this: updated function definition object @type amifun % - + vecs = {'x,','dx,','sx,','*sx,','sdx,','xB,','dxB,',... '*sxdot,','sxdot,','xdot,','xBdot,','qBdot,',... 'x0,','dx0,','*sx0,','*sdx0,',... 'v,','vB,','JDiag,','Jv,','JvB,',... 'xdot_old,'}; - + this.nvecs = {}; for iv = 1:length(vecs) if strfind(this.argstr,['N_Vector ' vecs{iv}]) this.nvecs = [this.nvecs,vecs{iv}(1:(end-1))]; end end - -end \ No newline at end of file + +end diff --git a/matlab/@amifun/getSensiFlag.m b/matlab/@amifun/getSensiFlag.m index 9d646509cb..195f119184 100644 --- a/matlab/@amifun/getSensiFlag.m +++ b/matlab/@amifun/getSensiFlag.m @@ -5,66 +5,66 @@ % % Return values: % this: updated function definition object @type amifun - + switch(this.funstr) case 'dxdotdp' this.sensiflag = true; - + case 'sx0' - this.sensiflag = true; - + this.sensiflag = true; + case 'sdx0' this.sensiflag = true; - - + + case 'dydp' this.sensiflag = true; - + case 'sy' this.sensiflag = true; - + case 'qBdot' this.sensiflag = true; - + case 'dsigma_ydp' this.sensiflag = true; - + case 'dsigma_zdp' this.sensiflag = true; - + case 'drzdp' this.sensiflag = true; - + case 'ddeltaxdp' this.sensiflag = true; - + case 'deltasx' this.sensiflag = true; - + case 'deltaqB' this.sensiflag = true; - + case 'dzdp' this.sensiflag = true; - + case 'sz' this.sensiflag = true; - + case 'dtaudp' this.sensiflag = true; - + case 'stau' this.sensiflag = true; - + case 'sroot' this.sensiflag = true; - + case 'srz' this.sensiflag = true; - + case 'sx' this.sensiflag = true; - + case 'sdx' this.sensiflag = true; @@ -72,4 +72,3 @@ this.sensiflag = false; end end - diff --git a/matlab/@amifun/getSyms.m b/matlab/@amifun/getSyms.m index ad741a8185..77c23f75af 100644 --- a/matlab/@amifun/getSyms.m +++ b/matlab/@amifun/getSyms.m @@ -7,19 +7,19 @@ % Return values: % this: updated function definition object @type amifun % model: updated model definition object @type amimodel - + % store often used variables for ease of notation, dependencies should % ensure that these variables are defined - + persistent x p sx w ndw jacw - + nx = model.nx; nevent = model.nevent; np = model.np; nk = model.nk; nz = model.nz; ny = model.ny; - + fprintf([this.funstr ' | ']) switch(this.funstr) case 'x' @@ -32,7 +32,7 @@ % transform into symbolic expression this.sym = sym(xs); x = this.sym; - + case 'dx' % create cell array of same size dxs = cell(nx,1); @@ -42,7 +42,7 @@ end % transform into symbolic expression this.sym = sym(dxs); - + case 'p' % create cell array of same size ps = cell(np,1); @@ -53,7 +53,7 @@ % transform into symbolic expression this.sym = sym(ps); p = this.sym; - + case 'k' % create cell array of same size ks = cell(nk,1); @@ -63,7 +63,7 @@ end % transform into symbolic expression this.sym = sym(ks); - + case 'sx' % create cell array of same size sxs = cell(nx,1); @@ -74,7 +74,7 @@ % transform into symbolic expression this.sym = repmat(sym(sxs),[1,np]); sx = this.sym; - + case 'sdx' % create cell array of same size sdx = cell(nx,np); @@ -86,7 +86,7 @@ end % transform into symbolic expression this.sym = sym(sdx); - + case 'xB' % create cell array of same size xBs = cell(nx,1); @@ -96,7 +96,7 @@ end % transform into symbolic expression this.sym = sym(xBs); - + case 'dxB' % create cell array of same size dxBs = cell(nx,1); @@ -106,14 +106,14 @@ end % transform into symbolic expression this.sym = sym(dxBs); - + case 'y' this.sym = model.sym.y; % replace unify symbolic expression this = unifySyms(this,model); this = makeStrSymsFull(this); - - + + % activate splines for iy = 1:ny if(not(all([model.splineflag,model.minflag,model.maxflag]))) @@ -128,41 +128,41 @@ model.minflag = true; end end - end - + end + case 'x0' this.sym = model.sym.x0; % replace unify symbolic expression this = unifySyms(this,model); - + case 'dx0' this.sym = model.sym.dx0; % replace unify symbolic expression this = unifySyms(this,model); - + case 'sigma_y' this.sym = model.sym.sigma_y; this = makeStrSymsFull(this); % replace unify symbolic expression this = unifySyms(this,model); - + case 'sigma_z' this.sym = model.sym.sigma_z; this = makeStrSymsFull(this); % replace unify symbolic expression this = unifySyms(this,model); - + case 'M' this.sym = sym(model.sym.M); % replace unify symbolic expression this = unifySyms(this,model); this = makeStrSyms(this); - + case 'xdot' this.sym = model.sym.xdot; % replace unify symbolic expression this = unifySyms(this,model); - + if(strcmp(model.wtype,'iw')) if(size(this.sym,2)>size(this.sym,1)) this.sym = -transpose(model.fun.M.sym*model.fun.dx.sym)+this.sym; @@ -170,7 +170,7 @@ this.sym = -model.fun.M.sym*model.fun.dx.sym+this.sym; end end - + % create cell array of same size xdots = cell(nx,1); xdot_olds = cell(nx,1); @@ -181,7 +181,7 @@ end this.strsym = sym(xdots); this.strsym_old = sym(xdot_olds); - + % activate splines for ix = 1:nx if(not(all([model.splineflag,model.minflag,model.maxflag]))) @@ -197,7 +197,7 @@ end end end - + case 'w' optimize = getoptimized(optsym(model.fun.xdot.sym)); tmpxdot = sym(char(optimize(end))); @@ -215,10 +215,10 @@ end % model.nw = 0; % nw = 0; -% this.sym = sym(zeros(0,1)); - +% this.sym = sym(zeros(0,1)); + + - ws = cell(nw,1); ts = cell(nw,1); % fill cell array @@ -249,7 +249,7 @@ jacx = jacobian(model.fun.w.sym,x); this.sym = jacx; for idw = 1:ndw - this.sym = this.sym + (jacw^idw)*jacx; % this part is only to get the right nonzero entries + this.sym = this.sym + (jacw^idw)*jacx; % this part is only to get the right nonzero entries end % fill cell array idx_w = find(this.sym); @@ -270,13 +270,13 @@ this.sym = sym(zeros(0,nx)); this.strsym = sym(zeros(0,nx)); end - + case 'dwdp' if(length(model.fun.w.sym)>0) jacp = jacobian(model.fun.w.sym,p); this.sym = jacp; for idw = 1:ndw - this.sym = this.sym + (jacw^idw)*jacp; % this part is only to get the right nonzero entries + this.sym = this.sym + (jacw^idw)*jacp; % this part is only to get the right nonzero entries end % fill cell array idx_w = find(this.sym); @@ -295,11 +295,11 @@ this.sym = sym(zeros(0,nx)); this.strsym = sym(zeros(0,nx)); end - + case 'dfdx' this.sym=jacobian(model.fun.xdot.sym,x) + jacobian(model.fun.xdot.sym,w)*model.fun.dwdx.strsym; this = makeStrSyms(this); - + case 'J' if(strcmp(model.wtype,'iw')) syms cj @@ -313,15 +313,15 @@ this.sym_noopt = this.sym; end end - + this = makeStrSymsSparse(this); - - - + + + case 'JDiag' this.sym = diag(model.fun.J.sym); this = makeStrSyms(this); - + case 'dxdotdp' if(~isempty(w)) this.sym=jacobian(model.fun.xdot.sym,p) + jacobian(model.fun.xdot.sym,w)*model.fun.dwdp.strsym; @@ -330,7 +330,7 @@ this.sym=jacobian(model.fun.xdot.sym,p); this.sym_noopt = this.sym; end - + %% % build short strings for reuse of dxdotdp % create cell array of same size @@ -341,13 +341,13 @@ end % create full symbolic array this.strsym = sym(dxdotdps); - + case 'sx0' this.sym=jacobian(model.fun.x0.sym,p); - + case 'sdx0' this.sym=jacobian(model.fun.dx0.sym,p); - + case 'sxdot' if(np>0) if(strcmp(model.wtype,'iw')) @@ -358,19 +358,19 @@ else this.sym = sym(zeros(size(sx,1),0)); end - + case 'dydx' this.sym=jacobian(model.fun.y.sym,x); % create cell array of same sizex this.strsym = sym(zeros(ny,nx)); % fill cell array this = makeStrSyms(this); - + case 'dydp' this.sym=jacobian(model.fun.y.sym,p); % create cell array of same size this = makeStrSyms(this); - + case 'xBdot' if(strcmp(model.wtype,'iw')) syms t @@ -378,7 +378,7 @@ else this.sym = model.fun.JB.sym * model.fun.xB.sym; end - + case 'qBdot' % If we do second order adjoints, we have to augment if (model.nxtrue < nx) @@ -388,7 +388,7 @@ this.sym(ig,:) = ... -transpose(model.fun.xB.sym(1:model.nxtrue)) * model.fun.dxdotdp.sym((ig-1)*model.nxtrue+1 : ig*model.nxtrue, :) ... -transpose(model.fun.xB.sym((ig-1)*model.nxtrue+1 : ig*model.nxtrue)) * model.fun.dxdotdp.sym(1:model.nxtrue, :); - end + end else this.sym = -transpose(model.fun.xB.sym)*model.fun.dxdotdp.sym; end @@ -396,7 +396,7 @@ case 'dsigma_ydp' this.sym = jacobian(model.fun.sigma_y.sym,p); this = makeStrSyms(this); - + case 'dsigma_zdp' if(nz>0) this.sym = jacobian(model.fun.sigma_z.sym,p); @@ -404,7 +404,7 @@ this.sym = sym(zeros(model.nz,np)); end this = makeStrSyms(this); - + case 'root' if(nevent>0) this.sym = transpose([model.event.trigger]); @@ -426,20 +426,20 @@ end end end - + case 'drootdp' this.sym = jacobian(model.fun.root.sym,p); - + case 'drootdx' this.sym = jacobian(model.fun.root.sym,x); - + case 'drootdt' % noopt is important here to get derivatives right this.sym = diff(model.fun.root.sym,'t') + model.fun.drootdx.sym*model.fun.xdot.sym_noopt; - + case 'sroot' this.sym = model.fun.drootdp.sym + model.fun.drootdx.sym*sx; - + case 'srz' if(isfield(model.sym,'rz')) % user defined input or from augmentation this.sym = jacobian(model.fun.rz.sym,p) + jacobian(model.fun.rz.sym,x)*sx; @@ -448,7 +448,7 @@ this.sym(iz,:) = model.fun.sroot.sym(model.z2event(iz),:); end end - + case 's2root' switch(model.o2flag) case 1 @@ -459,25 +459,25 @@ vec = model.sym.k((end-np+1):end); end for ievent = 1:nevent - + this.sym(ievent,:,:) = (jacobian(model.fun.sroot.sym(ievent,:),p) + jacobian(model.fun.sroot.sym(ievent,:),x(1:model.nxtrue))*sx(1:model.nxtrue,:) + jacobian(model.fun.sroot.sym(ievent,:),x(1:model.nxtrue))*sx(1:model.nxtrue,:))*vec; for ix = 1:model.nxtrue this.sym(ievent,:,:) = this.sym(ievent,:,:) + model.fun.drootdx.sym(ievent,ix)*s2x(ix,:,:); end end - + case 'dtaudp' this.sym = sym(zeros(nevent,np)); for ievent = 1:nevent this.sym(ievent,:) = - model.fun.drootdp.sym(ievent,:)/model.fun.drootdt.sym(ievent); end - + case 'dtaudx' this.sym = sym(zeros(nevent,nx)); for ievent = 1:nevent this.sym(ievent,:) = - model.fun.drootdx.sym(ievent,:)/model.fun.drootdt.sym(ievent); end - + case 'stau' this.sym = sym(zeros(nevent,np)); for ievent = 1:nevent @@ -493,7 +493,7 @@ staus = sym(staus); % multiply this.strsym = staus; - + case 'deltax' if(nevent>0) this.sym = [model.event.bolus]; @@ -501,16 +501,16 @@ else this.sym = sym(zeros(0,1)); end - + case 'deltaxdot' this.sym = model.fun.xdot.strsym-model.fun.xdot.strsym_old; - + case 'ddeltaxdp' this.sym = sym(zeros(nx,nevent,np)); for ievent = 1:nevent this.sym(:,ievent,:) = jacobian(model.fun.deltax.sym(:,ievent),p); end - + case 'ddeltaxdx' this.sym = sym(zeros(nx,nevent,nx)); if(nx>0) @@ -518,27 +518,27 @@ this.sym(:,ievent,:) = jacobian(model.fun.deltax.sym(:,ievent),x); end end - + case 'ddeltaxdt' this.sym = diff(model.fun.deltax.sym,'t'); - + case 'deltasx' - + if(nevent>0) for ievent = 1:nevent - + % dtdp = (1/drdt)*drdp dtdp = model.fun.stau.strsym; % this 1 here is correct, we explicitely do not want ievent here as the actual stau_tmp will only have dimension np - + % if we are just non-differentiable and but not - % discontinuous we can ignore some of the terms! + % discontinuous we can ignore some of the terms! if(any(logical(model.fun.deltax.sym(:,ievent)~=0))) % dxdp = dx/dt*dt/dp + dx/dp dxdp = sym(zeros(nx,np)); for ix = 1:nx dxdp(ix,:) = model.fun.xdot.sym(ix)*dtdp + sx(ix,:); end - + this.sym(:,:,ievent) = ... + permute(model.fun.ddeltaxdx.sym(:,ievent,:),[1 3 2])*dxdp ... + model.fun.ddeltaxdt.sym(:,ievent)*dtdp ... @@ -553,7 +553,7 @@ end end end - + case 'deltaqB' if (model.nxtrue < nx) ng_tmp = round(nx / model.nxtrue); @@ -561,21 +561,21 @@ else this.sym = sym(zeros(np,nevent)); end - + for ievent = 1:nevent this.sym(1:np,ievent) = -transpose(model.fun.xB.sym)*squeeze(model.fun.ddeltaxdp.sym(:,ievent,:)); % This is just a very quick fix. Events in adjoint systems % have to be implemented in a way more rigorous way later % on... Some day... end - + case 'deltaxB' this.sym = sym(zeros(nx,nevent)); for ievent = 1:nevent this.sym(:,ievent) = -transpose(squeeze(model.fun.ddeltaxdx.sym(:,ievent,:)))*model.fun.xB.sym; end - - + + case 'z' if(nevent>0) this.sym = transpose([model.event.z]); @@ -594,7 +594,7 @@ end end this = makeStrSymsFull(this); - + case 'rz' this.sym = sym(zeros(size(model.fun.z.sym))); if(isfield(model.sym,'rz')) @@ -606,38 +606,38 @@ end this = unifySyms(this,model); this = makeStrSymsFull(this); - + case 'dzdp' this.sym = jacobian(model.fun.z.sym,p); - + for iz = 1:nz this.sym(iz,:) = this.sym(iz,:) + diff(model.fun.z.sym(iz),sym('t'))*model.fun.dtaudp.sym(model.z2event(iz),:); end % create cell array of same size this = makeStrSyms(this); - + case 'drzdp' this.sym = jacobian(model.fun.rz.sym,p); this = makeStrSyms(this); - + case 'dzdx' this.sym = jacobian(model.fun.z.sym,x); for iz = 1:nz this.sym(iz,:) = this.sym(iz,:)+ diff(model.fun.z.sym(iz),sym('t'))*model.fun.dtaudx.sym(model.z2event(iz),:); end this = makeStrSyms(this); - + case 'drzdx' this.sym = jacobian(model.fun.rz.sym,x); this = makeStrSyms(this); - + case 'dzdt' if(nz>0) this.sym = diff(model.fun.z.sym,'t')+jacobian(model.fun.z.sym,x(1:model.nxtrue))*model.fun.xdot.sym_noopt(1:model.nxtrue); else this.sym = sym.empty(); end - + case 'sz' this.sym = sym(zeros(nz,np)); tmpsym = sym(zeros(nz-model.nztrue,np)); @@ -659,15 +659,15 @@ % symmetrise it and add it later on. % also the dzdp part contains second order sensitivities and the drootdt part does not (this leads % to 1:model.nxtrue indexing) - - + + if(model.o2flag==1) tmpsym(iz-model.nztrue,:) = jacobian(1/model.fun.drootdt.sym(model.z2event(iz)),p)*model.fun.z.sym(iz)*model.fun.drootdt.sym(model.z2event(iz)) ... + jacobian(1/model.fun.drootdt.sym(model.z2event(iz)),x(1:model.nxtrue))*sx(1:model.nxtrue,:)*model.fun.z.sym(iz)*model.fun.drootdt.sym(model.z2event(iz)); else - error('sz for directional second order sensis was never implemented and I do not know how to, you are on your own here.'); + error('sz for directional second order sensis was never implemented and I do not know how to, you are on your own here.'); end - + this.sym(iz,:) = ... + jacobian(model.fun.z.sym(iz)*model.fun.drootdt.sym(model.z2event(iz)),p)/model.fun.drootdt.sym(model.z2event(iz)) ... + jacobian(model.fun.z.sym(iz)*model.fun.drootdt.sym(model.z2event(iz)),x)*sx/model.fun.drootdt.sym(model.z2event(iz)) ... @@ -687,7 +687,7 @@ % you might not believe this, but this matrix should (and hopefully will) actually be symmetric ;) end end - + % create cell array of same size szs = cell(nz,np); % fill cell array @@ -698,14 +698,14 @@ end % transform into symbolic expression this.strsym = sym(szs); - + case 'JBand' %do nothing case 'JBandB' %do nothing case 'JSparse' %do nothing - + case 'Jy' this.sym = model.sym.Jy; % replace unify symbolic expression @@ -760,7 +760,7 @@ for iz = 1 : model.nztrue this.sym(iz,:,:) = jacobian(model.fun.Jrz.sym(iz,:),model.fun.sigma_z.strsym); end - + otherwise error('unknown function name') end @@ -821,4 +821,4 @@ else out = in; end -end \ No newline at end of file +end diff --git a/matlab/@amifun/writeCcode.m b/matlab/@amifun/writeCcode.m index d74709e958..b03232cd95 100644 --- a/matlab/@amifun/writeCcode.m +++ b/matlab/@amifun/writeCcode.m @@ -94,4 +94,4 @@ function writeCcode(this,model,fid) end -end \ No newline at end of file +end diff --git a/matlab/@amifun/writeCcode_sensi.m b/matlab/@amifun/writeCcode_sensi.m index b9238d3fc5..c660a5e43b 100644 --- a/matlab/@amifun/writeCcode_sensi.m +++ b/matlab/@amifun/writeCcode_sensi.m @@ -5,10 +5,10 @@ function writeCcode_sensi(this,model,fid) % Parameters: % model: model defintion object @type amimodel % fid: file id in which the final expression is written @type fileid -% +% % Return values: % void - + np = model.np; ng = model.ng; @@ -69,7 +69,7 @@ function writeCcode_sensi(this,model,fid) end end end -else +else nonzero = this.sym ~=0; if(any(any(nonzero))) tmpfun = this; @@ -88,4 +88,4 @@ function writeCcode_sensi(this,model,fid) end end end -end \ No newline at end of file +end diff --git a/matlab/@amifun/writeMcode.m b/matlab/@amifun/writeMcode.m index d78af5e628..4b5fb126c9 100644 --- a/matlab/@amifun/writeMcode.m +++ b/matlab/@amifun/writeMcode.m @@ -20,8 +20,8 @@ function writeMcode(this,model) end this.sym_noopt = subs(this.sym_noopt,h_vars,h_rep); end - + ami_mfun(this.sym_noopt, 'file', fullfile(model.wrap_path,'models',... model.modelname,[ this.funstr '_',model.modelname,'.m']), ... 'vars', {'t',model.fun.x.sym,model.fun.p.sym,model.fun.k.sym},'varnames',{'t','x','p','k'}); -end \ No newline at end of file +end diff --git a/matlab/@amimodel/amimodel.m b/matlab/@amimodel/amimodel.m index cef415bdfe..1aed263556 100644 --- a/matlab/@amimodel/amimodel.m +++ b/matlab/@amimodel/amimodel.m @@ -4,7 +4,7 @@ % classdef amimodel < handle % AMIMODEL carries all model definitions including functions and events - + properties ( GetAccess = 'public', SetAccess = 'private' ) % symbolic definition struct @type struct sym = struct.empty(); @@ -82,7 +82,7 @@ % storage for flags determining recompilation of individual % functions cfun = struct.empty(); - % flag which identifies augmented models + % flag which identifies augmented models % 0 indicates no augmentation % 1 indicates augmentation by first order sensitivities (yields % second order sensitivities) @@ -90,7 +90,7 @@ % order sensitivities (yields hessian-vector product) o2flag = 0; end - + properties ( GetAccess = 'public', SetAccess = 'public' ) % vector that maps outputs to events z2event = double.empty(); @@ -108,7 +108,7 @@ % number of derivatives of derived variables w, dwdp @type int ndwdp = 0; end - + methods function AM = amimodel(symfun,modelname) % amimodel initializes the model object based on the provided @@ -138,17 +138,17 @@ else error('invalid input symfun') end - + if(isfield(model,'sym')) AM.sym = model.sym; else error('symbolic definitions missing in struct returned by symfun') end - - - + + + props = fields(model); - + for j = 1:length(props) if(~strcmp(props{j},'sym')) % we already checked for the sym field if(isfield(model,props{j})) @@ -174,7 +174,7 @@ end end end - + AM.modelname = modelname; % set path and create folder AM.wrap_path=fileparts(fileparts(fileparts(mfilename('fullpath')))); @@ -192,7 +192,7 @@ AM.nztrue = AM.nz; end AM.nevent = length(AM.event); - + % check whether we have a DAE or ODE if(isfield(AM.sym,'M')) AM.wtype = 'iw'; % DAE @@ -201,7 +201,7 @@ end end end - + function updateRHS(this,xdot) % updateRHS updates the private fun property .fun.xdot.sym % (right hand side of the differential equation) @@ -214,7 +214,7 @@ function updateRHS(this,xdot) this.fun.xdot.sym_noopt = this.fun.xdot.sym; this.fun.xdot.sym = xdot; end - + function updateModelName(this,modelname) % updateModelName updates the modelname % @@ -225,7 +225,7 @@ function updateModelName(this,modelname) % void this.modelname = modelname; end - + function updateWrapPath(this,wrap_path) % updateModelName updates the modelname % @@ -236,38 +236,37 @@ function updateWrapPath(this,wrap_path) % void this.wrap_path = wrap_path; end - + parseModel(this) - + generateC(this) - + generateRebuildM(this) compileC(this) - + generateM(this,amimodelo2) - + getFun(this,HTable,funstr) - + makeEvents(this) - + makeSyms(this) - + cflag = checkDeps(this,HTable,deps) - + HTable = loadOldHashes(this) - + modelo2 = augmento2(this) - + modelo2vec = augmento2vec(this) - + end - + methods(Static) compileAndLinkModel(modelname, modelSourceFolder, coptim, debug, funs, cfun) - + generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFilename, modelname, pscale, forward, adjoint) end end - diff --git a/matlab/@amimodel/augmento2.m b/matlab/@amimodel/augmento2.m index baf51b70fe..e2a0852ebe 100644 --- a/matlab/@amimodel/augmento2.m +++ b/matlab/@amimodel/augmento2.m @@ -8,9 +8,9 @@ % Return values: % this: augmented system which contains symbolic definition of the % original system and its sensitivities @type amimodel - + syms Sx Sdot Sy S0 - + augmodel.nxtrue = length(this.sym.x); % number of states augmodel.nytrue = length(this.sym.y); % number of observables if(this.nevent>0) @@ -19,7 +19,7 @@ augmodel.nztrue = 0; end np = this.np; - + % augment states Sx = sym(zeros(length(this.sym.x),np)); for j = 1:length(this.sym.x) @@ -29,10 +29,10 @@ end end Sdot = jacobian(this.sym.xdot,this.sym.x)*Sx+jacobian(this.sym.xdot,this.sym.p); - + % augment output Sy = jacobian(this.sym.y,this.sym.x)*Sx+jacobian(this.sym.y,this.sym.p); - + % generate deltasx this.getFun([],'deltasx'); this.getFun([],'sz'); @@ -64,7 +64,7 @@ end augmodel.event(ievent) = amievent(this.event(ievent).trigger,bolusnew,znew); end - + % augment likelihood this.getFun([],'dsigma_ydp'); this.getFun([],'y'); @@ -79,7 +79,7 @@ SJy = jacobian(this.fun.Jy.sym,this.sym.p) ... + jacobian(this.fun.Jy.sym,this.fun.sigma_y.strsym)*this.fun.dsigma_ydp.sym ... + jacobian(this.fun.Jy.sym,this.fun.y.strsym)*aug_y_strsym; - + this.getFun([],'dsigma_zdp'); this.getFun([],'rz'); this.getFun([],'Jz'); @@ -92,7 +92,7 @@ SJz = jacobian(this.fun.Jz.sym,this.sym.p); if(~isempty(this.fun.sigma_z.strsym)) SJz = SJz + jacobian(this.fun.Jz.sym,this.fun.sigma_z.strsym)*this.fun.dsigma_zdp.sym ... - + jacobian(this.fun.Jz.sym,this.fun.z.strsym)*aug_z_strsym; + + jacobian(this.fun.Jz.sym,this.fun.z.strsym)*aug_z_strsym; end this.getFun([],'Jrz'); tmp = arrayfun(@(x) sym(['var_rz_' num2str(x)]),0:(augmodel.nztrue*(1+np)-1),'UniformOutput',false); @@ -104,14 +104,14 @@ SJrz = jacobian(this.fun.Jrz.sym,this.sym.p); if(~isempty(this.fun.sigma_z.strsym)) SJrz = SJrz + jacobian(this.fun.Jrz.sym,this.fun.sigma_z.strsym)*this.fun.dsigma_zdp.sym ... - + jacobian(this.fun.Jrz.sym,this.fun.rz.strsym)*aug_rz_strsym; + + jacobian(this.fun.Jrz.sym,this.fun.rz.strsym)*aug_rz_strsym; end - + % augment sigmas this.getFun([],'sigma_y'); this.getFun([],'sigma_z'); S0 = jacobian(this.sym.x0,this.sym.p); - + augmodel.sym.x = [this.sym.x;Sx(:)]; augmodel.sym.xdot = [this.sym.xdot;Sdot(:)]; augmodel.sym.f = augmodel.sym.xdot; @@ -127,7 +127,7 @@ augmodel.sym.k = this.sym.k; augmodel.sym.sigma_y = [transpose(this.sym.sigma_y(:)), reshape(transpose(this.fun.dsigma_ydp.sym), [1,numel(this.fun.dsigma_ydp.sym)])]; augmodel.sym.sigma_z = [transpose(this.sym.sigma_z(:)), reshape(transpose(this.fun.dsigma_zdp.sym), [1,numel(this.fun.dsigma_zdp.sym)])]; - + modelo2 = amimodel(augmodel,[this.modelname '_o2']); modelo2.o2flag = 1; modelo2.debug = this.debug; diff --git a/matlab/@amimodel/augmento2vec.m b/matlab/@amimodel/augmento2vec.m index 48df87e9c3..09ba3000ad 100644 --- a/matlab/@amimodel/augmento2vec.m +++ b/matlab/@amimodel/augmento2vec.m @@ -8,38 +8,38 @@ % Return values: % modelo2vec: augmented system which contains symbolic definition of the % original system and its sensitivities @type amimodel - + syms Sx Sdot Sy S0 - - + + augmodel.nxtrue = length(this.sym.x); % number of states augmodel.nytrue = length(this.sym.y); % number of observables augmodel.nztrue = this.nz; augmodel.coptim = this.coptim; augmodel.debug = this.debug; - + % multiplication vector (extension of kappa vecs = cell([length(this.sym.p),1]); for ivec = 1:length(this.sym.p) vecs{ivec} = sprintf('k_%i', length(this.sym.k) + ivec-1); end vec = sym(vecs); - + if(this.nevent>0) augmodel.nztrue = length([this.event.z]); % number of observables else augmodel.nztrue = 0; end np = this.np; - + % augment states sv = sym('sv',[length(this.sym.x),1]); Sdot = jacobian(this.sym.xdot,this.sym.x)*sv+jacobian(this.sym.xdot,this.sym.p)*vec; - + % augment output Sy = jacobian(this.sym.y,this.sym.x)*sv+jacobian(this.sym.y,this.sym.p)*vec; - + % generate deltasx this.getFun([],'deltasx'); for ievent = 1:this.nevent; @@ -60,7 +60,7 @@ augmodel.event(ievent) = amievent(this.event(ievent).trigger,bolusnew,znew); augmodel.event(ievent) = augmodel.event(ievent).setHflag([hflagold;zeros([numel(sv),1])]); end - + % augment likelihood this.getFun([],'dsigma_ydp'); this.getFun([],'y'); @@ -75,7 +75,7 @@ + jacobian(this.fun.Jy.sym,this.fun.sigma_y.strsym)*this.fun.dsigma_ydp.sym) ... * vec + jacobian(this.fun.Jy.sym,this.fun.y.strsym)*aug_y_strsym; this.getFun([],'dsigma_zdp'); - + this.getFun([],'dzdp'); this.getFun([],'Jz'); SJz = jacobian(this.fun.Jz.sym,this.sym.p); @@ -87,9 +87,9 @@ % augment sigmas this.getFun([],'sigma_y'); this.getFun([],'sigma_z'); - + S0 = jacobian(this.sym.x0,this.sym.p)*vec; - + augmodel.sym.x = [this.sym.x;sv]; augmodel.sym.xdot = [this.sym.xdot;Sdot]; augmodel.sym.f = augmodel.sym.xdot; @@ -101,7 +101,7 @@ augmodel.sym.p = this.sym.p; augmodel.sym.sigma_y = [this.sym.sigma_y, transpose(this.fun.dsigma_ydp.sym * vec)]; augmodel.sym.sigma_z = [this.sym.sigma_z, transpose(this.fun.dsigma_zdp.sym * vec)]; - + modelo2vec = amimodel(augmodel,[this.modelname '_o2vec']); modelo2vec.o2flag = 2; modelo2vec.debug = this.debug; diff --git a/matlab/@amimodel/checkDeps.m b/matlab/@amimodel/checkDeps.m index caebb514b7..77c40b7fb0 100644 --- a/matlab/@amimodel/checkDeps.m +++ b/matlab/@amimodel/checkDeps.m @@ -8,10 +8,10 @@ % deps: cell array with containing a list of dependencies @type cell % % Return values: - % cflag: boolean indicating whether any of the dependencies have + % cflag: boolean indicating whether any of the dependencies have % changed with respect to the hashes stored in HTable @type % bool - + if(~isempty(HTable)) cflags = zeros(length(deps),1); for id = 1:length(deps) @@ -47,4 +47,4 @@ end end end -end \ No newline at end of file +end diff --git a/matlab/@amimodel/compileAndLinkModel.m b/matlab/@amimodel/compileAndLinkModel.m index 4dd56bf80d..c75f575152 100644 --- a/matlab/@amimodel/compileAndLinkModel.m +++ b/matlab/@amimodel/compileAndLinkModel.m @@ -382,5 +382,3 @@ function updateFileHash(fileFolder,hashFolder,filename) str = regexprep(str,'[\s\.\-]','_'); versionstring = genvarname(str); % fix everything else we have missed end - - diff --git a/matlab/@amimodel/compileC.m b/matlab/@amimodel/compileC.m index 4056b77a3b..815e4e065c 100644 --- a/matlab/@amimodel/compileC.m +++ b/matlab/@amimodel/compileC.m @@ -3,6 +3,6 @@ function compileC(this) % % Return values: % void - + amimodel.compileAndLinkModel(this.modelname, fullfile(this.wrap_path,'models',this.modelname), this.coptim, this.debug, this.funs, this.cfun); -end +end diff --git a/matlab/@amimodel/generateM.m b/matlab/@amimodel/generateM.m index be85528e80..ea8c531f83 100644 --- a/matlab/@amimodel/generateM.m +++ b/matlab/@amimodel/generateM.m @@ -23,4 +23,3 @@ function generateM(this, amimodelo2) end - diff --git a/matlab/@amimodel/generateMatlabWrapper.m b/matlab/@amimodel/generateMatlabWrapper.m index b19521db14..ae89270c8a 100644 --- a/matlab/@amimodel/generateMatlabWrapper.m +++ b/matlab/@amimodel/generateMatlabWrapper.m @@ -462,4 +462,3 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fclose(fid); end - diff --git a/matlab/@amimodel/generateRebuildM.m b/matlab/@amimodel/generateRebuildM.m index 56b213af70..07bf5bf02a 100644 --- a/matlab/@amimodel/generateRebuildM.m +++ b/matlab/@amimodel/generateRebuildM.m @@ -23,4 +23,3 @@ function generateRebuildM(this) fclose(fid); end - diff --git a/matlab/@amimodel/getFun.m b/matlab/@amimodel/getFun.m index 4d126c585c..12f405b329 100644 --- a/matlab/@amimodel/getFun.m +++ b/matlab/@amimodel/getFun.m @@ -8,11 +8,11 @@ function getFun(this,HTable,funstr) % % Return values: % void - + [wrap_path,~,~]=fileparts(fileparts(which('amiwrap.m'))); - + fun = amifun(funstr,this); - + if(~isfield(this.fun,funstr)) % check whether we already computed the respective fun if(~all(strcmp(fun.deps,funstr))) % prevent infinite loops @@ -35,12 +35,12 @@ function getFun(this,HTable,funstr) else cflag = 0; end - - + + if(cflag) fun = amifun(funstr,this); [fun,this] = fun.getSyms(this); this.fun(1).(funstr) = fun; end - -end \ No newline at end of file + +end diff --git a/matlab/@amimodel/loadOldHashes.m b/matlab/@amimodel/loadOldHashes.m index 088aa826ef..fd56c3361b 100644 --- a/matlab/@amimodel/loadOldHashes.m +++ b/matlab/@amimodel/loadOldHashes.m @@ -4,7 +4,7 @@ % Return values: % HTable: struct with hashes of symbolic definition from the previous % compilation @type struct - + [wrap_path,~,~]=fileparts(fileparts(which('amiwrap.m'))); try load(fullfile(wrap_path,'models',this.modelname,['hashes.mat'])) @@ -25,7 +25,7 @@ this.sparseidxB = sparseidxB; catch err end - + catch HTable = struct(); end @@ -47,7 +47,7 @@ end DHTable.Jy = ''; DHTable.Jz = ''; - + DHTable.generateC = ''; DHTable.makeSyms = ''; DHTable.makeEvents = ''; @@ -62,6 +62,6 @@ DHTable.writeCcode_sensi = ''; DHTable.tdata = ''; - + HTable = am_setdefault(HTable,DHTable); end diff --git a/matlab/@amimodel/makeEvents.m b/matlab/@amimodel/makeEvents.m index f283ff9204..db6025403b 100644 --- a/matlab/@amimodel/makeEvents.m +++ b/matlab/@amimodel/makeEvents.m @@ -69,10 +69,10 @@ function makeEvents( this ) tmp_bolus{ievent} = sym(zeros([nx,1])); end syms polydirac - + % initialise hflag hflags = zeros([nx,nevent]); - + % heaviside event_dependency = zeros(nevent); for ievent = 1:nevent @@ -91,14 +91,14 @@ function makeEvents( this ) end end end - + % check for loops if(any(any(event_dependency^(size(event_dependency,1))))) error('Found loop in trigger dependency. This can lead to the simulation getting stuck and is thus currently not supported. Please check your model definition!') end - + P = 1:size(event_dependency,1); - + % make matrix upper triangular, this is to ensure that we dont end % up with partially replaced trigger functions that we no longer recognise while(~isempty(find(triu(event_dependency(P,P))-event_dependency(P,P)))) @@ -115,9 +115,9 @@ function makeEvents( this ) trigger = trigger(P); bolus = bolus(P); z = z(P); - - - + + + for ix = 1:nx symchar = char(this.sym.xdot(ix)); symvariable = this.sym.xdot(ix); @@ -140,18 +140,18 @@ function makeEvents( this ) end end if(strfind(symchar,'heaviside')) - + for ievent = 1:nevent % remove the heaviside function and replace by h % variable which is updated upon event occurrence in the % solver - + % h variables only change for one sign change but heaviside - % needs updating for both, thus we should + % needs updating for both, thus we should symvariable = subs(symvariable,heaviside( trigger{ievent}),betterSym(['h_' num2str(ievent-1)'])); symvariable = subs(symvariable,heaviside(-trigger{ievent}),betterSym(['(1-h_' num2str(ievent-1) ')'])); % set hflag - + % we can check whether dividing cfp(2) by % trigger{ievent} reduced the length of the symbolic % expression. If it does, this suggests that @@ -172,7 +172,7 @@ function makeEvents( this ) % update xdot this.sym.xdot(ix) = symvariable; end - + % loop until we no longer found any dynamic heaviside functions in the triggers in the previous loop nheavy = 1; while nheavy>0 @@ -196,13 +196,13 @@ function makeEvents( this ) trigger{ievent} = betterSym(symchar); end end - + % compute dtriggerdt and constant trigger functions for ievent = 1:nevent dtriggerdt(ievent) = diff(trigger{ievent},sym('t')) + jacobian(trigger{ievent},this.sym.x)*this.sym.xdot(:); end triggeridx = logical(dtriggerdt~=0); - + % multiply by the dtriggerdt factor, this should stay here as we % want the xdot to be cleaned of any dirac functions ievent = 1; @@ -221,7 +221,7 @@ function makeEvents( this ) ievent = ievent+1; end end - + % update hflags according to bolus for ievent = 1:nevent if(any(double(bolus{ievent}~=0))) @@ -236,9 +236,9 @@ function makeEvents( this ) end end end - + this.event = amievent.empty(); - + % update events for ievent = 1:nevent this.event(ievent) = amievent(trigger{ievent},bolus{ievent}(:),z{ievent}); @@ -279,11 +279,10 @@ function makeEvents( this ) this.sym.Jrz = sym(zeros(size(this.sym.Jz))); for iz = 1:length([this.event.z]) tmp = subs(this.sym.Jz(iz,:),var_z,var_rz); - this.sym.Jrz(iz,:) = subs(tmp,mz,sym(zeros(size(mz)))); + this.sym.Jrz(iz,:) = subs(tmp,mz,sym(zeros(size(mz)))); end end this.sym.Jrz = subs(this.sym.Jrz,rz,var_rz); end - diff --git a/matlab/@amimodel/makeSyms.m b/matlab/@amimodel/makeSyms.m index 13f0226780..4f6f382e40 100644 --- a/matlab/@amimodel/makeSyms.m +++ b/matlab/@amimodel/makeSyms.m @@ -103,7 +103,7 @@ function makeSyms( this ) catch error('Could not transform model.sym.k into a symbolic variable, please check the definition!') end - + end if(isfield(this.sym,'root')) @@ -141,4 +141,3 @@ function makeSyms( this ) % error(['The symbolic variable ' char(symvars(find(svaridx,1))) ' is used in the differential equation right hand side but was not specified as parameter/state/constant!']); % end end - diff --git a/matlab/@amimodel/parseModel.m b/matlab/@amimodel/parseModel.m index beea83800f..167819bb76 100644 --- a/matlab/@amimodel/parseModel.m +++ b/matlab/@amimodel/parseModel.m @@ -148,13 +148,13 @@ function parseModel(this) fprintf('sparse | ') M = double(logical(this.fun.J.sym~=sym(zeros(size(this.fun.J.sym))))); this.sparseidx = find(M); - + [ubw,lbw] = ami_bandwidth(M); - + this.ubw = ubw; this.lbw = lbw; this.nnz = length(find(M(:))); - + I = arrayfun(@(x) find(M(:,x))-1,1:nx,'UniformOutput',false); this.rowvals = []; this.colptrs = []; @@ -163,7 +163,7 @@ function parseModel(this) this.rowvals = [this.rowvals; I{ix}]; end this.colptrs(ix+1) = length(this.rowvals); - + if(this.adjoint) if(isfield(this.fun,'JB')) fprintf('sparseB | ') @@ -186,7 +186,7 @@ function parseModel(this) this.getFun([], 'M'); this.id = double(any(this.fun.M.sym)); else - + end else this.id = zeros(1, nx); diff --git a/matlab/@amised/amised.m b/matlab/@amised/amised.m index 2d69c2bb27..a8f1be6400 100644 --- a/matlab/@amised/amised.m +++ b/matlab/@amised/amised.m @@ -4,7 +4,7 @@ % classdef amised < handle % AMISED is a container for SED-ML objects - + properties ( GetAccess = 'public', SetAccess = 'private' ) % amimodel from the specified model model = struct('event',[],'sym',[]); @@ -20,16 +20,16 @@ varsym = sym([]); % symbolic expressions for data datasym = sym([]); - + end - + properties ( GetAccess = 'public', SetAccess = 'public' ) - + end - + methods function ASED = amised(sedname) - %amised reads in an SEDML document using the JAVA binding of + %amised reads in an SEDML document using the JAVA binding of % of libSEDML % % Parameters: @@ -38,7 +38,7 @@ % Return values: % ASED: amised object which contains all the information from % the SEDML document - + % get models for imodel = 1:length(ASED.sedml.listOfModels.model) % get the model sbml @@ -115,11 +115,10 @@ ASED.varsym(idata,ivar) = sym(variable.Attributes.id); end ASED.datasym(idata) = sym(variable.Attributes.id); - + end - + end end end - diff --git a/matlab/@optsym/optsym.m b/matlab/@optsym/optsym.m index b825a34bae..6209712fe2 100644 --- a/matlab/@optsym/optsym.m +++ b/matlab/@optsym/optsym.m @@ -4,12 +4,12 @@ % classdef optsym0) stoichsymbols = [reactant_id{:},product_id{:}]; stoichmath = [tmp_rs,tmp_ps]; - + stoichidx = not(strcmp(stoichsymbols,'')); stoichsymbols = stoichsymbols(stoichidx); stoichmath = stoichmath(stoichidx); @@ -440,7 +440,7 @@ function importSBML(this,filename) error('Event priorities are currently not supported!'); end end - + try tmp = cellfun(@(x) sym(sanitizeString(x)),{model.event.trigger},'UniformOutput',false); this.trigger = [tmp{:}]; @@ -459,10 +459,10 @@ function importSBML(this,filename) for ievent = 1:length(this.trigger) tmp = cellfun(@(x) {x.variable},{model.event(ievent).eventAssignment},'UniformOutput',false); assignments = sym(cat(2,tmp{:})); - + tmp = cellfun(@(x) {x.math},{model.event(ievent).eventAssignment},'UniformOutput',false); assignments_math = cleanedsym(cat(2,tmp{:})); - + for iassign = 1:length(assignments) state_assign_idx = find(assignments(iassign)==this.state); param_assign_idx = find(assignments(iassign)==this.param); @@ -470,37 +470,37 @@ function importSBML(this,filename) bound_assign_idx = find(assignments(iassign)==boundary_sym); stoich_assign_idx = find(assignments(iassign)==stoichsymbols); vol_assign_idx = find(assignments(iassign)==compartments_sym); - + if(np>0 && ~isempty(param_assign_idx)) error('Assignments of parameters via events are currently not supported') this.param(param_assign_idx) = this.param(param_assign_idx)*heaviside(-this.trigger(ievent)) + assignments_math(iassign)*heaviside(this.trigger(ievent)); end - + if(nk>0 && ~isempty(cond_assign_idx)) error('Assignments of constants via events are currently not supported') conditions(cond_assign_idx) = conditions(cond_assign_idx)*heaviside(-this.trigger(ievent)) + assignments_math(iassign)*heaviside(this.trigger(ievent)); end - + if(length(boundaries)>0 && ~isempty(bound_assign_idx)) error('Assignments of boundary conditions via events are currently not supported') boundaries(bound_assign_idx) = conditions(bound_assign_idx)*heaviside(-this.trigger(ievent)) + assignments_math(iassign)*heaviside(this.trigger(ievent)); end - + if(length(stoichsymbols)>0 && ~isempty(stoich_assign_idx)) error('Assignments of stoichiometries via events are currently not supported') stoichmath(stoich_assign_idx) = stoichmath(stoich_assign_idx)*heaviside(-this.trigger(ievent)) + assignments_math(iassign)*heaviside(this.trigger(ievent)); end - + if(length(compartments_sym)>0 && ~isempty(vol_assign_idx)) error('Assignments of compartment volumes via events are currently not supported') end - + if(length(this.state)>0 && ~isempty(state_assign_idx)) - + this.bolus(state_assign_idx,ievent) = -this.state(state_assign_idx); addToBolus = sym(zeros(size(this.bolus(:,ievent)))); addToBolus(state_assign_idx) = assignments_math(iassign); - + this.bolus(:,ievent) = this.bolus(:,ievent) + addToBolus; end @@ -526,15 +526,15 @@ function importSBML(this,filename) tmpfun = cellfun(@(x) ['fun_' num2str(x)],num2cell(1:length(model.functionDefinition)),'UniformOutput',false); this.funmath = strrep(this.funmath,{model.functionDefinition.id},tmpfun); % replace helper functions - + checkIllegalFunctions(this.funmath); this.funmath = replaceLogicalFunctions(this.funmath); - + this.funmath = strrep(this.funmath,tmpfun,{model.functionDefinition.id}); this.funarg = cellfun(@(x,y) [y '(' strjoin(transpose(x(1:end-1)),',') ')'],lambdas,replaceReservedFunctionIDs({model.functionDefinition.id}),'UniformOutput',false); - + % make functions available in this file - + for ifun = 1:length(this.funmath) token = regexp(this.funarg(ifun),'\(([0-9\w\,]*)\)','tokens'); start = regexp(this.funarg(ifun),'\(([0-9\w\,]*)\)'); @@ -567,7 +567,7 @@ function importSBML(this,filename) if(ismember(initassignments_sym(iIA),this.param)) if(ismember(sym(model.time_symbol),symvar(initassignments_math(iIA)))) error('Time dependent initial assignments are currently not supported!') - end + end param_idx = find(initassignments_sym(iIA)==this.param); parameter_sym(param_idx) = []; parameter_val(param_idx) = []; @@ -579,7 +579,7 @@ function importSBML(this,filename) this.param = subs(this.param,initassignments_sym(iIA),initassignments_math(iIA)); rulemath = subs(rulemath,initassignments_sym(iIA),initassignments_math(iIA)); np = np-1; - end + end end applyRule(this,model,'param',rulevars,rulemath) @@ -760,7 +760,7 @@ function checkIllegalFunctions(str) if(isfield(y,'math')) expr = cleanedsym(y.math); else - expr = cleanedsym(); + expr = cleanedsym(); end end @@ -771,4 +771,3 @@ function checkIllegalFunctions(str) id = {x.species}; end end - diff --git a/matlab/SBMLimporter/@SBMLode/writeAMICI.m b/matlab/SBMLimporter/@SBMLode/writeAMICI.m index 422a805150..3550241cd7 100644 --- a/matlab/SBMLimporter/@SBMLode/writeAMICI.m +++ b/matlab/SBMLimporter/@SBMLode/writeAMICI.m @@ -7,10 +7,10 @@ function writeAMICI(this,modelname) % % Return values: % void - + fprintf('writing file ...\n') fid = fopen([modelname '_syms.m'],'w'); - + fprintf(fid,['function model = ' modelname '_syms()\n']); fprintf(fid,'\n'); if(strcmp(this.time_symbol,'')) @@ -20,7 +20,7 @@ function writeAMICI(this,modelname) end fprintf(fid,'\n'); fprintf(fid,'avogadro = 6.02214179e23;'); - + % fprintf(fid,'model.debug = true;\n'); writeDefinition('STATES','x','state',this,fid) writeDefinition('PARAMETERS','p','parameter',this,fid) @@ -54,7 +54,7 @@ function writeAMICI(this,modelname) fprintf(fid,'\n'); fprintf(fid,'end\n'); fprintf(fid,'\n'); - + for ifun = 1:length(this.funmath) fprintf(fid,['function r = ' this.funarg{ifun} '\n']); fprintf(fid,'\n'); @@ -62,12 +62,12 @@ function writeAMICI(this,modelname) fprintf(fid,'\n'); fprintf(fid,'end\n'); fprintf(fid,'\n'); - end - + end + for fun = {'factorial','cei','psi'} fprintUnsupportedFunctionError(fun{1},fid) end - + fclose(fid); end @@ -99,4 +99,4 @@ function fprintUnsupportedFunctionError(functionName,fid) fprintf(fid,'\n'); fprintf(fid,'end\n'); fprintf(fid,'\n'); -end \ No newline at end of file +end diff --git a/matlab/SBMLimporter/computeBracketLevel.m b/matlab/SBMLimporter/computeBracketLevel.m index ec1c6621db..5bbffb961b 100644 --- a/matlab/SBMLimporter/computeBracketLevel.m +++ b/matlab/SBMLimporter/computeBracketLevel.m @@ -11,7 +11,7 @@ % % Return values: % brl: bracket levels @type *int - + % compute bracket levels add one for each (, (before) remove 1 for each % ) (after) open = (cstr == '('); @@ -24,6 +24,5 @@ for ifun = 1:length(fun_startidx) brl(fun_startidx(ifun):(fun_endidx(ifun)-1)) = brl(fun_endidx(ifun)); end - -end +end diff --git a/matlab/amiwrap.m b/matlab/amiwrap.m index 838d95c102..6fbf8353ae 100644 --- a/matlab/amiwrap.m +++ b/matlab/amiwrap.m @@ -199,4 +199,3 @@ function amiwrap( varargin ) end warning(warningreset); end - diff --git a/matlab/auxiliary/CalcMD5/CalcMD5.c b/matlab/auxiliary/CalcMD5/CalcMD5.c index d84dcfb811..a078a23496 100644 --- a/matlab/auxiliary/CalcMD5/CalcMD5.c +++ b/matlab/auxiliary/CalcMD5/CalcMD5.c @@ -90,7 +90,7 @@ ** documentation and/or software. ** ********************************************************************** */ - + /* % $JRev: R5.00z V:025 Sum:/kHGslMmCpAS Date:17-Dec-2009 12:46:26 $ % $File: CalcMD5\CalcMD5.c $ @@ -209,33 +209,33 @@ void MD5Update(MD5_CTX *context, UCHAR *input, UINT inputLen) /* Compute number of bytes mod 64: */ index = (UINT)((context->count[0] >> 3) & 0x3F); - + /* Update number of bits: */ if ((context->count[0] += ((UINT32)inputLen << 3)) < ((UINT32)inputLen << 3)) { context->count[1]++; } context->count[1] += ((UINT32)inputLen >> 29); - + partLen = 64 - index; - + /* Transform as many times as possible: */ if (inputLen >= partLen) { int i; memcpy((POINTER)&context->buffer[index], (POINTER)input, partLen); MD5Transform(context->state, context->buffer); - + inputLenM63 = inputLen - 63; for (i = partLen; i < inputLenM63; i += 64) { MD5Transform(context->state, &input[i]); } - + /* Buffer remaining input: index = 0 */ memcpy((POINTER)&context->buffer[0], (POINTER)&input[i], inputLen - i); } else { /* Buffer remaining input: i = 0 */ memcpy((POINTER)&context->buffer[index], (POINTER)input, inputLen); } - + return; } @@ -254,13 +254,13 @@ void MD5Final(UCHAR digest[16], MD5_CTX *context) index = (UINT)((context->count[0] >> 3) & 0x3f); padLen = (index < 56) ? (56 - index) : (120 - index); MD5Update(context, PADDING, padLen); - + /* Append length before padding: */ MD5Update(context, bits, 8); - + /* Store state in digest: */ MD5Encode(digest, context->state, 4); - + /* Zero sensitive information: */ memset((POINTER)context, 0, sizeof(MD5_CTX)); } @@ -312,7 +312,7 @@ void MD5Transform(UINT32 state[4], UCHAR block[64]) (((UINT32)block[58]) << 16) | (((UINT32)block[59]) << 24); x[15] = ( (UINT32)block[60]) | (((UINT32)block[61]) << 8) | (((UINT32)block[62]) << 16) | (((UINT32)block[63]) << 24); - + /* Round 1 */ FF(a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ FF(d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ @@ -398,7 +398,7 @@ void MD5Transform(UINT32 state[4], UCHAR block[64]) void MD5Encode(UCHAR *output, UINT32 *input, UINT len) { UINT j; - + for (j = 0; j < len; j++) { *output++ = (UCHAR)( *input & 0xff); *output++ = (UCHAR)((*input >> 8) & 0xff); @@ -417,17 +417,17 @@ void MD5Char(mxChar *array, mwSize inputLen, UCHAR digest[16]) MD5_CTX context; UINT Chunk; UCHAR *bufferP, *bufferEnd = buffer + BUFFER_LEN, *arrayP; - + /* Limit length to 32 bit address, because I cannot test this function */ /* with 64 bit arrays currently (under construction): */ if (inputLen >> 31 != 0) { /* Detect sign-bit if mwSize is int */ mexErrMsgTxt("*** CalcMD5[mex]: Input > 2^31 byte not handled yet."); } - + arrayP = (UCHAR *) array; /* UCHAR *, not mxChar *!*/ - + MD5Init(&context); - + /* Copy chunks of input data - only the first byte of each mxChar: */ Chunk = inputLen / BUFFER_LEN; while (Chunk--) { @@ -436,10 +436,10 @@ void MD5Char(mxChar *array, mwSize inputLen, UCHAR digest[16]) *bufferP++ = *arrayP; arrayP += 2; } - + MD5Update(&context, buffer, BUFFER_LEN); } - + /* Last chunk: */ Chunk = inputLen % BUFFER_LEN; if (Chunk != 0) { @@ -449,12 +449,12 @@ void MD5Char(mxChar *array, mwSize inputLen, UCHAR digest[16]) *bufferP++ = *arrayP; arrayP += 2; } - + MD5Update(&context, buffer, Chunk); } - + MD5Final(digest, &context); - + return; } @@ -462,13 +462,13 @@ void MD5Char(mxChar *array, mwSize inputLen, UCHAR digest[16]) void MD5Array(UCHAR *array, mwSize inputLen, UCHAR digest[16]) { MD5_CTX context; - + /* Limit length to 32 bit address, because I cannot test this function */ /* with 64 bit arrays currently (under construction): */ if (inputLen >> 31 != 0) { /* Detect sign-bit if mwSize is signed int */ mexErrMsgTxt("*** CalcMD5[mex]: Input > 2^31 byte not handled yet."); } - + MD5Init(&context); MD5Update(&context, array, (UINT) inputLen); MD5Final(digest, &context); @@ -481,13 +481,13 @@ void MD5File(char *filename, UCHAR digest[16]) MD5_CTX context; int len; UINT32 allLen = 0; - + /* Open the file in binary mode: */ if ((FID = fopen(filename, "rb")) == NULL) { mexPrintf("*** Error for file: [%s]\n", filename); mexErrMsgTxt("*** CalcMD5[mex]: Cannot open file."); } - + MD5Init(&context); while ((len = fread(buffer, 1, BUFFER_LEN, FID)) != 0) { /* Limit length to 32 bit address, because I cannot test this function */ @@ -497,7 +497,7 @@ void MD5File(char *filename, UCHAR digest[16]) fclose(FID); mexErrMsgTxt("*** CalcMD5[mex]: Cannot handle files > 2.1GB yet."); } - + MD5Update(&context, buffer, (UINT) len); } MD5Final(digest, &context); @@ -509,7 +509,7 @@ void MD5File(char *filename, UCHAR digest[16]) void ToHex(const UCHAR digest[16], char *output, int LowerCase) { char *outputEnd; - + if (LowerCase) { for (outputEnd = output + 32; output < outputEnd; output += 2) { sprintf(output, "%02x", *(digest++)); @@ -519,7 +519,7 @@ void ToHex(const UCHAR digest[16], char *output, int LowerCase) sprintf(output, "%02X", *(digest++)); } } - + return; } @@ -535,7 +535,7 @@ void ToBase64(const UCHAR In[16], char *Out) int i; char *p; const UCHAR *s; - + p = Out; s = In; for (i = 0; i < 5; i++) { @@ -545,11 +545,11 @@ void ToBase64(const UCHAR In[16], char *Out) *p++ = B64[s[2] & 0x3F]; s += 3; } - + *p++ = B64[(*s >> 2) & 0x3F]; *p++ = B64[((*s & 0x3) << 4)]; *p = '\0'; - + return; } @@ -560,12 +560,12 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) /* - Define default values of optional arguments. */ /* - Forward input data to different calculators according to the input type. */ /* - Convert digest to output format. */ - + char *FileName, InType, hexOut[33], b64Out[23]; UCHAR digest[16], *digestP, OutType = 'h'; int isFile = false, isUnicode = false; double *outP, *outEnd; - + /* Check number of inputs and outputs: */ if (nrhs == 0 || nrhs > 3) { mexErrMsgTxt("*** CalcMD5[mex]: 1 to 3 inputs required."); @@ -573,27 +573,27 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) if (nlhs > 1) { mexErrMsgTxt("*** CalcMD5[mex]: Too many output arguments."); } - + /* If 2nd input starts with 'f', treat string in 1st argument as file name: */ if (nrhs >= 2 && mxGetNumberOfElements(prhs[1]) > 0) { if (mxIsChar(prhs[1]) == 0) { mexErrMsgTxt("*** CalcMD5[mex]: 2nd input must be a string."); } - + InType = (char) tolower(*(POINTER) mxGetData(prhs[1])); isFile = (InType == 'f'); isUnicode = (InType == 'u'); } /* Default otherwise! */ - + /* Output type - default: hex: */ if (nrhs == 3 && !mxIsEmpty(prhs[2])) { if (mxIsChar(prhs[2]) == 0) { mexErrMsgTxt("*** CalcMD5[mex]: 3rd input must be a string."); } - + OutType = *(POINTER) mxGetData(prhs[2]); /* Just 1st character */ } - + /* Calculate check sum: */ if (isFile) { if ((FileName = mxArrayToString(prhs[0])) == NULL) { @@ -601,21 +601,21 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) } MD5File(FileName, digest); mxFree(FileName); - + } else if (mxIsNumeric(prhs[0]) || isUnicode) { MD5Array((POINTER) mxGetData(prhs[0]), mxGetNumberOfElements(prhs[0]) * mxGetElementSize(prhs[0]), digest); - + } else if (mxIsChar(prhs[0])) { MD5Char((mxChar *) mxGetData(prhs[0]), mxGetNumberOfElements(prhs[0]), digest); - + } else { mexErrMsgTxt("*** CalcMD5[mex]: Input type not accepted."); } - + /* Create output: */ switch (OutType) { case 'H': @@ -623,7 +623,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) ToHex(digest, hexOut, OutType == 'h'); plhs[0] = mxCreateString(hexOut); break; - + case 'D': case 'd': /* DOUBLE with integer values: */ plhs[0] = mxCreateDoubleMatrix(1, 16, mxREAL); @@ -633,7 +633,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) *outP = (double) *digestP++; } break; - + case 'B': case 'b': /* Base64: */ /* strtobase64(b64Out, 26, digest, 16); // included in LCC3.8 */ @@ -641,10 +641,10 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) ToBase64(digest, b64Out); /* Locally implemented */ plhs[0] = mxCreateString(b64Out); break; - + default: mexErrMsgTxt("*** CalcMD5[mex]: Unknown output type."); } - + return; } diff --git a/matlab/auxiliary/CalcMD5/TestCalcMD5.m b/matlab/auxiliary/CalcMD5/TestCalcMD5.m index 5cc14e4d2f..ec09ecfc6b 100644 --- a/matlab/auxiliary/CalcMD5/TestCalcMD5.m +++ b/matlab/auxiliary/CalcMD5/TestCalcMD5.m @@ -54,7 +54,7 @@ function TestCalcMD5(doSpeed) error(['*** ', FuncName, ': Failed for string:', ... char(10), '[', TestData{iTest, 1}, ']']); end - + % Check file input: FID = fopen(TestFile, 'wb+'); if FID < 0 @@ -63,7 +63,7 @@ function TestCalcMD5(doSpeed) end fwrite(FID, TestData{iTest, 1}, 'uchar'); fclose(FID); - + Str2 = CalcMD5(TestFile, 'file'); if strcmpi(Str2, TestData{iTest, 2}) == 0 fprintf('\n'); @@ -83,14 +83,14 @@ function TestCalcMD5(doSpeed) upHexOut = CalcMD5(data, 'char', 'HEX'); decOut = CalcMD5(data, 'char', 'Dec'); b64Out = CalcMD5(data, 'char', 'Base64'); - + if not(strcmpi(lowHexOut, upHexOut) && ... isequal(sscanf(lowHexOut, '%2x'), decOut(:)) && ... isequal(Base64decode(b64Out), decOut)) fprintf('\n'); error(['*** ', FuncName, ': Different results for output types.']); end - + % Check unicode, if the data length is a multiple of 2: if rem(length(data), 2) == 0 doubleData = double(data); @@ -110,12 +110,12 @@ function TestCalcMD5(doSpeed) disp('== Test speed:'); disp('(Short data: mainly the overhead of calling the function)'); Delay = 2; - + for Len = [10, 100, 1000, 10000, 1e5, 1e6, 1e7] [Number, Unit] = UnitPrint(Len); fprintf(' Data length: %s %s:\n', Number, Unit); data = uint8(fix(rand(1, Len) * 256)); - + % Measure java time: iniTime = cputime; finTime = iniTime + Delay; @@ -129,7 +129,7 @@ function TestCalcMD5(doSpeed) javaLoopPerSec = javaLoop / (cputime - iniTime); [Number, Unit] = UnitPrint(javaLoopPerSec * Len); fprintf(' java: %6s %s/sec\n', Number, Unit); - + % Measure Mex time: iniTime = cputime; finTime = iniTime + Delay; @@ -142,7 +142,7 @@ function TestCalcMD5(doSpeed) [Number, Unit] = UnitPrint(mexLoopPerSec * Len); fprintf(' mex: %6s %s/sec: %.1f times faster\n', ... Number, Unit, mexLoopPerSec / javaLoopPerSec); - + % Compare the results: if ~isequal(javaHash(:), mexHash(:)) error(['*** ', FuncName, ': Different results from java and Mex.']); @@ -151,7 +151,7 @@ function TestCalcMD5(doSpeed) end fprintf('\nCalcMD5 seems to work well.\n'); - + return; % ****************************************************************************** diff --git a/matlab/auxiliary/am_setdefault.m b/matlab/auxiliary/am_setdefault.m index 6065734eb6..f96ac7461d 100755 --- a/matlab/auxiliary/am_setdefault.m +++ b/matlab/auxiliary/am_setdefault.m @@ -7,7 +7,7 @@ % % Return values: % robj: updated obj @type struct - + fieldlist = fieldnames(obj); for i = 1:length(fieldlist) dobj.(fieldlist{i}) = obj.(fieldlist{i}); diff --git a/matlab/auxiliary/betterSym.m b/matlab/auxiliary/betterSym.m index e3eec9baf9..cf93b74098 100644 --- a/matlab/auxiliary/betterSym.m +++ b/matlab/auxiliary/betterSym.m @@ -5,4 +5,4 @@ else csym = sym(str); end -end \ No newline at end of file +end diff --git a/matlab/auxiliary/getCommitHash.m b/matlab/auxiliary/getCommitHash.m index b33e820e49..c39f33f7a3 100644 --- a/matlab/auxiliary/getCommitHash.m +++ b/matlab/auxiliary/getCommitHash.m @@ -9,7 +9,7 @@ % commit_hash: extracted hash value @type char % branch: branch of the repository @type char % url: employed remote origin @type char - + try fid = fopen(fullfile(wrap_path,'..','.git','FETCH_HEAD')); str = fgetl(fid); @@ -25,11 +25,11 @@ fid = fopen(fullfile(wrap_path,'.git','ORIG_HEAD')); commit_hash = ['dev_' fgetl(fid)]; fclose(fid); - + fid = fopen(fullfile(wrap_path,'.git','HEAD')); branch = strrep(fgetl(fid),'ref: refs/heads/',''); fclose(fid); - + url = 'local'; end catch @@ -38,4 +38,3 @@ url = 'unknown repository'; end end - diff --git a/matlab/auxiliary/struct2xml/struct2xml.m b/matlab/auxiliary/struct2xml/struct2xml.m index 228acb99fb..7fa900ff0e 100644 --- a/matlab/auxiliary/struct2xml/struct2xml.m +++ b/matlab/auxiliary/struct2xml/struct2xml.m @@ -1,5 +1,5 @@ function varargout = struct2xml( s, varargin ) -%Convert a MATLAB structure into a xml file +%Convert a MATLAB structure into a xml file % [ ] = struct2xml( s, file ) % xml = struct2xml( s ) % @@ -26,7 +26,7 @@ % On-screen output functionality added by P. Orth, 01-12-2010 % Multiple space to single space conversion adapted for speed by T. Lohuis, 11-04-2011 % Val2str subfunction bugfix by H. Gsenger, 19-9-2011 - + if (nargin ~= 2) if(nargout ~= 1 || nargin ~= 1) error(['Supported function calls:' sprintf('\n')... @@ -46,17 +46,17 @@ file = [file '.xml']; end end - + if (~isstruct(s)) error([inputname(1) ' is not a structure']); end - + if (length(fieldnames(s)) > 1) error(['Error processing the structure:' sprintf('\n') 'There should be a single field in the main structure.']); end xmlname = fieldnames(s); xmlname = xmlname{1}; - + %substitute special characters xmlname_sc = xmlname; xmlname_sc = strrep(xmlname_sc,'_dash_','-'); @@ -77,35 +77,35 @@ xmlwrite(file,docNode); else varargout{1} = xmlwrite(docNode); - end + end end % ----- Subfunction parseStruct ----- function [] = parseStruct(s,docNode,curNode,pName) - + fnames = fieldnames(s); for i = 1:length(fnames) curfield = fnames{i}; - + %substitute special characters curfield_sc = curfield; curfield_sc = strrep(curfield_sc,'_dash_','-'); curfield_sc = strrep(curfield_sc,'_colon_',':'); curfield_sc = strrep(curfield_sc,'_dot_','.'); - + if (strcmp(curfield,'Attributes')) %Attribute data if (isstruct(s.(curfield))) attr_names = fieldnames(s.Attributes); for a = 1:length(attr_names) cur_attr = attr_names{a}; - + %substitute special characters cur_attr_sc = cur_attr; cur_attr_sc = strrep(cur_attr_sc,'_dash_','-'); cur_attr_sc = strrep(cur_attr_sc,'_colon_',':'); cur_attr_sc = strrep(cur_attr_sc,'_dot_','.'); - + [cur_str,succes] = val2str(s.Attributes.(cur_attr)); if (succes) curNode.setAttribute(cur_attr_sc,cur_str); @@ -161,10 +161,10 @@ %----- Subfunction val2str ----- function [str,succes] = val2str(val) - + succes = true; str = []; - + if (isempty(val)) return; %bugfix from H. Gsenger elseif (ischar(val)) @@ -174,16 +174,16 @@ else succes = false; end - + if (ischar(val)) %add line breaks to all lines except the last (for multiline strings) lines = size(val,1); val = [val char(sprintf('\n')*[ones(lines-1,1);0])]; - - %transpose is required since indexing (i.e., val(nonspace) or val(:)) produces a 1-D vector. + + %transpose is required since indexing (i.e., val(nonspace) or val(:)) produces a 1-D vector. %This should be row based (line based) and not column based. valt = val'; - + remove_multiple_white_spaces = true; if (remove_multiple_white_spaces) %remove multiple white spaces using isspace, suggestion of T. Lohuis diff --git a/matlab/auxiliary/structToHDF5Attribute.m b/matlab/auxiliary/structToHDF5Attribute.m index a692ed69eb..78b2f4096c 100644 --- a/matlab/auxiliary/structToHDF5Attribute.m +++ b/matlab/auxiliary/structToHDF5Attribute.m @@ -39,4 +39,4 @@ end end end -end \ No newline at end of file +end diff --git a/matlab/auxiliary/template.m b/matlab/auxiliary/template.m index 2d1ff3732e..24fd06ff86 100644 --- a/matlab/auxiliary/template.m +++ b/matlab/auxiliary/template.m @@ -1,13 +1,13 @@ classdef template < handle %TEMPLATE A class to replace strings in template files - + properties % strings in the template to be replaced templateStrings = {}; % strings for replacement templateReplacements = {}; end - + methods function replace(this, infile, outfile) % apply all provided template substitutions to infile and write @@ -22,17 +22,17 @@ function replace(this, infile, outfile) fclose(fin); fclose(fout); end - + function add(this, templateStr, replacementStr) % add a new template string and replacement nextIdx = numel(this.templateStrings); this.templateStrings{nextIdx + 1} = templateStr; this.templateReplacements{nextIdx + 1} = replacementStr; end - + function s = replaceStr(this, s) - % apply all provided template substitutions to s - + % apply all provided template substitutions to s + % do not use cellfun to guarantee order of replacements for n = 1:numel(this.templateStrings) s = strrep(s, this.templateStrings(n), this.templateReplacements(n)); @@ -40,6 +40,5 @@ function add(this, templateStr, replacementStr) end end end - -end +end diff --git a/matlab/auxiliary/xml2struct/xml2struct.m b/matlab/auxiliary/xml2struct/xml2struct.m index dcd85a207b..a7b93058f1 100644 --- a/matlab/auxiliary/xml2struct/xml2struct.m +++ b/matlab/auxiliary/xml2struct/xml2struct.m @@ -32,7 +32,7 @@ help xml2struct return end - + if isa(file, 'org.apache.xerces.dom.DeferredDocumentImpl') || isa(file, 'org.apache.xerces.dom.DeferredElementImpl') % input is a java xml object xDoc = file; @@ -44,7 +44,7 @@ if (isempty(strfind(file,'.xml'))) file = [file '.xml']; end - + if (exist(file,'file') == 0) error(['The file ' file ' could not be found']); end @@ -52,10 +52,10 @@ %read the xml file xDoc = xmlread(file); end - + %parse xDoc into a MATLAB structure s = parseChildNodes(xDoc); - + end % ----- Subfunction parseChildNodes ----- @@ -70,7 +70,7 @@ for count = 1:numChildNodes theChild = item(childNodes,count-1); [text,name,attr,childs,textflag] = getNodeData(theChild); - + if (~strcmp(name,'#text') && ~strcmp(name,'#comment') && ~strcmp(name,'#cdata_dash_section')) %XML allows the same elements to be defined multiple times, %put each in a different cell @@ -83,19 +83,19 @@ %add new element children.(name){index} = childs; if(~isempty(fieldnames(text))) - children.(name){index} = text; + children.(name){index} = text; end - if(~isempty(attr)) - children.(name){index}.('Attributes') = attr; + if(~isempty(attr)) + children.(name){index}.('Attributes') = attr; end else %add previously unknown (new) element to the structure children.(name) = childs; if(~isempty(text) && ~isempty(fieldnames(text))) - children.(name) = text; + children.(name) = text; end - if(~isempty(attr)) - children.(name).('Attributes') = attr; + if(~isempty(attr)) + children.(name).('Attributes') = attr; end end else @@ -105,25 +105,25 @@ elseif (strcmp(name, '#comment')) ptextflag = 'Comment'; end - - %this is the text in an element (i.e., the parentNode) + + %this is the text in an element (i.e., the parentNode) if (~isempty(regexprep(text.(textflag),'[\s]*',''))) if (~isfield(ptext,ptextflag) || isempty(ptext.(ptextflag))) ptext.(ptextflag) = text.(textflag); else %what to do when element data is as follows: %Text More text - + %put the text in different cells: % if (~iscell(ptext)) ptext = {ptext}; end % ptext{length(ptext)+1} = text; - + %just append the text ptext.(ptextflag) = [ptext.(ptextflag) text.(textflag)]; end end end - + end end end @@ -131,7 +131,7 @@ % ----- Subfunction getNodeData ----- function [text,name,attr,childs,textflag] = getNodeData(theNode) % Create structure of node info. - + %make sure name is allowed as structure name name = toCharArray(getNodeName(theNode))'; name = strrep(name, '-', '_dash_'); @@ -139,13 +139,13 @@ name = strrep(name, '.', '_dot_'); attr = parseAttributes(theNode); - if (isempty(fieldnames(attr))) - attr = []; + if (isempty(fieldnames(attr))) + attr = []; end - + %parse child nodes [childs,text,textflag] = parseChildNodes(theNode); - + if (isempty(fieldnames(childs)) && isempty(fieldnames(text))) %get the data of any childless nodes % faster than if any(strcmp(methods(theNode), 'getData')) @@ -153,7 +153,7 @@ % faster than text = char(getData(theNode)); text.(textflag) = toCharArray(getTextContent(theNode))'; end - + end % ----- Subfunction parseAttributes ----- @@ -172,7 +172,7 @@ %Suggestion of Adrian Wanner str = toCharArray(toString(item(theAttributes,count-1)))'; - k = strfind(str,'='); + k = strfind(str,'='); attr_name = str(1:(k(1)-1)); attr_name = strrep(attr_name, '-', '_dash_'); attr_name = strrep(attr_name, ':', '_colon_'); @@ -180,4 +180,4 @@ attributes.(attr_name) = str((k(1)+2):(end-1)); end end -end \ No newline at end of file +end diff --git a/matlab/examples/example_adjoint/example_adjoint.m b/matlab/examples/example_adjoint/example_adjoint.m index fc0584b2da..3e787c2110 100644 --- a/matlab/examples/example_adjoint/example_adjoint.m +++ b/matlab/examples/example_adjoint/example_adjoint.m @@ -42,17 +42,17 @@ function example_adjoint() errorbar(t,D.Y,D.Sigma_Y) hold on % plot(t,sol.y) - + xlabel('time t') ylabel('observable') title(['log-likelihood: ' num2str(sol.llh) ]) - + y = (p(2)*t + p(3)).*(t<2) + ( (2*p(2)+p(3)-p(2)/p(1))*exp(-p(1)*(t-2))+p(2)/p(1) ).*(t>=2); - - + + tfine = linspace(0,4,100001); xfine = (p(2)*tfine + 1).*(tfine<2) + ( (2*p(2)+p(3)-p(2)/p(1))*exp(-p(1)*(tfine-2))+p(2)/p(1) ).*(tfine>=2); - + mu = zeros(1,length(tfine)); for it = 1:length(t) if(t(it)<=2) @@ -69,9 +69,9 @@ function example_adjoint() ylabel('adjoint') xlabel('time t') xlim([min(t)-0.5,max(t)+0.5]) - + subplot(3,1,3) - + plot(fliplr(tfine),-cumsum(fliplr(-mu.*xfine.*(tfine>2)))*p(1)*log(10)*(t(end)/numel(tfine))) hold on plot(fliplr(tfine),-cumsum(fliplr(mu))*p(2)*log(10)*(t(end)/numel(tfine))) @@ -79,13 +79,13 @@ function example_adjoint() xlim([min(t)-0.5,max(t)+0.5]) ylabel('integral') xlabel('time t') - + legend('p1','p2','p3') - + grad(1,1) = -trapz(tfine,-mu.*xfine.*(tfine>2))*p(1)*log(10); grad(2,1) = -trapz(tfine,mu)*p(2)*log(10); grad(3,1) = -mu(1)*p(3)*log(10); - + plot(zeros(3,1),grad,'ko') end @@ -123,7 +123,7 @@ function example_adjoint() xlabel('analytic absolute value of gradient element') ylabel('computed absolute value of gradient element') set(gcf,'Position',[100 300 1200 500]) - + drawnow end diff --git a/matlab/examples/example_adjoint/model_adjoint_syms.m b/matlab/examples/example_adjoint/model_adjoint_syms.m index 8473fcc6d4..0eddd4d122 100644 --- a/matlab/examples/example_adjoint/model_adjoint_syms.m +++ b/matlab/examples/example_adjoint/model_adjoint_syms.m @@ -13,9 +13,9 @@ % PARAMETERS ( for these sensitivities will be computed ) % create parameter syms -syms p1 p2 p3 +syms p1 p2 p3 -% create parameter vector +% create parameter vector model.sym.p = [p1 p2 p3]; @@ -48,4 +48,4 @@ model.sym.y(1) = x1; -end \ No newline at end of file +end diff --git a/matlab/examples/example_adjoint_hessian/example_adjoint_hessian.m b/matlab/examples/example_adjoint_hessian/example_adjoint_hessian.m index 6133dc6560..91aa105bd5 100644 --- a/matlab/examples/example_adjoint_hessian/example_adjoint_hessian.m +++ b/matlab/examples/example_adjoint_hessian/example_adjoint_hessian.m @@ -91,4 +91,4 @@ success=0; end -end \ No newline at end of file +end diff --git a/matlab/examples/example_adjoint_hessian/model_adjoint_hessian_syms.m b/matlab/examples/example_adjoint_hessian/model_adjoint_hessian_syms.m index 05b06bd4d7..3648341dbb 100644 --- a/matlab/examples/example_adjoint_hessian/model_adjoint_hessian_syms.m +++ b/matlab/examples/example_adjoint_hessian/model_adjoint_hessian_syms.m @@ -13,9 +13,9 @@ % PARAMETERS ( for these sensitivities will be computed ) % create parameter syms -syms p1 p2 p3 +syms p1 p2 p3 -% create parameter vector +% create parameter vector model.sym.p = [p1 p2 p3]; @@ -48,4 +48,4 @@ model.sym.y(1) = x1; -end \ No newline at end of file +end diff --git a/matlab/examples/example_calvetti/example_calvetti.m b/matlab/examples/example_calvetti/example_calvetti.m index 334f977be0..e786677161 100755 --- a/matlab/examples/example_calvetti/example_calvetti.m +++ b/matlab/examples/example_calvetti/example_calvetti.m @@ -26,13 +26,13 @@ function example_calvetti() % ODE15S y0 = [k(1); k(3); k(5); 1; 1; 1;]; -M = [1 0 0 0 0 0 +M = [1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]; - + function [xdot] = dae_system(t,x,p,k,it) if it<3 h0 = 0; @@ -95,7 +95,7 @@ function example_calvetti() legend('error x1','error x2','error x3','error x4','error x5','error x6','Location','NorthEastOutside') legend boxoff ylabel('x') - + set(gcf,'Position',[100 300 1200 500]) end end diff --git a/matlab/examples/example_calvetti/model_calvetti_syms.m b/matlab/examples/example_calvetti/model_calvetti_syms.m index 5fc3661ebf..697e4c1340 100755 --- a/matlab/examples/example_calvetti/model_calvetti_syms.m +++ b/matlab/examples/example_calvetti/model_calvetti_syms.m @@ -6,28 +6,28 @@ %% % STATES -% create state syms +% create state syms syms V1 V2 V3 f1 f2 f3 % create state vector model.sym.x = [V1, V2, V3, f1, f2, f3]; %% % PARAMETERS ( for these sensitivities will be computed ) -% create parameter syms -% create parameter vector +% create parameter syms +% create parameter vector model.sym.p = [ ]; -%% +%% % CONSTANTS ( for these no sensitivities will be computed ) % this part is optional and can be ommited % create parameter syms syms V1ss R1ss V2ss R2ss V3ss R3ss -% create parameter vector +% create parameter vector model.sym.k = [V1ss, R1ss, V2ss, R2ss, V3ss, R3ss]; %% % SYSTEM EQUATIONS % create symbolic variable for time -syms t f0 +syms t f0 model.sym.xdot = sym(zeros(size(model.sym.x))); p1=1; p2=1-R1ss; @@ -72,4 +72,3 @@ model.sym.y(5)=f1; model.sym.y(6)=f2; end - diff --git a/matlab/examples/example_dirac/example_dirac.m b/matlab/examples/example_dirac/example_dirac.m index 937a590027..ec1dc38326 100644 --- a/matlab/examples/example_dirac/example_dirac.m +++ b/matlab/examples/example_dirac/example_dirac.m @@ -56,7 +56,7 @@ function example_dirac() hold on plot(t,X_ode45(:,ix),'--','Color',c_x(ix,:)) end - + legend('x1','x1_{ode45}','x2','x2_{ode15s}','Location','NorthEastOutside') legend boxoff xlabel('time t') @@ -68,7 +68,7 @@ function example_dirac() ylim([1e-10,1e0]) legend('error x1','error x2','Location','NorthEastOutside') legend boxoff - + subplot(2,2,3) plot(t,sol.y,'.-','Color',c_x(1,:)) hold on @@ -78,7 +78,7 @@ function example_dirac() xlabel('time t') ylabel('y') box on - + subplot(2,2,4) plot(t,abs(sol.y-X_ode45(:,2)),'--') set(gca,'YScale','log') @@ -130,7 +130,7 @@ function example_dirac() xlabel('time t') ylabel('x') box on - + subplot(length(options.sens_ind),2,ip*2) plot(t,abs(sol.sx(:,:,ip)-sx_fd(:,:,ip)),'r--') legend('error x1','error x2','Location','NorthEastOutside') @@ -143,7 +143,7 @@ function example_dirac() box on end set(gcf,'Position',[100 300 1200 500]) - + figure for ip = 1:length(options.sens_ind) subplot(length(options.sens_ind),2,ip*2-1) @@ -159,7 +159,7 @@ function example_dirac() xlabel('time t') ylabel('y') box on - + subplot(length(options.sens_ind),2,ip*2) plot(t,abs(sol.sy(:,:,ip)-sy_fd(:,:,ip)),'r--') legend('error y1','Location','NorthEastOutside') @@ -172,8 +172,8 @@ function example_dirac() box on end set(gcf,'Position',[100 300 1200 500]) - + drawnow end -end \ No newline at end of file +end diff --git a/matlab/examples/example_dirac/model_dirac_syms.m b/matlab/examples/example_dirac/model_dirac_syms.m index 6c89ab26e2..3c0c9dd80a 100644 --- a/matlab/examples/example_dirac/model_dirac_syms.m +++ b/matlab/examples/example_dirac/model_dirac_syms.m @@ -15,12 +15,12 @@ % create parameter syms syms p1 p2 p3 p4 -% create parameter vector +% create parameter vector model.sym.p = [p1,p2,p3,p4]; % set the parametrisation of the problem options are 'log', 'log10' and % 'lin' (default). -model.param = 'log10'; +model.param = 'log10'; %% % SYSTEM EQUATIONS @@ -49,4 +49,4 @@ model.sym.y = sym(zeros(1,1)); model.sym.y(1) = x2; -end \ No newline at end of file +end diff --git a/matlab/examples/example_dirac_adjoint/example_dirac_adjoint.m b/matlab/examples/example_dirac_adjoint/example_dirac_adjoint.m index f5f2b23da0..60c1d327f9 100644 --- a/matlab/examples/example_dirac_adjoint/example_dirac_adjoint.m +++ b/matlab/examples/example_dirac_adjoint/example_dirac_adjoint.m @@ -79,7 +79,7 @@ function example_dirac_adjoint() xlabel('adjoint sensitivity absolute value of gradient element') ylabel('computed absolute value of gradient element') set(gcf,'Position',[100 300 1200 500]) - + drawnow end -end \ No newline at end of file +end diff --git a/matlab/examples/example_dirac_adjoint/example_model_5_paper.m b/matlab/examples/example_dirac_adjoint/example_model_5_paper.m index d3c0758f60..93aab4b315 100644 --- a/matlab/examples/example_dirac_adjoint/example_model_5_paper.m +++ b/matlab/examples/example_dirac_adjoint/example_model_5_paper.m @@ -63,7 +63,7 @@ syms xB1(t) xB2(t) eqn1B = diff(xB1) == p(1)*xB1 - p(3)*xB2; eqn3B = diff(xB2) == p(4)*xB2; -syms sigma my +syms sigma my x = sym('x',[2,1]); J = -0.5*((x(2) - my)/sigma)^2; dJdx = jacobian(J,x); @@ -241,9 +241,3 @@ syms xB1(t) xB2(t) xlim([-0.1,4.1]) set(gcf,'PaperPositionMode','auto','Position',[100 300 300 200]) print('-depsc','-r300',['sJ_asa']) - - - - - - diff --git a/matlab/examples/example_dirac_adjoint/model_dirac_adjoint_syms.m b/matlab/examples/example_dirac_adjoint/model_dirac_adjoint_syms.m index 488d97b901..ce2d21f207 100644 --- a/matlab/examples/example_dirac_adjoint/model_dirac_adjoint_syms.m +++ b/matlab/examples/example_dirac_adjoint/model_dirac_adjoint_syms.m @@ -16,7 +16,7 @@ % create parameter syms syms p1 p2 p3 p4 -% create parameter vector +% create parameter vector model.sym.p = [p1,p2,p3,p4]; % set the parametrisation of the problem options are 'log', 'log10' and @@ -51,4 +51,4 @@ model.sym.y(1) = x2; -end \ No newline at end of file +end diff --git a/matlab/examples/example_dirac_adjoint_hessVecProd/example_dirac_adjoint_hessVecProd.m b/matlab/examples/example_dirac_adjoint_hessVecProd/example_dirac_adjoint_hessVecProd.m index 25cbd23ffb..4c4a76c9b1 100644 --- a/matlab/examples/example_dirac_adjoint_hessVecProd/example_dirac_adjoint_hessVecProd.m +++ b/matlab/examples/example_dirac_adjoint_hessVecProd/example_dirac_adjoint_hessVecProd.m @@ -56,4 +56,3 @@ fprintf('Finite differences, HVP: \n'); disp(FD_HVP); - diff --git a/matlab/examples/example_dirac_adjoint_hessVecProd/model_dirac_adjoint_hessVecProd_syms.m b/matlab/examples/example_dirac_adjoint_hessVecProd/model_dirac_adjoint_hessVecProd_syms.m index b254bfcc5f..8f144cb4c9 100644 --- a/matlab/examples/example_dirac_adjoint_hessVecProd/model_dirac_adjoint_hessVecProd_syms.m +++ b/matlab/examples/example_dirac_adjoint_hessVecProd/model_dirac_adjoint_hessVecProd_syms.m @@ -16,7 +16,7 @@ % create parameter syms syms p1 p2 p3 p4 -% create parameter vector +% create parameter vector model.sym.p = [p1,p2,p3,p4]; % set the parametrisation of the problem options are 'log', 'log10' and @@ -51,4 +51,4 @@ model.sym.y(1) = x2; -end \ No newline at end of file +end diff --git a/matlab/examples/example_dirac_secondorder/example_dirac_secondorder.m b/matlab/examples/example_dirac_secondorder/example_dirac_secondorder.m index cba5ba56cc..fe9392ead6 100644 --- a/matlab/examples/example_dirac_secondorder/example_dirac_secondorder.m +++ b/matlab/examples/example_dirac_secondorder/example_dirac_secondorder.m @@ -87,7 +87,7 @@ function example_dirac_secondorder() end end set(gcf,'Position',[100 300 1200 500]) - + drawnow end diff --git a/matlab/examples/example_dirac_secondorder/model_dirac_secondorder_syms.m b/matlab/examples/example_dirac_secondorder/model_dirac_secondorder_syms.m index 657f2216a8..c2db18d77d 100644 --- a/matlab/examples/example_dirac_secondorder/model_dirac_secondorder_syms.m +++ b/matlab/examples/example_dirac_secondorder/model_dirac_secondorder_syms.m @@ -1,5 +1,5 @@ function [model] = model_dirac_secondorder_syms() - + %% % STATES @@ -15,7 +15,7 @@ % create parameter syms syms p1 p2 p3 p4 -% create parameter vector +% create parameter vector model.sym.p = [p1,p2,p3,p4]; % set the parametrisation of the problem options are 'log', 'log10' and @@ -49,4 +49,4 @@ model.sym.y = sym(zeros(1,1)); model.sym.y(1) = x2; -end \ No newline at end of file +end diff --git a/matlab/examples/example_dirac_secondorder_vectmult/example_dirac_secondorder_vectmult.m b/matlab/examples/example_dirac_secondorder_vectmult/example_dirac_secondorder_vectmult.m index 327e268faf..e7f4c9e3b3 100644 --- a/matlab/examples/example_dirac_secondorder_vectmult/example_dirac_secondorder_vectmult.m +++ b/matlab/examples/example_dirac_secondorder_vectmult/example_dirac_secondorder_vectmult.m @@ -64,7 +64,7 @@ function example_dirac_secondorder_vectmult() xlabel('time t') ylabel('x') box on - + subplot(4,2,ip*2) plot(t,abs(sol.s2x(:,:,ip)-s2x_fd(:,:,ip)),'r--') legend('error x1','error x2','Location','NorthEastOutside') @@ -77,7 +77,7 @@ function example_dirac_secondorder_vectmult() box on end set(gcf,'Position',[100 300 1200 500]) - + figure for ip = 1:4 subplot(4,2,ip*2-1) @@ -93,7 +93,7 @@ function example_dirac_secondorder_vectmult() xlabel('time t') ylabel('y') box on - + subplot(4,2,ip*2) plot(t,abs(sol.s2y(:,:,ip)-s2y_fd(:,:,ip)),'r--') legend('error y1','Location','NorthEastOutside') @@ -106,8 +106,8 @@ function example_dirac_secondorder_vectmult() box on end set(gcf,'Position',[100 300 1200 500]) - + drawnow end -end \ No newline at end of file +end diff --git a/matlab/examples/example_dirac_secondorder_vectmult/model_dirac_secondorder_vectmult_syms.m b/matlab/examples/example_dirac_secondorder_vectmult/model_dirac_secondorder_vectmult_syms.m index cc366495f1..5f217f4dad 100644 --- a/matlab/examples/example_dirac_secondorder_vectmult/model_dirac_secondorder_vectmult_syms.m +++ b/matlab/examples/example_dirac_secondorder_vectmult/model_dirac_secondorder_vectmult_syms.m @@ -1,5 +1,5 @@ function [model] = model_dirac_secondorder_vectmult_syms() - + %% % STATES @@ -15,12 +15,12 @@ % create parameter syms syms p1 p2 p3 p4 -% create parameter vector +% create parameter vector model.sym.p = [p1,p2,p3,p4]; % set the parametrisation of the problem options are 'log', 'log10' and % 'lin' (default). -model.param = 'log10'; +model.param = 'log10'; %% % SYSTEM EQUATIONS @@ -49,4 +49,4 @@ model.sym.y = sym(zeros(1,1)); model.sym.y(1) = x2; -end \ No newline at end of file +end diff --git a/matlab/examples/example_events/example_events.m b/matlab/examples/example_events/example_events.m index 1f5e2a47f4..8dfcdbb21e 100644 --- a/matlab/examples/example_events/example_events.m +++ b/matlab/examples/example_events/example_events.m @@ -64,7 +64,7 @@ function example_events() legend('error x1','error x2','error x3','Location','NorthEastOutside') legend boxoff ylabel('x') - + subplot(2,2,3) plot(t,sol.y,'.-','Color',c_x(1,:)) hold on @@ -74,7 +74,7 @@ function example_events() xlabel('time t') ylabel('y') box on - + subplot(2,2,4) plot(t,abs(sol.y-p(4)*sum(X_ode15s,2)),'--') set(gca,'YScale','log') @@ -83,7 +83,7 @@ function example_events() xlabel('time t') ylabel('y') box on - + set(gcf,'Position',[100 300 1200 500]) end @@ -125,7 +125,7 @@ function example_events() xlabel('time t') ylabel('sx') box on - + subplot(4,2,ip*2) plot(t,abs(sol.sx(:,:,ip)-sx_fd(:,:,ip)),'--') legend('error sx1','error sx2','error sx3','Location','NorthEastOutside') @@ -137,7 +137,7 @@ function example_events() box on end set(gcf,'Position',[100 300 1200 500]) - + figure for ip = 1:4 subplot(4,2,ip*2-1) @@ -152,7 +152,7 @@ function example_events() xlabel('time t') ylabel('sy') box on - + subplot(4,2,ip*2) plot(t,abs(sol.sy(:,:,ip)-sy_fd(:,:,ip)),'--') legend('error sy1','Location','NorthEastOutside') @@ -164,7 +164,7 @@ function example_events() box on end set(gcf,'Position',[100 300 1200 500]) - + figure for ip = 1:4 subplot(4,2,2*ip-1) @@ -177,7 +177,7 @@ function example_events() xlabel('event #') ylabel('sz') box on - + subplot(4,2,2*ip) bar(1:D.ne,sol.sz(1:D.ne,:,ip)-sz_fd(1:D.ne,:,ip),0.8) hold on @@ -189,7 +189,7 @@ function example_events() box on end set(gcf,'Position',[100 300 1200 500]) - + drawnow end -end \ No newline at end of file +end diff --git a/matlab/examples/example_events/model_events_syms.m b/matlab/examples/example_events/model_events_syms.m index f98298c8dd..7bf9ac6d32 100644 --- a/matlab/examples/example_events/model_events_syms.m +++ b/matlab/examples/example_events/model_events_syms.m @@ -22,12 +22,12 @@ % create parameter syms syms p1 p2 p3 p4 -% create parameter vector +% create parameter vector model.sym.p = [p1,p2,p3,p4]; % set the parametrisation of the problem options are 'log', 'log10' and % 'lin' (default). -model.param = 'log10'; +model.param = 'log10'; %% % CONSTANTS ( for these no sensitivities will be computed ) @@ -36,7 +36,7 @@ % create parameter syms syms k1 k2 k3 k4 -% create parameter vector +% create parameter vector model.sym.k = [k1 k2 k3 k4]; %% @@ -79,4 +79,4 @@ model.event(1) = amievent(am_ge(x2,x3),0,t); model.event(2) = amievent(am_ge(x1,x3),0,t); -end \ No newline at end of file +end diff --git a/matlab/examples/example_jakstat_adjoint/example_jakstat_adjoint.m b/matlab/examples/example_jakstat_adjoint/example_jakstat_adjoint.m index 55cd78dbe0..45a9002629 100644 --- a/matlab/examples/example_jakstat_adjoint/example_jakstat_adjoint.m +++ b/matlab/examples/example_jakstat_adjoint/example_jakstat_adjoint.m @@ -1,17 +1,17 @@ function example_jakstat_adjoint() - + % compile the model [exdir,~,~]=fileparts(which('example_jakstat_adjoint.m')); amiwrap('model_jakstat_adjoint','model_jakstat_adjoint_syms',exdir,1) - + num = xlsread(fullfile(exdir,'pnas_data_original.xls')); - + D.t = num(:,1); D.condition= [1.4,0.45]; D.Y = num(:,[2,4,6]); D.Sigma_Y = NaN(size(D.Y)); D = amidata(D); - + xi = [0.60 3 -0.95 @@ -29,10 +29,10 @@ function example_jakstat_adjoint() -0.5 0 -0.5]; - + options.sensi = 0; sol = simulate_model_jakstat_adjoint([],xi,[],D,options); - + if(usejava('jvm')) figure for iy = 1:3 @@ -54,7 +54,7 @@ function example_jakstat_adjoint() end set(gcf,'Position',[100 300 1200 500]) end - + % generate new xi_rand = xi + 0.1; options.sensi = 2; @@ -62,7 +62,7 @@ function example_jakstat_adjoint() sol = simulate_model_jakstat_adjoint([],xi_rand,[],D,options); options.sensi_meth = 'forward'; solf = simulate_model_jakstat_adjoint([],xi_rand,[],D,options); - + options.sensi = 1; eps = 1e-4; fd_grad = NaN(length(xi),1); @@ -73,7 +73,7 @@ function example_jakstat_adjoint() fd_grad(ip) = (psol.llh-sol.llh)/eps; fd_hess(:,ip) = (psol.sllh-sol.sllh)/eps; end - + if(usejava('jvm')) figure subplot(1,2,1) @@ -91,7 +91,7 @@ function example_jakstat_adjoint() axis square xlabel('absolute value forward sensitivity gradient entries') ylabel('absolute value gradient entries') - + subplot(1,2,2) plot(abs(solf.s2llh(:)),abs(fd_hess(:)),'rx') hold on @@ -107,11 +107,11 @@ function example_jakstat_adjoint() axis square xlabel('absolute value forward sensitivity hessian entries') ylabel('absolute value hessian entries') - + set(gcf,'Position',[100 300 1200 500]) end - - + + drawnow - + end diff --git a/matlab/examples/example_jakstat_adjoint/model_jakstat_adjoint_syms.m b/matlab/examples/example_jakstat_adjoint/model_jakstat_adjoint_syms.m index d1426d53be..a1e937e162 100644 --- a/matlab/examples/example_jakstat_adjoint/model_jakstat_adjoint_syms.m +++ b/matlab/examples/example_jakstat_adjoint/model_jakstat_adjoint_syms.m @@ -1,36 +1,36 @@ function [model] = model_jakstat_syms() - + %% % STATES - + syms STAT pSTAT pSTAT_pSTAT npSTAT_npSTAT nSTAT1 nSTAT2 nSTAT3 nSTAT4 nSTAT5 - + model.sym.x = [ STAT, pSTAT, pSTAT_pSTAT, npSTAT_npSTAT, nSTAT1, nSTAT2, nSTAT3, nSTAT4, nSTAT5 ... ]; %% % PARAMETERS - + syms p1 p2 p3 p4 init_STAT Omega_cyt Omega_nuc sp1 sp2 sp3 sp4 sp5 offset_tSTAT offset_pSTAT scale_tSTAT scale_pSTAT sigma_pSTAT sigma_tSTAT sigma_pEpoR - + model.sym.p = [p1,p2,p3,p4,init_STAT,sp1,sp2,sp3,sp4,sp5,offset_tSTAT,offset_pSTAT,scale_tSTAT,scale_pSTAT,sigma_pSTAT,sigma_tSTAT,sigma_pEpoR]; - + model.param = 'log10'; - + model.sym.k = [Omega_cyt,Omega_nuc]; - + %% % INPUT syms t % u(1) = spline_pos5(t, 0.0, sp1, 5.0, sp2, 10.0, sp3, 20.0, sp4, 60.0, sp5, 0, 0.0); u(1) = am_spline_pos(t, 5, 0.0, sp1, 5.0, sp2, 10.0, sp3, 20.0, sp4, 60.0, sp5, 0, 0.0); - + %% % SYSTEM EQUATIONS - + model.sym.xdot = sym(zeros(size(model.sym.x))); - + model.sym.xdot(1) = (Omega_nuc*p4*nSTAT5 - Omega_cyt*STAT*p1*u(1))/Omega_cyt; model.sym.xdot(2) = STAT*p1*u(1) - 2*p2*pSTAT^2; model.sym.xdot(3) = p2*pSTAT^2 - p3*pSTAT_pSTAT; @@ -40,30 +40,30 @@ model.sym.xdot(7) = p4*(nSTAT2 - nSTAT3); model.sym.xdot(8) = p4*(nSTAT3 - nSTAT4); model.sym.xdot(9) = p4*(nSTAT4 - nSTAT5); - + %% % INITIAL CONDITIONS - + model.sym.x0 = sym(zeros(size(model.sym.x))); - + model.sym.x0(1) = init_STAT; - + %% % OBSERVABLES - + model.sym.y = sym(zeros(3,1)); - + model.sym.y(1) = offset_pSTAT + scale_pSTAT/init_STAT*(pSTAT + 2*pSTAT_pSTAT); model.sym.y(2) = offset_tSTAT + scale_tSTAT/init_STAT*(STAT + pSTAT + 2*(pSTAT_pSTAT)); model.sym.y(3) = u(1); - + %% % SIGMA - + model.sym.sigma_y = sym(size(model.sym.y)); - + model.sym.sigma_y(1) = sigma_pSTAT; model.sym.sigma_y(2) = sigma_tSTAT; model.sym.sigma_y(3) = sigma_pEpoR; - -end \ No newline at end of file + +end diff --git a/matlab/examples/example_jakstat_adjoint_hvp/example_jakstat_adjoint_hvp.m b/matlab/examples/example_jakstat_adjoint_hvp/example_jakstat_adjoint_hvp.m index 2654e7bd93..2865fd7b22 100644 --- a/matlab/examples/example_jakstat_adjoint_hvp/example_jakstat_adjoint_hvp.m +++ b/matlab/examples/example_jakstat_adjoint_hvp/example_jakstat_adjoint_hvp.m @@ -1,16 +1,16 @@ function example_jakstat_adjoint_hvp() - + % compile the model [exdir,~,~]=fileparts(which('example_jakstat_adjoint_hvp.m')); amiwrap('model_jakstat_adjoint_hvp','model_jakstat_adjoint_hvp_syms',exdir,2) num = xlsread(fullfile(exdir,'pnas_data_original.xls')); - + D.t = num(:,1); D.condition= [1.4,0.45]; D.Y = num(:,[2,4,6]); D.Sigma_Y = NaN(size(D.Y)); D = amidata(D); - + xi = [0.60 3 -0.95 @@ -28,26 +28,26 @@ function example_jakstat_adjoint_hvp() -0.5 0 -0.5]; - - + + % generate new xi_rand = xi - 0.1; options.atol = 1e-12; options.rtol = 1e-12; - + % Get time for simulation tic; options.sensi = 0; sol0 = simulate_model_jakstat_adjoint_hvp([],xi_rand,[],D,options); t0 = toc; - + % Get time for usual evaluation tic; options.sensi = 1; options.sensi_meth = 'adjoint'; sol1 = simulate_model_jakstat_adjoint_hvp([],xi_rand,[],D,options); t1 = toc; - + % Get time for Finite Differences hvp = zeros(17,1); hvp_f = zeros(17,1); @@ -63,7 +63,7 @@ function example_jakstat_adjoint_hvp() hvp_f = hvp_f + (solp.sllh - sol2.sllh) / (delta); hvp_b = hvp_b + (sol2.sllh - solm.sllh) / (delta); t2 = toc; - + % Get time for Second order adjoints hvpasa = zeros(17,1); tic; @@ -81,7 +81,7 @@ function example_jakstat_adjoint_hvp() if(usejava('jvm')) figure(); - + subplot(1,2,1); bar([abs((sol.s2llh-hvp)./sol.s2llh),abs((sol.s2llh-hvp_f)./sol.s2llh),abs((sol.s2llh-hvp_b)./sol.s2llh),abs((sol.s2llh-solf.s2llh)./sol.s2llh)]) hold on @@ -95,13 +95,13 @@ function example_jakstat_adjoint_hvp() legend('FD_{central}','FD_{forward}','FD_{backward}','forward sensitivities','Orientation','horizontal') legend boxoff set(gcf,'Position',[100 300 1200 500]) - + subplot(1,2,2); hold on; bar([t0,t1,t2,t3]); xlabel('runtime [s]') set(gca,'XTick',1:4,'XTickLabel',{'ODE Integration', 'Gradient computation (ASA)', 'HVP from FD via 1st order ASA', 'HVP via 2nd order ASA'},'XTickLabelRotation',20); - + box on; hold off; end diff --git a/matlab/examples/example_jakstat_adjoint_hvp/model_jakstat_adjoint_hvp_syms.m b/matlab/examples/example_jakstat_adjoint_hvp/model_jakstat_adjoint_hvp_syms.m index b14eaacdb5..ab145a0472 100644 --- a/matlab/examples/example_jakstat_adjoint_hvp/model_jakstat_adjoint_hvp_syms.m +++ b/matlab/examples/example_jakstat_adjoint_hvp/model_jakstat_adjoint_hvp_syms.m @@ -1,36 +1,36 @@ function [model] = model_jakstat_adjoint_hvp_syms() - + %% % STATES - + syms STAT pSTAT pSTAT_pSTAT npSTAT_npSTAT nSTAT1 nSTAT2 nSTAT3 nSTAT4 nSTAT5 - + model.sym.x = [ STAT, pSTAT, pSTAT_pSTAT, npSTAT_npSTAT, nSTAT1, nSTAT2, nSTAT3, nSTAT4, nSTAT5 ... ]; - + %% % PARAMETERS - + syms p1 p2 p3 p4 init_STAT Omega_cyt Omega_nuc sp1 sp2 sp3 sp4 sp5 offset_tSTAT offset_pSTAT scale_tSTAT scale_pSTAT sigma_pSTAT sigma_tSTAT sigma_pEpoR - + model.sym.p = [p1,p2,p3,p4,init_STAT,sp1,sp2,sp3,sp4,sp5,offset_tSTAT,offset_pSTAT,scale_tSTAT,scale_pSTAT,sigma_pSTAT,sigma_tSTAT,sigma_pEpoR]; - + model.param = 'log10'; - + model.sym.k = [Omega_cyt,Omega_nuc]; - + %% % INPUT syms t u(1) = am_spline_pos(t, 5, 0, sp1, 5.0, sp2, 10.0, sp3, 20.0, sp4, 60.0, sp5, 0, 0); % u(1) = spline_pos5(t, 0, sp1, 5.0, sp2, 10.0, sp3, 20.0, sp4, 60.0, sp5, 0, 0); - + %% % SYSTEM EQUATIONS - + model.sym.xdot = sym(zeros(size(model.sym.x))); - + model.sym.xdot(1) = (Omega_nuc*p4*nSTAT5 - Omega_cyt*STAT*p1*u(1))/Omega_cyt; model.sym.xdot(2) = STAT*p1*u(1) - 2*p2*pSTAT^2; model.sym.xdot(3) = p2*pSTAT^2 - p3*pSTAT_pSTAT; @@ -40,30 +40,30 @@ model.sym.xdot(7) = p4*(nSTAT2 - nSTAT3); model.sym.xdot(8) = p4*(nSTAT3 - nSTAT4); model.sym.xdot(9) = p4*(nSTAT4 - nSTAT5); - + %% % INITIAL CONDITIONS - + model.sym.x0 = sym(zeros(size(model.sym.x))); model.sym.x0(1) = init_STAT; - + %% % OBSERVABLES - + model.sym.y = sym(zeros(3,1)); - + model.sym.y(1) = offset_pSTAT + scale_pSTAT/init_STAT*(pSTAT + 2*pSTAT_pSTAT); model.sym.y(2) = offset_tSTAT + scale_tSTAT/init_STAT*(STAT + pSTAT + 2*(pSTAT_pSTAT)); model.sym.y(3) = u(1); - + %% % SIGMA - + model.sym.sigma_y = sym(size(model.sym.y)); - + model.sym.sigma_y(1) = sigma_pSTAT; model.sym.sigma_y(2) = sigma_tSTAT; model.sym.sigma_y(3) = sigma_pEpoR; - -end \ No newline at end of file + +end diff --git a/matlab/examples/example_nested_events/example_nested_events.m b/matlab/examples/example_nested_events/example_nested_events.m index 1bd5ed9d5b..52d46b97a3 100644 --- a/matlab/examples/example_nested_events/example_nested_events.m +++ b/matlab/examples/example_nested_events/example_nested_events.m @@ -70,7 +70,7 @@ function example_events() legend('error x1','Location','NorthEastOutside') legend boxoff ylabel('x') - + set(gcf,'Position',[100 300 1200 300]) end @@ -111,7 +111,7 @@ function example_events() xlabel('time t') ylabel('sx') box on - + subplot(5,2,ip*2) plot(t,abs(sol.sx(:,:,ip)-sx_fd(:,:,ip)),'--') legend('error sx1','Location','NorthEastOutside') @@ -123,7 +123,7 @@ function example_events() box on end set(gcf,'Position',[100 300 1200 500]) - + drawnow end -end \ No newline at end of file +end diff --git a/matlab/examples/example_nested_events/model_nested_events_syms.m b/matlab/examples/example_nested_events/model_nested_events_syms.m index 42a1b48ae1..1bfa941a35 100644 --- a/matlab/examples/example_nested_events/model_nested_events_syms.m +++ b/matlab/examples/example_nested_events/model_nested_events_syms.m @@ -1,4 +1,4 @@ -function [model] = model_nested_events_syms() +function [model] = model_nested_events_syms() %% CVODES OPTIONS % set the parametrisation of the problem options are 'log', 'log10' and 'lin' (default) @@ -61,4 +61,4 @@ model.sym.x0 = x0; model.sym.y = y; -end \ No newline at end of file +end diff --git a/matlab/examples/example_neuron/example_neuron.m b/matlab/examples/example_neuron/example_neuron.m index d05b0cfe65..603c1af0c6 100644 --- a/matlab/examples/example_neuron/example_neuron.m +++ b/matlab/examples/example_neuron/example_neuron.m @@ -34,7 +34,7 @@ function example_neuron() t = linspace(0,D.Z(end)-0.1,100); D.Z = D.Z + 0.5*randn(size(D.Z)); D.Z(3) = NaN; -D.Sigma_Z = 0.5*ones(size(D.Z)); +D.Sigma_Z = 0.5*ones(size(D.Z)); D.Z = D.Z + D.Sigma_Z.*randn(size(D.Z)); D.t = t; @@ -64,7 +64,7 @@ function example_neuron() hold on end stem(sol.z,zeros(size(sol.z))) - + legend('x1','x2','events','Location','NorthEastOutside') legend boxoff xlabel('time t') @@ -135,7 +135,7 @@ function example_neuron() set(gca,'XScale','log') box on axis square - + subplot(2,3,4) plot(abs(sol.sz(:)),abs(sol.sz(:)-sz_fd(:)),'ro') hold on @@ -149,7 +149,7 @@ function example_neuron() set(gca,'XScale','log') box on axis square - + subplot(2,3,2) hold on plot(abs(sol.srz(:)),abs(srz_fd(:)),'bo') @@ -164,7 +164,7 @@ function example_neuron() set(gca,'XScale','log') box on axis square - + subplot(2,3,5) plot(abs(sol.srz(:)),abs(sol.srz(:)-srz_fd(:)),'ro') hold on @@ -178,7 +178,7 @@ function example_neuron() set(gca,'XScale','log') box on axis square - + subplot(2,3,3) hold on plot(abs(sol.sllh),abs(sllh_fd),'ko') @@ -193,7 +193,7 @@ function example_neuron() title('abs llh sensitivity') box on axis square - + subplot(2,3,6) plot(abs(sol.sllh),abs(sol.sllh-sllh_fd),'ro') hold on @@ -208,7 +208,7 @@ function example_neuron() box on axis square set(gcf,'Position',[100 300 1200 500]) - + figure subplot(2,3,1) hold on @@ -224,7 +224,7 @@ function example_neuron() set(gca,'XScale','log') box on axis square - + subplot(2,3,4) plot(abs(sol.s2z(:)),abs(sol.s2z(:)-s2z_fd(:)),'ro') hold on @@ -238,7 +238,7 @@ function example_neuron() set(gca,'XScale','log') box on axis square - + subplot(2,3,2) hold on plot(abs(sol.s2rz(:)),abs(s2rz_fd(:)),'bo') @@ -253,7 +253,7 @@ function example_neuron() set(gca,'XScale','log') box on axis square - + subplot(2,3,5) plot(abs(sol.s2rz(:)),abs(sol.s2rz(:)-s2rz_fd(:)),'ro') hold on @@ -267,7 +267,7 @@ function example_neuron() set(gca,'XScale','log') box on axis square - + subplot(2,3,3) hold on plot(abs(sol.s2llh),abs(s2llh_fd),'ko') @@ -282,7 +282,7 @@ function example_neuron() title('abs llh sensitivity') box on axis square - + subplot(2,3,6) plot(abs(sol.s2llh),abs(sol.s2llh-s2llh_fd),'ro') hold on @@ -297,8 +297,8 @@ function example_neuron() box on axis square set(gcf,'Position',[100 300 1200 500]) - + drawnow end -end \ No newline at end of file +end diff --git a/matlab/examples/example_neuron/model_neuron_syms.m b/matlab/examples/example_neuron/model_neuron_syms.m index e5786fb8ee..fddb869464 100644 --- a/matlab/examples/example_neuron/model_neuron_syms.m +++ b/matlab/examples/example_neuron/model_neuron_syms.m @@ -1,33 +1,33 @@ function model = neuron_syms() - + model.param = 'log10'; - - syms a b c d - + + syms a b c d + p = [a b c d]; - + syms v0 I0 - + k = [v0,I0]; - + syms v u - + x = [v u]; - + syms I t - + I = I0; - + f(1) = 0.04*v^2 + 5*v + 140 - u + I ; f(2) = a*(b*v - u); y(1) = v; - - + + x0 = [v0,b*v0]; - + event = amievent(v-30,[-c-v,d],t); - + model.sym.p = p; model.sym.k = k; model.sym.x = x; @@ -35,6 +35,6 @@ model.sym.f = f; model.event = event; model.sym.x0 = x0; - - -end \ No newline at end of file + + +end diff --git a/matlab/examples/example_robertson/example_robertson.m b/matlab/examples/example_robertson/example_robertson.m index a5daeaa8b4..b6b2bc08fc 100644 --- a/matlab/examples/example_robertson/example_robertson.m +++ b/matlab/examples/example_robertson/example_robertson.m @@ -70,7 +70,7 @@ function example_robertson() legend('error x1','error x2','error x3','Location','NorthEastOutside') legend boxoff ylabel('x') - + set(gcf,'Position',[100 300 1200 500]) end @@ -111,7 +111,7 @@ function example_robertson() set(gca,'XScale','log') ylabel('sx') box on - + subplot(length(p),2,ip*2) plot(t,abs(sol.sy(:,:,ip)-sy_fd(:,:,ip)),'--') legend('error sy1','error sy2','error sy3','Location','NorthEastOutside') @@ -124,8 +124,8 @@ function example_robertson() box on end set(gcf,'Position',[100 300 1200 500]) - - + + drawnow end -end \ No newline at end of file +end diff --git a/matlab/examples/example_robertson/model_robertson_syms.m b/matlab/examples/example_robertson/model_robertson_syms.m index bf95c21212..9cca91e566 100644 --- a/matlab/examples/example_robertson/model_robertson_syms.m +++ b/matlab/examples/example_robertson/model_robertson_syms.m @@ -18,12 +18,12 @@ % create parameter syms syms p1 p2 p3 -% create parameter vector +% create parameter vector model.sym.p = [p1,p2,p3]; % set the parametrisation of the problem options are 'log', 'log10' and % 'lin' (default). -model.param = 'log10'; +model.param = 'log10'; %% @@ -33,7 +33,7 @@ % create parameter syms syms k1 -% create parameter vector +% create parameter vector model.sym.k = [k1]; %% @@ -65,4 +65,4 @@ model.sym.y = model.sym.x; model.sym.y(2) = 1e4*model.sym.x(2); -end \ No newline at end of file +end diff --git a/matlab/examples/example_steadystate/example_steadystate.m b/matlab/examples/example_steadystate/example_steadystate.m index 118b1f5cf9..a6503527ff 100644 --- a/matlab/examples/example_steadystate/example_steadystate.m +++ b/matlab/examples/example_steadystate/example_steadystate.m @@ -1,46 +1,46 @@ function example_steadystate %% % COMPILATION - + [exdir,~,~]=fileparts(which('example_steadystate.m')); % compile the model amiwrap('model_steadystate','model_steadystate_syms',exdir) - + %% % SIMULATION - + % time vector t = linspace(0,100,50); p = [1;0.5;0.4;2;0.1]; k = [0.1,0.4,0.7,1]; - + options = amioption(... 'sensi', 0, ... 'maxsteps', 1e4 ... ); - + % load mex into memory simulate_model_steadystate(t,log10(p),k,[],options); - + tic; sol = simulate_model_steadystate([t, inf],log10(p),k,[],options); display(['Time elapsed with cvodes: ' num2str(toc) ' seconds']); - + %% % ODE15S - + ode_system = @(t,x,p,k) [-2*p(1)*x(1)^2 - p(2)*x(1)*x(2) + 2*p(3)*x(2) + p(4)*x(3) + p(5); + p(1)*x(1)^2 - p(2)*x(1)*x(2) - p(3)*x(2) + p(4)*x(3); + p(2)*x(1)*x(2) - p(4)*x(3) - k(4)*x(3)]; options_ode15s = odeset('RelTol',options.rtol,'AbsTol',options.atol,'MaxStep',options.maxsteps); - + tic; [~, X_ode15s] = ode15s(@(t,x) ode_system(t,x,p,k),t,k(1:3),options_ode15s); disp(['Time elapsed with ode15s: ' num2str(toc) ' seconds']) - + %% % PLOTTING - + if(usejava('jvm')) figure('Name', 'Example SteadyState'); c_x = get(gca,'ColorOrder'); @@ -63,19 +63,19 @@ legend boxoff; set(gcf,'Position',[100 300 1200 500]); end - + %% % FORWARD SENSITIVITY ANALYSIS - + options.sensi = 1; options.sens_ind = [3,1,2,4]; sol = simulate_model_steadystate([t, inf],log10(p),k,[],options); - + %% % FINITE DIFFERENCES - + eps = 1e-3; - + xi = log10(p); sx_ffd = zeros(length(t)+1, 3, length(p)); sx_bfd = zeros(length(t)+1, 3, length(p)); @@ -91,7 +91,7 @@ sx_bfd(:,:,ip) = (sol.x - solm.x) / eps; sx_cfd(:,:,ip) = (solp.x - solm.x) / (2*eps); end - + %% % PLOTTING if(usejava('jvm')) @@ -122,10 +122,10 @@ box on; end set(gcf,'Position',[100 300 1200 500]); - + sxss = squeeze(sol.sx(length(t),:,:)); sxss_fd = squeeze(sx_cfd(length(t),:,options.sens_ind)); - + % Sensitivities for steady state figure('Name', 'Example SteadyState'); subplot(1,2,1); @@ -148,8 +148,8 @@ xlabel('Steady state sensitivities'); ylabel('finite differences'); box on; - - + + subplot(1,2,2); hold on; for ip = 1:4 @@ -172,10 +172,10 @@ set(gca,'YScale','log'); set(gcf,'Position',[100 300 1200 500]); end - + %% % XDOT FOR DIFFERENT TIME POINTS - + t = [10,25,100,250,1000]; options.sensi = 0; ssxdot = NaN(length(t), size(sol.x, 2)); @@ -187,13 +187,13 @@ % Compute steady state wihtout integration before sol = simulate_model_steadystate(inf,log10(p),k,[],options); - + % Test recapturing in the case of Newton solver failing options.newton_maxsteps = 4; options.maxsteps = 300; sol_newton_fail = simulate_model_steadystate(inf,log10(p),k,[],options); - + %% % PLOTTING if(usejava('jvm')) @@ -216,7 +216,7 @@ box on; set(gca,'YScale','log'); set(gca,'XScale','log'); - + subplot(1,3,3); hold on; bar(sol_newton_fail.diagnosis.posteq_numsteps([1, 3])); @@ -229,10 +229,8 @@ a = gca(); a.Children.BarWidth = 0.6; box on; - + set(gcf,'Position',[100 300 1200 500]); end - -end - +end diff --git a/matlab/examples/example_steadystate/model_steadystate_syms.m b/matlab/examples/example_steadystate/model_steadystate_syms.m index 713cd1ad6a..75b441cff6 100644 --- a/matlab/examples/example_steadystate/model_steadystate_syms.m +++ b/matlab/examples/example_steadystate/model_steadystate_syms.m @@ -18,12 +18,12 @@ % create parameter syms syms p1 p2 p3 p4 p5 -% create parameter vector +% create parameter vector model.sym.p = [p1,p2,p3,p4,p5]; % set the parametrisation of the problem options are 'log', 'log10' and % 'lin' (default). -model.param = 'log10'; +model.param = 'log10'; %% @@ -33,7 +33,7 @@ % create parameter syms syms k1 k2 k3 k4 -% create parameter vector +% create parameter vector model.sym.k = [k1 k2 k3 k4]; %% @@ -63,4 +63,4 @@ % OBSERVALES model.sym.y = model.sym.x; -end \ No newline at end of file +end diff --git a/matlab/installAMICI.m b/matlab/installAMICI.m index 9a6ccbc1c4..d3e40dd107 100644 --- a/matlab/installAMICI.m +++ b/matlab/installAMICI.m @@ -3,4 +3,4 @@ addpath(fullfile(amipath,'auxiliary')) addpath(fullfile(amipath,'auxiliary','CalcMD5')) addpath(fullfile(amipath,'symbolic')) -addpath(fullfile(amipath,'SBMLimporter')) \ No newline at end of file +addpath(fullfile(amipath,'SBMLimporter')) diff --git a/matlab/mtoc/MatlabDocMaker.m b/matlab/mtoc/MatlabDocMaker.m index cdbcfd7dd9..8160635238 100644 --- a/matlab/mtoc/MatlabDocMaker.m +++ b/matlab/mtoc/MatlabDocMaker.m @@ -23,7 +23,7 @@ % @change{1,5,dw,2013-12-03} Fixed default value selection for properties, % now not having set a description or logo does not cause an error to be % thrown. -% +% % @change{1,5,dw,2013-02-21} Fixed the callback for suggested direct documentation creation % after MatlabDocMaker.setup (Thanks to Aurelien Queffurust) % @@ -111,7 +111,7 @@ % % @type char @default 'Doxyfile.template' DOXYFILE_TEMPLATE = 'Doxyfile.template'; - + % File name for the latex extras style file processed by the MatlabDocMaker. % % Assumed to reside in the MatlabDocMaker.getConfigDirectory. @@ -119,7 +119,7 @@ % % @type char @default 'latexextras.template' LATEXEXTRAS_TEMPLATE = 'latexextras.template'; - + % File name the mtoc++ configuration file. % % Assumed to reside in the MatlabDocMaker.getConfigDirectory. @@ -139,17 +139,17 @@ % % Return values: % name: The project name @type char - + %error('Please replace this by returning your project name as string.'); % Example: name = 'AMICI'; end end - + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% End of user defined part. %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - + methods(Static, Sealed) function dir = getOutputDirectory % Returns the directory where the applications source files @@ -159,7 +159,7 @@ % dir: The output directory @type char dir = MatlabDocMaker.getPref('outdir'); end - + function dir = getSourceDirectory % Returns the directory where the applications source files % reside @@ -168,7 +168,7 @@ % dir: The project source directory @type char dir = MatlabDocMaker.getPref('srcdir'); end - + function dir = getConfigDirectory % Returns the directory where the applications documentation % configuration files reside @@ -180,7 +180,7 @@ % dir: The documentation configuration directory @type char dir = MatlabDocMaker.getPref('confdir'); end - + function desc = getProjectDescription % Returns the short project description. % @@ -190,7 +190,7 @@ % See also: setProjectDescription desc = MatlabDocMaker.getPref('proj_desc', ''); end - + function setProjectDescription(value) % Sets the project description. % @@ -203,7 +203,7 @@ function setProjectDescription(value) end MatlabDocMaker.setPref('proj_desc', value); end - + function version = getProjectVersion % Returns the current version of the project. % @@ -216,7 +216,7 @@ function setProjectDescription(value) % See also: setProjectVersion version = MatlabDocMaker.getPref('proj_ver', '0'); end - + function setProjectVersion(value) % Sets the project version. % @@ -229,7 +229,7 @@ function setProjectVersion(value) end MatlabDocMaker.setPref('proj_ver', value); end - + function fullPath = getProjectLogo % Returns the logo image file for the project. Either an absolute path or a plain % filename. For the latter case the image file is assumed to reside inside the @@ -253,7 +253,7 @@ function setProjectVersion(value) end end end - + function setProjectLogo(value) % Sets the project logo. Set to '' to unset. % @@ -289,9 +289,9 @@ function setProjectLogo(value) MatlabDocMaker.setPref('proj_logo', value); end end - + methods(Static) - + function open % Opens the generated documentation. % @@ -308,7 +308,7 @@ function setProjectLogo(value) end end end - + function create(varargin) % Creates the Doxygen documentation % @@ -318,14 +318,14 @@ function create(varargin) % successful compilation @type logical @default false % latex: Set to true if `\text{\LaTeX}` output should be generated, too. @type logical % @default false - + %% Preparations ip = inputParser; ip.addParameter('open',false,@islogical); ip.addParameter('latex',false,@islogical); ip.parse(varargin{:}); genlatex = ip.Results.latex; - + % Check for correct setup cdir = MatlabDocMaker.getConfigDirectory; srcdir = MatlabDocMaker.getSourceDirectory; @@ -335,7 +335,7 @@ function create(varargin) if exist(doxyfile_in,'file') ~= 2 error('No doxygen configuration file template found at "%s"',doxyfile_in); end - + lstr = ''; if genlatex lstr = '(+Latex)'; @@ -344,7 +344,7 @@ function create(varargin) 'Sources: %s\nOutput to: %s\nCreating config files...'],lstr,... MatlabDocMaker.getProjectName,MatlabDocMaker.getProjectVersion,... srcdir,outdir); - + % Operation-system dependent strings strs = struct; if isunix @@ -356,17 +356,17 @@ function create(varargin) else error('Current platform not supported.'); end - + % Save current working dir and change into the KerMor home % directory; only from there all classes and packages are % detected properly. curdir = pwd; cd(srcdir); - + % Append the configuration file directory to the current PATH pathadd = [pathsep cdir]; setenv('PATH',[getenv('PATH') pathadd]); - + mtoc_conf = fullfile(cdir,MatlabDocMaker.MTOCPP_CONFIGFILE); filter = sprintf('%smtocpp',strs.silencer); if exist(mtoc_conf,'file') @@ -381,7 +381,7 @@ function create(varargin) end %% Creation part cdir = MatlabDocMaker.getConfigDirectory; - % Create "configured" filter script for inclusion in doxygen + % Create "configured" filter script for inclusion in doxygen filter = fullfile(cdir,strs.filter); f = fopen(filter,'w'); fprintf(f,'%smtocpp %s %s',strs.silencer,strs.farg,mtoc_conf); @@ -390,7 +390,7 @@ function create(varargin) unix(['chmod +x ' filter]); end end - + %% Prepare placeholders in the Doxyfile template m = {'_OutputDir_' strrep(outdir,'\','\\'); ... '_SourceDir_' strrep(MatlabDocMaker.getSourceDirectory,'\','\\');... @@ -401,11 +401,11 @@ function create(varargin) '_ProjectVersion_' MatlabDocMaker.getProjectVersion; ... '_MTOCFILTER_' strrep(filter,'\','\\'); ... }; - + % Check for latex extra stuff texin = fullfile(cdir,MatlabDocMaker.LATEXEXTRAS_TEMPLATE); latexextras = ''; - if exist(texin,'file') == 2 + if exist(texin,'file') == 2 latexstr = strrep(fileread(texin),'_ConfDir_',strrep(cdir,'\','/')); latexextras = fullfile(cdir,'latexextras.sty'); fid = fopen(latexextras,'w+'); fprintf(fid,'%s',latexstr); fclose(fid); @@ -418,7 +418,7 @@ function create(varargin) L = 'YES'; end m(end+1,:) = {'_GenLatex_',L}; - + % Check how to set the HAVE_DOT flag [s, ~] = system('dot -V'); if s == 0 @@ -428,12 +428,12 @@ function create(varargin) fprintf('no "dot" found...'); end m(end+1,:) = {'_HaveDot_',HD}; - + % Read, replace & write doxygen config file doxyfile = fullfile(cdir,'Doxyfile'); doxyconfstr = regexprep(fileread(doxyfile_in),m(:,1),m(:,2)); fid = fopen(doxyfile,'w'); fprintf(fid,'%s',doxyconfstr); fclose(fid); - + % Fix for unix systems where the MatLab installation uses older % GLIBSTD libraries than doxygen/mtoc++ ldpath = ''; @@ -443,7 +443,7 @@ function create(varargin) % Call doxygen fprintf('running doxygen with mtoc++ filter...'); [~,warn] = system(sprintf('%sdoxygen "%s" 1>%s',ldpath, doxyfile, strs.null)); - + % Postprocess fprintf('running mtoc++ postprocessor...'); [~,postwarn] = system(sprintf('%smtocpp_post "%s" 1>%s',ldpath,... @@ -451,7 +451,7 @@ function create(varargin) if ~isempty(postwarn) warn = [warn sprintf('mtoc++ postprocessor messages:\n') postwarn]; end - + % Create latex document if desired if genlatex oldd = pwd; @@ -460,7 +460,7 @@ function create(varargin) if exist(latexdir,'dir') == 7 if exist(fullfile(latexdir,'refman.tex'),'file') == 2 fprintf('compiling LaTeX output...'); - cd(latexdir); + cd(latexdir); [s, latexmsg] = system('make'); if s ~= 0 warn = [warn sprintf('LaTeX compiler output:\n') latexmsg]; @@ -472,7 +472,7 @@ function create(varargin) end cd(oldd); end - + % Tidy up fprintf('cleaning up...'); if isfield(strs,'filter') @@ -482,14 +482,14 @@ function create(varargin) delete(latexextras); end delete(doxyfile); - - %% Post generation phase + + %% Post generation phase cd(curdir); % Restore PATH to previous value curpath = getenv('PATH'); setenv('PATH',curpath(1:end-length(pathadd))); fprintf('done!\n'); - + % Process warnings showchars = 800; warn = strtrim(warn); @@ -514,21 +514,21 @@ function create(varargin) else fprintf('MatlabDocMaker finished with no warnings!\n'); end - + % Open index.html if wanted if ip.Results.open MatlabDocMaker.open; end end - + function setup % Runs the setup script for MatlabDocMaker and collects all % necessary paths in order for the documentation creation to % work properly. - + %% Validity checks fprintf('<<<< Welcome to the MatlabDocMaker setup for your project "%s"! >>>>\n',MatlabDocMaker.getProjectName); - + %% Setup directories % Source directory srcdir = MatlabDocMaker.getPref('srcdir',''); @@ -547,7 +547,7 @@ function create(varargin) srcdir = d; end MatlabDocMaker.setPref('srcdir',srcdir); - + % Config directory confdir = MatlabDocMaker.getPref('confdir',''); word = 'keep'; @@ -565,7 +565,7 @@ function create(varargin) confdir = d; end MatlabDocMaker.setPref('confdir',confdir); - + % Output directory outdir = MatlabDocMaker.getPref('outdir',''); word = 'keep'; @@ -583,7 +583,7 @@ function create(varargin) outdir = d; end MatlabDocMaker.setPref('outdir',outdir); - + %% Additional Project properties if isequal(lower(input(['Do you want to specify further project details?\n'... 'You can set them later using provided set methods. (Y)es/(N)o?: '],'s')),'y') @@ -591,7 +591,7 @@ function create(varargin) MatlabDocMaker.setPref('proj_desc',input('Please specify a short project description: ','s')); MatlabDocMaker.setProjectLogo; end - + %% Check for necessary and recommended tools hasall = true; setenv('PATH',[getenv('PATH') pathsep confdir]); @@ -601,7 +601,7 @@ function create(varargin) fprintf(' found %s\n',vers(1:end-1)); else fprintf(2,' not found!\n'); - hasall = false; + hasall = false; end fprintf('[Required] Checking for mtoc++... '); ldpath = ''; @@ -653,20 +653,20 @@ function create(varargin) end end end - + methods(Static, Access=private) function value = getProjPrefTag % Gets the tag for the MatLab preferences struct. - % + % % @change{0,7,dw,2013-04-02} Now also removing "~" and "-" characters from ProjectName tags for preferences. str = regexprep(strrep(strtrim(MatlabDocMaker.getProjectName),' ','_'),'[^\d\w]',''); value = sprintf('MatlabDocMaker_on_%s',str); end - + function value = getPref(name, default) if nargin < 2 def = []; - else + else def = default; end value = getpref(MatlabDocMaker.getProjPrefTag,name,def); @@ -674,7 +674,7 @@ function create(varargin) error('MatlabDocMaker preferences not found/set correctly. (Re-)Run the MatlabDocMaker.setup method.'); end end - + function value = setPref(name, value) setpref(MatlabDocMaker.getProjPrefTag,name,value); end diff --git a/matlab/mtoc/config/customdoxygen.css b/matlab/mtoc/config/customdoxygen.css index 685cdb5d00..8a2de47fbf 100644 --- a/matlab/mtoc/config/customdoxygen.css +++ b/matlab/mtoc/config/customdoxygen.css @@ -138,11 +138,11 @@ a.elRef { } a.code, a.code:visited, a.line, a.line:visited { - color: #4665A2; + color: #4665A2; } a.codeRef, a.codeRef:visited, a.lineRef, a.lineRef:visited { - color: #4665A2; + color: #4665A2; } /* @end */ @@ -294,7 +294,7 @@ p.formulaDsp { } img.formulaDsp { - + } img.formulaInl { @@ -352,20 +352,20 @@ span.charliteral { color: #008080 } -span.vhdldigit { - color: #ff00ff +span.vhdldigit { + color: #ff00ff } -span.vhdlchar { - color: #000000 +span.vhdlchar { + color: #000000 } -span.vhdlkeyword { - color: #700070 +span.vhdlkeyword { + color: #700070 } -span.vhdllogic { - color: #ff0000 +span.vhdllogic { + color: #ff0000 } blockquote { @@ -560,9 +560,9 @@ table.memberdecls { } .memdoc, dl.reflist dd { - border-bottom: 1px solid #A8B8D9; - border-left: 1px solid #A8B8D9; - border-right: 1px solid #A8B8D9; + border-bottom: 1px solid #A8B8D9; + border-left: 1px solid #A8B8D9; + border-right: 1px solid #A8B8D9; padding: 6px 10px 2px 10px; border-top-width: 0; background-image:url('nav_g.png'); @@ -613,18 +613,18 @@ dl.reflist dd { .params, .retval, .exception, .tparams { margin-left: 0px; padding-left: 0px; -} +} .params .paramname, .retval .paramname { font-weight: bold; vertical-align: top; } - + .params .paramtype { font-style: italic; vertical-align: top; -} - +} + .params .paramdir { font-family: "courier new",courier,monospace; vertical-align: top; @@ -876,8 +876,8 @@ table.fieldtable { .fieldtable td.fielddoc p:first-child { margin-top: 0px; -} - +} + .fieldtable td.fielddoc p:last-child { margin-bottom: 2px; } @@ -950,7 +950,7 @@ table.fieldtable { color: #283A5D; font-family: 'Lucida Grande',Geneva,Helvetica,Arial,sans-serif; text-shadow: 0px 1px 1px rgba(255, 255, 255, 0.9); - text-decoration: none; + text-decoration: none; } .navpath li.navelem a:hover @@ -979,7 +979,7 @@ div.summary padding-right: 5px; width: 50%; text-align: right; -} +} div.summary a { @@ -1091,12 +1091,12 @@ dl.section dd { vertical-align: bottom; border-collapse: separate; } - + #projectlogo img -{ +{ border: 0px none; } - + #projectalign { vertical-align: middle; @@ -1107,7 +1107,7 @@ dl.section dd { font-size: 200%; font-weight: bold; } - + #projectbrief { font-size: 100%; @@ -1206,7 +1206,7 @@ div.toc ul { list-style: none outside none; border: medium none; padding: 0px; -} +} div.toc li.level1 { margin-left: 0px; @@ -1522,5 +1522,3 @@ div#nav-path ul { border-left: 1px solid #d1d5da; border-right: 1px solid #d1d5da; } - - diff --git a/matlab/mtoc/config/latexextras.template b/matlab/mtoc/config/latexextras.template index 352d89e27e..5abd2c045c 100644 --- a/matlab/mtoc/config/latexextras.template +++ b/matlab/mtoc/config/latexextras.template @@ -1,7 +1,7 @@ % Additional LaTeX inclusions for mtoc++/doxygen tools % % Use the _ConfDir_ tag to insert the folder where this file resides. -% Thus you can include more custom latex files/styles/packages which reside in this folder +% Thus you can include more custom latex files/styles/packages which reside in this folder % Default packages \usepackage{amsmath} @@ -12,4 +12,4 @@ %\input{_ConfDirFwdSlash_/myexternalstyle.sty} \setcounter{tocdepth}{2} -%\uchyph=0 \ No newline at end of file +%\uchyph=0 diff --git a/matlab/mtoc/config/mtocpp.conf b/matlab/mtoc/config/mtocpp.conf index 2a792649a1..9be3840ae2 100644 --- a/matlab/mtoc/config/mtocpp.conf +++ b/matlab/mtoc/config/mtocpp.conf @@ -71,9 +71,9 @@ COPY_TYPIFIED_FIELD_DOCU := false; # By default their documentation strings are ignored. GENERATE_SUBFUNTION_DOCUMENTATION := true; -# Leave this ## there, it marks the end of variable definitions +# Leave this ## there, it marks the end of variable definitions # and switches the parser to mtoc++ rules! -## +## # ########################### mtoc++ rules ############################ @@ -102,6 +102,6 @@ GENERATE_SUBFUNTION_DOCUMENTATION := true; # # }; # } - + # add(doc) = """ docu for all functions !!! """; # add(extra) = """ extra comments: @ref s_rand !!!! """; diff --git a/matlab/symbolic/am_and.m b/matlab/symbolic/am_and.m index 6362b6e8a0..5d8f5a1e06 100644 --- a/matlab/symbolic/am_and.m +++ b/matlab/symbolic/am_and.m @@ -8,4 +8,4 @@ % Return values: % fun: logical value, negative for false, positive for true fun = am_min(a,b); -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_eq.m b/matlab/symbolic/am_eq.m index 6ede0e5609..a366dbbe40 100644 --- a/matlab/symbolic/am_eq.m +++ b/matlab/symbolic/am_eq.m @@ -2,9 +2,9 @@ % am_eq is currently a placeholder that simply produces an error message % % Parameters: -% varargin: elements for chain of equalities +% varargin: elements for chain of equalities % % Return values: % fun: logical value, negative for false, positive for true error('Logical operator ''eq'' is currently not supported!'); -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_ge.m b/matlab/symbolic/am_ge.m index 8225db7069..28ba88ff8b 100644 --- a/matlab/symbolic/am_ge.m +++ b/matlab/symbolic/am_ge.m @@ -14,4 +14,4 @@ if(nargin>2) fun = am_and(a-b,am_ge(varargin{2:end})); end -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_gt.m b/matlab/symbolic/am_gt.m index 3c076b3ab0..cd2b420334 100644 --- a/matlab/symbolic/am_gt.m +++ b/matlab/symbolic/am_gt.m @@ -14,4 +14,4 @@ if(nargin>2) fun = am_and(a-b,am_gt(varargin{2:end})); end -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_if.m b/matlab/symbolic/am_if.m index 6673023a68..729df34153 100644 --- a/matlab/symbolic/am_if.m +++ b/matlab/symbolic/am_if.m @@ -21,4 +21,4 @@ fun = falsepart; end end -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_le.m b/matlab/symbolic/am_le.m index 3016b7d6b2..49fda85721 100644 --- a/matlab/symbolic/am_le.m +++ b/matlab/symbolic/am_le.m @@ -14,4 +14,4 @@ if(nargin>2) fun = am_and(b-a,am_le(varargin{2:end})); end -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_lt.m b/matlab/symbolic/am_lt.m index 67ec79f601..53b3006bd9 100644 --- a/matlab/symbolic/am_lt.m +++ b/matlab/symbolic/am_lt.m @@ -14,4 +14,4 @@ if(nargin>2) fun = am_and(b-a,am_lt(varargin{2:end})); end -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_min.m b/matlab/symbolic/am_min.m index 068d2dc2d6..3059eccaff 100644 --- a/matlab/symbolic/am_min.m +++ b/matlab/symbolic/am_min.m @@ -8,4 +8,4 @@ % Return values: % fun: minimum of a and b fun = -am_max(-a,-b); -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_or.m b/matlab/symbolic/am_or.m index e948070061..0c5d484232 100644 --- a/matlab/symbolic/am_or.m +++ b/matlab/symbolic/am_or.m @@ -8,4 +8,4 @@ % Return values: % fun: logical value, negative for false, positive for true fun = am_max(a,b); -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_piecewise.m b/matlab/symbolic/am_piecewise.m index 7a9be67bff..07b2f6205d 100644 --- a/matlab/symbolic/am_piecewise.m +++ b/matlab/symbolic/am_piecewise.m @@ -10,4 +10,3 @@ % fun: return value, piece if condition is true, default if not fun = am_if(condition,piece,default); end - diff --git a/matlab/symbolic/am_spline_pos.m b/matlab/symbolic/am_spline_pos.m index 32775d48e6..de3f9e2c49 100644 --- a/matlab/symbolic/am_spline_pos.m +++ b/matlab/symbolic/am_spline_pos.m @@ -28,6 +28,6 @@ end str = strcat('(',strcat(strcat(str, char(varargin{n})), ')')); str = strrep(str, ' ', ''); - + splinefun = sym(strcat('spline_pos', str)); end diff --git a/matlab/symbolic/am_stepfun.m b/matlab/symbolic/am_stepfun.m index 53749f1d4c..3cc3052572 100644 --- a/matlab/symbolic/am_stepfun.m +++ b/matlab/symbolic/am_stepfun.m @@ -1,5 +1,5 @@ function fun = am_stepfun(t,tstart,vstart,tend,vend) -% am_stepfun is the amici implementation of the step function +% am_stepfun is the amici implementation of the step function % % Parameters: % t: input variable @type sym @@ -11,4 +11,4 @@ % Return values: % fun: 0 before tstart, vstart between tstart and tend and vend after tend fun = heaviside(t-tstart)*vstart - heaviside(t-tend)*(vstart-vend); -end \ No newline at end of file +end diff --git a/matlab/symbolic/am_xor.m b/matlab/symbolic/am_xor.m index 9f8b26a238..93514c6c50 100644 --- a/matlab/symbolic/am_xor.m +++ b/matlab/symbolic/am_xor.m @@ -9,4 +9,4 @@ % fun: logical value, negative for false, positive for true fun = am_and(am_or(a,b),-am_and(a,b)); -end \ No newline at end of file +end diff --git a/python/benchmark/benchmark_pysb.py b/python/benchmark/benchmark_pysb.py index add85092ec..0b8b168d82 100644 --- a/python/benchmark/benchmark_pysb.py +++ b/python/benchmark/benchmark_pysb.py @@ -18,7 +18,7 @@ from pysb.simulator import ScipyOdeSimulator -sys.path.insert(0, os.path.join('..', 'tests')) +sys.path.insert(0, os.path.join("..", "tests")) from test_pysb import pysb_models simulation_times = dict() @@ -32,37 +32,39 @@ simulation_times[example] = dict() with amici.add_path(os.path.dirname(pysb.examples.__file__)): - with amici.add_path(os.path.join(os.path.dirname(__file__), '..', - 'tests', 'pysb_test_models')): - + with amici.add_path( + os.path.join(os.path.dirname(__file__), "..", "tests", "pysb_test_models") + ): pysb.SelfExporter.cleanup() # reset pysb pysb.SelfExporter.do_export = True module = importlib.import_module(example) pysb_model = module.model - pysb_model.name = pysb_model.name.replace('pysb.examples.', '') + pysb_model.name = pysb_model.name.replace("pysb.examples.", "") # avoid naming clash for custom pysb models - pysb_model.name += '_amici' + pysb_model.name += "_amici" # pysb part tspan = np.linspace(0, 100, 101) sim = ScipyOdeSimulator( pysb_model, tspan=tspan, - integrator_options={'rtol': rtol, 'atol': atol}, + integrator_options={"rtol": rtol, "atol": atol}, + ) + time_pysb = ( + timeit.Timer("pysb_simres = sim.run()", globals={"sim": sim}).timeit( + number=N_REPEATS + ) + / N_REPEATS ) - time_pysb = timeit.Timer( - 'pysb_simres = sim.run()', - globals={'sim': sim} - ).timeit(number=N_REPEATS)/N_REPEATS - simulation_times[example]['pysb'] = time_pysb - print(f'PySB average simulation time {example}: {time_pysb}') + simulation_times[example]["pysb"] = time_pysb + print(f"PySB average simulation time {example}: {time_pysb}") # amici part outdir = pysb_model.name - if pysb_model.name in ['move_connected_amici']: + if pysb_model.name in ["move_connected_amici"]: compute_conservation_laws = False else: compute_conservation_laws = True @@ -71,11 +73,10 @@ pysb_model, outdir, compute_conservation_laws=compute_conservation_laws, - observables=list(pysb_model.observables.keys()) + observables=list(pysb_model.observables.keys()), ) - amici_model_module = amici.import_model_module(pysb_model.name, - outdir) + amici_model_module = amici.import_model_module(pysb_model.name, outdir) model_pysb = amici_model_module.getModel() @@ -85,27 +86,29 @@ solver.setMaxSteps(int(1e6)) solver.setAbsoluteTolerance(atol) solver.setRelativeTolerance(rtol) - time_amici = timeit.Timer( - 'rdata = amici.runAmiciSimulation(model, solver)', - globals={'model': model_pysb, 'solver': solver, - 'amici': amici} - ).timeit(number=N_REPEATS)/N_REPEATS - simulation_times[example]['amici'] = time_amici - print(f'AMICI average simulation time {example}: {time_amici}') + time_amici = ( + timeit.Timer( + "rdata = amici.runAmiciSimulation(model, solver)", + globals={"model": model_pysb, "solver": solver, "amici": amici}, + ).timeit(number=N_REPEATS) + / N_REPEATS + ) + simulation_times[example]["amici"] = time_amici + print(f"AMICI average simulation time {example}: {time_amici}") times = pd.DataFrame(simulation_times) -ax = times.T.plot(kind='scatter', x='pysb', y='amici') -ax.set_xscale('log') -ax.set_yscale('log') -ax.set_aspect('equal') +ax = times.T.plot(kind="scatter", x="pysb", y="amici") +ax.set_xscale("log") +ax.set_yscale("log") +ax.set_aspect("equal") xy_min = np.min([ax.get_xlim()[0], ax.get_ylim()[0]]) xy_max = np.max([ax.get_xlim()[1], ax.get_ylim()[1]]) ax.set_xlim([xy_min, xy_max]) ax.set_ylim([xy_min, xy_max]) -ax.set_ylabel('simulation time AMICI [s]') -ax.set_xlabel('simulation time PySB [s]') -ax.plot([xy_min, xy_max], [xy_min, xy_max], 'k:') +ax.set_ylabel("simulation time AMICI [s]") +ax.set_xlabel("simulation time PySB [s]") +ax.plot([xy_min, xy_max], [xy_min, xy_max], "k:") plt.tight_layout() -plt.savefig('benchmark_pysb.eps') +plt.savefig("benchmark_pysb.eps") diff --git a/python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb b/python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb index 40ecb9f7aa..7c8ceec6cd 100644 --- a/python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb +++ b/python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb @@ -1204,4 +1204,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/python/examples/example_petab/petab.ipynb b/python/examples/example_petab/petab.ipynb index e1861e5f13..27ee96e449 100644 --- a/python/examples/example_petab/petab.ipynb +++ b/python/examples/example_petab/petab.ipynb @@ -370,4 +370,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/python/examples/example_presimulation/createModelPresimulation.py b/python/examples/example_presimulation/createModelPresimulation.py index c2d373156c..a1e672594e 100644 --- a/python/examples/example_presimulation/createModelPresimulation.py +++ b/python/examples/example_presimulation/createModelPresimulation.py @@ -1,60 +1,59 @@ -from pysb.core import ( - Rule, Parameter, Model, Monomer, Expression, Initial, Observable -) +from pysb.core import Rule, Parameter, Model, Monomer, Expression, Initial, Observable import pysb.export import os model = Model() -prot = Monomer('PROT', ['kin', 'drug', 'phospho'], {'phospho': ['u', 'p']}) -prot_0 = Parameter('PROT_0', 10) -Initial(prot(phospho='u', drug=None, kin=None), - Expression('initProt', prot_0)) - -drug = Monomer('DRUG', ['bound']) -drug_0 = Parameter('DRUG_0', 9) -Initial(drug(bound=None), - Expression('initDrug', drug_0)) - -kin = Monomer('KIN', ['bound']) -kin_0 = Parameter('KIN_0', 1) -Initial(kin(bound=None), - Expression('initKin', kin_0)) - -Rule('PROT_DRUG_bind', - drug(bound=None) + prot(phospho='u', drug=None, kin=None) | - drug(bound=1) % prot(phospho='u', drug=1, kin=None), - Parameter('kon_prot_drug', 0.1), - Parameter('koff_prot_drug', 0.1) - ) - -Rule('PROT_KIN_bind', - kin(bound=None) + prot(phospho='u', drug=None, kin=None) >> - kin(bound=1) % prot(phospho='u', drug=None, kin=1), - Parameter('kon_prot_kin', 0.1), - ) - -Rule('PROT_KIN_phospho', - kin(bound=1) % prot(phospho='u', drug=None, kin=1) >> - kin(bound=None) + prot(phospho='p', drug=None, kin=None), - Parameter('kphospho_prot_kin', 0.1) - ) - -Rule('PROT_dephospho', - prot(phospho='p', drug=None, kin=None) >> - prot(phospho='u', drug=None, kin=None), - Parameter('kdephospho_prot', 0.1) - ) - -pProt = Observable('pPROT', prot(phospho='p')) -tProt = Observable('tPROT', prot()) - -Expression('pPROT_obs', pProt/tProt) - -sbml_output = pysb.export.export(model, format='sbml') - -outfile = os.path.join(os.path.dirname(os.path.realpath(__file__)), - 'model_presimulation.xml') -with open(outfile, 'w') as f: +prot = Monomer("PROT", ["kin", "drug", "phospho"], {"phospho": ["u", "p"]}) +prot_0 = Parameter("PROT_0", 10) +Initial(prot(phospho="u", drug=None, kin=None), Expression("initProt", prot_0)) + +drug = Monomer("DRUG", ["bound"]) +drug_0 = Parameter("DRUG_0", 9) +Initial(drug(bound=None), Expression("initDrug", drug_0)) + +kin = Monomer("KIN", ["bound"]) +kin_0 = Parameter("KIN_0", 1) +Initial(kin(bound=None), Expression("initKin", kin_0)) + +Rule( + "PROT_DRUG_bind", + drug(bound=None) + prot(phospho="u", drug=None, kin=None) + | drug(bound=1) % prot(phospho="u", drug=1, kin=None), + Parameter("kon_prot_drug", 0.1), + Parameter("koff_prot_drug", 0.1), +) + +Rule( + "PROT_KIN_bind", + kin(bound=None) + prot(phospho="u", drug=None, kin=None) + >> kin(bound=1) % prot(phospho="u", drug=None, kin=1), + Parameter("kon_prot_kin", 0.1), +) + +Rule( + "PROT_KIN_phospho", + kin(bound=1) % prot(phospho="u", drug=None, kin=1) + >> kin(bound=None) + prot(phospho="p", drug=None, kin=None), + Parameter("kphospho_prot_kin", 0.1), +) + +Rule( + "PROT_dephospho", + prot(phospho="p", drug=None, kin=None) >> prot(phospho="u", drug=None, kin=None), + Parameter("kdephospho_prot", 0.1), +) + +pProt = Observable("pPROT", prot(phospho="p")) +tProt = Observable("tPROT", prot()) + +Expression("pPROT_obs", pProt / tProt) + +sbml_output = pysb.export.export(model, format="sbml") + +outfile = os.path.join( + os.path.dirname(os.path.realpath(__file__)), "model_presimulation.xml" +) +with open(outfile, "w") as f: f.write(sbml_output) diff --git a/python/examples/example_splines_swameye/Swameye_PNAS2003/swameye2003_conditions.tsv b/python/examples/example_splines_swameye/Swameye_PNAS2003/swameye2003_conditions.tsv index 50320c9453..97ed387788 100644 --- a/python/examples/example_splines_swameye/Swameye_PNAS2003/swameye2003_conditions.tsv +++ b/python/examples/example_splines_swameye/Swameye_PNAS2003/swameye2003_conditions.tsv @@ -1,2 +1,2 @@ conditionId conditionName -condition1 +condition1 diff --git a/python/examples/example_steadystate/ExampleSteadystate.ipynb b/python/examples/example_steadystate/ExampleSteadystate.ipynb index 52f014dba8..0d9765e727 100644 --- a/python/examples/example_steadystate/ExampleSteadystate.ipynb +++ b/python/examples/example_steadystate/ExampleSteadystate.ipynb @@ -1975,4 +1975,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/python/examples/example_steadystate/model_steadystate_scaled.xml b/python/examples/example_steadystate/model_steadystate_scaled.xml index dc5ae669f7..980b7b72a3 100644 --- a/python/examples/example_steadystate/model_steadystate_scaled.xml +++ b/python/examples/example_steadystate/model_steadystate_scaled.xml @@ -196,4 +196,3 @@ - diff --git a/python/examples/example_steadystate/model_steadystate_scaled_without_observables.xml b/python/examples/example_steadystate/model_steadystate_scaled_without_observables.xml index 62c673e513..e55a2c7892 100644 --- a/python/examples/example_steadystate/model_steadystate_scaled_without_observables.xml +++ b/python/examples/example_steadystate/model_steadystate_scaled_without_observables.xml @@ -150,4 +150,3 @@ - diff --git a/python/examples/example_units/model_units.xml b/python/examples/example_units/model_units.xml index 9108ad78cf..09e2e40ac4 100644 --- a/python/examples/example_units/model_units.xml +++ b/python/examples/example_units/model_units.xml @@ -86,4 +86,4 @@ - \ No newline at end of file + diff --git a/python/sdist/amici/__init__.py b/python/sdist/amici/__init__.py index ec6756204a..c1f5736501 100644 --- a/python/sdist/amici/__init__.py +++ b/python/sdist/amici/__init__.py @@ -23,7 +23,7 @@ def _get_amici_path(): repository, get repository root """ basedir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) - if os.path.exists(os.path.join(basedir, '.git')): + if os.path.exists(os.path.join(basedir, ".git")): return os.path.abspath(basedir) return os.path.dirname(__file__) @@ -33,18 +33,20 @@ def _get_commit_hash(): basedir = os.path.dirname(os.path.dirname(os.path.dirname(amici_path))) commitfile = next( ( - file for file in [ - os.path.join(basedir, '.git', 'FETCH_HEAD'), - os.path.join(basedir, '.git', 'ORIG_HEAD'), ] + file + for file in [ + os.path.join(basedir, ".git", "FETCH_HEAD"), + os.path.join(basedir, ".git", "ORIG_HEAD"), + ] if os.path.isfile(file) ), - None + None, ) if commitfile: with open(commitfile) as f: - return str(re.search(r'^([\w]*)', f.read().strip()).group()) - return 'unknown' + return str(re.search(r"^([\w]*)", f.read().strip()).group()) + return "unknown" def _imported_from_setup() -> bool: @@ -64,8 +66,8 @@ def _imported_from_setup() -> bool: # requires the AMICI extension during its installation, but seems # unlikely... frame_path = os.path.realpath(os.path.expanduser(frame.filename)) - if (frame_path == os.path.join(package_root, 'setup.py') - or frame_path.endswith(f"{sep}setuptools{sep}build_meta.py") + if frame_path == os.path.join(package_root, "setup.py") or frame_path.endswith( + f"{sep}setuptools{sep}build_meta.py" ): return True @@ -76,20 +78,22 @@ def _imported_from_setup() -> bool: #: absolute root path of the amici repository or Python package amici_path = _get_amici_path() #: absolute path of the amici swig directory -amiciSwigPath = os.path.join(amici_path, 'swig') +amiciSwigPath = os.path.join(amici_path, "swig") #: absolute path of the amici source directory -amiciSrcPath = os.path.join(amici_path, 'src') +amiciSrcPath = os.path.join(amici_path, "src") #: absolute root path of the amici module amiciModulePath = os.path.dirname(__file__) #: boolean indicating if this is the full package with swig interface or # the raw package without extension -has_clibs: bool = any(os.path.isfile(os.path.join(amici_path, wrapper)) - for wrapper in ['amici.py', 'amici_without_hdf5.py']) +has_clibs: bool = any( + os.path.isfile(os.path.join(amici_path, wrapper)) + for wrapper in ["amici.py", "amici_without_hdf5.py"] +) #: boolean indicating if amici was compiled with hdf5 support hdf5_enabled: bool = False # Get version number from file -with open(os.path.join(amici_path, 'version.txt')) as f: +with open(os.path.join(amici_path, "version.txt")) as f: __version__ = f.read().strip() __commit__ = _get_commit_hash() @@ -99,9 +103,10 @@ def _imported_from_setup() -> bool: if has_clibs: from . import amici from .amici import * + # has to be done before importing readSolverSettingsFromHDF5 # from .swig_wrappers - hdf5_enabled = 'readSolverSettingsFromHDF5' in dir() + hdf5_enabled = "readSolverSettingsFromHDF5" in dir() from .swig_wrappers import * # These modules require the swig interface and other dependencies @@ -114,7 +119,6 @@ def _imported_from_setup() -> bool: from typing import Protocol, runtime_checkable - @runtime_checkable class ModelModule(Protocol): """Type of AMICI-generated model modules. @@ -146,8 +150,7 @@ def __exit__(self, exc_type, exc_value, traceback): def import_model_module( - module_name: str, - module_path: Optional[Union[Path, str]] = None + module_name: str, module_path: Optional[Union[Path, str]] = None ) -> ModelModule: """ Import Python module of an AMICI model @@ -177,9 +180,11 @@ def import_model_module( # be imported. del sys.modules[module_name] # collect first, don't delete while iterating - to_unload = {loaded_module_name for loaded_module_name in - sys.modules.keys() if - loaded_module_name.startswith(f"{module_name}.")} + to_unload = { + loaded_module_name + for loaded_module_name in sys.modules.keys() + if loaded_module_name.startswith(f"{module_name}.") + } for m in to_unload: del sys.modules[m] @@ -190,12 +195,14 @@ def import_model_module( class AmiciVersionError(RuntimeError): """Error thrown if an AMICI model is loaded that is incompatible with the installed AMICI base package""" + pass def _get_default_argument(func: Callable, arg: str) -> Any: """Get the default value of the given argument in the given function.""" import inspect + signature = inspect.signature(func) if (default := signature.parameters[arg].default) is not inspect.Parameter.empty: return default diff --git a/python/sdist/amici/__init__.template.py b/python/sdist/amici/__init__.template.py index bc83c1d4c9..358b3828ee 100644 --- a/python/sdist/amici/__init__.template.py +++ b/python/sdist/amici/__init__.template.py @@ -4,17 +4,17 @@ from pathlib import Path # Ensure we are binary-compatible, see #556 -if 'TPL_AMICI_VERSION' != amici.__version__: +if "TPL_AMICI_VERSION" != amici.__version__: raise amici.AmiciVersionError( - f'Cannot use model `TPL_MODELNAME` in {Path(__file__).parent}, ' - 'generated with amici==TPL_AMICI_VERSION, ' - f'together with amici=={amici.__version__} ' - 'which is currently installed. To use this model, install ' - 'amici==TPL_AMICI_VERSION or re-import the model with the amici ' - 'version currently installed.' + f"Cannot use model `TPL_MODELNAME` in {Path(__file__).parent}, " + "generated with amici==TPL_AMICI_VERSION, " + f"together with amici=={amici.__version__} " + "which is currently installed. To use this model, install " + "amici==TPL_AMICI_VERSION or re-import the model with the amici " + "version currently installed." ) from .TPL_MODELNAME import * from .TPL_MODELNAME import getModel as get_model -__version__ = 'TPL_PACKAGE_VERSION' +__version__ = "TPL_PACKAGE_VERSION" diff --git a/python/sdist/amici/__main__.py b/python/sdist/amici/__main__.py index dac5230270..8614a59c2f 100644 --- a/python/sdist/amici/__main__.py +++ b/python/sdist/amici/__main__.py @@ -4,6 +4,7 @@ import os import sys + def print_info(): """Displays information on the current AMICI installation. @@ -21,5 +22,6 @@ def print_info(): print(f"AMICI ({sys.platform}) version {__version__} ({','.join(features)})") -if __name__ == '__main__': + +if __name__ == "__main__": print_info() diff --git a/python/sdist/amici/bngl_import.py b/python/sdist/amici/bngl_import.py index 840e4a4229..960413dbe6 100644 --- a/python/sdist/amici/bngl_import.py +++ b/python/sdist/amici/bngl_import.py @@ -26,7 +26,7 @@ def bngl2amici(bngl_model: str, *args, **kwargs) -> None: see :func:`amici.pysb_import.pysb2amici` for additional arguments """ - if 'model' in kwargs: - raise ValueError('model argument not allowed') + if "model" in kwargs: + raise ValueError("model argument not allowed") pysb_model = model_from_bngl(bngl_model) pysb2amici(pysb_model, *args, **kwargs) diff --git a/python/sdist/amici/conserved_quantities_demartino.py b/python/sdist/amici/conserved_quantities_demartino.py index 28fe3f9e77..64511eb00a 100644 --- a/python/sdist/amici/conserved_quantities_demartino.py +++ b/python/sdist/amici/conserved_quantities_demartino.py @@ -16,12 +16,12 @@ def compute_moiety_conservation_laws( - stoichiometric_list: Sequence[float], - num_species: int, - num_reactions: int, - max_num_monte_carlo: int = 20, - rng_seed: Union[None, bool, int] = False, - species_names: Optional[Sequence[str]] = None, + stoichiometric_list: Sequence[float], + num_species: int, + num_reactions: int, + max_num_monte_carlo: int = 20, + rng_seed: Union[None, bool, int] = False, + species_names: Optional[Sequence[str]] = None, ) -> Tuple[List[List[int]], List[List[float]]]: """Compute moiety conservation laws. @@ -47,16 +47,20 @@ def compute_moiety_conservation_laws( list of lists of corresponding coefficients. """ # compute semi-positive conservation laws - (kernel_dim, engaged_species, int_kernel_dim, conserved_moieties, - cls_species_idxs, cls_coefficients) = _kernel( - stoichiometric_list, num_species, num_reactions) + ( + kernel_dim, + engaged_species, + int_kernel_dim, + conserved_moieties, + cls_species_idxs, + cls_coefficients, + ) = _kernel(stoichiometric_list, num_species, num_reactions) # if the number of integer MCLs equals total MCLS no MC relaxation - done = (int_kernel_dim == kernel_dim) + done = int_kernel_dim == kernel_dim if not done: # construct interaction matrix - J, J2, fields = _fill(stoichiometric_list, engaged_species, - num_species) + J, J2, fields = _fill(stoichiometric_list, engaged_species, num_species) # seed random number generator if rng_seed is not False: @@ -66,68 +70,83 @@ def compute_moiety_conservation_laws( # maximum number of montecarlo search before starting relaxation while not done: yes, int_kernel_dim, conserved_moieties = _monte_carlo( - engaged_species, J, J2, fields, conserved_moieties, - int_kernel_dim, cls_species_idxs, cls_coefficients, - num_species, max_iter=max_num_monte_carlo + engaged_species, + J, + J2, + fields, + conserved_moieties, + int_kernel_dim, + cls_species_idxs, + cls_coefficients, + num_species, + max_iter=max_num_monte_carlo, ) # if the number of integer MCLs equals total MCLS then MC done - done = (int_kernel_dim == kernel_dim) + done = int_kernel_dim == kernel_dim timer = 0 if yes else timer + 1 if timer == max_num_monte_carlo: - done = _relax(stoichiometric_list, conserved_moieties, - num_reactions, num_species) + done = _relax( + stoichiometric_list, conserved_moieties, num_reactions, num_species + ) timer = 0 _reduce(int_kernel_dim, cls_species_idxs, cls_coefficients, num_species) - _output(int_kernel_dim, kernel_dim, engaged_species, cls_species_idxs, - cls_coefficients, species_names, verbose=True) + _output( + int_kernel_dim, + kernel_dim, + engaged_species, + cls_species_idxs, + cls_coefficients, + species_names, + verbose=True, + ) return cls_species_idxs[:int_kernel_dim], cls_coefficients[:int_kernel_dim] def _output( - int_kernel_dim: int, - kernel_dim: int, - int_matched: List[int], - species_indices: List[List[int]], - species_coefficients: List[List[float]], - species_names: Optional[Sequence[str]] = None, - verbose: bool = False, - log_level: int = logging.DEBUG + int_kernel_dim: int, + kernel_dim: int, + int_matched: List[int], + species_indices: List[List[int]], + species_coefficients: List[List[float]], + species_names: Optional[Sequence[str]] = None, + verbose: bool = False, + log_level: int = logging.DEBUG, ): """Log infos on identified conservation laws""" + def log(*args, **kwargs): logger.log(log_level, *args, **kwargs) - log(f"There are {int_kernel_dim} linearly independent conserved " - f"moieties, engaging {len(int_matched)} state variables.") + log( + f"There are {int_kernel_dim} linearly independent conserved " + f"moieties, engaging {len(int_matched)} state variables." + ) if int_kernel_dim == kernel_dim: log("They generate all the conservation laws") else: - log(f"They don't generate all the conservation laws, " + log( + f"They don't generate all the conservation laws, " f"{kernel_dim - int_kernel_dim} of them are not reducible to " - "moieties") + "moieties" + ) # print all conserved quantities if verbose: - for i, (coefficients, engaged_species_idxs) \ - in enumerate(zip(species_coefficients, species_indices)): + for i, (coefficients, engaged_species_idxs) in enumerate( + zip(species_coefficients, species_indices) + ): if not engaged_species_idxs: continue - log(f"Moiety number {i + 1} engages {len(engaged_species_idxs)} " - "species:") - for species_idx, coefficient \ - in zip(engaged_species_idxs, coefficients): - name = species_names[species_idx] if species_names \ - else species_idx + log( + f"Moiety number {i + 1} engages {len(engaged_species_idxs)} " "species:" + ) + for species_idx, coefficient in zip(engaged_species_idxs, coefficients): + name = species_names[species_idx] if species_names else species_idx log(f"\t{name}\t{coefficient}") -def _qsort( - k: int, - km: int, - order: MutableSequence[int], - pivots: Sequence[int] -) -> None: +def _qsort(k: int, km: int, order: MutableSequence[int], pivots: Sequence[int]) -> None: """Quicksort Recursive implementation of the quicksort algorithm @@ -169,11 +188,8 @@ def _qsort( def _kernel( - stoichiometric_list: Sequence[float], - num_species: int, - num_reactions: int -) -> Tuple[int, List[int], int, List[int], - List[List[int]], List[List[float]]]: + stoichiometric_list: Sequence[float], num_species: int, num_reactions: int +) -> Tuple[int, List[int], int, List[int], List[List[int]], List[List[float]]]: """ Kernel (left nullspace of :math:`S`) calculation by Gaussian elimination @@ -214,8 +230,7 @@ def _kernel( matrix2[i].append(1) order: List[int] = list(range(num_species)) - pivots = [matrix[i][0] if len(matrix[i]) else _MAX - for i in range(num_species)] + pivots = [matrix[i][0] if len(matrix[i]) else _MAX for i in range(num_species)] done = False while not done: @@ -225,14 +240,17 @@ def _kernel( min1 = _MAX if len(matrix[order[j]]) > 1: for i in range(len(matrix[order[j]])): - min1 = min(min1, abs(matrix2[order[j]][0] - / matrix2[order[j]][i])) + min1 = min( + min1, abs(matrix2[order[j]][0] / matrix2[order[j]][i]) + ) min2 = _MAX if len(matrix[order[j + 1]]) > 1: for i in range(len(matrix[order[j + 1]])): - min2 = min(min2, abs(matrix2[order[j + 1]][0] - / matrix2[order[j + 1]][i])) + min2 = min( + min2, + abs(matrix2[order[j + 1]][0] / matrix2[order[j + 1]][i]), + ) if min2 > min1: # swap @@ -271,8 +289,7 @@ def _kernel( kernel_dim = 0 for i in range(num_species): - done = all(matrix[i][j] >= num_reactions - for j in range(len(matrix[i]))) + done = all(matrix[i][j] >= num_reactions for j in range(len(matrix[i]))) if done and len(matrix[i]): for j in range(len(matrix[i])): RSolutions[kernel_dim].append(matrix[i][j] - num_reactions) @@ -292,8 +309,7 @@ def _kernel( if RSolutions2[i][j] * RSolutions2[i][0] < 0: ok2 = False if not matched or all( - cur_matched != RSolutions[i][j] for cur_matched in - matched + cur_matched != RSolutions[i][j] for cur_matched in matched ): matched.append(RSolutions[i][j]) if ok2 and len(RSolutions[i]): @@ -303,8 +319,8 @@ def _kernel( cls_coefficients[i2].append(abs(RSolutions2[i][j])) min_value = min(min_value, abs(RSolutions2[i][j])) if not int_matched or all( - cur_int_matched != cls_species_idxs[i2][j] - for cur_int_matched in int_matched + cur_int_matched != cls_species_idxs[i2][j] + for cur_int_matched in int_matched ): int_matched.append(cls_species_idxs[i2][j]) for j in range(len(cls_species_idxs[i2])): @@ -313,17 +329,21 @@ def _kernel( int_kernel_dim = i2 assert int_kernel_dim <= kernel_dim - assert len(cls_species_idxs) == len(cls_coefficients), \ - "Inconsistent number of conserved quantities in coefficients and " \ - "species" - return (kernel_dim, matched, int_kernel_dim, int_matched, cls_species_idxs, - cls_coefficients) + assert len(cls_species_idxs) == len(cls_coefficients), ( + "Inconsistent number of conserved quantities in coefficients and " "species" + ) + return ( + kernel_dim, + matched, + int_kernel_dim, + int_matched, + cls_species_idxs, + cls_coefficients, + ) def _fill( - stoichiometric_list: Sequence[float], - matched: Sequence[int], - num_species: int + stoichiometric_list: Sequence[float], matched: Sequence[int], num_species: int ) -> Tuple[List[List[int]], List[List[int]], List[int]]: """Construct interaction matrix @@ -381,12 +401,12 @@ def _fill( def _is_linearly_dependent( - vector: Sequence[float], - int_kernel_dim: int, - cls_species_idxs: Sequence[Sequence[int]], - cls_coefficients: Sequence[Sequence[float]], - matched: Sequence[int], - num_species: int + vector: Sequence[float], + int_kernel_dim: int, + cls_species_idxs: Sequence[Sequence[int]], + cls_coefficients: Sequence[Sequence[float]], + matched: Sequence[int], + num_species: int, ) -> bool: """Check for linear dependence between MCLs @@ -439,13 +459,16 @@ def _is_linearly_dependent( min1 = _MAX if len(matrix[order[j]]) > 1: for i in range(len(matrix[order[j]])): - min1 = min(min1, abs(matrix2[order[j]][0] - / matrix2[order[j]][i])) + min1 = min( + min1, abs(matrix2[order[j]][0] / matrix2[order[j]][i]) + ) min2 = _MAX if len(matrix[order[j + 1]]) > 1: for i in range(len(matrix[order[j + 1]])): - min2 = min(min2, abs(matrix2[order[j + 1]][0] - / matrix2[order[j + 1]][i])) + min2 = min( + min2, + abs(matrix2[order[j + 1]][0] / matrix2[order[j + 1]][i]), + ) if min2 > min1: # swap k2 = order[j + 1] @@ -476,18 +499,18 @@ def _is_linearly_dependent( def _monte_carlo( - matched: Sequence[int], - J: Sequence[Sequence[int]], - J2: Sequence[Sequence[float]], - fields: Sequence[float], - int_matched: MutableSequence[int], - int_kernel_dim: int, - cls_species_idxs: MutableSequence[MutableSequence[int]], - cls_coefficients: MutableSequence[MutableSequence[float]], - num_species: int, - initial_temperature: float = 1, - cool_rate: float = 1e-3, - max_iter: int = 10 + matched: Sequence[int], + J: Sequence[Sequence[int]], + J2: Sequence[Sequence[float]], + fields: Sequence[float], + int_matched: MutableSequence[int], + int_kernel_dim: int, + cls_species_idxs: MutableSequence[MutableSequence[int]], + cls_coefficients: MutableSequence[MutableSequence[float]], + num_species: int, + initial_temperature: float = 1, + cool_rate: float = 1e-3, + max_iter: int = 10, ) -> Tuple[bool, int, Sequence[int]]: """MonteCarlo simulated annealing for finding integer MCLs @@ -526,8 +549,7 @@ def _monte_carlo( considered otherwise the algorithm retries Monte Carlo up to max_iter """ dim = len(matched) - num = [int(2 * random.uniform(0, 1)) if len(J[i]) else 0 - for i in range(dim)] + num = [int(2 * random.uniform(0, 1)) if len(J[i]) else 0 for i in range(dim)] numtot = sum(num) def compute_h(): @@ -589,10 +611,9 @@ def compute_h(): # founds MCLS? need to check for linear independence if len(int_matched) and not _is_linearly_dependent( - num, int_kernel_dim, cls_species_idxs, - cls_coefficients, matched, num_species): - logger.debug( - "Found a moiety but it is linearly dependent... next.") + num, int_kernel_dim, cls_species_idxs, cls_coefficients, matched, num_species + ): + logger.debug("Found a moiety but it is linearly dependent... next.") return False, int_kernel_dim, int_matched # reduce by MC procedure @@ -607,10 +628,10 @@ def compute_h(): _reduce(int_kernel_dim, cls_species_idxs, cls_coefficients, num_species) min_value = 1000 for i in range(len(cls_species_idxs[int_kernel_dim - 1])): - if not len(int_matched) \ - or all(cur_int_matched - != cls_species_idxs[int_kernel_dim - 1][i] - for cur_int_matched in int_matched): + if not len(int_matched) or all( + cur_int_matched != cls_species_idxs[int_kernel_dim - 1][i] + for cur_int_matched in int_matched + ): int_matched.append(cls_species_idxs[int_kernel_dim - 1][i]) min_value = min(min_value, cls_coefficients[int_kernel_dim - 1][i]) @@ -619,18 +640,19 @@ def compute_h(): logger.debug( f"Found linearly independent moiety, now there are " - f"{int_kernel_dim} engaging {len(int_matched)} species") + f"{int_kernel_dim} engaging {len(int_matched)} species" + ) return True, int_kernel_dim, int_matched def _relax( - stoichiometric_list: Sequence[float], - int_matched: Sequence[int], - num_reactions: int, - num_species: int, - relaxation_max: float = 1e6, - relaxation_step: float = 1.9 + stoichiometric_list: Sequence[float], + int_matched: Sequence[int], + num_reactions: int, + num_species: int, + relaxation_max: float = 1e6, + relaxation_step: float = 1.9, ) -> bool: """Relaxation scheme for Monte Carlo final solution @@ -685,13 +707,16 @@ def _relax( min1 = _MAX if len(matrix[order[j]]) > 1: for i in range(len(matrix[order[j]])): - min1 = min(min1, abs(matrix2[order[j]][0] - / matrix2[order[j]][i])) + min1 = min( + min1, abs(matrix2[order[j]][0] / matrix2[order[j]][i]) + ) min2 = _MAX if len(matrix[order[j + 1]]) > 1: for i in range(len(matrix[order[j + 1]])): - min2 = min(min2, abs(matrix2[order[j + 1]][0] - / matrix2[order[j + 1]][i])) + min2 = min( + min2, + abs(matrix2[order[j + 1]][0] / matrix2[order[j + 1]][i]), + ) if min2 > min1: # swap k2 = order[j + 1] @@ -748,8 +773,9 @@ def _relax( for a in range(len(matrix[j])): row_k[matrix[j][a]] -= matrix2[j][a] * matrix2[k][i] # filter - matrix[k] = [row_idx for row_idx, row_val in enumerate(row_k) - if row_val != 0] + matrix[k] = [ + row_idx for row_idx, row_val in enumerate(row_k) if row_val != 0 + ] matrix2[k] = [row_val for row_val in row_k if row_val != 0] if len(matrix[k]) <= i: @@ -838,7 +864,7 @@ def _relax( # Motzkin relaxation alpha = -relaxation_step * cmin - fact = sum(val ** 2 for val in matrixb2[cmin_idx]) + fact = sum(val**2 for val in matrixb2[cmin_idx]) alpha /= fact alpha = max(1e-9 * _MIN, alpha) for j in range(len(matrixb[cmin_idx])): @@ -853,10 +879,10 @@ def _relax( def _reduce( - int_kernel_dim: int, - cls_species_idxs: MutableSequence[MutableSequence[int]], - cls_coefficients: MutableSequence[MutableSequence[float]], - num_species: int + int_kernel_dim: int, + cls_species_idxs: MutableSequence[MutableSequence[int]], + cls_coefficients: MutableSequence[MutableSequence[float]], + num_species: int, ) -> None: """Reducing the solution which has been found by the Monte Carlo process @@ -888,12 +914,14 @@ def _reduce( for j in range(i + 1, K): k2 = order[j] column: List[float] = [0] * num_species - for species_idx, coefficient \ - in zip(cls_species_idxs[k1], cls_coefficients[k1]): + for species_idx, coefficient in zip( + cls_species_idxs[k1], cls_coefficients[k1] + ): column[species_idx] = coefficient ok1 = True - for species_idx, coefficient \ - in zip(cls_species_idxs[k2], cls_coefficients[k2]): + for species_idx, coefficient in zip( + cls_species_idxs[k2], cls_coefficients[k2] + ): column[species_idx] -= coefficient if column[species_idx] < -_MIN: ok1 = False diff --git a/python/sdist/amici/conserved_quantities_rref.py b/python/sdist/amici/conserved_quantities_rref.py index 4c401293cf..46028e94b0 100644 --- a/python/sdist/amici/conserved_quantities_rref.py +++ b/python/sdist/amici/conserved_quantities_rref.py @@ -6,8 +6,7 @@ def rref( - mat: np.array, - round_ndigits: Optional[Union[Literal[False], int]] = None + mat: np.array, round_ndigits: Optional[Union[Literal[False], int]] = None ) -> np.array: """ Bring matrix ``mat`` to reduced row echelon form @@ -25,14 +24,15 @@ def rref( # no-op def _round(mat): return mat + else: if round_ndigits is None: # drop the least significant digit (more or less) - round_ndigits = - int(np.ceil(np.log10(np.spacing(1)))) + round_ndigits = -int(np.ceil(np.log10(np.spacing(1)))) def _round(mat): mat = np.round(mat, round_ndigits) - mat[np.abs(mat) <= 10**(-round_ndigits)] = 0 + mat[np.abs(mat) <= 10 ** (-round_ndigits)] = 0 return mat # create a copy that will be modified diff --git a/python/sdist/amici/constants.py b/python/sdist/amici/constants.py index 12acd87252..74b365889c 100644 --- a/python/sdist/amici/constants.py +++ b/python/sdist/amici/constants.py @@ -19,17 +19,18 @@ class SymbolId(str, enum.Enum): SymbolId.SPECIES], which is how the field should be accessed programmatically. """ - SPECIES = 'species' - ALGEBRAIC_STATE = 'algebraic_state' - ALGEBRAIC_EQUATION = 'algebraic_equation' - PARAMETER = 'parameter' - FIXED_PARAMETER = 'fixed_parameter' - OBSERVABLE = 'observable' - EXPRESSION = 'expression' - SIGMAY = 'sigmay' - LLHY = 'llhy' - EVENT = 'event' - EVENT_OBSERVABLE = 'event_observable' - SIGMAZ = 'sigmaz' - LLHZ = 'llhz' - LLHRZ = 'llhrz' + + SPECIES = "species" + ALGEBRAIC_STATE = "algebraic_state" + ALGEBRAIC_EQUATION = "algebraic_equation" + PARAMETER = "parameter" + FIXED_PARAMETER = "fixed_parameter" + OBSERVABLE = "observable" + EXPRESSION = "expression" + SIGMAY = "sigmay" + LLHY = "llhy" + EVENT = "event" + EVENT_OBSERVABLE = "event_observable" + SIGMAZ = "sigmaz" + LLHZ = "llhz" + LLHRZ = "llhrz" diff --git a/python/sdist/amici/custom_commands.py b/python/sdist/amici/custom_commands.py index d8db24c083..ad2c09829b 100644 --- a/python/sdist/amici/custom_commands.py +++ b/python/sdist/amici/custom_commands.py @@ -17,11 +17,12 @@ class AmiciInstall(install): """Custom `install` command to handle extra arguments""" + print("running AmiciInstall") # Passing --no-clibs allows to install the Python-only part of AMICI user_options = install.user_options + [ - ('no-clibs', None, "Don't build AMICI C++ extension"), + ("no-clibs", None, "Don't build AMICI C++ extension"), ] def initialize_options(self): @@ -39,7 +40,7 @@ class AmiciDevelop(develop): # Passing --no-clibs allows to install the Python-only part of AMICI user_options = develop.user_options + [ - ('no-clibs', None, "Don't build AMICI C++ extension"), + ("no-clibs", None, "Don't build AMICI C++ extension"), ] def initialize_options(self): @@ -63,14 +64,21 @@ def run(self): """ print("running AmiciInstallLib") - if os.environ.get('ENABLE_AMICI_DEBUGGING') == 'TRUE' \ - and sys.platform == 'darwin': - search_dir = os.path.join(os.getcwd(), self.build_dir, 'amici') + if ( + os.environ.get("ENABLE_AMICI_DEBUGGING") == "TRUE" + and sys.platform == "darwin" + ): + search_dir = os.path.join(os.getcwd(), self.build_dir, "amici") for file in os.listdir(search_dir): - if file.endswith('.so'): - subprocess.run(['dsymutil', os.path.join(search_dir, file), - '-o', - os.path.join(search_dir, f'{file}.dSYM')]) + if file.endswith(".so"): + subprocess.run( + [ + "dsymutil", + os.path.join(search_dir, file), + "-o", + os.path.join(search_dir, f"{file}.dSYM"), + ] + ) # Continue with the actual installation super().run() @@ -96,8 +104,14 @@ def save_git_version(): """ with open(os.path.join("amici", "git_version.txt"), "w") as f: try: - cmd = ['git', 'describe', '--abbrev=4', '--dirty=-dirty', - '--always', '--tags'] + cmd = [ + "git", + "describe", + "--abbrev=4", + "--dirty=-dirty", + "--always", + "--tags", + ] subprocess.run(cmd, stdout=f) except Exception as e: print(e) @@ -120,10 +134,14 @@ def run(self): print(f"running {self.__class__.__name__}") # custom flag to build without extensions - no_clibs = 'develop' in self.distribution.command_obj \ - and self.get_finalized_command('develop').no_clibs - no_clibs |= 'install' in self.distribution.command_obj \ - and self.get_finalized_command('install').no_clibs + no_clibs = ( + "develop" in self.distribution.command_obj + and self.get_finalized_command("develop").no_clibs + ) + no_clibs |= ( + "install" in self.distribution.command_obj + and self.get_finalized_command("install").no_clibs + ) if no_clibs: # Nothing to build @@ -144,9 +162,7 @@ def run(self): return result - def build_extension( - self, ext: CMakeExtension - ) -> None: + def build_extension(self, ext: CMakeExtension) -> None: # put some structure into CMake output print("-" * 30, ext.name, "-" * 30, file=sys.stderr) @@ -156,8 +172,8 @@ def build_extension( build_dir = self.build_lib if self.inplace == 0 else os.getcwd() build_dir = Path(build_dir).absolute().as_posix() ext.cmake_configure_options = [ - x.replace("${build_dir}", build_dir) for x in - ext.cmake_configure_options] + x.replace("${build_dir}", build_dir) for x in ext.cmake_configure_options + ] super().build_extension(ext) diff --git a/python/sdist/amici/cxxcodeprinter.py b/python/sdist/amici/cxxcodeprinter.py index 1a5f106850..ab85d6eae1 100644 --- a/python/sdist/amici/cxxcodeprinter.py +++ b/python/sdist/amici/cxxcodeprinter.py @@ -34,8 +34,11 @@ def __init__(self): super().__init__() # extract common subexpressions in matrix functions? - self.extract_cse = (os.getenv("AMICI_EXTRACT_CSE", "0").lower() - in ('1', 'on', 'true')) + self.extract_cse = os.getenv("AMICI_EXTRACT_CSE", "0").lower() in ( + "1", + "on", + "true", + ) # Floating-point optimizations # e.g., log(1 + x) --> logp1(x) @@ -54,7 +57,7 @@ def doprint(self, expr: sp.Expr, assign_to: Optional[str] = None) -> str: try: # floating point code = super().doprint(expr, assign_to) - code = re.sub(r'(^|\W)M_PI(\W|$)', r'\1amici::pi\2', code) + code = re.sub(r"(^|\W)M_PI(\W|$)", r"\1amici::pi\2", code) return code except TypeError as e: @@ -65,26 +68,28 @@ def doprint(self, expr: sp.Expr, assign_to: Optional[str] = None) -> str: def _print_min_max(self, expr, cpp_fun: str, sympy_fun): # C++ doesn't like mixing int and double for arguments for min/max, # therefore, we just always convert to float - arg0 = sp.Float(expr.args[0]) if expr.args[0].is_number \ - else expr.args[0] + arg0 = sp.Float(expr.args[0]) if expr.args[0].is_number else expr.args[0] if len(expr.args) == 1: return self._print(arg0) - return "%s%s(%s, %s)" % (self._ns, cpp_fun, self._print(arg0), - self._print(sympy_fun(*expr.args[1:]))) + return "%s%s(%s, %s)" % ( + self._ns, + cpp_fun, + self._print(arg0), + self._print(sympy_fun(*expr.args[1:])), + ) def _print_Min(self, expr): from sympy.functions.elementary.miscellaneous import Min + return self._print_min_max(expr, "min", Min) def _print_Max(self, expr): from sympy.functions.elementary.miscellaneous import Max + return self._print_min_max(expr, "max", Max) def _get_sym_lines_array( - self, - equations: sp.Matrix, - variable: str, - indent_level: int + self, equations: sp.Matrix, variable: str, indent_level: int ) -> List[str]: """ Generate C++ code for assigning symbolic terms in symbols to C++ array @@ -103,17 +108,13 @@ def _get_sym_lines_array( C++ code as list of lines """ return [ - ' ' * indent_level + f'{variable}[{index}] = ' - f'{self.doprint(math)};' + " " * indent_level + f"{variable}[{index}] = " f"{self.doprint(math)};" for index, math in enumerate(equations) if math not in [0, 0.0] ] def _get_sym_lines_symbols( - self, symbols: sp.Matrix, - equations: sp.Matrix, - variable: str, - indent_level: int + self, symbols: sp.Matrix, equations: sp.Matrix, variable: str, indent_level: int ) -> List[str]: """ Generate C++ code for where array elements are directly replaced with @@ -138,44 +139,47 @@ def _get_sym_lines_symbols( def format_regular_line(symbol, math, index): return ( - f'{indent}{self.doprint(symbol)} = {self.doprint(math)};' - f' // {variable}[{index}]'.replace('\n', '\n' + indent) + f"{indent}{self.doprint(symbol)} = {self.doprint(math)};" + f" // {variable}[{index}]".replace("\n", "\n" + indent) ) if self.extract_cse: # Extract common subexpressions cse_sym_prefix = "__amici_cse_" - symbol_generator = numbered_symbols( - cls=sp.Symbol, prefix=cse_sym_prefix) + symbol_generator = numbered_symbols(cls=sp.Symbol, prefix=cse_sym_prefix) replacements, reduced_exprs = sp.cse( equations, symbols=symbol_generator, - order='none', + order="none", list=False, ) if replacements: # we need toposort to handle the dependencies of extracted # subexpressions - expr_dict = dict(itertools.chain(zip(symbols, reduced_exprs), - replacements)) - sorted_symbols = toposort({ - identifier: { - s for s in definition.free_symbols - if s in expr_dict + expr_dict = dict( + itertools.chain(zip(symbols, reduced_exprs), replacements) + ) + sorted_symbols = toposort( + { + identifier: { + s for s in definition.free_symbols if s in expr_dict + } + for (identifier, definition) in expr_dict.items() } - for (identifier, definition) in expr_dict.items() - }) + ) symbol_to_idx = {sym: idx for idx, sym in enumerate(symbols)} def format_line(symbol: sp.Symbol): math = expr_dict[symbol] if str(symbol).startswith(cse_sym_prefix): - return f'{indent}const realtype ' \ - f'{self.doprint(symbol)} ' \ - f'= {self.doprint(math)};' + return ( + f"{indent}const realtype " + f"{self.doprint(symbol)} " + f"= {self.doprint(math)};" + ) elif math not in [0, 0.0]: - return format_regular_line( - symbol, math, symbol_to_idx[symbol]) + return format_regular_line(symbol, math, symbol_to_idx[symbol]) + return [ line for symbol_group in sorted_symbols @@ -190,15 +194,13 @@ def format_line(symbol: sp.Symbol): ] def csc_matrix( - self, - matrix: sp.Matrix, - rownames: List[sp.Symbol], - colnames: List[sp.Symbol], - identifier: Optional[int] = 0, - pattern_only: Optional[bool] = False - ) -> Tuple[ - List[int], List[int], sp.Matrix, List[str], sp.Matrix - ]: + self, + matrix: sp.Matrix, + rownames: List[sp.Symbol], + colnames: List[sp.Symbol], + identifier: Optional[int] = 0, + pattern_only: Optional[bool] = False, + ) -> Tuple[List[int], List[int], sp.Matrix, List[str], sp.Matrix]: """ Generates the sparse symbolic identifiers, symbolic identifiers, sparse matrix, column pointers and row values for a symbolic @@ -245,10 +247,9 @@ def csc_matrix( symbol_row_vals.append(row) idx += 1 - symbol_name = f'd{rownames[row].name}' \ - f'_d{colnames[col].name}' + symbol_name = f"d{rownames[row].name}" f"_d{colnames[col].name}" if identifier: - symbol_name += f'_{identifier}' + symbol_name += f"_{identifier}" symbol_list.append(symbol_name) if pattern_only: continue @@ -266,8 +267,7 @@ def csc_matrix( else: sparse_list = sp.Matrix(sparse_list) - return symbol_col_ptrs, symbol_row_vals, sparse_list, symbol_list, \ - sparse_matrix + return symbol_col_ptrs, symbol_row_vals, sparse_list, symbol_list, sparse_matrix @staticmethod def print_bool(expr) -> str: @@ -275,9 +275,12 @@ def print_bool(expr) -> str: return "true" if bool(expr) else "false" -def get_switch_statement(condition: str, cases: Dict[int, List[str]], - indentation_level: Optional[int] = 0, - indentation_step: Optional[str] = ' ' * 4): +def get_switch_statement( + condition: str, + cases: Dict[int, List[str]], + indentation_level: Optional[int] = 0, + indentation_step: Optional[str] = " " * 4, +): """ Generate code for switch statement @@ -307,14 +310,16 @@ def get_switch_statement(condition: str, cases: Dict[int, List[str]], indent2 = (indentation_level + 2) * indentation_step for expression, statements in cases.items(): if statements: - lines.extend([ - f'{indent1}case {expression}:', - *(f"{indent2}{statement}" for statement in statements), - f'{indent2}break;' - ]) + lines.extend( + [ + f"{indent1}case {expression}:", + *(f"{indent2}{statement}" for statement in statements), + f"{indent2}break;", + ] + ) if lines: - lines.insert(0, f'{indent0}switch({condition}) {{') - lines.append(indent0 + '}') + lines.insert(0, f"{indent0}switch({condition}) {{") + lines.append(indent0 + "}") return lines diff --git a/python/sdist/amici/de_export.py b/python/sdist/amici/de_export.py index bfd4afecf0..4651316a0d 100644 --- a/python/sdist/amici/de_export.py +++ b/python/sdist/amici/de_export.py @@ -21,38 +21,46 @@ from itertools import chain, starmap from pathlib import Path from string import Template -from typing import (Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, - Union) +from typing import Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Union import numpy as np import sympy as sp from sympy.matrices.dense import MutableDenseMatrix from sympy.matrices.immutable import ImmutableDenseMatrix -from . import (__commit__, __version__, amiciModulePath, amiciSrcPath, - amiciSwigPath, sbml_import, splines) +from . import ( + __commit__, + __version__, + amiciModulePath, + amiciSrcPath, + amiciSwigPath, + sbml_import, + splines, +) from .constants import SymbolId from .cxxcodeprinter import AmiciCxxCodePrinter, get_switch_statement -from .import_utils import (ObservableTransformation, generate_flux_symbol, - smart_subs_dict, strip_pysb, - symbol_with_assumptions, toposort_symbols, - SBMLException) +from .import_utils import ( + ObservableTransformation, + generate_flux_symbol, + smart_subs_dict, + strip_pysb, + symbol_with_assumptions, + toposort_symbols, + SBMLException, +) from .logging import get_logger, log_execution_time, set_log_level from .de_model import * - # Template for model simulation main.cpp file -CXX_MAIN_TEMPLATE_FILE = os.path.join(amiciSrcPath, 'main.template.cpp') +CXX_MAIN_TEMPLATE_FILE = os.path.join(amiciSrcPath, "main.template.cpp") # Template for model/swig/CMakeLists.txt -SWIG_CMAKE_TEMPLATE_FILE = os.path.join(amiciSwigPath, - 'CMakeLists_model.cmake') +SWIG_CMAKE_TEMPLATE_FILE = os.path.join(amiciSwigPath, "CMakeLists_model.cmake") # Template for model/CMakeLists.txt -MODEL_CMAKE_TEMPLATE_FILE = os.path.join(amiciSrcPath, - 'CMakeLists.template.cmake') +MODEL_CMAKE_TEMPLATE_FILE = os.path.join(amiciSrcPath, "CMakeLists.template.cmake") -IDENTIFIER_PATTERN = re.compile(r'^[a-zA-Z_]\w*$') -DERIVATIVE_PATTERN = re.compile(r'^d(x_rdata|xdot|\w+?)d(\w+?)(?:_explicit)?$') +IDENTIFIER_PATTERN = re.compile(r"^[a-zA-Z_]\w*$") +DERIVATIVE_PATTERN = re.compile(r"^d(x_rdata|xdot|\w+?)d(\w+?)(?:_explicit)?$") @dataclass @@ -78,13 +86,14 @@ class _FunctionInfo: :ivar body: the actual function body. will be filled later """ - ode_arguments: str = '' - dae_arguments: str = '' - return_type: str = 'void' + + ode_arguments: str = "" + dae_arguments: str = "" + return_type: str = "void" assume_pow_positivity: bool = False sparse: bool = False generate_body: bool = True - body: str = '' + body: str = "" def arguments(self, ode: bool = True) -> str: """Get the arguments for the ODE or DAE function""" @@ -96,401 +105,343 @@ def arguments(self, ode: bool = True) -> str: # Information on a model-specific generated C++ function # prototype for generated C++ functions, keys are the names of functions functions = { - 'Jy': - _FunctionInfo( - 'realtype *Jy, const int iy, const realtype *p, ' - 'const realtype *k, const realtype *y, const realtype *sigmay, ' - 'const realtype *my' - ), - 'dJydsigma': - _FunctionInfo( - 'realtype *dJydsigma, const int iy, const realtype *p, ' - 'const realtype *k, const realtype *y, const realtype *sigmay, ' - 'const realtype *my' - ), - 'dJydy': - _FunctionInfo( - 'realtype *dJydy, const int iy, const realtype *p, ' - 'const realtype *k, const realtype *y, ' - 'const realtype *sigmay, const realtype *my', - sparse=True - ), - 'Jz': - _FunctionInfo( - 'realtype *Jz, const int iz, const realtype *p, const realtype *k, ' - 'const realtype *z, const realtype *sigmaz, const realtype *mz' - ), - 'dJzdsigma': - _FunctionInfo( - 'realtype *dJzdsigma, const int iz, const realtype *p, ' - 'const realtype *k, const realtype *z, const realtype *sigmaz, ' - 'const realtype *mz' - ), - 'dJzdz': - _FunctionInfo( - 'realtype *dJzdz, const int iz, const realtype *p, ' - 'const realtype *k, const realtype *z, const realtype *sigmaz, ' - 'const double *mz', - ), - 'Jrz': - _FunctionInfo( - 'realtype *Jrz, const int iz, const realtype *p, ' - 'const realtype *k, const realtype *rz, const realtype *sigmaz' - ), - 'dJrzdsigma': - _FunctionInfo( - 'realtype *dJrzdsigma, const int iz, const realtype *p, ' - 'const realtype *k, const realtype *rz, const realtype *sigmaz' - ), - 'dJrzdz': - _FunctionInfo( - 'realtype *dJrzdz, const int iz, const realtype *p, ' - 'const realtype *k, const realtype *rz, const realtype *sigmaz', - ), - 'root': - _FunctionInfo( - 'realtype *root, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *tcl' - ), - 'dwdp': - _FunctionInfo( - 'realtype *dwdp, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *w, const realtype *tcl, const realtype *dtcldp, ' - 'const realtype *spl, const realtype *sspl', - assume_pow_positivity=True, sparse=True - ), - 'dwdx': - _FunctionInfo( - 'realtype *dwdx, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *w, const realtype *tcl, const realtype *spl', - assume_pow_positivity=True, sparse=True - ), - 'create_splines': - _FunctionInfo( - 'const realtype *p, const realtype *k', - return_type='std::vector', - ), - 'spl': - _FunctionInfo(generate_body=False), - 'sspl': - _FunctionInfo(generate_body=False), - 'spline_values': - _FunctionInfo( - 'const realtype *p, const realtype *k', - generate_body=False - ), - 'spline_slopes': - _FunctionInfo( - 'const realtype *p, const realtype *k', - generate_body=False - ), - 'dspline_valuesdp': - _FunctionInfo( - 'realtype *dspline_valuesdp, const realtype *p, const realtype *k, const int ip' - ), - 'dspline_slopesdp': - _FunctionInfo( - 'realtype *dspline_slopesdp, const realtype *p, const realtype *k, const int ip' - ), - 'dwdw': - _FunctionInfo( - 'realtype *dwdw, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *w, const realtype *tcl', - assume_pow_positivity=True, sparse=True - ), - 'dxdotdw': - _FunctionInfo( - 'realtype *dxdotdw, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *w', - 'realtype *dxdotdw, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *dx, const realtype *w', - assume_pow_positivity=True, sparse=True - ), - 'dxdotdx_explicit': - _FunctionInfo( - 'realtype *dxdotdx_explicit, const realtype t, ' - 'const realtype *x, const realtype *p, const realtype *k, ' - 'const realtype *h, const realtype *w', - 'realtype *dxdotdx_explicit, const realtype t, ' - 'const realtype *x, const realtype *p, const realtype *k, ' - 'const realtype *h, const realtype *dx, const realtype *w', - assume_pow_positivity=True, sparse=True - ), - 'dxdotdp_explicit': - _FunctionInfo( - 'realtype *dxdotdp_explicit, const realtype t, ' - 'const realtype *x, const realtype *p, const realtype *k, ' - 'const realtype *h, const realtype *w', - 'realtype *dxdotdp_explicit, const realtype t, ' - 'const realtype *x, const realtype *p, const realtype *k, ' - 'const realtype *h, const realtype *dx, const realtype *w', - assume_pow_positivity=True, sparse=True - ), - 'dydx': - _FunctionInfo( - 'realtype *dydx, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *w, const realtype *dwdx', - ), - 'dydp': - _FunctionInfo( - 'realtype *dydp, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const int ip, const realtype *w, const realtype *tcl, ' - 'const realtype *dtcldp, const realtype *spl, const realtype *sspl' - ), - 'dzdx': - _FunctionInfo( - 'realtype *dzdx, const int ie, const realtype t, ' - 'const realtype *x, const realtype *p, const realtype *k, ' - 'const realtype *h', - ), - 'dzdp': - _FunctionInfo( - 'realtype *dzdp, const int ie, const realtype t, ' - 'const realtype *x, const realtype *p, const realtype *k, ' - 'const realtype *h, const int ip', - ), - 'drzdx': - _FunctionInfo( - 'realtype *drzdx, const int ie, const realtype t, ' - 'const realtype *x, const realtype *p, const realtype *k, ' - 'const realtype *h', - ), - 'drzdp': - _FunctionInfo( - 'realtype *drzdp, const int ie, const realtype t, ' - 'const realtype *x, const realtype *p, const realtype *k, ' - 'const realtype *h, const int ip', - ), - 'dsigmaydy': - _FunctionInfo( - 'realtype *dsigmaydy, const realtype t, const realtype *p, ' - 'const realtype *k, const realtype *y' - ), - 'dsigmaydp': - _FunctionInfo( - 'realtype *dsigmaydp, const realtype t, const realtype *p, ' - 'const realtype *k, const realtype *y, const int ip', - ), - 'sigmay': - _FunctionInfo( - 'realtype *sigmay, const realtype t, const realtype *p, ' - 'const realtype *k, const realtype *y', - ), - 'dsigmazdp': - _FunctionInfo( - 'realtype *dsigmazdp, const realtype t, const realtype *p,' - ' const realtype *k, const int ip', - ), - 'sigmaz': - _FunctionInfo( - 'realtype *sigmaz, const realtype t, const realtype *p, ' - 'const realtype *k', - ), - 'sroot': - _FunctionInfo( - 'realtype *stau, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *sx, const int ip, const int ie, ' - 'const realtype *tcl', - generate_body=False - ), - 'drootdt': - _FunctionInfo(generate_body=False), - 'drootdt_total': - _FunctionInfo(generate_body=False), - 'drootdp': - _FunctionInfo(generate_body=False), - 'drootdx': - _FunctionInfo(generate_body=False), - 'stau': - _FunctionInfo( - 'realtype *stau, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *tcl, const realtype *sx, const int ip, ' - 'const int ie' - ), - 'deltax': - _FunctionInfo( - 'double *deltax, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const int ie, const realtype *xdot, const realtype *xdot_old' - ), - 'ddeltaxdx': - _FunctionInfo(generate_body=False), - 'ddeltaxdt': - _FunctionInfo(generate_body=False), - 'ddeltaxdp': - _FunctionInfo(generate_body=False), - 'deltasx': - _FunctionInfo( - 'realtype *deltasx, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *w, const int ip, const int ie, ' - 'const realtype *xdot, const realtype *xdot_old, ' - 'const realtype *sx, const realtype *stau, const realtype *tcl' - ), - 'w': - _FunctionInfo( - 'realtype *w, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, ' - 'const realtype *h, const realtype *tcl, const realtype *spl', - assume_pow_positivity=True - ), - 'x0': - _FunctionInfo( - 'realtype *x0, const realtype t, const realtype *p, ' - 'const realtype *k' - ), - 'x0_fixedParameters': - _FunctionInfo( - 'realtype *x0_fixedParameters, const realtype t, ' - 'const realtype *p, const realtype *k, ' - 'gsl::span reinitialization_state_idxs', - ), - 'sx0': - _FunctionInfo( - 'realtype *sx0, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const int ip', - ), - 'sx0_fixedParameters': - _FunctionInfo( - 'realtype *sx0_fixedParameters, const realtype t, ' - 'const realtype *x0, const realtype *p, const realtype *k, ' - 'const int ip, gsl::span reinitialization_state_idxs', - ), - 'xdot': - _FunctionInfo( - 'realtype *xdot, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *w', - 'realtype *xdot, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h, ' - 'const realtype *dx, const realtype *w', - assume_pow_positivity=True - ), - 'xdot_old': - _FunctionInfo(generate_body=False), - 'y': - _FunctionInfo( - 'realtype *y, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, ' - 'const realtype *h, const realtype *w', - ), - 'x_rdata': - _FunctionInfo( - 'realtype *x_rdata, const realtype *x, const realtype *tcl, ' - 'const realtype *p, const realtype *k' - ), - 'total_cl': - _FunctionInfo( - 'realtype *total_cl, const realtype *x_rdata, ' - 'const realtype *p, const realtype *k' - ), - 'dtotal_cldp': - _FunctionInfo( - 'realtype *dtotal_cldp, const realtype *x_rdata, ' - 'const realtype *p, const realtype *k, const int ip' - ), - 'dtotal_cldx_rdata': - _FunctionInfo( - 'realtype *dtotal_cldx_rdata, const realtype *x_rdata, ' - 'const realtype *p, const realtype *k, const realtype *tcl', - sparse=True - ), - 'x_solver': - _FunctionInfo('realtype *x_solver, const realtype *x_rdata'), - 'dx_rdatadx_solver': - _FunctionInfo( - 'realtype *dx_rdatadx_solver, const realtype *x, ' - 'const realtype *tcl, const realtype *p, const realtype *k', - sparse=True - ), - 'dx_rdatadp': - _FunctionInfo( - 'realtype *dx_rdatadp, const realtype *x, ' - 'const realtype *tcl, const realtype *p, const realtype *k, ' - 'const int ip' - ), - 'dx_rdatadtcl': - _FunctionInfo( - 'realtype *dx_rdatadtcl, const realtype *x, ' - 'const realtype *tcl, const realtype *p, const realtype *k', - sparse=True - ), - 'z': - _FunctionInfo( - 'realtype *z, const int ie, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h' - ), - 'rz': - _FunctionInfo( - 'realtype *rz, const int ie, const realtype t, const realtype *x, ' - 'const realtype *p, const realtype *k, const realtype *h' - ), + "Jy": _FunctionInfo( + "realtype *Jy, const int iy, const realtype *p, " + "const realtype *k, const realtype *y, const realtype *sigmay, " + "const realtype *my" + ), + "dJydsigma": _FunctionInfo( + "realtype *dJydsigma, const int iy, const realtype *p, " + "const realtype *k, const realtype *y, const realtype *sigmay, " + "const realtype *my" + ), + "dJydy": _FunctionInfo( + "realtype *dJydy, const int iy, const realtype *p, " + "const realtype *k, const realtype *y, " + "const realtype *sigmay, const realtype *my", + sparse=True, + ), + "Jz": _FunctionInfo( + "realtype *Jz, const int iz, const realtype *p, const realtype *k, " + "const realtype *z, const realtype *sigmaz, const realtype *mz" + ), + "dJzdsigma": _FunctionInfo( + "realtype *dJzdsigma, const int iz, const realtype *p, " + "const realtype *k, const realtype *z, const realtype *sigmaz, " + "const realtype *mz" + ), + "dJzdz": _FunctionInfo( + "realtype *dJzdz, const int iz, const realtype *p, " + "const realtype *k, const realtype *z, const realtype *sigmaz, " + "const double *mz", + ), + "Jrz": _FunctionInfo( + "realtype *Jrz, const int iz, const realtype *p, " + "const realtype *k, const realtype *rz, const realtype *sigmaz" + ), + "dJrzdsigma": _FunctionInfo( + "realtype *dJrzdsigma, const int iz, const realtype *p, " + "const realtype *k, const realtype *rz, const realtype *sigmaz" + ), + "dJrzdz": _FunctionInfo( + "realtype *dJrzdz, const int iz, const realtype *p, " + "const realtype *k, const realtype *rz, const realtype *sigmaz", + ), + "root": _FunctionInfo( + "realtype *root, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *tcl" + ), + "dwdp": _FunctionInfo( + "realtype *dwdp, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *w, const realtype *tcl, const realtype *dtcldp, " + "const realtype *spl, const realtype *sspl", + assume_pow_positivity=True, + sparse=True, + ), + "dwdx": _FunctionInfo( + "realtype *dwdx, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *w, const realtype *tcl, const realtype *spl", + assume_pow_positivity=True, + sparse=True, + ), + "create_splines": _FunctionInfo( + "const realtype *p, const realtype *k", + return_type="std::vector", + ), + "spl": _FunctionInfo(generate_body=False), + "sspl": _FunctionInfo(generate_body=False), + "spline_values": _FunctionInfo( + "const realtype *p, const realtype *k", generate_body=False + ), + "spline_slopes": _FunctionInfo( + "const realtype *p, const realtype *k", generate_body=False + ), + "dspline_valuesdp": _FunctionInfo( + "realtype *dspline_valuesdp, const realtype *p, const realtype *k, const int ip" + ), + "dspline_slopesdp": _FunctionInfo( + "realtype *dspline_slopesdp, const realtype *p, const realtype *k, const int ip" + ), + "dwdw": _FunctionInfo( + "realtype *dwdw, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *w, const realtype *tcl", + assume_pow_positivity=True, + sparse=True, + ), + "dxdotdw": _FunctionInfo( + "realtype *dxdotdw, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *w", + "realtype *dxdotdw, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *dx, const realtype *w", + assume_pow_positivity=True, + sparse=True, + ), + "dxdotdx_explicit": _FunctionInfo( + "realtype *dxdotdx_explicit, const realtype t, " + "const realtype *x, const realtype *p, const realtype *k, " + "const realtype *h, const realtype *w", + "realtype *dxdotdx_explicit, const realtype t, " + "const realtype *x, const realtype *p, const realtype *k, " + "const realtype *h, const realtype *dx, const realtype *w", + assume_pow_positivity=True, + sparse=True, + ), + "dxdotdp_explicit": _FunctionInfo( + "realtype *dxdotdp_explicit, const realtype t, " + "const realtype *x, const realtype *p, const realtype *k, " + "const realtype *h, const realtype *w", + "realtype *dxdotdp_explicit, const realtype t, " + "const realtype *x, const realtype *p, const realtype *k, " + "const realtype *h, const realtype *dx, const realtype *w", + assume_pow_positivity=True, + sparse=True, + ), + "dydx": _FunctionInfo( + "realtype *dydx, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *w, const realtype *dwdx", + ), + "dydp": _FunctionInfo( + "realtype *dydp, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const int ip, const realtype *w, const realtype *tcl, " + "const realtype *dtcldp, const realtype *spl, const realtype *sspl" + ), + "dzdx": _FunctionInfo( + "realtype *dzdx, const int ie, const realtype t, " + "const realtype *x, const realtype *p, const realtype *k, " + "const realtype *h", + ), + "dzdp": _FunctionInfo( + "realtype *dzdp, const int ie, const realtype t, " + "const realtype *x, const realtype *p, const realtype *k, " + "const realtype *h, const int ip", + ), + "drzdx": _FunctionInfo( + "realtype *drzdx, const int ie, const realtype t, " + "const realtype *x, const realtype *p, const realtype *k, " + "const realtype *h", + ), + "drzdp": _FunctionInfo( + "realtype *drzdp, const int ie, const realtype t, " + "const realtype *x, const realtype *p, const realtype *k, " + "const realtype *h, const int ip", + ), + "dsigmaydy": _FunctionInfo( + "realtype *dsigmaydy, const realtype t, const realtype *p, " + "const realtype *k, const realtype *y" + ), + "dsigmaydp": _FunctionInfo( + "realtype *dsigmaydp, const realtype t, const realtype *p, " + "const realtype *k, const realtype *y, const int ip", + ), + "sigmay": _FunctionInfo( + "realtype *sigmay, const realtype t, const realtype *p, " + "const realtype *k, const realtype *y", + ), + "dsigmazdp": _FunctionInfo( + "realtype *dsigmazdp, const realtype t, const realtype *p," + " const realtype *k, const int ip", + ), + "sigmaz": _FunctionInfo( + "realtype *sigmaz, const realtype t, const realtype *p, " "const realtype *k", + ), + "sroot": _FunctionInfo( + "realtype *stau, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *sx, const int ip, const int ie, " + "const realtype *tcl", + generate_body=False, + ), + "drootdt": _FunctionInfo(generate_body=False), + "drootdt_total": _FunctionInfo(generate_body=False), + "drootdp": _FunctionInfo(generate_body=False), + "drootdx": _FunctionInfo(generate_body=False), + "stau": _FunctionInfo( + "realtype *stau, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *tcl, const realtype *sx, const int ip, " + "const int ie" + ), + "deltax": _FunctionInfo( + "double *deltax, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const int ie, const realtype *xdot, const realtype *xdot_old" + ), + "ddeltaxdx": _FunctionInfo(generate_body=False), + "ddeltaxdt": _FunctionInfo(generate_body=False), + "ddeltaxdp": _FunctionInfo(generate_body=False), + "deltasx": _FunctionInfo( + "realtype *deltasx, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *w, const int ip, const int ie, " + "const realtype *xdot, const realtype *xdot_old, " + "const realtype *sx, const realtype *stau, const realtype *tcl" + ), + "w": _FunctionInfo( + "realtype *w, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, " + "const realtype *h, const realtype *tcl, const realtype *spl", + assume_pow_positivity=True, + ), + "x0": _FunctionInfo( + "realtype *x0, const realtype t, const realtype *p, " "const realtype *k" + ), + "x0_fixedParameters": _FunctionInfo( + "realtype *x0_fixedParameters, const realtype t, " + "const realtype *p, const realtype *k, " + "gsl::span reinitialization_state_idxs", + ), + "sx0": _FunctionInfo( + "realtype *sx0, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const int ip", + ), + "sx0_fixedParameters": _FunctionInfo( + "realtype *sx0_fixedParameters, const realtype t, " + "const realtype *x0, const realtype *p, const realtype *k, " + "const int ip, gsl::span reinitialization_state_idxs", + ), + "xdot": _FunctionInfo( + "realtype *xdot, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *w", + "realtype *xdot, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h, " + "const realtype *dx, const realtype *w", + assume_pow_positivity=True, + ), + "xdot_old": _FunctionInfo(generate_body=False), + "y": _FunctionInfo( + "realtype *y, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, " + "const realtype *h, const realtype *w", + ), + "x_rdata": _FunctionInfo( + "realtype *x_rdata, const realtype *x, const realtype *tcl, " + "const realtype *p, const realtype *k" + ), + "total_cl": _FunctionInfo( + "realtype *total_cl, const realtype *x_rdata, " + "const realtype *p, const realtype *k" + ), + "dtotal_cldp": _FunctionInfo( + "realtype *dtotal_cldp, const realtype *x_rdata, " + "const realtype *p, const realtype *k, const int ip" + ), + "dtotal_cldx_rdata": _FunctionInfo( + "realtype *dtotal_cldx_rdata, const realtype *x_rdata, " + "const realtype *p, const realtype *k, const realtype *tcl", + sparse=True, + ), + "x_solver": _FunctionInfo("realtype *x_solver, const realtype *x_rdata"), + "dx_rdatadx_solver": _FunctionInfo( + "realtype *dx_rdatadx_solver, const realtype *x, " + "const realtype *tcl, const realtype *p, const realtype *k", + sparse=True, + ), + "dx_rdatadp": _FunctionInfo( + "realtype *dx_rdatadp, const realtype *x, " + "const realtype *tcl, const realtype *p, const realtype *k, " + "const int ip" + ), + "dx_rdatadtcl": _FunctionInfo( + "realtype *dx_rdatadtcl, const realtype *x, " + "const realtype *tcl, const realtype *p, const realtype *k", + sparse=True, + ), + "z": _FunctionInfo( + "realtype *z, const int ie, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h" + ), + "rz": _FunctionInfo( + "realtype *rz, const int ie, const realtype t, const realtype *x, " + "const realtype *p, const realtype *k, const realtype *h" + ), } # list of sparse functions sparse_functions = [ - func_name for func_name, func_info in functions.items() - if func_info.sparse + func_name for func_name, func_info in functions.items() if func_info.sparse ] # list of nobody functions nobody_functions = [ - func_name for func_name, func_info in functions.items() + func_name + for func_name, func_info in functions.items() if not func_info.generate_body ] # list of sensitivity functions sensi_functions = [ - func_name for func_name, func_info in functions.items() - if 'const int ip' in func_info.arguments() + func_name + for func_name, func_info in functions.items() + if "const int ip" in func_info.arguments() ] # list of sensitivity functions sparse_sensi_functions = [ - func_name for func_name, func_info in functions.items() - if 'const int ip' not in func_info.arguments() - and func_name.endswith('dp') or func_name.endswith('dp_explicit') + func_name + for func_name, func_info in functions.items() + if "const int ip" not in func_info.arguments() + and func_name.endswith("dp") + or func_name.endswith("dp_explicit") ] # list of event functions event_functions = [ - func_name for func_name, func_info in functions.items() - if 'const int ie' in func_info.arguments() and - 'const int ip' not in func_info.arguments() + func_name + for func_name, func_info in functions.items() + if "const int ie" in func_info.arguments() + and "const int ip" not in func_info.arguments() ] event_sensi_functions = [ - func_name for func_name, func_info in functions.items() - if 'const int ie' in func_info.arguments() and - 'const int ip' in func_info.arguments() + func_name + for func_name, func_info in functions.items() + if "const int ie" in func_info.arguments() + and "const int ip" in func_info.arguments() ] # list of multiobs functions multiobs_functions = [ - func_name for func_name, func_info in functions.items() - if 'const int iy' in func_info.arguments() - or 'const int iz' in func_info.arguments() + func_name + for func_name, func_info in functions.items() + if "const int iy" in func_info.arguments() + or "const int iz" in func_info.arguments() ] # list of equations that have ids which may not be unique -non_unique_id_symbols = [ - 'x_rdata', 'y' -] +non_unique_id_symbols = ["x_rdata", "y"] # custom c++ function replacements CUSTOM_FUNCTIONS = [ - {'sympy': 'polygamma', - 'c++': 'boost::math::polygamma', - 'include': '#include ', - 'build_hint': 'Using polygamma requires libboost-math header files.' - }, - {'sympy': 'Heaviside', - 'c++': 'amici::heaviside'}, - {'sympy': 'DiracDelta', - 'c++': 'amici::dirac'} + { + "sympy": "polygamma", + "c++": "boost::math::polygamma", + "include": "#include ", + "build_hint": "Using polygamma requires libboost-math header files.", + }, + {"sympy": "Heaviside", "c++": "amici::heaviside"}, + {"sympy": "DiracDelta", "c++": "amici::dirac"}, ] # python log manager @@ -513,11 +464,10 @@ def var_in_function_signature(name: str, varname: str, ode: bool) -> bool: boolean indicating whether the variable occurs in the function signature """ - return name in functions \ - and re.search( - rf'const (realtype|double) \*{varname}[0]*(,|$)+', - functions[name].arguments(ode=ode) - ) + return name in functions and re.search( + rf"const (realtype|double) \*{varname}[0]*(,|$)+", + functions[name].arguments(ode=ode), + ) # defines the type of some attributes in DEModel @@ -535,14 +485,13 @@ def var_in_function_signature(name: str, varname: str, ode: bool) -> bool: SymbolId.LLHZ: LogLikelihoodZ, SymbolId.LLHRZ: LogLikelihoodRZ, SymbolId.EXPRESSION: Expression, - SymbolId.EVENT: Event + SymbolId.EVENT: Event, } -@log_execution_time('running smart_jacobian', logger) +@log_execution_time("running smart_jacobian", logger) def smart_jacobian( - eq: sp.MutableDenseMatrix, - sym_var: sp.MutableDenseMatrix + eq: sp.MutableDenseMatrix, sym_var: sp.MutableDenseMatrix ) -> sp.MutableSparseMatrix: """ Wrapper around symbolic jacobian with some additional checks that reduce @@ -575,24 +524,24 @@ def smart_jacobian( if (n_procs := int(os.environ.get("AMICI_IMPORT_NPROCS", 1))) == 1: # serial - return sp.MutableSparseMatrix(nrow, ncol, - dict(starmap(_jacobian_element, elements)) + return sp.MutableSparseMatrix( + nrow, ncol, dict(starmap(_jacobian_element, elements)) ) # parallel from multiprocessing import get_context + # "spawn" should avoid potential deadlocks occurring with fork # see e.g. https://stackoverflow.com/a/66113051 - ctx = get_context('spawn') + ctx = get_context("spawn") with ctx.Pool(n_procs) as p: mapped = p.starmap(_jacobian_element, elements) return sp.MutableSparseMatrix(nrow, ncol, dict(mapped)) -@log_execution_time('running smart_multiply', logger) +@log_execution_time("running smart_multiply", logger) def smart_multiply( - x: Union[sp.MutableDenseMatrix, sp.MutableSparseMatrix], - y: sp.MutableDenseMatrix + x: Union[sp.MutableDenseMatrix, sp.MutableSparseMatrix], y: sp.MutableDenseMatrix ) -> Union[sp.MutableDenseMatrix, sp.MutableSparseMatrix]: """ Wrapper around symbolic multiplication with some additional checks that @@ -605,14 +554,19 @@ def smart_multiply( :return: product """ - if not x.shape[0] or not y.shape[1] or smart_is_zero_matrix(x) or \ - smart_is_zero_matrix(y): + if ( + not x.shape[0] + or not y.shape[1] + or smart_is_zero_matrix(x) + or smart_is_zero_matrix(y) + ): return sp.zeros(x.shape[0], y.shape[1]) return x.multiply(y) -def smart_is_zero_matrix(x: Union[sp.MutableDenseMatrix, - sp.MutableSparseMatrix]) -> bool: +def smart_is_zero_matrix( + x: Union[sp.MutableDenseMatrix, sp.MutableSparseMatrix] +) -> bool: """A faster implementation of sympy's is_zero_matrix Avoids repeated indexer type checks and double iteration to distinguish @@ -761,9 +715,12 @@ class DEModel: list of event indices for each event observable """ - def __init__(self, verbose: Optional[Union[bool, int]] = False, - simplify: Optional[Callable] = _default_simplify, - cache_simplify: bool = False): + def __init__( + self, + verbose: Optional[Union[bool, int]] = False, + simplify: Optional[Callable] = _default_simplify, + cache_simplify: bool = False, + ): """ Create a new DEModel instance. @@ -795,15 +752,17 @@ def __init__(self, verbose: Optional[Union[bool, int]] = False, self._events: List[Event] = [] self.splines = [] self._symboldim_funs: Dict[str, Callable[[], int]] = { - 'sx': self.num_states_solver, - 'v': self.num_states_solver, - 'vB': self.num_states_solver, - 'xB': self.num_states_solver, - 'sigmay': self.num_obs, - 'sigmaz': self.num_eventobs, + "sx": self.num_states_solver, + "v": self.num_states_solver, + "vB": self.num_states_solver, + "xB": self.num_states_solver, + "sigmay": self.num_obs, + "sigmaz": self.num_eventobs, } - self._eqs: Dict[str, Union[sp.Matrix, sp.SparseMatrix, - List[Union[sp.Matrix, sp.SparseMatrix]]]] = dict() + self._eqs: Dict[ + str, + Union[sp.Matrix, sp.SparseMatrix, List[Union[sp.Matrix, sp.SparseMatrix]]], + ] = dict() self._sparseeqs: Dict[str, Union[sp.Matrix, List[sp.Matrix]]] = dict() self._vals: Dict[str, List[sp.Expr]] = dict() self._names: Dict[str, List[str]] = dict() @@ -813,46 +772,48 @@ def __init__(self, verbose: Optional[Union[bool, int]] = False, self._rowvals: Dict[str, Union[List[int], List[List[int]]]] = dict() self._equation_prototype: Dict[str, Callable] = { - 'total_cl': self.conservation_laws, - 'x0': self.states, - 'y': self.observables, - 'Jy': self.log_likelihood_ys, - 'Jz': self.log_likelihood_zs, - 'Jrz': self.log_likelihood_rzs, - 'w': self.expressions, - 'root': self.events, - 'sigmay': self.sigma_ys, - 'sigmaz': self.sigma_zs + "total_cl": self.conservation_laws, + "x0": self.states, + "y": self.observables, + "Jy": self.log_likelihood_ys, + "Jz": self.log_likelihood_zs, + "Jrz": self.log_likelihood_rzs, + "w": self.expressions, + "root": self.events, + "sigmay": self.sigma_ys, + "sigmaz": self.sigma_zs, } self._variable_prototype: Dict[str, Callable] = { - 'tcl': self.conservation_laws, - 'x_rdata': self.states, - 'y': self.observables, - 'z': self.event_observables, - 'p': self.parameters, - 'k': self.constants, - 'w': self.expressions, - 'sigmay': self.sigma_ys, - 'sigmaz': self.sigma_zs, - 'h': self.events, + "tcl": self.conservation_laws, + "x_rdata": self.states, + "y": self.observables, + "z": self.event_observables, + "p": self.parameters, + "k": self.constants, + "w": self.expressions, + "sigmay": self.sigma_ys, + "sigmaz": self.sigma_zs, + "h": self.events, } self._value_prototype: Dict[str, Callable] = { - 'p': self.parameters, - 'k': self.constants, + "p": self.parameters, + "k": self.constants, } - self._total_derivative_prototypes: \ - Dict[str, Dict[str, Union[str, List[str]]]] = { - 'sroot': { - 'eq': 'root', - 'chainvars': ['x'], - 'var': 'p', - 'dxdz_name': 'sx', - }, + self._total_derivative_prototypes: Dict[ + str, Dict[str, Union[str, List[str]]] + ] = { + "sroot": { + "eq": "root", + "chainvars": ["x"], + "var": "p", + "dxdz_name": "sx", + }, } self._lock_total_derivative: List[str] = list() self._simplify: Callable = simplify if cache_simplify and simplify is not None: + def cached_simplify( expr: sp.Expr, _simplified: Dict[str, sp.Expr] = {}, @@ -880,6 +841,7 @@ def cached_simplify( if expr_str not in _simplified: _simplified[expr_str] = _simplify(expr) return _simplified[expr_str] + self._simplify = cached_simplify self._x0_fixedParameters_idx: Union[None, Sequence[int]] self._w_recursion_depth: int = 0 @@ -888,7 +850,7 @@ def cached_simplify( self._code_printer = AmiciCxxCodePrinter() for fun in CUSTOM_FUNCTIONS: - self._code_printer.known_functions[fun['sympy']] = fun['c++'] + self._code_printer.known_functions[fun["sympy"]] = fun["c++"] def differential_states(self) -> List[DifferentialState]: """Get all differential states.""" @@ -954,11 +916,9 @@ def states(self) -> List[State]: """Get all states.""" return self._differential_states + self._algebraic_states - @log_execution_time('importing SbmlImporter', logger) + @log_execution_time("importing SbmlImporter", logger) def import_from_sbml_importer( - self, - si: 'sbml_import.SbmlImporter', - compute_cls: Optional[bool] = True + self, si: "sbml_import.SbmlImporter", compute_cls: Optional[bool] = True ) -> None: """ Imports a model specification from a @@ -977,11 +937,11 @@ def import_from_sbml_importer( for ispl, spl in enumerate(si.splines): spline_expr = spl.ode_model_symbol(si) spline_subs[spl.sbml_id] = spline_expr - self.add_component(Expression( - identifier=spl.sbml_id, - name=str(spl.sbml_id), - value=spline_expr - )) + self.add_component( + Expression( + identifier=spl.sbml_id, name=str(spl.sbml_id), value=spline_expr + ) + ) self.splines = si.splines # get symbolic expression from SBML importers @@ -989,8 +949,10 @@ def import_from_sbml_importer( # assemble fluxes and add them as expressions to the model assert len(si.flux_ids) == len(si.flux_vector) - fluxes = [generate_flux_symbol(ir, name=flux_id) - for ir, flux_id in enumerate(si.flux_ids)] + fluxes = [ + generate_flux_symbol(ir, name=flux_id) + for ir, flux_id in enumerate(si.flux_ids) + ] # correct time derivatives for compartment changes def transform_dxdt_to_concentration(species_id, dxdt): @@ -1019,9 +981,9 @@ def transform_dxdt_to_concentration(species_id, dxdt): # volume, respectively. species = si.symbols[SymbolId.SPECIES][species_id] - comp = species['compartment'] + comp = species["compartment"] if comp in si.symbols[SymbolId.SPECIES]: - dv_dt = si.symbols[SymbolId.SPECIES][comp]['dt'] + dv_dt = si.symbols[SymbolId.SPECIES][comp]["dt"] xdot = (dxdt - dv_dt * species_id) / comp return xdot elif comp in si.compartment_assignment_rules: @@ -1029,24 +991,23 @@ def transform_dxdt_to_concentration(species_id, dxdt): # we need to flatten out assignments in the compartment in # order to ensure that we catch all species dependencies - v = smart_subs_dict(v, si.symbols[SymbolId.EXPRESSION], - 'value') + v = smart_subs_dict(v, si.symbols[SymbolId.EXPRESSION], "value") dv_dt = v.diff(si.amici_time_symbol) # we may end up with a time derivative of the compartment # volume due to parameter rate rules - comp_rate_vars = [p for p in v.free_symbols - if p in si.symbols[SymbolId.SPECIES]] + comp_rate_vars = [ + p for p in v.free_symbols if p in si.symbols[SymbolId.SPECIES] + ] for var in comp_rate_vars: - dv_dt += \ - v.diff(var) * si.symbols[SymbolId.SPECIES][var]['dt'] + dv_dt += v.diff(var) * si.symbols[SymbolId.SPECIES][var]["dt"] dv_dx = v.diff(species_id) xdot = (dxdt - dv_dt * species_id) / (dv_dx * species_id + v) return xdot elif comp in si.symbols[SymbolId.ALGEBRAIC_STATE]: raise SBMLException( - f'Species {species_id} is in a compartment {comp} that is' - f' defined by an algebraic equation. This is not' - f' supported.' + f"Species {species_id} is in a compartment {comp} that is" + f" defined by an algebraic equation. This is not" + f" supported." ) else: v = si.compartments[comp] @@ -1057,45 +1018,39 @@ def transform_dxdt_to_concentration(species_id, dxdt): return dxdt / v # create dynamics without respecting conservation laws first - dxdt = smart_multiply(si.stoichiometric_matrix, - MutableDenseMatrix(fluxes)) - for ix, ((species_id, species), formula) in enumerate(zip( - symbols[SymbolId.SPECIES].items(), - dxdt - )): + dxdt = smart_multiply(si.stoichiometric_matrix, MutableDenseMatrix(fluxes)) + for ix, ((species_id, species), formula) in enumerate( + zip(symbols[SymbolId.SPECIES].items(), dxdt) + ): # rate rules and amount species don't need to be updated - if 'dt' in species: + if "dt" in species: continue - if species['amount']: - species['dt'] = formula + if species["amount"]: + species["dt"] = formula else: - species['dt'] = transform_dxdt_to_concentration(species_id, - formula) + species["dt"] = transform_dxdt_to_concentration(species_id, formula) # create all basic components of the DE model and add them. for symbol_name in symbols: # transform dict of lists into a list of dicts - args = ['name', 'identifier'] + args = ["name", "identifier"] if symbol_name == SymbolId.SPECIES: - args += ['dt', 'init'] + args += ["dt", "init"] elif symbol_name == SymbolId.ALGEBRAIC_STATE: - args += ['init'] + args += ["init"] else: - args += ['value'] + args += ["value"] if symbol_name == SymbolId.EVENT: - args += ['state_update', 'initial_value'] + args += ["state_update", "initial_value"] elif symbol_name == SymbolId.OBSERVABLE: - args += ['transformation'] + args += ["transformation"] elif symbol_name == SymbolId.EVENT_OBSERVABLE: - args += ['event'] + args += ["event"] comp_kwargs = [ - { - 'identifier': var_id, - **{k: v for k, v in var.items() if k in args} - } + {"identifier": var_id, **{k: v for k, v in var.items() if k in args}} for var_id, var in symbols[symbol_name].items() ] @@ -1107,11 +1062,9 @@ def transform_dxdt_to_concentration(species_id, dxdt): for flux_id, flux in zip(fluxes, si.flux_vector): # replace splines inside fluxes flux = flux.subs(spline_subs) - self.add_component(Expression( - identifier=flux_id, - name=str(flux_id), - value=flux - )) + self.add_component( + Expression(identifier=flux_id, name=str(flux_id), value=flux) + ) # process conservation laws if compute_cls: @@ -1120,13 +1073,13 @@ def transform_dxdt_to_concentration(species_id, dxdt): # fill in 'self._sym' based on prototypes and components in ode_model self.generate_basic_variables() self._has_quadratic_nllh = all( - llh['dist'] in ['normal', 'lin-normal', 'log-normal', - 'log10-normal'] + llh["dist"] in ["normal", "lin-normal", "log-normal", "log10-normal"] for llh in si.symbols[SymbolId.LLHY].values() ) - def add_component(self, component: ModelQuantity, - insert_first: Optional[bool] = False) -> None: + def add_component( + self, component: ModelQuantity, insert_first: Optional[bool] = False + ) -> None: """ Adds a new ModelQuantity to the model. @@ -1138,28 +1091,45 @@ def add_component(self, component: ModelQuantity, may refer to other components of the same type. """ if type(component) not in { - Observable, Expression, Parameter, Constant, DifferentialState, - AlgebraicState, AlgebraicEquation, - LogLikelihoodY, LogLikelihoodZ, LogLikelihoodRZ, - SigmaY, SigmaZ, ConservationLaw, Event, EventObservable + Observable, + Expression, + Parameter, + Constant, + DifferentialState, + AlgebraicState, + AlgebraicEquation, + LogLikelihoodY, + LogLikelihoodZ, + LogLikelihoodRZ, + SigmaY, + SigmaZ, + ConservationLaw, + Event, + EventObservable, }: - raise ValueError(f'Invalid component type {type(component)}') + raise ValueError(f"Invalid component type {type(component)}") component_list = getattr( - self, '_' + '_'.join( - s.lower() for s in re.split(r"([A-Z][^A-Z]+)", - type(component).__name__) if s - ) + 's' + self, + "_" + + "_".join( + s.lower() + for s in re.split(r"([A-Z][^A-Z]+)", type(component).__name__) + if s + ) + + "s", ) if insert_first: component_list.insert(0, component) else: component_list.append(component) - def add_conservation_law(self, - state: sp.Symbol, - total_abundance: sp.Symbol, - coefficients: Dict[sp.Symbol, sp.Expr]) -> None: + def add_conservation_law( + self, + state: sp.Symbol, + total_abundance: sp.Symbol, + coefficients: Dict[sp.Symbol, sp.Expr], + ) -> None: r""" Adds a new conservation law to the model. A conservation law is defined by the conserved quantity :math:`T = \sum_i(a_i * x_i)`, where @@ -1177,18 +1147,24 @@ def add_conservation_law(self, Dictionary of coefficients {x_i: a_i} """ try: - ix = next(filter(lambda is_s: is_s[1].get_id() == state, - enumerate(self._differential_states)))[0] + ix = next( + filter( + lambda is_s: is_s[1].get_id() == state, + enumerate(self._differential_states), + ) + )[0] except StopIteration: - raise ValueError(f'Specified state {state} was not found in the ' - f'model states.') + raise ValueError( + f"Specified state {state} was not found in the " f"model states." + ) state_id = self._differential_states[ix].get_id() # \sum_{i≠j}(a_i * x_i)/a_j - target_expression = sp.Add(*( - c_i*x_i for x_i, c_i in coefficients.items() if x_i != state - )) / coefficients[state] + target_expression = ( + sp.Add(*(c_i * x_i for x_i, c_i in coefficients.items() if x_i != state)) + / coefficients[state] + ) # x_j = T/a_j - \sum_{i≠j}(a_i * x_i)/a_j state_expr = total_abundance - target_expression @@ -1197,13 +1173,11 @@ def add_conservation_law(self, abundance_expr = target_expression + state_id self.add_component( - Expression(state_id, str(state_id), state_expr), - insert_first=True + Expression(state_id, str(state_id), state_expr), insert_first=True ) cl = ConservationLaw( - total_abundance, f'total_{state_id}', abundance_expr, - coefficients, state_id + total_abundance, f"total_{state_id}", abundance_expr, coefficients, state_id ) self.add_component(cl) @@ -1225,7 +1199,7 @@ def num_states_rdata(self) -> int: :return: number of state variable symbols """ - return len(self.sym('x_rdata')) + return len(self.sym("x_rdata")) def num_states_solver(self) -> int: """ @@ -1234,7 +1208,7 @@ def num_states_solver(self) -> int: :return: number of state variable symbols """ - return len(self.sym('x')) + return len(self.sym("x")) def num_cons_law(self) -> int: """ @@ -1253,8 +1227,8 @@ def num_state_reinits(self) -> int: :return: number of state variable symbols with reinitialization """ - reinit_states = self.eq('x0_fixedParameters') - solver_states = self.eq('x_solver') + reinit_states = self.eq("x0_fixedParameters") + solver_states = self.eq("x_solver") return sum(ix in solver_states for ix in reinit_states) def num_obs(self) -> int: @@ -1264,7 +1238,7 @@ def num_obs(self) -> int: :return: number of observable symbols """ - return len(self.sym('y')) + return len(self.sym("y")) def num_eventobs(self) -> int: """ @@ -1273,7 +1247,7 @@ def num_eventobs(self) -> int: :return: number of event observable symbols """ - return len(self.sym('z')) + return len(self.sym("z")) def num_const(self) -> int: """ @@ -1282,7 +1256,7 @@ def num_const(self) -> int: :return: number of constant symbols """ - return len(self.sym('k')) + return len(self.sym("k")) def num_par(self) -> int: """ @@ -1291,7 +1265,7 @@ def num_par(self) -> int: :return: number of parameter symbols """ - return len(self.sym('p')) + return len(self.sym("p")) def num_expr(self) -> int: """ @@ -1300,7 +1274,7 @@ def num_expr(self) -> int: :return: number of expression symbols """ - return len(self.sym('w')) + return len(self.sym("w")) def num_events(self) -> int: """ @@ -1309,7 +1283,7 @@ def num_events(self) -> int: :return: number of event symbols (length of the root vector in AMICI) """ - return len(self.sym('h')) + return len(self.sym("h")) def sym(self, name: str) -> sp.Matrix: """ @@ -1342,7 +1316,7 @@ def sparsesym(self, name: str, force_generate: bool = True) -> List[str]: linearized Matrix containing the symbolic identifiers """ if name not in sparse_functions: - raise ValueError(f'{name} is not marked as sparse') + raise ValueError(f"{name} is not marked as sparse") if name not in self._sparsesyms and force_generate: self._generate_sparse_symbol(name) return self._sparsesyms.get(name, []) @@ -1360,7 +1334,7 @@ def eq(self, name: str) -> sp.Matrix: """ if name not in self._eqs: - dec = log_execution_time(f'computing {name}', logger) + dec = log_execution_time(f"computing {name}", logger) dec(self._compute_equation)(name) return self._eqs[name] @@ -1376,13 +1350,12 @@ def sparseeq(self, name) -> sp.Matrix: linearized matrix containing the symbolic formulas """ if name not in sparse_functions: - raise ValueError(f'{name} is not marked as sparse') + raise ValueError(f"{name} is not marked as sparse") if name not in self._sparseeqs: self._generate_sparse_symbol(name) return self._sparseeqs[name] - def colptrs(self, name: str) -> Union[List[sp.Number], - List[List[sp.Number]]]: + def colptrs(self, name: str) -> Union[List[sp.Number], List[List[sp.Number]]]: """ Returns (and constructs if necessary) the column pointers for a sparsified symbolic variable. @@ -1394,13 +1367,12 @@ def colptrs(self, name: str) -> Union[List[sp.Number], list containing the column pointers """ if name not in sparse_functions: - raise ValueError(f'{name} is not marked as sparse') + raise ValueError(f"{name} is not marked as sparse") if name not in self._sparseeqs: self._generate_sparse_symbol(name) return self._colptrs[name] - def rowvals(self, name: str) -> Union[List[sp.Number], - List[List[sp.Number]]]: + def rowvals(self, name: str) -> Union[List[sp.Number], List[List[sp.Number]]]: """ Returns (and constructs if necessary) the row values for a sparsified symbolic variable. @@ -1412,7 +1384,7 @@ def rowvals(self, name: str) -> Union[List[sp.Number], list containing the row values """ if name not in sparse_functions: - raise ValueError(f'{name} is not marked as sparse') + raise ValueError(f"{name} is not marked as sparse") if name not in self._sparseeqs: self._generate_sparse_symbol(name) return self._rowvals[name] @@ -1452,10 +1424,12 @@ def free_symbols(self) -> Set[sp.Basic]: Returns list of free symbols that appear in RHS and initial conditions. """ - return set(chain.from_iterable( - state.get_free_symbols() - for state in self.states() + self.algebraic_equations() - )) + return set( + chain.from_iterable( + state.get_free_symbols() + for state in self.states() + self.algebraic_equations() + ) + ) def _generate_symbol(self, name: str) -> None: """ @@ -1466,105 +1440,115 @@ def _generate_symbol(self, name: str) -> None: """ if name in self._variable_prototype: components = self._variable_prototype[name]() - self._syms[name] = sp.Matrix([ - comp.get_id() - for comp in components - ]) - if name == 'y': - self._syms['my'] = sp.Matrix([ - comp.get_measurement_symbol() - for comp in components - ]) - if name == 'z': - self._syms['mz'] = sp.Matrix([ - comp.get_measurement_symbol() - for comp in components - ]) - self._syms['rz'] = sp.Matrix([ - comp.get_regularization_symbol() - for comp in components - ]) + self._syms[name] = sp.Matrix([comp.get_id() for comp in components]) + if name == "y": + self._syms["my"] = sp.Matrix( + [comp.get_measurement_symbol() for comp in components] + ) + if name == "z": + self._syms["mz"] = sp.Matrix( + [comp.get_measurement_symbol() for comp in components] + ) + self._syms["rz"] = sp.Matrix( + [comp.get_regularization_symbol() for comp in components] + ) return - elif name == 'x': - self._syms[name] = sp.Matrix([ - state.get_id() - for state in self.states() - if not state.has_conservation_law() - ]) + elif name == "x": + self._syms[name] = sp.Matrix( + [ + state.get_id() + for state in self.states() + if not state.has_conservation_law() + ] + ) return - elif name == 'xdot': - self._syms[name] = sp.Matrix([ - f'd{x.get_id()}dt' if self.is_ode() else f'de_{ix}' - for ix, x in enumerate(self._differential_states) - if not x.has_conservation_law() - ] + [ - f'ae_{ix}' - for ix in range(len(self._algebraic_equations)) - ]) + elif name == "xdot": + self._syms[name] = sp.Matrix( + [ + f"d{x.get_id()}dt" if self.is_ode() else f"de_{ix}" + for ix, x in enumerate(self._differential_states) + if not x.has_conservation_law() + ] + + [f"ae_{ix}" for ix in range(len(self._algebraic_equations))] + ) return - elif name == 'dx': - self._syms[name] = sp.Matrix([ - f'd{state.get_id()}dt' - for state in self.states() - if not state.has_conservation_law() - ]) + elif name == "dx": + self._syms[name] = sp.Matrix( + [ + f"d{state.get_id()}dt" + for state in self.states() + if not state.has_conservation_law() + ] + ) return - elif name == 'sx0': - self._syms[name] = sp.Matrix([ - f's{state.get_id()}_0' - for state in self.states() - if not state.has_conservation_law() - ]) + elif name == "sx0": + self._syms[name] = sp.Matrix( + [ + f"s{state.get_id()}_0" + for state in self.states() + if not state.has_conservation_law() + ] + ) return - elif name == 'sx_rdata': - self._syms[name] = sp.Matrix([ - f'sx_rdata_{i}' - for i in range(len(self.states())) - ]) + elif name == "sx_rdata": + self._syms[name] = sp.Matrix( + [f"sx_rdata_{i}" for i in range(len(self.states()))] + ) return - elif name == 'dtcldp': + elif name == "dtcldp": # check, whether the CL consists of only one state. Then, # sensitivities drop out, otherwise generate symbols - self._syms[name] = sp.Matrix([ - [sp.Symbol(f's{strip_pysb(tcl.get_id())}__' - f'{strip_pysb(par.get_id())}', real=True) - for par in self._parameters] - if self.conservation_law_has_multispecies(tcl) - else [0] * self.num_par() - for tcl in self._conservation_laws - ]) + self._syms[name] = sp.Matrix( + [ + [ + sp.Symbol( + f"s{strip_pysb(tcl.get_id())}__" + f"{strip_pysb(par.get_id())}", + real=True, + ) + for par in self._parameters + ] + if self.conservation_law_has_multispecies(tcl) + else [0] * self.num_par() + for tcl in self._conservation_laws + ] + ) return - elif name == 'xdot_old': - length = len(self.eq('xdot')) + elif name == "xdot_old": + length = len(self.eq("xdot")) elif name in sparse_functions: self._generate_sparse_symbol(name) return elif name in self._symboldim_funs: length = self._symboldim_funs[name]() - elif name == 'stau': + elif name == "stau": length = self.eq(name)[0].shape[1] elif name in sensi_functions: length = self.eq(name).shape[0] - elif name == 'spl': + elif name == "spl": # placeholders for the numeric spline values. # Need to create symbols - self._syms[name] = sp.Matrix([ - [f'spl_{isp}' for isp in range(len(self.splines))] - ]) + self._syms[name] = sp.Matrix( + [[f"spl_{isp}" for isp in range(len(self.splines))]] + ) return - elif name == 'sspl': + elif name == "sspl": # placeholders for spline sensitivities. Need to create symbols - self._syms[name] = sp.Matrix([ - [f'sspl_{isp}_{ip}' for ip in range(len(self._syms['p']))] - for isp in range(len(self.splines)) - ]) + self._syms[name] = sp.Matrix( + [ + [f"sspl_{isp}_{ip}" for ip in range(len(self._syms["p"]))] + for isp in range(len(self.splines)) + ] + ) return else: length = len(self.eq(name)) - self._syms[name] = sp.Matrix([ - sp.Symbol(f'{name}{0 if name == "stau" else i}', real=True) - for i in range(length) - ]) + self._syms[name] = sp.Matrix( + [ + sp.Symbol(f'{name}{0 if name == "stau" else i}', real=True) + for i in range(length) + ] + ) def generate_basic_variables(self) -> None: """ @@ -1579,10 +1563,10 @@ def generate_basic_variables(self) -> None: if var not in self._syms: self._generate_symbol(var) # symbols for spline values need to be created in addition - for var in ['spl', 'sspl']: + for var in ["spl", "sspl"]: self._generate_symbol(var) - self._generate_symbol('x') + self._generate_symbol("x") def parse_events(self) -> None: """ @@ -1624,26 +1608,23 @@ def get_appearance_counts(self, idxs: List[int]) -> List[int]: list of counts for the states ordered according to the provided indices """ - free_symbols_dt = list(itertools.chain.from_iterable( - [ - str(symbol) - for symbol in state.get_dt().free_symbols - ] - for state in self.states() - )) + free_symbols_dt = list( + itertools.chain.from_iterable( + [str(symbol) for symbol in state.get_dt().free_symbols] + for state in self.states() + ) + ) - free_symbols_expr = list(itertools.chain.from_iterable( - [ - str(symbol) - for symbol in expr.get_val().free_symbols - ] - for expr in self._expressions - )) + free_symbols_expr = list( + itertools.chain.from_iterable( + [str(symbol) for symbol in expr.get_val().free_symbols] + for expr in self._expressions + ) + ) return [ free_symbols_dt.count(str(self._differential_states[idx].get_id())) - + - free_symbols_expr.count(str(self._differential_states[idx].get_id())) + + free_symbols_expr.count(str(self._differential_states[idx].get_id())) for idx in idxs ] @@ -1665,7 +1646,7 @@ def _generate_sparse_symbol(self, name: str) -> None: rownames = self.sym(eq) colnames = self.sym(var) - if name == 'dJydy': + if name == "dJydy": # One entry per y-slice self._colptrs[name] = [] self._rowvals[name] = [] @@ -1674,21 +1655,33 @@ def _generate_sparse_symbol(self, name: str) -> None: self._syms[name] = [] for iy in range(self.num_obs()): - symbol_col_ptrs, symbol_row_vals, sparse_list, symbol_list, \ - sparse_matrix = self._code_printer.csc_matrix( - matrix[iy, :], rownames=rownames, colnames=colnames, - identifier=iy) + ( + symbol_col_ptrs, + symbol_row_vals, + sparse_list, + symbol_list, + sparse_matrix, + ) = self._code_printer.csc_matrix( + matrix[iy, :], rownames=rownames, colnames=colnames, identifier=iy + ) self._colptrs[name].append(symbol_col_ptrs) self._rowvals[name].append(symbol_row_vals) self._sparseeqs[name].append(sparse_list) self._sparsesyms[name].append(symbol_list) self._syms[name].append(sparse_matrix) else: - symbol_col_ptrs, symbol_row_vals, sparse_list, symbol_list, \ - sparse_matrix = self._code_printer.csc_matrix( - matrix, rownames=rownames, colnames=colnames, - pattern_only=name in nobody_functions - ) + ( + symbol_col_ptrs, + symbol_row_vals, + sparse_list, + symbol_list, + sparse_matrix, + ) = self._code_printer.csc_matrix( + matrix, + rownames=rownames, + colnames=colnames, + pattern_only=name in nobody_functions, + ) self._colptrs[name] = symbol_col_ptrs self._rowvals[name] = symbol_row_vals @@ -1706,99 +1699,107 @@ def _compute_equation(self, name: str) -> None: # replacement ensures that we don't have to adapt name in abstract # model and keep backwards compatibility with matlab match_deriv = DERIVATIVE_PATTERN.match( - re.sub(r'dJ(y|z|rz)dsigma', r'dJ\1dsigma\1', name) - .replace('sigmarz', 'sigmaz') - .replace('dJrzdz', 'dJrzdrz') + re.sub(r"dJ(y|z|rz)dsigma", r"dJ\1dsigma\1", name) + .replace("sigmarz", "sigmaz") + .replace("dJrzdz", "dJrzdrz") ) - time_symbol = sp.Matrix([symbol_with_assumptions('t')]) + time_symbol = sp.Matrix([symbol_with_assumptions("t")]) if name in self._equation_prototype: self._equation_from_components(name, self._equation_prototype[name]()) elif name in self._total_derivative_prototypes: args = self._total_derivative_prototypes[name] - args['name'] = name - self._lock_total_derivative += args['chainvars'] + args["name"] = name + self._lock_total_derivative += args["chainvars"] self._total_derivative(**args) - for cv in args['chainvars']: + for cv in args["chainvars"]: self._lock_total_derivative.remove(cv) - elif name == 'xdot': + elif name == "xdot": if self.is_ode(): - self._eqs[name] = sp.Matrix([ - state.get_dt() for state in self._differential_states - if not state.has_conservation_law() - ]) + self._eqs[name] = sp.Matrix( + [ + state.get_dt() + for state in self._differential_states + if not state.has_conservation_law() + ] + ) else: - self._eqs[name] = sp.Matrix([ - x.get_dt() - dx - for x, dx in zip( - (s for s in self._differential_states - if not s.has_conservation_law()), - self.sym('dx') - ) - ] + [ - eq.get_val() - for eq in self._algebraic_equations - ]) - - elif name == 'x_rdata': - self._eqs[name] = sp.Matrix([ - state.get_x_rdata() - for state in self.states() - ]) + self._eqs[name] = sp.Matrix( + [ + x.get_dt() - dx + for x, dx in zip( + ( + s + for s in self._differential_states + if not s.has_conservation_law() + ), + self.sym("dx"), + ) + ] + + [eq.get_val() for eq in self._algebraic_equations] + ) - elif name == 'x_solver': - self._eqs[name] = sp.Matrix([ - state.get_id() - for state in self.states() - if not state.has_conservation_law() - ]) + elif name == "x_rdata": + self._eqs[name] = sp.Matrix( + [state.get_x_rdata() for state in self.states()] + ) - elif name == 'sx_solver': - self._eqs[name] = sp.Matrix([ - self.sym('sx_rdata')[ix] - for ix, state in enumerate(self.states()) - if not state.has_conservation_law() - ]) + elif name == "x_solver": + self._eqs[name] = sp.Matrix( + [ + state.get_id() + for state in self.states() + if not state.has_conservation_law() + ] + ) + + elif name == "sx_solver": + self._eqs[name] = sp.Matrix( + [ + self.sym("sx_rdata")[ix] + for ix, state in enumerate(self.states()) + if not state.has_conservation_law() + ] + ) - elif name == 'sx0': - self._derivative(name[1:], 'p', name=name) + elif name == "sx0": + self._derivative(name[1:], "p", name=name) - elif name == 'sx0_fixedParameters': + elif name == "sx0_fixedParameters": # deltax = -x+x0_fixedParameters if x0_fixedParameters>0 else 0 # deltasx = -sx+dx0_fixed_parametersdx*sx+dx0_fixedParametersdp # if x0_fixedParameters>0 else 0 # sx0_fixedParameters = sx+deltasx = # dx0_fixed_parametersdx*sx+dx0_fixedParametersdp self._eqs[name] = smart_jacobian( - self.eq('x0_fixedParameters'), self.sym('p') + self.eq("x0_fixedParameters"), self.sym("p") ) dx0_fixed_parametersdx = smart_jacobian( - self.eq('x0_fixedParameters'), self.sym('x') + self.eq("x0_fixedParameters"), self.sym("x") ) if not smart_is_zero_matrix(dx0_fixed_parametersdx): if isinstance(self._eqs[name], ImmutableDenseMatrix): self._eqs[name] = MutableDenseMatrix(self._eqs[name]) - tmp = smart_multiply(dx0_fixed_parametersdx, self.sym('sx0')) + tmp = smart_multiply(dx0_fixed_parametersdx, self.sym("sx0")) for ip in range(self._eqs[name].shape[1]): self._eqs[name][:, ip] += tmp - elif name == 'x0_fixedParameters': - k = self.sym('k') + elif name == "x0_fixedParameters": + k = self.sym("k") self._x0_fixedParameters_idx = [ ix - for ix, eq in enumerate(self.eq('x0')) + for ix, eq in enumerate(self.eq("x0")) if any(sym in eq.free_symbols for sym in k) ] - eq = self.eq('x0') - self._eqs[name] = sp.Matrix([eq[ix] for ix in - self._x0_fixedParameters_idx]) + eq = self.eq("x0") + self._eqs[name] = sp.Matrix([eq[ix] for ix in self._x0_fixedParameters_idx]) - elif name == 'dtotal_cldx_rdata': - x_rdata = self.sym('x_rdata') + elif name == "dtotal_cldx_rdata": + x_rdata = self.sym("x_rdata") self._eqs[name] = sp.Matrix( [ [cl.get_ncoeff(xr) for xr in x_rdata] @@ -1806,18 +1807,17 @@ def _compute_equation(self, name: str) -> None: ] ) - elif name == 'dtcldx': + elif name == "dtcldx": # this is always zero - self._eqs[name] = \ - sp.zeros(self.num_cons_law(), self.num_states_solver()) + self._eqs[name] = sp.zeros(self.num_cons_law(), self.num_states_solver()) - elif name == 'dtcldp': + elif name == "dtcldp": # force symbols self._eqs[name] = self.sym(name) - elif name == 'dx_rdatadx_solver': + elif name == "dx_rdatadx_solver": if self.num_cons_law(): - x_solver = self.sym('x') + x_solver = self.sym("x") self._eqs[name] = sp.Matrix( [ [state.get_dx_rdata_dx_solver(xs) for xs in x_solver] @@ -1828,74 +1828,71 @@ def _compute_equation(self, name: str) -> None: # so far, dx_rdatadx_solver is only required for sx_rdata # in case of no conservation laws, C++ code will directly use # sx, we don't need this - self._eqs[name] = \ - sp.zeros(self.num_states_rdata(), - self.num_states_solver()) + self._eqs[name] = sp.zeros( + self.num_states_rdata(), self.num_states_solver() + ) - elif name == 'dx_rdatadp': + elif name == "dx_rdatadp": if self.num_cons_law(): - self._eqs[name] = smart_jacobian(self.eq('x_rdata'), - self.sym('p')) + self._eqs[name] = smart_jacobian(self.eq("x_rdata"), self.sym("p")) else: # so far, dx_rdatadp is only required for sx_rdata # in case of no conservation laws, C++ code will directly use # sx, we don't need this - self._eqs[name] = \ - sp.zeros(self.num_states_rdata(), - self.num_par()) + self._eqs[name] = sp.zeros(self.num_states_rdata(), self.num_par()) - elif name == 'dx_rdatadtcl': - self._eqs[name] = smart_jacobian(self.eq('x_rdata'), - self.sym('tcl')) + elif name == "dx_rdatadtcl": + self._eqs[name] = smart_jacobian(self.eq("x_rdata"), self.sym("tcl")) - elif name == 'dxdotdx_explicit': + elif name == "dxdotdx_explicit": # force symbols - self._derivative('xdot', 'x', name=name) + self._derivative("xdot", "x", name=name) - elif name == 'dxdotdp_explicit': + elif name == "dxdotdp_explicit": # force symbols - self._derivative('xdot', 'p', name=name) + self._derivative("xdot", "p", name=name) - elif name == 'spl': + elif name == "spl": self._eqs[name] = self.sym(name) - elif name == 'sspl': + elif name == "sspl": # force symbols self._eqs[name] = self.sym(name) - elif name == 'spline_values': + elif name == "spline_values": # force symbols - self._eqs[name] = sp.Matrix([ - y for spline in self.splines - for y in spline.values_at_nodes - ]) + self._eqs[name] = sp.Matrix( + [y for spline in self.splines for y in spline.values_at_nodes] + ) - elif name == 'spline_slopes': + elif name == "spline_slopes": # force symbols - self._eqs[name] = sp.Matrix([ - d for spline in self.splines - for d in ( - sp.zeros(len(spline.derivatives_at_nodes), 1) - if spline.derivatives_by_fd - else spline.derivatives_at_nodes - ) - ]) + self._eqs[name] = sp.Matrix( + [ + d + for spline in self.splines + for d in ( + sp.zeros(len(spline.derivatives_at_nodes), 1) + if spline.derivatives_by_fd + else spline.derivatives_at_nodes + ) + ] + ) - elif name == 'drootdt': - self._eqs[name] = smart_jacobian(self.eq('root'), time_symbol) + elif name == "drootdt": + self._eqs[name] = smart_jacobian(self.eq("root"), time_symbol) - elif name == 'drootdt_total': + elif name == "drootdt_total": # backsubstitution of optimized right-hand side terms into RHS # calling subs() is costly. Due to looping over events though, the # following lines are only evaluated if a model has events - w_sorted = \ - toposort_symbols(dict(zip(self.sym('w'), self.eq('w')))) - tmp_xdot = smart_subs_dict(self.eq('xdot'), w_sorted) - self._eqs[name] = self.eq('drootdt') + w_sorted = toposort_symbols(dict(zip(self.sym("w"), self.eq("w")))) + tmp_xdot = smart_subs_dict(self.eq("xdot"), w_sorted) + self._eqs[name] = self.eq("drootdt") if self.num_states_solver(): - self._eqs[name] += smart_multiply(self.eq('drootdx'), tmp_xdot) + self._eqs[name] += smart_multiply(self.eq("drootdx"), tmp_xdot) - elif name == 'deltax': + elif name == "deltax": # fill boluses for Heaviside functions, as empty state updates # would cause problems when writing the function file later event_eqs = [] @@ -1907,29 +1904,23 @@ def _compute_equation(self, name: str) -> None: self._eqs[name] = event_eqs - elif name == 'z': - event_observables = [ - sp.zeros(self.num_eventobs(), 1) - for _ in self._events - ] - event_ids = [ - e.get_id() for e in self._events - ] + elif name == "z": + event_observables = [sp.zeros(self.num_eventobs(), 1) for _ in self._events] + event_ids = [e.get_id() for e in self._events] # TODO: get rid of this stupid 1-based indexing as soon as we can # the matlab interface z2event = [ event_ids.index(event_obs.get_event()) + 1 for event_obs in self._event_observables ] - for (iz, ie), event_obs in zip(enumerate(z2event), - self._event_observables): - event_observables[ie-1][iz] = event_obs.get_val() + for (iz, ie), event_obs in zip(enumerate(z2event), self._event_observables): + event_observables[ie - 1][iz] = event_obs.get_val() self._eqs[name] = event_observables self._z2event = z2event - elif name in ['ddeltaxdx', 'ddeltaxdp', 'ddeltaxdt', 'dzdp', 'dzdx']: - if match_deriv[2] == 't': + elif name in ["ddeltaxdx", "ddeltaxdp", "ddeltaxdt", "dzdp", "dzdx"]: + if match_deriv[2] == "t": var = time_symbol else: var = self.sym(match_deriv[2]) @@ -1938,128 +1929,125 @@ def _compute_equation(self, name: str) -> None: smart_jacobian(self.eq(match_deriv[1])[ie], var) for ie in range(self.num_events()) ] - if name == 'dzdx': + if name == "dzdx": for ie in range(self.num_events()): - dtaudx = -self.eq('drootdx')[ie, :] / \ - self.eq('drootdt_total')[ie] + dtaudx = -self.eq("drootdx")[ie, :] / self.eq("drootdt_total")[ie] for iz in range(self.num_eventobs()): - if ie != self._z2event[iz]-1: + if ie != self._z2event[iz] - 1: continue - dzdt = sp.diff(self.eq('z')[ie][iz], time_symbol) + dzdt = sp.diff(self.eq("z")[ie][iz], time_symbol) self._eqs[name][ie][iz, :] += dzdt * dtaudx - elif name in ['rz', 'drzdx', 'drzdp']: + elif name in ["rz", "drzdx", "drzdp"]: eq_events = [] for ie in range(self.num_events()): val = sp.zeros( self.num_eventobs(), - 1 if name == 'rz' else len(self.sym(match_deriv[2])) + 1 if name == "rz" else len(self.sym(match_deriv[2])), ) # match event observables to root function for iz in range(self.num_eventobs()): - if ie == self._z2event[iz]-1: - val[iz, :] = self.eq(name.replace('rz', 'root'))[ie, :] + if ie == self._z2event[iz] - 1: + val[iz, :] = self.eq(name.replace("rz", "root"))[ie, :] eq_events.append(val) self._eqs[name] = eq_events - elif name == 'stau': + elif name == "stau": self._eqs[name] = [ - -self.eq('sroot')[ie, :] / self.eq('drootdt_total')[ie] - if not self.eq('drootdt_total')[ie].is_zero else - sp.zeros(*self.eq('sroot')[ie, :].shape) + -self.eq("sroot")[ie, :] / self.eq("drootdt_total")[ie] + if not self.eq("drootdt_total")[ie].is_zero + else sp.zeros(*self.eq("sroot")[ie, :].shape) for ie in range(self.num_events()) ] - elif name == 'deltasx': + elif name == "deltasx": event_eqs = [] for ie, event in enumerate(self._events): - tmp_eq = sp.zeros(self.num_states_solver(), self.num_par()) # need to check if equations are zero since we are using # symbols - if not smart_is_zero_matrix(self.eq('stau')[ie]): + if not smart_is_zero_matrix(self.eq("stau")[ie]): tmp_eq += smart_multiply( - (self.sym('xdot_old') - self.sym('xdot')), - self.sym('stau').T) + (self.sym("xdot_old") - self.sym("xdot")), self.sym("stau").T + ) # only add deltax part if there is state update if event._state_update is not None: # partial derivative for the parameters - tmp_eq += self.eq('ddeltaxdp')[ie] + tmp_eq += self.eq("ddeltaxdp")[ie] # initial part of chain rule state variables - tmp_dxdp = self.sym('sx') * sp.ones(1, self.num_par()) + tmp_dxdp = self.sym("sx") * sp.ones(1, self.num_par()) # need to check if equations are zero since we are using # symbols - if not smart_is_zero_matrix(self.eq('stau')[ie]): + if not smart_is_zero_matrix(self.eq("stau")[ie]): # chain rule for the time point - tmp_eq += smart_multiply(self.eq('ddeltaxdt')[ie], - self.sym('stau').T) + tmp_eq += smart_multiply( + self.eq("ddeltaxdt")[ie], self.sym("stau").T + ) # additional part of chain rule state variables # This part only works if we use self.eq('xdot') # instead of self.sym('xdot'). Not immediately clear # why that is. - tmp_dxdp += smart_multiply(self.eq('xdot'), - self.sym('stau').T) + tmp_dxdp += smart_multiply(self.eq("xdot"), self.sym("stau").T) # finish chain rule for the state variables - tmp_eq += smart_multiply(self.eq('ddeltaxdx')[ie], - tmp_dxdp) + tmp_eq += smart_multiply(self.eq("ddeltaxdx")[ie], tmp_dxdp) event_eqs.append(tmp_eq) self._eqs[name] = event_eqs - elif name == 'xdot_old': + elif name == "xdot_old": # force symbols self._eqs[name] = self.sym(name) - elif name == 'dwdx': - x = self.sym('x') - self._eqs[name] = sp.Matrix([ - [-cl.get_ncoeff(xs) for xs in x] - # the insert first in ode_model._add_conservation_law() means - # that we need to reverse the order here - for cl in reversed(self._conservation_laws) - ]).col_join(smart_jacobian(self.eq('w')[self.num_cons_law():, :], - x)) + elif name == "dwdx": + x = self.sym("x") + self._eqs[name] = sp.Matrix( + [ + [-cl.get_ncoeff(xs) for xs in x] + # the insert first in ode_model._add_conservation_law() means + # that we need to reverse the order here + for cl in reversed(self._conservation_laws) + ] + ).col_join(smart_jacobian(self.eq("w")[self.num_cons_law() :, :], x)) elif match_deriv: self._derivative(match_deriv[1], match_deriv[2], name) else: - raise ValueError(f'Unknown equation {name}') + raise ValueError(f"Unknown equation {name}") - if name == 'root': + if name == "root": # Events are processed after the model has been set up. # Equations are there, but symbols for roots must be added - self.sym('h') + self.sym("h") - if name in {'Jy', 'dydx'}: + if name in {"Jy", "dydx"}: # do not transpose if we compute the partial derivative as part of # a total derivative if not len(self._lock_total_derivative): self._eqs[name] = self._eqs[name].transpose() - if name in {'dzdx', 'drzdx'}: - self._eqs[name] = [ - e.T for e in self._eqs[name] - ] + if name in {"dzdx", "drzdx"}: + self._eqs[name] = [e.T for e in self._eqs[name]] if self._simplify: - dec = log_execution_time(f'simplifying {name}', logger) + dec = log_execution_time(f"simplifying {name}", logger) if isinstance(self._eqs[name], list): self._eqs[name] = [ dec(_parallel_applyfunc)(sub_eq, self._simplify) for sub_eq in self._eqs[name] ] else: - self._eqs[name] = dec(_parallel_applyfunc)(self._eqs[name], - self._simplify) + self._eqs[name] = dec(_parallel_applyfunc)( + self._eqs[name], self._simplify + ) def sym_names(self) -> List[str]: """ @@ -2085,25 +2073,23 @@ def _derivative(self, eq: str, var: str, name: str = None) -> None: name of resulting symbolic variable, default is ``d{eq}d{var}`` """ if not name: - name = f'd{eq}d{var}' + name = f"d{eq}d{var}" ignore_chainrule = { - ('xdot', 'p'): 'w', # has generic implementation in c++ code - ('xdot', 'x'): 'w', # has generic implementation in c++ code - ('w', 'w'): 'tcl', # dtcldw = 0 - ('w', 'x'): 'tcl', # dtcldx = 0 + ("xdot", "p"): "w", # has generic implementation in c++ code + ("xdot", "x"): "w", # has generic implementation in c++ code + ("w", "w"): "tcl", # dtcldw = 0 + ("w", "x"): "tcl", # dtcldx = 0 } # automatically detect chainrule chainvars = [ - cv for cv in ['w', 'tcl'] + cv + for cv in ["w", "tcl"] if var_in_function_signature(eq, cv, self.is_ode()) - and cv not in self._lock_total_derivative - and var != cv - and min(self.sym(cv).shape) - and ( - (eq, var) not in ignore_chainrule - or ignore_chainrule[(eq, var)] != cv - ) + and cv not in self._lock_total_derivative + and var != cv + and min(self.sym(cv).shape) + and ((eq, var) not in ignore_chainrule or ignore_chainrule[(eq, var)] != cv) ] if len(chainvars): self._lock_total_derivative += chainvars @@ -2113,7 +2099,7 @@ def _derivative(self, eq: str, var: str, name: str = None) -> None: return # partial derivative - sym_eq = self.eq(eq).transpose() if eq == 'Jy' else self.eq(eq) + sym_eq = self.eq(eq).transpose() if eq == "Jy" else self.eq(eq) sym_var = self.sym(var) @@ -2123,7 +2109,7 @@ def _derivative(self, eq: str, var: str, name: str = None) -> None: # compute recursion depth based on nilpotency of jacobian. computing # nilpotency can be done more efficiently on numerical sparsity pattern - if name == 'dwdw': + if name == "dwdw": nonzeros = np.asarray( derivative.applyfunc(lambda x: int(not x.is_zero)) ).astype(np.int64) @@ -2134,14 +2120,14 @@ def _derivative(self, eq: str, var: str, name: str = None) -> None: self._w_recursion_depth += 1 if self._w_recursion_depth > len(sym_eq): raise RuntimeError( - 'dwdw is not nilpotent. Something, somewhere went ' - 'terribly wrong. Please file a bug report at ' - 'https://github.com/AMICI-dev/AMICI/issues and ' - 'attach this model.' + "dwdw is not nilpotent. Something, somewhere went " + "terribly wrong. Please file a bug report at " + "https://github.com/AMICI-dev/AMICI/issues and " + "attach this model." ) - if name == 'dydw' and not smart_is_zero_matrix(derivative): - dwdw = self.eq('dwdw') + if name == "dydw" and not smart_is_zero_matrix(derivative): + dwdw = self.eq("dwdw") # h(k) = d{eq}dw*dwdw^k* (k=1) h = smart_multiply(derivative, dwdw) while not smart_is_zero_matrix(h): @@ -2149,9 +2135,15 @@ def _derivative(self, eq: str, var: str, name: str = None) -> None: # h(k+1) = d{eq}dw*dwdw^(k+1) = h(k)*dwdw h = smart_multiply(h, dwdw) - def _total_derivative(self, name: str, eq: str, chainvars: List[str], - var: str, dydx_name: str = None, - dxdz_name: str = None) -> None: + def _total_derivative( + self, + name: str, + eq: str, + chainvars: List[str], + var: str, + dydx_name: str = None, + dxdz_name: str = None, + ) -> None: """ Creates a new symbolic variable according to a total derivative using the chain rule @@ -2184,7 +2176,7 @@ def _total_derivative(self, name: str, eq: str, chainvars: List[str], # Dydz = dydx*dxdz + dydz # initialize with partial derivative dydz without chain rule - self._eqs[name] = self.sym_or_eq(name, f'd{eq}d{var}') + self._eqs[name] = self.sym_or_eq(name, f"d{eq}d{var}") if not isinstance(self._eqs[name], sp.Symbol): # if not a Symbol, create a copy using sympy API # NB deepcopy does not work safely, see sympy issue #7672 @@ -2192,19 +2184,17 @@ def _total_derivative(self, name: str, eq: str, chainvars: List[str], for chainvar in chainvars: if dydx_name is None: - dydx_name = f'd{eq}d{chainvar}' + dydx_name = f"d{eq}d{chainvar}" if dxdz_name is None: - dxdz_name = f'd{chainvar}d{var}' + dxdz_name = f"d{chainvar}d{var}" dydx = self.sym_or_eq(name, dydx_name) dxdz = self.sym_or_eq(name, dxdz_name) # Save time for large models if one multiplicand is zero, # which is not checked for by sympy - if not smart_is_zero_matrix(dydx) and not \ - smart_is_zero_matrix(dxdz): + if not smart_is_zero_matrix(dydx) and not smart_is_zero_matrix(dxdz): dydx_times_dxdz = smart_multiply(dydx, dxdz) - if dxdz.shape[1] == 1 and \ - self._eqs[name].shape[1] != dxdz.shape[1]: + if dxdz.shape[1] == 1 and self._eqs[name].shape[1] != dxdz.shape[1]: for iz in range(self._eqs[name].shape[1]): self._eqs[name][:, iz] += dydx_times_dxdz else: @@ -2230,15 +2220,22 @@ def sym_or_eq(self, name: str, varname: str) -> sp.Matrix: # within a column may differ from the initialization of symbols here, # so those are not safe to use. Not removing them from signature as # this would break backwards compatibility. - if var_in_function_signature(name, varname, self.is_ode()) \ - and varname not in ['dwdx', 'dwdp']: + if var_in_function_signature(name, varname, self.is_ode()) and varname not in [ + "dwdx", + "dwdp", + ]: return self.sym(varname) else: return self.eq(varname) - def _multiplication(self, name: str, x: str, y: str, - transpose_x: Optional[bool] = False, - sign: Optional[int] = 1): + def _multiplication( + self, + name: str, + x: str, + y: str, + transpose_x: Optional[bool] = False, + sign: Optional[int] = 1, + ): """ Creates a new symbolic variable according to a multiplication @@ -2259,7 +2256,7 @@ def _multiplication(self, name: str, x: str, y: str, defines the sign of the product, should be +1 or -1 """ if sign not in [-1, 1]: - raise TypeError(f'sign must be +1 or -1, was {sign}') + raise TypeError(f"sign must be +1 or -1, was {sign}") variables = { varname: self.sym(varname) @@ -2273,8 +2270,9 @@ def _multiplication(self, name: str, x: str, y: str, self._eqs[name] = sign * smart_multiply(xx, yy) - def _equation_from_components(self, name: str, - components: List[ModelQuantity]) -> None: + def _equation_from_components( + self, name: str, components: List[ModelQuantity] + ) -> None: """ Generates the formulas of a symbolic variable from the attributes @@ -2284,9 +2282,7 @@ def _equation_from_components(self, name: str, :param component: name of the attribute """ - self._eqs[name] = sp.Matrix( - [comp.get_val() for comp in components] - ) + self._eqs[name] = sp.Matrix([comp.get_val() for comp in components]) def get_conservation_laws(self) -> List[Tuple[sp.Symbol, sp.Expr]]: """Returns a list of states with conservation law set @@ -2311,10 +2307,9 @@ def _generate_value(self, name: str) -> None: if name in self._value_prototype: components = self._value_prototype[name]() else: - raise ValueError(f'No values for {name}') + raise ValueError(f"No values for {name}") - self._vals[name] = [comp.get_val() - for comp in components] + self._vals[name] = [comp.get_val() for comp in components] def _generate_name(self, name: str) -> None: """ @@ -2329,10 +2324,9 @@ def _generate_name(self, name: str) -> None: elif name in self._equation_prototype: components = self._equation_prototype[name]() else: - raise ValueError(f'No names for {name}') + raise ValueError(f"No names for {name}") - self._names[name] = [comp.get_name() - for comp in components] + self._names[name] = [comp.get_name() for comp in components] def state_has_fixed_parameter_initial_condition(self, ix: int) -> bool: """ @@ -2350,8 +2344,7 @@ def state_has_fixed_parameter_initial_condition(self, ix: int) -> bool: if not isinstance(ic, sp.Basic): return False return any( - fp in (c.get_id() for c in self._constants) - for fp in ic.free_symbols + fp in (c.get_id() for c in self._constants) for fp in ic.free_symbols ) def state_has_conservation_law(self, ix: int) -> bool: @@ -2399,8 +2392,7 @@ def state_is_constant(self, ix: int) -> bool: return state.get_dt() == 0.0 - def conservation_law_has_multispecies(self, - tcl: ConservationLaw) -> bool: + def conservation_law_has_multispecies(self, tcl: ConservationLaw) -> bool: """ Checks whether a conservation law has multiple species or it just defines one constant species @@ -2411,7 +2403,7 @@ def conservation_law_has_multispecies(self, :return: boolean indicating if conservation_law is not None """ - state_set = set(self.sym('x_rdata')) + state_set = set(self.sym("x_rdata")) n_species = len(state_set.intersection(tcl.get_val().free_symbols)) return n_species > 1 @@ -2429,7 +2421,7 @@ def _expr_is_time_dependent(self, expr: sp.Expr) -> bool: expr_syms = {str(sym) for sym in expr.free_symbols} # Check if the time variable is in the expression. - if 't' in expr_syms: + if "t" in expr_syms: return True # Check if any time-dependent states are in the expression. @@ -2440,9 +2432,9 @@ def _expr_is_time_dependent(self, expr: sp.Expr) -> bool: ) def _get_unique_root( - self, - root_found: sp.Expr, - roots: List[Event], + self, + root_found: sp.Expr, + roots: List[Event], ) -> Union[sp.Symbol, None]: """ Collects roots of Heaviside functions and events and stores them in @@ -2466,18 +2458,20 @@ def _get_unique_root( return root.get_id() # create an event for a new root function - root_symstr = f'Heaviside_{len(roots)}' - roots.append(Event( - identifier=sp.Symbol(root_symstr), - name=root_symstr, - value=root_found, - state_update=None, - )) + root_symstr = f"Heaviside_{len(roots)}" + roots.append( + Event( + identifier=sp.Symbol(root_symstr), + name=root_symstr, + value=root_found, + state_update=None, + ) + ) return roots[-1].get_id() def _collect_heaviside_roots( - self, - args: Sequence[sp.Expr], + self, + args: Sequence[sp.Expr], ) -> List[sp.Expr]: """ Recursively checks an expression for the occurrence of Heaviside @@ -2500,21 +2494,22 @@ def _collect_heaviside_roots( # substitute 'w' expressions into root expressions now, to avoid # rewriting 'root.cpp' and 'stau.cpp' headers # to include 'w.h' - w_sorted = toposort_symbols(dict(zip( - [expr.get_id() for expr in self._expressions], - [expr.get_val() for expr in self._expressions], - ))) - root_funs = [ - r.subs(w_sorted) - for r in root_funs - ] + w_sorted = toposort_symbols( + dict( + zip( + [expr.get_id() for expr in self._expressions], + [expr.get_val() for expr in self._expressions], + ) + ) + ) + root_funs = [r.subs(w_sorted) for r in root_funs] return root_funs def _process_heavisides( - self, - dxdt: sp.Expr, - roots: List[Event], + self, + dxdt: sp.Expr, + roots: List[Event], ) -> sp.Expr: """ Parses the RHS of a state variable, checks for Heaviside functions, @@ -2546,7 +2541,7 @@ def _process_heavisides( if tmp_new is None: continue # For Heavisides, we need to add the negative function as well - self._get_unique_root(sp.sympify(- tmp_old), roots) + self._get_unique_root(sp.sympify(-tmp_old), roots) heavisides.append((sp.Heaviside(tmp_old), tmp_new)) if heavisides: @@ -2609,15 +2604,15 @@ class DEExporter: """ def __init__( - self, - de_model: DEModel, - outdir: Optional[Union[Path, str]] = None, - verbose: Optional[Union[bool, int]] = False, - assume_pow_positivity: Optional[bool] = False, - compiler: Optional[str] = None, - allow_reinit_fixpar_initcond: Optional[bool] = True, - generate_sensitivity_code: Optional[bool] = True, - model_name: Optional[str] = 'model' + self, + de_model: DEModel, + outdir: Optional[Union[Path, str]] = None, + verbose: Optional[Union[bool, int]] = False, + assume_pow_positivity: Optional[bool] = False, + compiler: Optional[str] = None, + allow_reinit_fixpar_initcond: Optional[bool] = True, + generate_sensitivity_code: Optional[bool] = True, + model_name: Optional[str] = "model", ): """ Generate AMICI C++ files for the DE provided to the constructor. @@ -2656,8 +2651,8 @@ def __init__( self.assume_pow_positivity: bool = assume_pow_positivity self.compiler: str = compiler - self.model_path: str = '' - self.model_swig_path: str = '' + self.model_path: str = "" + self.model_swig_path: str = "" self.set_name(model_name) self.set_paths(outdir) @@ -2666,8 +2661,8 @@ def __init__( # include/amici/model.h for details) self.model: DEModel = de_model self.model._code_printer.known_functions.update( - splines.spline_user_functions( - self.model.splines, self._get_index('p'))) + splines.spline_user_functions(self.model.splines, self._get_index("p")) + ) # To only generate a subset of functions, apply subselection here self.functions: Dict[str, _FunctionInfo] = copy.deepcopy(functions) @@ -2676,26 +2671,23 @@ def __init__( self._build_hints = set() self.generate_sensitivity_code: bool = generate_sensitivity_code - @log_execution_time('generating cpp code', logger) + @log_execution_time("generating cpp code", logger) def generate_model_code(self) -> None: """ Generates the native C++ code for the loaded model and a Matlab script that can be run to compile a mex file from the C++ code """ - with _monkeypatched(sp.Pow, '_eval_derivative', - _custom_pow_eval_derivative): - + with _monkeypatched(sp.Pow, "_eval_derivative", _custom_pow_eval_derivative): self._prepare_model_folder() self._generate_c_code() self._generate_m_code() - @log_execution_time('compiling cpp code', logger) + @log_execution_time("compiling cpp code", logger) def compile_model(self) -> None: """ Compiles the generated code it into a simulatable module """ - self._compile_c_code(compiler=self.compiler, - verbose=self.verbose) + self._compile_c_code(compiler=self.compiler, verbose=self.verbose) def _prepare_model_folder(self) -> None: """ @@ -2715,26 +2707,28 @@ def _generate_c_code(self) -> None: :attribute:`DEExporter.model`. """ for func_name, func_info in self.functions.items(): - if func_name in sensi_functions + sparse_sensi_functions and \ - not self.generate_sensitivity_code: + if ( + func_name in sensi_functions + sparse_sensi_functions + and not self.generate_sensitivity_code + ): continue if func_info.generate_body: - dec = log_execution_time(f'writing {func_name}.cpp', logger) + dec = log_execution_time(f"writing {func_name}.cpp", logger) dec(self._write_function_file)(func_name) if func_name in sparse_functions and func_info.body: - self._write_function_index(func_name, 'colptrs') - self._write_function_index(func_name, 'rowvals') + self._write_function_index(func_name, "colptrs") + self._write_function_index(func_name, "rowvals") for name in self.model.sym_names(): # only generate for those that have nontrivial implementation, # check for both basic variables (not in functions) and function # computed values - if (name in self.functions + if ( + name in self.functions and not self.functions[name].body - and name not in nobody_functions) \ - or (name not in self.functions and - len(self.model.sym(name)) == 0): + and name not in nobody_functions + ) or (name not in self.functions and len(self.model.sym(name)) == 0): continue self._write_index_files(name) @@ -2745,12 +2739,13 @@ def _generate_c_code(self) -> None: self._write_swig_files() self._write_module_setup() - shutil.copy(CXX_MAIN_TEMPLATE_FILE, - os.path.join(self.model_path, 'main.cpp')) + shutil.copy(CXX_MAIN_TEMPLATE_FILE, os.path.join(self.model_path, "main.cpp")) - def _compile_c_code(self, - verbose: Optional[Union[bool, int]] = False, - compiler: Optional[str] = None) -> None: + def _compile_c_code( + self, + verbose: Optional[Union[bool, int]] = False, + compiler: Optional[str] = None, + ) -> None: """ Compile the generated model code @@ -2763,44 +2758,48 @@ def _compile_c_code(self, """ # setup.py assumes it is run from within the model directory module_dir = self.model_path - script_args = [sys.executable, os.path.join(module_dir, 'setup.py')] + script_args = [sys.executable, os.path.join(module_dir, "setup.py")] if verbose: - script_args.append('--verbose') + script_args.append("--verbose") else: - script_args.append('--quiet') - - script_args.extend([ - 'build_ext', - f'--build-lib={module_dir}', - # This is generally not required, but helps to reduce the path - # length of intermediate build files, that may easily become - # problematic on Windows, due to its ridiculous 255-character path - # length limit. - f'--build-temp={Path(module_dir, "build")}', - ]) + script_args.append("--quiet") + + script_args.extend( + [ + "build_ext", + f"--build-lib={module_dir}", + # This is generally not required, but helps to reduce the path + # length of intermediate build files, that may easily become + # problematic on Windows, due to its ridiculous 255-character path + # length limit. + f'--build-temp={Path(module_dir, "build")}', + ] + ) if compiler is not None: - script_args.extend([f'--compiler={compiler}']) + script_args.extend([f"--compiler={compiler}"]) # distutils.core.run_setup looks nicer, but does not let us check the # result easily try: - result = subprocess.run(script_args, - cwd=module_dir, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - check=True) + result = subprocess.run( + script_args, + cwd=module_dir, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + ) except subprocess.CalledProcessError as e: - print(e.output.decode('utf-8')) + print(e.output.decode("utf-8")) print("Failed building the model extension.") if self._build_hints: print("Note:") - print('\n'.join(self._build_hints)) + print("\n".join(self._build_hints)) raise if verbose: - print(result.stdout.decode('utf-8')) + print(result.stdout.decode("utf-8")) def _generate_m_code(self) -> None: """ @@ -2816,26 +2815,24 @@ def _generate_m_code(self) -> None: o2flag = 0 lines = [ - '% This compile script was automatically created from' - ' Python SBML import.', - '% If mex compiler is set up within MATLAB, it can be run' - ' from MATLAB ', - '% in order to compile a mex-file from the Python' - ' generated C++ files.', - '', + "% This compile script was automatically created from" + " Python SBML import.", + "% If mex compiler is set up within MATLAB, it can be run" " from MATLAB ", + "% in order to compile a mex-file from the Python" " generated C++ files.", + "", f"modelName = '{self.model_name}';", "amimodel.compileAndLinkModel(modelName, '', [], [], [], []);", f"amimodel.generateMatlabWrapper({nxtrue_rdata}, " f"{nytrue}, {self.model.num_par()}, " f"{self.model.num_const()}, {nztrue}, {o2flag}, ...", " [], ['simulate_' modelName '.m'], modelName, ...", - " 'lin', 1, 1);" + " 'lin', 1, 1);", ] # write compile script (for mex) - compile_script = os.path.join(self.model_path, 'compileMexFile.m') - with open(compile_script, 'w') as fileout: - fileout.write('\n'.join(lines)) + compile_script = os.path.join(self.model_path, "compileMexFile.m") + with open(compile_script, "w") as fileout: + fileout.write("\n".join(lines)) def _get_index(self, name: str) -> Dict[sp.Symbol, int]: """ @@ -2851,12 +2848,9 @@ def _get_index(self, name: str) -> Dict[sp.Symbol, int]: else: symbols = self.model.sym(name).T else: - raise ValueError(f'Unknown symbolic array: {name}') + raise ValueError(f"Unknown symbolic array: {name}") - return { - strip_pysb(symbol).name: index - for index, symbol in enumerate(symbols) - } + return {strip_pysb(symbol).name: index for index, symbol in enumerate(symbols)} def _write_index_files(self, name: str) -> None: """ @@ -2867,10 +2861,13 @@ def _write_index_files(self, name: str) -> None: be written """ if name not in self.model.sym_names(): - raise ValueError(f'Unknown symbolic array: {name}') + raise ValueError(f"Unknown symbolic array: {name}") - symbols = self.model.sparsesym(name) if name in sparse_functions \ + symbols = ( + self.model.sparsesym(name) + if name in sparse_functions else self.model.sym(name).T + ) # flatten multiobs if isinstance(next(iter(symbols), None), list): @@ -2879,18 +2876,18 @@ def _write_index_files(self, name: str) -> None: lines = [] for index, symbol in enumerate(symbols): symbol_name = strip_pysb(symbol) - if str(symbol) == '0': + if str(symbol) == "0": continue - if str(symbol_name) == '': + if str(symbol_name) == "": raise ValueError(f'{name} contains a symbol called ""') - lines.append(f'#define {symbol_name} {name}[{index}]') - if name == 'stau': + lines.append(f"#define {symbol_name} {name}[{index}]") + if name == "stau": # we only need a single macro, as all entries have the same symbol break - filename = os.path.join(self.model_path, f'{name}.h') - with open(filename, 'w') as fileout: - fileout.write('\n'.join(lines)) + filename = os.path.join(self.model_path, f"{name}.h") + with open(filename, "w") as fileout: + fileout.write("\n".join(lines)) def _write_function_file(self, function: str) -> None: """ @@ -2905,11 +2902,12 @@ def _write_function_file(self, function: str) -> None: # need in subsequent steps if function in sparse_functions: equations = self.model.sparseeq(function) - elif not self.allow_reinit_fixpar_initcond \ - and function == 'sx0_fixedParameters': + elif ( + not self.allow_reinit_fixpar_initcond and function == "sx0_fixedParameters" + ): # Not required. Will create empty function body. equations = sp.Matrix() - elif function == 'create_splines': + elif function == "create_splines": # nothing to do pass else: @@ -2920,14 +2918,13 @@ def _write_function_file(self, function: str) -> None: '#include "amici/symbolic_functions.h"', '#include "amici/defines.h"', '#include "sundials/sundials_types.h"', - '', - '#include ', - '#include ', - '' + "", + "#include ", + "#include ", + "", ] - if function == 'create_splines': - lines += ['#include "amici/splinefunctions.h"', - '#include '] + if function == "create_splines": + lines += ['#include "amici/splinefunctions.h"', "#include "] func_info = self.functions[function] @@ -2936,8 +2933,8 @@ def _write_function_file(self, function: str) -> None: # Unfortunately we cannot check for `self.functions[sym].body` # here since it may not have been generated yet. for sym in re.findall( - r'const (?:realtype|double) \*([\w]+)[0]*(?:,|$)', - func_info.arguments(self.model.is_ode()) + r"const (?:realtype|double) \*([\w]+)[0]*(?:,|$)", + func_info.arguments(self.model.is_ode()), ): if sym not in self.model.sym_names(): continue @@ -2955,21 +2952,22 @@ def _write_function_file(self, function: str) -> None: lines.append(f'#include "{sym}.h"') # include return symbols - if function in self.model.sym_names() and \ - function not in non_unique_id_symbols: + if function in self.model.sym_names() and function not in non_unique_id_symbols: lines.append(f'#include "{function}.h"') - lines.extend([ - '', - 'namespace amici {', - f'namespace model_{self.model_name} {{', - '', - f'{func_info.return_type} {function}_{self.model_name}' - f'({func_info.arguments(self.model.is_ode())}){{' - ]) + lines.extend( + [ + "", + "namespace amici {", + f"namespace model_{self.model_name} {{", + "", + f"{func_info.return_type} {function}_{self.model_name}" + f"({func_info.arguments(self.model.is_ode())}){{", + ] + ) # function body - if function == 'create_splines': + if function == "create_splines": body = self._get_create_splines_body() else: body = self._get_function_body(function, equations) @@ -2977,37 +2975,37 @@ def _write_function_file(self, function: str) -> None: return if self.assume_pow_positivity and func_info.assume_pow_positivity: - pow_rx = re.compile(r'(^|\W)std::pow\(') + pow_rx = re.compile(r"(^|\W)std::pow\(") body = [ # execute this twice to catch cases where the ending '(' would # be the starting (^|\W) for the following match - pow_rx.sub(r'\1amici::pos_pow(', - pow_rx.sub(r'\1amici::pos_pow(', line)) + pow_rx.sub(r"\1amici::pos_pow(", pow_rx.sub(r"\1amici::pos_pow(", line)) for line in body ] self.functions[function].body = body lines += body - lines.extend([ - '}', - '', - f'}} // namespace model_{self.model_name}', - '} // namespace amici\n', - ]) + lines.extend( + [ + "}", + "", + f"}} // namespace model_{self.model_name}", + "} // namespace amici\n", + ] + ) # check custom functions for fun in CUSTOM_FUNCTIONS: - if 'include' in fun and any(fun['c++'] in line for line in lines): - if 'build_hint' in fun: - self._build_hints.add(fun['build_hint']) - lines.insert(0, fun['include']) + if "include" in fun and any(fun["c++"] in line for line in lines): + if "build_hint" in fun: + self._build_hints.add(fun["build_hint"]) + lines.insert(0, fun["include"]) # if not body is None: - filename = os.path.join(self.model_path, - f'{function}.cpp') - with open(filename, 'w') as fileout: - fileout.write('\n'.join(lines)) + filename = os.path.join(self.model_path, f"{function}.cpp") + with open(filename, "w") as fileout: + fileout.write("\n".join(lines)) def _write_function_index(self, function: str, indextype: str) -> None: """ @@ -3020,32 +3018,34 @@ def _write_function_index(self, function: str, indextype: str) -> None: :param indextype: type of index {'colptrs', 'rowvals'} """ - if indextype == 'colptrs': + if indextype == "colptrs": values = self.model.colptrs(function) - setter = 'indexptrs' - elif indextype == 'rowvals': + setter = "indexptrs" + elif indextype == "rowvals": values = self.model.rowvals(function) - setter = 'indexvals' + setter = "indexvals" else: - raise ValueError('Invalid value for indextype, must be colptrs or ' - f'rowvals: {indextype}') + raise ValueError( + "Invalid value for indextype, must be colptrs or " + f"rowvals: {indextype}" + ) # function signature if function in multiobs_functions: - signature = f'(SUNMatrixWrapper &{function}, int index)' + signature = f"(SUNMatrixWrapper &{function}, int index)" else: - signature = f'(SUNMatrixWrapper &{function})' + signature = f"(SUNMatrixWrapper &{function})" lines = [ '#include "amici/sundials_matrix_wrapper.h"', '#include "sundials/sundials_types.h"', - '', - '#include ', - '#include ', - '', - 'namespace amici {', - f'namespace model_{self.model_name} {{', - '', + "", + "#include ", + "#include ", + "", + "namespace amici {", + f"namespace model_{self.model_name} {{", + "", ] # Generate static array with indices @@ -3058,23 +3058,30 @@ def _write_function_index(self, function: str, indextype: str) -> None: f"{len(values[0])}>, {len(values)}> " f"{static_array_name} = {{{{" ) - lines.extend([' {' - + ', '.join(map(str, index_vector)) + '}, ' - for index_vector in values]) + lines.extend( + [ + " {" + ", ".join(map(str, index_vector)) + "}, " + for index_vector in values + ] + ) lines.append("}};") else: # single index vector - lines.extend([ - "static constexpr std::array {static_array_name} = {{", - ' ' + ', '.join(map(str, values)), - "};" - ]) - - lines.extend([ - '', - f'void {function}_{indextype}_{self.model_name}{signature}{{', - ]) + lines.extend( + [ + "static constexpr std::array {static_array_name} = {{", + " " + ", ".join(map(str, values)), + "};", + ] + ) + + lines.extend( + [ + "", + f"void {function}_{indextype}_{self.model_name}{signature}{{", + ] + ) if len(values): if function in multiobs_functions: @@ -3088,24 +3095,21 @@ def _write_function_index(self, function: str, indextype: str) -> None: f"(gsl::make_span({static_array_name}));" ) - lines.extend([ - '}' - '', - f'}} // namespace model_{self.model_name}', - '} // namespace amici\n', - ]) + lines.extend( + [ + "}" "", + f"}} // namespace model_{self.model_name}", + "} // namespace amici\n", + ] + ) - filename = f'{function}_{indextype}.cpp' + filename = f"{function}_{indextype}.cpp" filename = os.path.join(self.model_path, filename) - with open(filename, 'w') as fileout: - fileout.write('\n'.join(lines)) + with open(filename, "w") as fileout: + fileout.write("\n".join(lines)) - def _get_function_body( - self, - function: str, - equations: sp.Matrix - ) -> List[str]: + def _get_function_body(self, function: str, equations: sp.Matrix) -> List[str]: """ Generate C++ code for body of function ``function``. @@ -3120,88 +3124,85 @@ def _get_function_body( """ lines = [] - if ( - len(equations) == 0 - or ( - isinstance(equations, (sp.Matrix, sp.ImmutableDenseMatrix)) - and min(equations.shape) == 0 - ) + if len(equations) == 0 or ( + isinstance(equations, (sp.Matrix, sp.ImmutableDenseMatrix)) + and min(equations.shape) == 0 ): # dJydy is a list return lines if not self.allow_reinit_fixpar_initcond and function in { - 'sx0_fixedParameters', - 'x0_fixedParameters', + "sx0_fixedParameters", + "x0_fixedParameters", }: return lines - if function == 'sx0_fixedParameters': + if function == "sx0_fixedParameters": # here we only want to overwrite values where x0_fixedParameters # was applied - lines.extend([ - # Keep list of indices of fixed parameters occurring in x0 - " static const std::array _x0_fixedParameters_idxs = {", - " " - + ', '.join(str(x) - for x in self.model._x0_fixedParameters_idx), - " };", - "", - # Set all parameters that are to be reset to 0, so that the - # switch statement below only needs to handle non-zero entries - # (which usually reduces file size and speeds up - # compilation significantly). - " for(auto idx: reinitialization_state_idxs) {", - " if(std::find(_x0_fixedParameters_idxs.cbegin(), " - "_x0_fixedParameters_idxs.cend(), idx) != " - "_x0_fixedParameters_idxs.cend())\n" - " sx0_fixedParameters[idx] = 0.0;", - " }" - ]) + lines.extend( + [ + # Keep list of indices of fixed parameters occurring in x0 + " static const std::array _x0_fixedParameters_idxs = {", + " " + + ", ".join(str(x) for x in self.model._x0_fixedParameters_idx), + " };", + "", + # Set all parameters that are to be reset to 0, so that the + # switch statement below only needs to handle non-zero entries + # (which usually reduces file size and speeds up + # compilation significantly). + " for(auto idx: reinitialization_state_idxs) {", + " if(std::find(_x0_fixedParameters_idxs.cbegin(), " + "_x0_fixedParameters_idxs.cend(), idx) != " + "_x0_fixedParameters_idxs.cend())\n" + " sx0_fixedParameters[idx] = 0.0;", + " }", + ] + ) cases = {} for ipar in range(self.model.num_par()): expressions = [] for index, formula in zip( - self.model._x0_fixedParameters_idx, - equations[:, ipar] + self.model._x0_fixedParameters_idx, equations[:, ipar] ): if not formula.is_zero: - expressions.extend([ - f'if(std::find(' - 'reinitialization_state_idxs.cbegin(), ' - f'reinitialization_state_idxs.cend(), {index}) != ' - 'reinitialization_state_idxs.cend())', - f' {function}[{index}] = ' - f'{self.model._code_printer.doprint(formula)};' - ]) + expressions.extend( + [ + f"if(std::find(" + "reinitialization_state_idxs.cbegin(), " + f"reinitialization_state_idxs.cend(), {index}) != " + "reinitialization_state_idxs.cend())", + f" {function}[{index}] = " + f"{self.model._code_printer.doprint(formula)};", + ] + ) cases[ipar] = expressions - lines.extend(get_switch_statement('ip', cases, 1)) + lines.extend(get_switch_statement("ip", cases, 1)) - elif function == 'x0_fixedParameters': - for index, formula in zip( - self.model._x0_fixedParameters_idx, - equations - ): + elif function == "x0_fixedParameters": + for index, formula in zip(self.model._x0_fixedParameters_idx, equations): lines.append( - f' if(std::find(reinitialization_state_idxs.cbegin(), ' - f'reinitialization_state_idxs.cend(), {index}) != ' - 'reinitialization_state_idxs.cend())\n ' - f'{function}[{index}] = ' - f'{self.model._code_printer.doprint(formula)};' + f" if(std::find(reinitialization_state_idxs.cbegin(), " + f"reinitialization_state_idxs.cend(), {index}) != " + "reinitialization_state_idxs.cend())\n " + f"{function}[{index}] = " + f"{self.model._code_printer.doprint(formula)};" ) elif function in event_functions: cases = { ie: self.model._code_printer._get_sym_lines_array( - equations[ie], function, 0) + equations[ie], function, 0 + ) for ie in range(self.model.num_events()) if not smart_is_zero_matrix(equations[ie]) } - lines.extend(get_switch_statement('ie', cases, 1)) + lines.extend(get_switch_statement("ie", cases, 1)) elif function in event_sensi_functions: outer_cases = {} @@ -3209,57 +3210,62 @@ def _get_function_body( inner_lines = [] inner_cases = { ipar: self.model._code_printer._get_sym_lines_array( - inner_equations[:, ipar], function, 0) + inner_equations[:, ipar], function, 0 + ) for ipar in range(self.model.num_par()) if not smart_is_zero_matrix(inner_equations[:, ipar]) } - inner_lines.extend(get_switch_statement( - 'ip', inner_cases, 0)) + inner_lines.extend(get_switch_statement("ip", inner_cases, 0)) outer_cases[ie] = copy.copy(inner_lines) - lines.extend(get_switch_statement('ie', outer_cases, 1)) + lines.extend(get_switch_statement("ie", outer_cases, 1)) - elif function in sensi_functions \ - and equations.shape[1] == self.model.num_par(): + elif function in sensi_functions and equations.shape[1] == self.model.num_par(): cases = { ipar: self.model._code_printer._get_sym_lines_array( - equations[:, ipar], function, 0) + equations[:, ipar], function, 0 + ) for ipar in range(self.model.num_par()) if not smart_is_zero_matrix(equations[:, ipar]) } - lines.extend(get_switch_statement('ip', cases, 1)) + lines.extend(get_switch_statement("ip", cases, 1)) elif function in multiobs_functions: - if function == 'dJydy': + if function == "dJydy": cases = { iobs: self.model._code_printer._get_sym_lines_array( - equations[iobs], function, 0) + equations[iobs], function, 0 + ) for iobs in range(self.model.num_obs()) if not smart_is_zero_matrix(equations[iobs]) } else: cases = { iobs: self.model._code_printer._get_sym_lines_array( - equations[:, iobs], function, 0) + equations[:, iobs], function, 0 + ) for iobs in range(equations.shape[1]) if not smart_is_zero_matrix(equations[:, iobs]) } - if function.startswith(('Jz', 'dJz', 'Jrz', 'dJrz')): - iterator = 'iz' + if function.startswith(("Jz", "dJz", "Jrz", "dJrz")): + iterator = "iz" else: - iterator = 'iy' + iterator = "iy" lines.extend(get_switch_statement(iterator, cases, 1)) - elif function in self.model.sym_names() \ - and function not in non_unique_id_symbols: + elif ( + function in self.model.sym_names() and function not in non_unique_id_symbols + ): if function in sparse_functions: symbols = list(map(sp.Symbol, self.model.sparsesym(function))) else: symbols = self.model.sym(function) lines += self.model._code_printer._get_sym_lines_symbols( - symbols, equations, function, 4) + symbols, equations, function, 4 + ) else: lines += self.model._code_printer._get_sym_lines_array( - equations, function, 4) + equations, function, 4 + ) return [line for line in lines if line] @@ -3267,8 +3273,8 @@ def _get_create_splines_body(self): if not self.model.splines: return [" return {};"] - ind4 = ' ' * 4 - ind8 = ' ' * 8 + ind4 = " " * 4 + ind8 = " " * 8 body = ["return {"] for ispl, spline in enumerate(self.model.splines): @@ -3283,180 +3289,194 @@ def _get_create_splines_body(self): if spline.derivatives_by_fd: slopes = f"{ind8}{{}}," else: - slopes = f"{ind8}{{{', '.join(map(str, spline.derivatives_at_nodes))}}}," + slopes = ( + f"{ind8}{{{', '.join(map(str, spline.derivatives_at_nodes))}}}," + ) - body.extend([ - f"{ind4}HermiteSpline(", - nodes, - values, - slopes, - ]) + body.extend( + [ + f"{ind4}HermiteSpline(", + nodes, + values, + slopes, + ] + ) bc_to_cpp = { - None: 'SplineBoundaryCondition::given, ', - 'zeroderivative': 'SplineBoundaryCondition::zeroDerivative, ', - 'natural': 'SplineBoundaryCondition::natural, ', - 'zeroderivative+natural': - 'SplineBoundaryCondition::naturalZeroDerivative, ', - 'periodic': 'SplineBoundaryCondition::periodic, ' + None: "SplineBoundaryCondition::given, ", + "zeroderivative": "SplineBoundaryCondition::zeroDerivative, ", + "natural": "SplineBoundaryCondition::natural, ", + "zeroderivative+natural": "SplineBoundaryCondition::naturalZeroDerivative, ", + "periodic": "SplineBoundaryCondition::periodic, ", } for bc in spline.bc: try: body.append(ind8 + bc_to_cpp[bc]) except KeyError: - raise ValueError(f"Unknown boundary condition '{bc}' " - "found in spline object") + raise ValueError( + f"Unknown boundary condition '{bc}' " "found in spline object" + ) extrapolate_to_cpp = { - None: 'SplineExtrapolation::noExtrapolation, ', - 'polynomial': 'SplineExtrapolation::polynomial, ', - 'constant': 'SplineExtrapolation::constant, ', - 'linear': 'SplineExtrapolation::linear, ', - 'periodic': 'SplineExtrapolation::periodic, ', + None: "SplineExtrapolation::noExtrapolation, ", + "polynomial": "SplineExtrapolation::polynomial, ", + "constant": "SplineExtrapolation::constant, ", + "linear": "SplineExtrapolation::linear, ", + "periodic": "SplineExtrapolation::periodic, ", } for extr in spline.extrapolate: try: body.append(ind8 + extrapolate_to_cpp[extr]) except KeyError: - raise ValueError(f"Unknown extrapolation '{extr}' " - "found in spline object") + raise ValueError( + f"Unknown extrapolation '{extr}' " "found in spline object" + ) line = ind8 - line += 'true, ' if spline.derivatives_by_fd else 'false, ' - line += 'true, ' if isinstance(spline.nodes, splines.UniformGrid) \ - else 'false, ' - line += 'true' if spline.logarithmic_parametrization \ - else 'false' + line += "true, " if spline.derivatives_by_fd else "false, " + line += ( + "true, " if isinstance(spline.nodes, splines.UniformGrid) else "false, " + ) + line += "true" if spline.logarithmic_parametrization else "false" body.append(line) body.append(f"{ind4}),") - body.append('};') - return [' ' + line for line in body] + body.append("};") + return [" " + line for line in body] def _write_wrapfunctions_cpp(self) -> None: """ Write model-specific 'wrapper' file (``wrapfunctions.cpp``). """ - template_data = {'MODELNAME': self.model_name} + template_data = {"MODELNAME": self.model_name} apply_template( - os.path.join(amiciSrcPath, 'wrapfunctions.template.cpp'), - os.path.join(self.model_path, 'wrapfunctions.cpp'), - template_data + os.path.join(amiciSrcPath, "wrapfunctions.template.cpp"), + os.path.join(self.model_path, "wrapfunctions.cpp"), + template_data, ) def _write_wrapfunctions_header(self) -> None: """ Write model-specific header file (``wrapfunctions.h``). """ - template_data = {'MODELNAME': str(self.model_name)} + template_data = {"MODELNAME": str(self.model_name)} apply_template( - os.path.join(amiciSrcPath, 'wrapfunctions.template.h'), - os.path.join(self.model_path, 'wrapfunctions.h'), - template_data + os.path.join(amiciSrcPath, "wrapfunctions.template.h"), + os.path.join(self.model_path, "wrapfunctions.h"), + template_data, ) def _write_model_header_cpp(self) -> None: """ Write model-specific header and cpp file (MODELNAME.{h,cpp}). """ - model_type = 'ODE' if self.model.is_ode() else 'DAE' + model_type = "ODE" if self.model.is_ode() else "DAE" tpl_data = { - 'MODEL_TYPE_LOWER': model_type.lower(), - 'MODEL_TYPE_UPPER': model_type, - 'MODELNAME': self.model_name, - 'NX_RDATA': self.model.num_states_rdata(), - 'NXTRUE_RDATA': self.model.num_states_rdata(), - 'NX_SOLVER': self.model.num_states_solver(), - 'NXTRUE_SOLVER': self.model.num_states_solver(), - 'NX_SOLVER_REINIT': self.model.num_state_reinits(), - 'NY': self.model.num_obs(), - 'NYTRUE': self.model.num_obs(), - 'NZ': self.model.num_eventobs(), - 'NZTRUE': self.model.num_eventobs(), - 'NEVENT': self.model.num_events(), - 'NOBJECTIVE': '1', - 'NSPL': len(self.model.splines), - 'NW': len(self.model.sym('w')), - 'NDWDP': len(self.model.sparsesym( - 'dwdp', force_generate=self.generate_sensitivity_code - )), - 'NDWDX': len(self.model.sparsesym('dwdx')), - 'NDWDW': len(self.model.sparsesym('dwdw')), - 'NDXDOTDW': len(self.model.sparsesym('dxdotdw')), - 'NDXDOTDP_EXPLICIT': len(self.model.sparsesym( - 'dxdotdp_explicit', - force_generate=self.generate_sensitivity_code - )), - 'NDXDOTDX_EXPLICIT': len(self.model.sparsesym( - 'dxdotdx_explicit')), - 'NDJYDY': 'std::vector{%s}' - % ','.join(str(len(x)) - for x in self.model.sparsesym('dJydy')), - 'NDXRDATADXSOLVER': len(self.model.sparsesym('dx_rdatadx_solver')), - 'NDXRDATADTCL': len(self.model.sparsesym('dx_rdatadtcl')), - 'NDTOTALCLDXRDATA': len(self.model.sparsesym('dtotal_cldx_rdata')), - 'UBW': self.model.num_states_solver(), - 'LBW': self.model.num_states_solver(), - 'NP': self.model.num_par(), - 'NK': self.model.num_const(), - 'O2MODE': 'amici::SecondOrderMode::none', + "MODEL_TYPE_LOWER": model_type.lower(), + "MODEL_TYPE_UPPER": model_type, + "MODELNAME": self.model_name, + "NX_RDATA": self.model.num_states_rdata(), + "NXTRUE_RDATA": self.model.num_states_rdata(), + "NX_SOLVER": self.model.num_states_solver(), + "NXTRUE_SOLVER": self.model.num_states_solver(), + "NX_SOLVER_REINIT": self.model.num_state_reinits(), + "NY": self.model.num_obs(), + "NYTRUE": self.model.num_obs(), + "NZ": self.model.num_eventobs(), + "NZTRUE": self.model.num_eventobs(), + "NEVENT": self.model.num_events(), + "NOBJECTIVE": "1", + "NSPL": len(self.model.splines), + "NW": len(self.model.sym("w")), + "NDWDP": len( + self.model.sparsesym( + "dwdp", force_generate=self.generate_sensitivity_code + ) + ), + "NDWDX": len(self.model.sparsesym("dwdx")), + "NDWDW": len(self.model.sparsesym("dwdw")), + "NDXDOTDW": len(self.model.sparsesym("dxdotdw")), + "NDXDOTDP_EXPLICIT": len( + self.model.sparsesym( + "dxdotdp_explicit", force_generate=self.generate_sensitivity_code + ) + ), + "NDXDOTDX_EXPLICIT": len(self.model.sparsesym("dxdotdx_explicit")), + "NDJYDY": "std::vector{%s}" + % ",".join(str(len(x)) for x in self.model.sparsesym("dJydy")), + "NDXRDATADXSOLVER": len(self.model.sparsesym("dx_rdatadx_solver")), + "NDXRDATADTCL": len(self.model.sparsesym("dx_rdatadtcl")), + "NDTOTALCLDXRDATA": len(self.model.sparsesym("dtotal_cldx_rdata")), + "UBW": self.model.num_states_solver(), + "LBW": self.model.num_states_solver(), + "NP": self.model.num_par(), + "NK": self.model.num_const(), + "O2MODE": "amici::SecondOrderMode::none", # using code printer ensures proper handling of nan/inf - 'PARAMETERS': self.model._code_printer.doprint( - self.model.val('p'))[1:-1], - 'FIXED_PARAMETERS': self.model._code_printer.doprint( - self.model.val('k'))[1:-1], - 'PARAMETER_NAMES_INITIALIZER_LIST': - self._get_symbol_name_initializer_list('p'), - 'STATE_NAMES_INITIALIZER_LIST': - self._get_symbol_name_initializer_list('x_rdata'), - 'FIXED_PARAMETER_NAMES_INITIALIZER_LIST': - self._get_symbol_name_initializer_list('k'), - 'OBSERVABLE_NAMES_INITIALIZER_LIST': - self._get_symbol_name_initializer_list('y'), - 'OBSERVABLE_TRAFO_INITIALIZER_LIST': - '\n'.join( - f'ObservableScaling::{trafo.value}, // y[{idx}]' - for idx, trafo in enumerate( - self.model.get_observable_transformations() - ) - ), - 'EXPRESSION_NAMES_INITIALIZER_LIST': - self._get_symbol_name_initializer_list('w'), - 'PARAMETER_IDS_INITIALIZER_LIST': - self._get_symbol_id_initializer_list('p'), - 'STATE_IDS_INITIALIZER_LIST': - self._get_symbol_id_initializer_list('x_rdata'), - 'FIXED_PARAMETER_IDS_INITIALIZER_LIST': - self._get_symbol_id_initializer_list('k'), - 'OBSERVABLE_IDS_INITIALIZER_LIST': - self._get_symbol_id_initializer_list('y'), - 'EXPRESSION_IDS_INITIALIZER_LIST': - self._get_symbol_id_initializer_list('w'), - 'STATE_IDXS_SOLVER_INITIALIZER_LIST': - ', '.join( - str(idx) - for idx, state in enumerate(self.model.states()) - if not state.has_conservation_law() - ), - 'REINIT_FIXPAR_INITCOND': - AmiciCxxCodePrinter.print_bool( - self.allow_reinit_fixpar_initcond), - 'AMICI_VERSION_STRING': __version__, - 'AMICI_COMMIT_STRING': __commit__, - 'W_RECURSION_DEPTH': self.model._w_recursion_depth, - 'QUADRATIC_LLH': AmiciCxxCodePrinter.print_bool( - self.model._has_quadratic_nllh), - 'ROOT_INITIAL_VALUES': - ', '.join(map( + "PARAMETERS": self.model._code_printer.doprint(self.model.val("p"))[1:-1], + "FIXED_PARAMETERS": self.model._code_printer.doprint(self.model.val("k"))[ + 1:-1 + ], + "PARAMETER_NAMES_INITIALIZER_LIST": self._get_symbol_name_initializer_list( + "p" + ), + "STATE_NAMES_INITIALIZER_LIST": self._get_symbol_name_initializer_list( + "x_rdata" + ), + "FIXED_PARAMETER_NAMES_INITIALIZER_LIST": self._get_symbol_name_initializer_list( + "k" + ), + "OBSERVABLE_NAMES_INITIALIZER_LIST": self._get_symbol_name_initializer_list( + "y" + ), + "OBSERVABLE_TRAFO_INITIALIZER_LIST": "\n".join( + f"ObservableScaling::{trafo.value}, // y[{idx}]" + for idx, trafo in enumerate(self.model.get_observable_transformations()) + ), + "EXPRESSION_NAMES_INITIALIZER_LIST": self._get_symbol_name_initializer_list( + "w" + ), + "PARAMETER_IDS_INITIALIZER_LIST": self._get_symbol_id_initializer_list("p"), + "STATE_IDS_INITIALIZER_LIST": self._get_symbol_id_initializer_list( + "x_rdata" + ), + "FIXED_PARAMETER_IDS_INITIALIZER_LIST": self._get_symbol_id_initializer_list( + "k" + ), + "OBSERVABLE_IDS_INITIALIZER_LIST": self._get_symbol_id_initializer_list( + "y" + ), + "EXPRESSION_IDS_INITIALIZER_LIST": self._get_symbol_id_initializer_list( + "w" + ), + "STATE_IDXS_SOLVER_INITIALIZER_LIST": ", ".join( + str(idx) + for idx, state in enumerate(self.model.states()) + if not state.has_conservation_law() + ), + "REINIT_FIXPAR_INITCOND": AmiciCxxCodePrinter.print_bool( + self.allow_reinit_fixpar_initcond + ), + "AMICI_VERSION_STRING": __version__, + "AMICI_COMMIT_STRING": __commit__, + "W_RECURSION_DEPTH": self.model._w_recursion_depth, + "QUADRATIC_LLH": AmiciCxxCodePrinter.print_bool( + self.model._has_quadratic_nllh + ), + "ROOT_INITIAL_VALUES": ", ".join( + map( lambda event: AmiciCxxCodePrinter.print_bool( - event.get_initial_value()), - self.model.events())), - 'Z2EVENT': - ', '.join(map(str, self.model._z2event)), - 'ID': - ', '.join(( + event.get_initial_value() + ), + self.model.events(), + ) + ), + "Z2EVENT": ", ".join(map(str, self.model._z2event)), + "ID": ", ".join( + ( str(float(isinstance(s, DifferentialState))) for s in self.model.states() if not s.has_conservation_law() - )) + ) + ), } for func_name, func_info in self.functions.items(): @@ -3464,71 +3484,81 @@ def _write_model_header_cpp(self) -> None: continue if not func_info.body: - tpl_data[f'{func_name.upper()}_DEF'] = '' + tpl_data[f"{func_name.upper()}_DEF"] = "" - if func_name in sensi_functions + sparse_sensi_functions and \ - not self.generate_sensitivity_code: - impl = '' + if ( + func_name in sensi_functions + sparse_sensi_functions + and not self.generate_sensitivity_code + ): + impl = "" else: impl = get_model_override_implementation( - func_name, self.model_name, self.model.is_ode(), - nobody=True + func_name, self.model_name, self.model.is_ode(), nobody=True ) - tpl_data[f'{func_name.upper()}_IMPL'] = impl + tpl_data[f"{func_name.upper()}_IMPL"] = impl if func_name in sparse_functions: - for indexfield in ['colptrs', 'rowvals']: - if func_name in sparse_sensi_functions and \ - not self.generate_sensitivity_code: - impl = '' + for indexfield in ["colptrs", "rowvals"]: + if ( + func_name in sparse_sensi_functions + and not self.generate_sensitivity_code + ): + impl = "" else: impl = get_sunindex_override_implementation( - func_name, self.model_name, indexfield, - nobody=True + func_name, self.model_name, indexfield, nobody=True ) - tpl_data[f'{func_name.upper()}_{indexfield.upper()}_DEF'] \ - = '' - tpl_data[f'{func_name.upper()}_{indexfield.upper()}_IMPL'] \ - = impl + tpl_data[f"{func_name.upper()}_{indexfield.upper()}_DEF"] = "" + tpl_data[ + f"{func_name.upper()}_{indexfield.upper()}_IMPL" + ] = impl continue - tpl_data[f'{func_name.upper()}_DEF'] = \ - get_function_extern_declaration(func_name, self.model_name, - self.model.is_ode()) - tpl_data[f'{func_name.upper()}_IMPL'] = \ - get_model_override_implementation(func_name, self.model_name, - self.model.is_ode()) + tpl_data[f"{func_name.upper()}_DEF"] = get_function_extern_declaration( + func_name, self.model_name, self.model.is_ode() + ) + tpl_data[f"{func_name.upper()}_IMPL"] = get_model_override_implementation( + func_name, self.model_name, self.model.is_ode() + ) if func_name in sparse_functions: - tpl_data[f'{func_name.upper()}_COLPTRS_DEF'] = \ - get_sunindex_extern_declaration( - func_name, self.model_name, 'colptrs') - tpl_data[f'{func_name.upper()}_COLPTRS_IMPL'] = \ - get_sunindex_override_implementation( - func_name, self.model_name, 'colptrs') - tpl_data[f'{func_name.upper()}_ROWVALS_DEF'] = \ - get_sunindex_extern_declaration( - func_name, self.model_name, 'rowvals') - tpl_data[f'{func_name.upper()}_ROWVALS_IMPL'] = \ - get_sunindex_override_implementation( - func_name, self.model_name, 'rowvals') + tpl_data[ + f"{func_name.upper()}_COLPTRS_DEF" + ] = get_sunindex_extern_declaration( + func_name, self.model_name, "colptrs" + ) + tpl_data[ + f"{func_name.upper()}_COLPTRS_IMPL" + ] = get_sunindex_override_implementation( + func_name, self.model_name, "colptrs" + ) + tpl_data[ + f"{func_name.upper()}_ROWVALS_DEF" + ] = get_sunindex_extern_declaration( + func_name, self.model_name, "rowvals" + ) + tpl_data[ + f"{func_name.upper()}_ROWVALS_IMPL" + ] = get_sunindex_override_implementation( + func_name, self.model_name, "rowvals" + ) if self.model.num_states_solver() == self.model.num_states_rdata(): - tpl_data['X_RDATA_DEF'] = '' - tpl_data['X_RDATA_IMPL'] = '' + tpl_data["X_RDATA_DEF"] = "" + tpl_data["X_RDATA_IMPL"] = "" tpl_data = {k: str(v) for k, v in tpl_data.items()} apply_template( - os.path.join(amiciSrcPath, 'model_header.template.h'), - os.path.join(self.model_path, f'{self.model_name}.h'), - tpl_data + os.path.join(amiciSrcPath, "model_header.template.h"), + os.path.join(self.model_path, f"{self.model_name}.h"), + tpl_data, ) apply_template( - os.path.join(amiciSrcPath, 'model.template.cpp'), - os.path.join(self.model_path, f'{self.model_name}.cpp'), - tpl_data + os.path.join(amiciSrcPath, "model.template.cpp"), + os.path.join(self.model_path, f"{self.model_name}.cpp"), + tpl_data, ) def _get_symbol_name_initializer_list(self, name: str) -> str: @@ -3542,7 +3572,7 @@ def _get_symbol_name_initializer_list(self, name: str) -> str: :return: Template initializer list of names """ - return '\n'.join( + return "\n".join( f'"{symbol}", // {name}[{idx}]' for idx, symbol in enumerate(self.model.name(name)) ) @@ -3558,59 +3588,73 @@ def _get_symbol_id_initializer_list(self, name: str) -> str: :return: Template initializer list of ids """ - return '\n'.join( + return "\n".join( f'"{self.model._code_printer.doprint(symbol)}", // {name}[{idx}]' for idx, symbol in enumerate(self.model.sym(name)) ) def _write_c_make_file(self): """Write CMake ``CMakeLists.txt`` file for this model.""" - sources = '\n'.join( - f + ' ' for f in os.listdir(self.model_path) - if f.endswith(('.cpp', '.h'),) and f != 'main.cpp' + sources = "\n".join( + f + " " + for f in os.listdir(self.model_path) + if f.endswith( + (".cpp", ".h"), + ) + and f != "main.cpp" ) - template_data = {'MODELNAME': self.model_name, - 'SOURCES': sources, - 'AMICI_VERSION': __version__} + template_data = { + "MODELNAME": self.model_name, + "SOURCES": sources, + "AMICI_VERSION": __version__, + } apply_template( MODEL_CMAKE_TEMPLATE_FILE, - Path(self.model_path, 'CMakeLists.txt'), - template_data + Path(self.model_path, "CMakeLists.txt"), + template_data, ) def _write_swig_files(self) -> None: """Write SWIG interface files for this model.""" Path(self.model_swig_path).mkdir(exist_ok=True) - template_data = {'MODELNAME': self.model_name} + template_data = {"MODELNAME": self.model_name} apply_template( - Path(amiciSwigPath, 'modelname.template.i'), - Path(self.model_swig_path, self.model_name + '.i'), - template_data + Path(amiciSwigPath, "modelname.template.i"), + Path(self.model_swig_path, self.model_name + ".i"), + template_data, + ) + shutil.copy( + SWIG_CMAKE_TEMPLATE_FILE, Path(self.model_swig_path, "CMakeLists.txt") ) - shutil.copy(SWIG_CMAKE_TEMPLATE_FILE, - Path(self.model_swig_path, 'CMakeLists.txt')) def _write_module_setup(self) -> None: """ Create a setuptools ``setup.py`` file for compile the model module. """ - template_data = {'MODELNAME': self.model_name, - 'AMICI_VERSION': __version__, - 'PACKAGE_VERSION': '0.1.0'} - apply_template(Path(amiciModulePath, 'setup.template.py'), - Path(self.model_path, 'setup.py'), - template_data) - apply_template(Path(amiciModulePath, 'MANIFEST.template.in'), - Path(self.model_path, 'MANIFEST.in'), {}) + template_data = { + "MODELNAME": self.model_name, + "AMICI_VERSION": __version__, + "PACKAGE_VERSION": "0.1.0", + } + apply_template( + Path(amiciModulePath, "setup.template.py"), + Path(self.model_path, "setup.py"), + template_data, + ) + apply_template( + Path(amiciModulePath, "MANIFEST.template.in"), + Path(self.model_path, "MANIFEST.in"), + {}, + ) # write __init__.py for the model module Path(self.model_path, self.model_name).mkdir(exist_ok=True) apply_template( - Path(amiciModulePath, '__init__.template.py'), - Path(self.model_path, self.model_name, '__init__.py'), - template_data + Path(amiciModulePath, "__init__.template.py"), + Path(self.model_path, self.model_name, "__init__.py"), + template_data, ) def set_paths(self, output_dir: Optional[Union[str, Path]] = None) -> None: @@ -3625,11 +3669,10 @@ def set_paths(self, output_dir: Optional[Union[str, Path]] = None) -> None: """ if output_dir is None: - output_dir = os.path.join(os.getcwd(), - f'amici-{self.model_name}') + output_dir = os.path.join(os.getcwd(), f"amici-{self.model_name}") self.model_path = os.path.abspath(output_dir) - self.model_swig_path = os.path.join(self.model_path, 'swig') + self.model_swig_path = os.path.join(self.model_path, "swig") def set_name(self, model_name: str) -> None: """ @@ -3643,7 +3686,8 @@ def set_name(self, model_name: str) -> None: raise ValueError( f"'{model_name}' is not a valid model name. " "Model name may only contain upper and lower case letters, " - "digits and underscores, and must not start with a digit.") + "digits and underscores, and must not start with a digit." + ) self.model_name = model_name @@ -3656,12 +3700,15 @@ class TemplateAmici(Template): :cvar delimiter: delimiter that identifies template variables """ - delimiter = 'TPL_' + + delimiter = "TPL_" -def apply_template(source_file: Union[str, Path], - target_file: Union[str, Path], - template_data: Dict[str, str]) -> None: +def apply_template( + source_file: Union[str, Path], + target_file: Union[str, Path], + template_data: Dict[str, str], +) -> None: """ Load source file, apply template substitution as provided in templateData and save as targetFile. @@ -3679,7 +3726,7 @@ def apply_template(source_file: Union[str, Path], with open(source_file) as filein: src = TemplateAmici(filein.read()) result = src.safe_substitute(template_data) - with open(target_file, 'w') as fileout: + with open(target_file, "w") as fileout: fileout.write(result) @@ -3698,11 +3745,10 @@ def get_function_extern_declaration(fun: str, name: str, ode: bool) -> str: C++ function definition string """ f = functions[fun] - return f'extern {f.return_type} {fun}_{name}({f.arguments(ode)});' + return f"extern {f.return_type} {fun}_{name}({f.arguments(ode)});" -def get_sunindex_extern_declaration(fun: str, name: str, - indextype: str) -> str: +def get_sunindex_extern_declaration(fun: str, name: str, indextype: str) -> str: """ Constructs the function declaration for an index function of a given function @@ -3719,14 +3765,16 @@ def get_sunindex_extern_declaration(fun: str, name: str, :return: C++ function declaration string """ - index_arg = ', int index' if fun in multiobs_functions else '' - return \ - f'extern void {fun}_{indextype}_{name}' \ - f'(SUNMatrixWrapper &{indextype}{index_arg});' + index_arg = ", int index" if fun in multiobs_functions else "" + return ( + f"extern void {fun}_{indextype}_{name}" + f"(SUNMatrixWrapper &{indextype}{index_arg});" + ) -def get_model_override_implementation(fun: str, name: str, ode: bool, - nobody: bool = False) -> str: +def get_model_override_implementation( + fun: str, name: str, ode: bool, nobody: bool = False +) -> str: """ Constructs ``amici::Model::*`` override implementation for a given function @@ -3743,17 +3791,19 @@ def get_model_override_implementation(fun: str, name: str, ode: bool, C++ function implementation string """ func_info = functions[fun] - body = "" if nobody \ - else '\n{ind8}{maybe_return}{fun}_{name}({eval_signature});{ind4}\n' \ - .format( - ind4=' ' * 4, - ind8=' ' * 8, + body = ( + "" + if nobody + else "\n{ind8}{maybe_return}{fun}_{name}({eval_signature});{ind4}\n".format( + ind4=" " * 4, + ind8=" " * 8, maybe_return="" if func_info.return_type == "void" else "return ", fun=fun, name=name, eval_signature=remove_argument_types(func_info.arguments(ode)), ) - return '{return_type} f{fun}({signature}) override {{{body}}}\n'.format( + ) + return "{return_type} f{fun}({signature}) override {{{body}}}\n".format( return_type=func_info.return_type, fun=fun, signature=func_info.arguments(ode), @@ -3761,9 +3811,9 @@ def get_model_override_implementation(fun: str, name: str, ode: bool, ) -def get_sunindex_override_implementation(fun: str, name: str, - indextype: str, - nobody: bool = False) -> str: +def get_sunindex_override_implementation( + fun: str, name: str, indextype: str, nobody: bool = False +) -> str: """ Constructs the ``amici::Model`` function implementation for an index function of a given function @@ -3783,24 +3833,24 @@ def get_sunindex_override_implementation(fun: str, name: str, :return: C++ function implementation string """ - index_arg = ', int index' if fun in multiobs_functions else '' - index_arg_eval = ', index' if fun in multiobs_functions else '' + index_arg = ", int index" if fun in multiobs_functions else "" + index_arg_eval = ", index" if fun in multiobs_functions else "" - impl = 'void f{fun}_{indextype}({signature}) override {{' + impl = "void f{fun}_{indextype}({signature}) override {{" if nobody: - impl += '}}\n' + impl += "}}\n" else: - impl += '{ind8}{fun}_{indextype}_{name}({eval_signature});\n{ind4}}}\n' + impl += "{ind8}{fun}_{indextype}_{name}({eval_signature});\n{ind4}}}\n" return impl.format( - ind4=' ' * 4, - ind8=' ' * 8, + ind4=" " * 4, + ind8=" " * 8, fun=fun, indextype=indextype, name=name, - signature=f'SUNMatrixWrapper &{indextype}{index_arg}', - eval_signature=f'{indextype}{index_arg_eval}', + signature=f"SUNMatrixWrapper &{indextype}{index_arg}", + eval_signature=f"{indextype}{index_arg_eval}", ) @@ -3821,19 +3871,19 @@ def remove_argument_types(signature: str) -> str: # # always add whitespace after type definition for cosmetic reasons known_types = [ - 'const realtype *', - 'const double *', - 'const realtype ', - 'double *', - 'realtype *', - 'const int ', - 'int ', - 'SUNMatrixContent_Sparse ', - 'gsl::span' + "const realtype *", + "const double *", + "const realtype ", + "double *", + "realtype *", + "const int ", + "int ", + "SUNMatrixContent_Sparse ", + "gsl::span", ] for type_str in known_types: - signature = signature.replace(type_str, '') + signature = signature.replace(type_str, "") return signature @@ -3897,8 +3947,7 @@ def _custom_pow_eval_derivative(self, s): return part1 + part2 return part1 + sp.Piecewise( - (self.base, sp.And(sp.Eq(self.base, 0), sp.Eq(dbase, 0))), - (part2, True) + (self.base, sp.And(sp.Eq(self.base, 0), sp.Eq(dbase, 0))), (part2, True) ) @@ -3907,10 +3956,7 @@ def _jacobian_element(i, j, eq_i, sym_var_j): return (i, j), eq_i.diff(sym_var_j) -def _parallel_applyfunc( - obj: sp.Matrix, - func: Callable -) -> sp.Matrix: +def _parallel_applyfunc(obj: sp.Matrix, func: Callable) -> sp.Matrix: """Parallel implementation of sympy's Matrix.applyfunc""" if (n_procs := int(os.environ.get("AMICI_IMPORT_NPROCS", 1))) == 1: # serial @@ -3920,9 +3966,10 @@ def _parallel_applyfunc( from pickle import PicklingError from sympy.matrices.dense import DenseMatrix from multiprocessing import get_context + # "spawn" should avoid potential deadlocks occurring with fork # see e.g. https://stackoverflow.com/a/66113051 - ctx = get_context('spawn') + ctx = get_context("spawn") with ctx.Pool(n_procs) as p: try: if isinstance(obj, DenseMatrix): diff --git a/python/sdist/amici/de_model.py b/python/sdist/amici/de_model.py index b5b3cb3419..2a552510bd 100644 --- a/python/sdist/amici/de_model.py +++ b/python/sdist/amici/de_model.py @@ -4,21 +4,35 @@ import sympy as sp import numbers -from typing import ( - Optional, Union, Dict, SupportsFloat, Set -) +from typing import Optional, Union, Dict, SupportsFloat, Set -from .import_utils import ObservableTransformation, \ - generate_measurement_symbol, generate_regularization_symbol,\ - RESERVED_SYMBOLS +from .import_utils import ( + ObservableTransformation, + generate_measurement_symbol, + generate_regularization_symbol, + RESERVED_SYMBOLS, +) from .import_utils import cast_to_sym __all__ = [ - 'ConservationLaw', 'Constant', 'Event', 'Expression', 'LogLikelihoodY', - 'LogLikelihoodZ', 'LogLikelihoodRZ', 'ModelQuantity', 'Observable', - 'Parameter', 'SigmaY', 'SigmaZ', 'DifferentialState', 'EventObservable', - 'AlgebraicState', 'AlgebraicEquation', 'State' + "ConservationLaw", + "Constant", + "Event", + "Expression", + "LogLikelihoodY", + "LogLikelihoodZ", + "LogLikelihoodRZ", + "ModelQuantity", + "Observable", + "Parameter", + "SigmaY", + "SigmaZ", + "DifferentialState", + "EventObservable", + "AlgebraicState", + "AlgebraicEquation", + "State", ] @@ -26,10 +40,13 @@ class ModelQuantity: """ Base class for model components """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: Union[SupportsFloat, numbers.Number, sp.Expr]): + + def __init__( + self, + identifier: sp.Symbol, + name: str, + value: Union[SupportsFloat, numbers.Number, sp.Expr], + ): """ Create a new ModelQuantity instance. @@ -44,22 +61,24 @@ def __init__(self, """ if not isinstance(identifier, sp.Symbol): - raise TypeError(f'identifier must be sympy.Symbol, was ' - f'{type(identifier)}') - - if str(identifier) in RESERVED_SYMBOLS or \ - (hasattr(identifier, 'name') and - identifier.name in RESERVED_SYMBOLS): - raise ValueError(f'Cannot add model quantity with name "{name}", ' - f'please rename.') + raise TypeError( + f"identifier must be sympy.Symbol, was " f"{type(identifier)}" + ) + + if str(identifier) in RESERVED_SYMBOLS or ( + hasattr(identifier, "name") and identifier.name in RESERVED_SYMBOLS + ): + raise ValueError( + f'Cannot add model quantity with name "{name}", ' f"please rename." + ) self._identifier: sp.Symbol = identifier if not isinstance(name, str): - raise TypeError(f'name must be str, was {type(name)}') + raise TypeError(f"name must be str, was {type(name)}") self._name: str = name - self._value: sp.Expr = cast_to_sym(value, 'value') + self._value: sp.Expr = cast_to_sym(value, "value") def __repr__(self) -> str: """ @@ -104,7 +123,7 @@ def set_val(self, val: sp.Expr): :return: value of the ModelQuantity """ - self._value = cast_to_sym(val, 'value') + self._value = cast_to_sym(val, "value") class ConservationLaw(ModelQuantity): @@ -113,12 +132,15 @@ class ConservationLaw(ModelQuantity): (weighted) sum of states """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: sp.Expr, - coefficients: Dict[sp.Symbol, sp.Expr], - state_id: sp.Symbol): + + def __init__( + self, + identifier: sp.Symbol, + name: str, + value: sp.Expr, + coefficients: Dict[sp.Symbol, sp.Expr], + state_id: sp.Symbol, + ): """ Create a new ConservationLaw instance. @@ -170,6 +192,7 @@ class AlgebraicEquation(ModelQuantity): """ An AlgebraicEquation defines an algebraic equation. """ + def __init__(self, identifier: str, value: sp.Expr): """ Create a new AlgebraicEquation instance. @@ -193,6 +216,7 @@ class State(ModelQuantity): """ Base class for differential and algebraic model states """ + _conservation_law: Optional[ConservationLaw] = None def get_x_rdata(self): @@ -235,10 +259,7 @@ class AlgebraicState(State): An AlgebraicState defines an entity that is algebraically determined """ - def __init__(self, - identifier: sp.Symbol, - name: str, - init: sp.Expr): + def __init__(self, identifier: sp.Symbol, name: str, init: sp.Expr): """ Create a new AlgebraicState instance. @@ -281,11 +302,8 @@ class DifferentialState(State): algebraic formula that defines the temporal derivative of this state """ - def __init__(self, - identifier: sp.Symbol, - name: str, - init: sp.Expr, - dt: sp.Expr): + + def __init__(self, identifier: sp.Symbol, name: str, init: sp.Expr, dt: sp.Expr): """ Create a new State instance. Extends :meth:`ModelQuantity.__init__` by ``dt`` @@ -303,7 +321,7 @@ def __init__(self, time derivative """ super(DifferentialState, self).__init__(identifier, name, init) - self._dt = cast_to_sym(dt, 'dt') + self._dt = cast_to_sym(dt, "dt") self._conservation_law: Union[ConservationLaw, None] = None def set_conservation_law(self, law: ConservationLaw) -> None: @@ -318,20 +336,20 @@ def set_conservation_law(self, law: ConservationLaw) -> None: constant over time """ if not isinstance(law, ConservationLaw): - raise TypeError(f'conservation law must have type ConservationLaw' - f', was {type(law)}') + raise TypeError( + f"conservation law must have type ConservationLaw" f", was {type(law)}" + ) self._conservation_law = law - def set_dt(self, - dt: sp.Expr) -> None: + def set_dt(self, dt: sp.Expr) -> None: """ Sets the time derivative :param dt: time derivative """ - self._dt = cast_to_sym(dt, 'dt') + self._dt = cast_to_sym(dt, "dt") def get_dt(self) -> sp.Expr: """ @@ -377,13 +395,14 @@ class Observable(ModelQuantity): _measurement_symbol: Union[sp.Symbol, None] = None def __init__( - self, - identifier: sp.Symbol, - name: str, - value: sp.Expr, - measurement_symbol: Optional[sp.Symbol] = None, - transformation: Optional[ - ObservableTransformation] = ObservableTransformation.LIN + self, + identifier: sp.Symbol, + name: str, + value: sp.Expr, + measurement_symbol: Optional[sp.Symbol] = None, + transformation: Optional[ + ObservableTransformation + ] = ObservableTransformation.LIN, ): """ Create a new Observable instance. @@ -408,17 +427,13 @@ def __init__( def get_measurement_symbol(self) -> sp.Symbol: if self._measurement_symbol is None: - self._measurement_symbol = generate_measurement_symbol( - self.get_id() - ) + self._measurement_symbol = generate_measurement_symbol(self.get_id()) return self._measurement_symbol def get_regularization_symbol(self) -> sp.Symbol: if self._regularization_symbol is None: - self._regularization_symbol = generate_regularization_symbol( - self.get_id() - ) + self._regularization_symbol = generate_regularization_symbol(self.get_id()) return self._regularization_symbol @@ -432,13 +447,15 @@ class EventObservable(Observable): symbolic event identifier """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: sp.Expr, - event: sp.Symbol, - measurement_symbol: Optional[sp.Symbol] = None, - transformation: Optional[ObservableTransformation] = 'lin',): + def __init__( + self, + identifier: sp.Symbol, + name: str, + value: sp.Expr, + event: sp.Symbol, + measurement_symbol: Optional[sp.Symbol] = None, + transformation: Optional[ObservableTransformation] = "lin", + ): """ Create a new EventObservable instance. @@ -457,9 +474,9 @@ def __init__(self, :param event: Symbolic identifier of the corresponding event. """ - super(EventObservable, self).__init__(identifier, name, value, - measurement_symbol, - transformation) + super(EventObservable, self).__init__( + identifier, name, value, measurement_symbol, transformation + ) self._event: sp.Symbol = event def get_event(self) -> sp.Symbol: @@ -477,10 +494,8 @@ class Sigma(ModelQuantity): and measurements when computing residuals or objective functions, abbreviated by ``sigma{y,z}``. """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: sp.Expr): + + def __init__(self, identifier: sp.Symbol, name: str, value: sp.Expr): """ Create a new Standard Deviation instance. @@ -520,10 +535,8 @@ class Expression(ModelQuantity): shorter model compilation times, but may also reduce model simulation time. Abbreviated by ``w``. """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: sp.Expr): + + def __init__(self, identifier: sp.Symbol, name: str, value: sp.Expr): """ Create a new Expression instance. @@ -545,10 +558,7 @@ class Parameter(ModelQuantity): sensitivities may be computed, abbreviated by ``p``. """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: numbers.Number): + def __init__(self, identifier: sp.Symbol, name: str, value: numbers.Number): """ Create a new Expression instance. @@ -571,10 +581,7 @@ class Constant(ModelQuantity): sensitivities cannot be computed, abbreviated by ``k``. """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: numbers.Number): + def __init__(self, identifier: sp.Symbol, name: str, value: numbers.Number): """ Create a new Expression instance. @@ -598,10 +605,7 @@ class LogLikelihood(ModelQuantity): instances evaluated at all timepoints, abbreviated by ``Jy``. """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: sp.Expr): + def __init__(self, identifier: sp.Symbol, name: str, value: sp.Expr): """ Create a new Expression instance. @@ -649,12 +653,14 @@ class Event(ModelQuantity): themselves, causing a reinitialization of the solver. """ - def __init__(self, - identifier: sp.Symbol, - name: str, - value: sp.Expr, - state_update: Union[sp.Expr, None], - initial_value: Optional[bool] = True): + def __init__( + self, + identifier: sp.Symbol, + name: str, + value: sp.Expr, + state_update: Union[sp.Expr, None], + initial_value: Optional[bool] = True, + ): """ Create a new Event instance. @@ -694,5 +700,6 @@ def __eq__(self, other): Check equality of events at the level of trigger/root functions, as we need to collect unique root functions for ``roots.cpp`` """ - return self.get_val() == other.get_val() and \ - (self.get_initial_value() == other.get_initial_value()) + return self.get_val() == other.get_val() and ( + self.get_initial_value() == other.get_initial_value() + ) diff --git a/python/sdist/amici/gradient_check.py b/python/sdist/amici/gradient_check.py index 76a17817c2..05f8d1c72f 100644 --- a/python/sdist/amici/gradient_check.py +++ b/python/sdist/amici/gradient_check.py @@ -6,8 +6,16 @@ """ from . import ( - runAmiciSimulation, SensitivityOrder, AMICI_SUCCESS, SensitivityMethod, - Model, Solver, ExpData, ReturnData, ParameterScaling) + runAmiciSimulation, + SensitivityOrder, + AMICI_SUCCESS, + SensitivityMethod, + Model, + Solver, + ExpData, + ReturnData, + ParameterScaling, +) import numpy as np import copy @@ -15,15 +23,15 @@ def check_finite_difference( - x0: Sequence[float], - model: Model, - solver: Solver, - edata: ExpData, - ip: int, - fields: List[str], - atol: Optional[float] = 1e-4, - rtol: Optional[float] = 1e-4, - epsilon: Optional[float] = 1e-3 + x0: Sequence[float], + model: Model, + solver: Solver, + edata: ExpData, + ip: int, + fields: List[str], + atol: Optional[float] = 1e-4, + rtol: Optional[float] = 1e-4, + epsilon: Optional[float] = 1e-3, ) -> None: """ Checks the computed sensitivity based derivatives against a finite @@ -76,7 +84,7 @@ def check_finite_difference( if int(og_sensitivity_order) < int(SensitivityOrder.first): solver.setSensitivityOrder(SensitivityOrder.first) rdata = runAmiciSimulation(model, solver, edata) - if rdata['status'] != AMICI_SUCCESS: + if rdata["status"] != AMICI_SUCCESS: raise AssertionError(f"Simulation failed (status {rdata['status']}") # finite difference @@ -95,17 +103,17 @@ def check_finite_difference( # forward: model.setParameters(pf) rdataf = runAmiciSimulation(model, solver, edata) - if rdataf['status'] != AMICI_SUCCESS: + if rdataf["status"] != AMICI_SUCCESS: raise AssertionError(f"Simulation failed (status {rdataf['status']}") # backward: model.setParameters(pb) rdatab = runAmiciSimulation(model, solver, edata) - if rdatab['status'] != AMICI_SUCCESS: + if rdatab["status"] != AMICI_SUCCESS: raise AssertionError(f"Simulation failed (status {rdatab['status']}") for field in fields: - sensi_raw = rdata[f's{field}'] + sensi_raw = rdata[f"s{field}"] fd = (rdataf[field] - rdatab[field]) / (pf[ip] - pb[ip]) if len(sensi_raw.shape) == 1: sensi = sensi_raw[0] @@ -126,14 +134,14 @@ def check_finite_difference( def check_derivatives( - model: Model, - solver: Solver, - edata: Optional[ExpData] = None, - atol: Optional[float] = 1e-4, - rtol: Optional[float] = 1e-4, - epsilon: Optional[float] = 1e-3, - check_least_squares: bool = True, - skip_zero_pars: bool = False + model: Model, + solver: Solver, + edata: Optional[ExpData] = None, + atol: Optional[float] = 1e-4, + rtol: Optional[float] = 1e-4, + epsilon: Optional[float] = 1e-3, + check_least_squares: bool = True, + skip_zero_pars: bool = False, ) -> None: """ Finite differences check for likelihood gradient. @@ -172,50 +180,58 @@ def check_derivatives( rdata = runAmiciSimulation(model, solver, edata) solver.setSensitivityOrder(og_sens_order) - if rdata['status'] != AMICI_SUCCESS: + if rdata["status"] != AMICI_SUCCESS: raise AssertionError(f"Simulation failed (status {rdata['status']}") fields = [] - if solver.getSensitivityMethod() == SensitivityMethod.forward and \ - solver.getSensitivityOrder() <= SensitivityOrder.first: - fields.append('x') - - leastsquares_applicable = \ - solver.getSensitivityMethod() == SensitivityMethod.forward \ - and edata is not None - - if 'ssigmay' in rdata.keys() \ - and rdata['ssigmay'] is not None \ - and rdata['ssigmay'].any() and not model.getAddSigmaResiduals(): + if ( + solver.getSensitivityMethod() == SensitivityMethod.forward + and solver.getSensitivityOrder() <= SensitivityOrder.first + ): + fields.append("x") + + leastsquares_applicable = ( + solver.getSensitivityMethod() == SensitivityMethod.forward and edata is not None + ) + + if ( + "ssigmay" in rdata.keys() + and rdata["ssigmay"] is not None + and rdata["ssigmay"].any() + and not model.getAddSigmaResiduals() + ): leastsquares_applicable = False if check_least_squares and leastsquares_applicable: - fields += ['res', 'y'] + fields += ["res", "y"] - _check_results(rdata, 'FIM', np.dot(rdata['sres'].T, rdata['sres']), - atol=1e-8, rtol=1e-4) - _check_results(rdata, 'sllh', -np.dot(rdata['res'].T, rdata['sres']), - atol=1e-8, rtol=1e-4) + _check_results( + rdata, "FIM", np.dot(rdata["sres"].T, rdata["sres"]), atol=1e-8, rtol=1e-4 + ) + _check_results( + rdata, "sllh", -np.dot(rdata["res"].T, rdata["sres"]), atol=1e-8, rtol=1e-4 + ) if edata is not None: - fields.append('llh') + fields.append("llh") for ip, pval in enumerate(p): if pval == 0.0 and skip_zero_pars: continue - check_finite_difference(p, model, solver, edata, ip, fields, - atol=atol, rtol=rtol, epsilon=epsilon) + check_finite_difference( + p, model, solver, edata, ip, fields, atol=atol, rtol=rtol, epsilon=epsilon + ) def _check_close( - result: np.array, - expected: np.array, - atol: float, - rtol: float, - field: str, - ip: Optional[int] = None, - verbose: Optional[bool] = True, + result: np.array, + expected: np.array, + atol: float, + rtol: float, + field: str, + ip: Optional[int] = None, + verbose: Optional[bool] = True, ) -> None: """ Compares computed values against expected values and provides rich @@ -247,14 +263,16 @@ def _check_close( return if ip is None: - index_str = '' - check_type = 'Regression check' + index_str = "" + check_type = "Regression check" else: - index_str = f'at index ip={ip} ' - check_type = 'FD check' + index_str = f"at index ip={ip} " + check_type = "FD check" - lines = [f'{check_type} failed for {field} {index_str}for ' - f'{close.size - close.sum()} indices:'] + lines = [ + f"{check_type} failed for {field} {index_str}for " + f"{close.size - close.sum()} indices:" + ] if verbose: for idx in np.argwhere(~close): idx = tuple(idx) @@ -262,22 +280,17 @@ def _check_close( rr = result[idx] else: rr = result - lines.append( - f"\tat {idx}: Expected {expected[idx]}, got {rr}") + lines.append(f"\tat {idx}: Expected {expected[idx]}, got {rr}") adev = np.abs(result - expected) rdev = np.abs((result - expected) / (expected + atol)) - lines.append(f'max(adev): {adev.max()}, max(rdev): {rdev.max()}') + lines.append(f"max(adev): {adev.max()}, max(rdev): {rdev.max()}") raise AssertionError("\n".join(lines)) def _check_results( - rdata: ReturnData, - field: str, - expected: np.array, - atol: float, - rtol: float - ) -> None: + rdata: ReturnData, field: str, expected: np.array, atol: float, rtol: float +) -> None: """ Checks whether rdata[field] agrees with expected according to provided tolerances. @@ -303,5 +316,4 @@ def _check_results( if type(result) is float: result = np.array(result) - _check_close(result=result, expected=expected, - atol=atol, rtol=rtol, field=field) + _check_close(result=result, expected=expected, atol=atol, rtol=rtol, field=field) diff --git a/python/sdist/amici/import_utils.py b/python/sdist/amici/import_utils.py index 939c4fb75d..0de2029566 100644 --- a/python/sdist/amici/import_utils.py +++ b/python/sdist/amici/import_utils.py @@ -4,15 +4,24 @@ import itertools as itt import numbers import sys -from typing import (Any, Callable, Dict, Iterable, Optional, Sequence, - SupportsFloat, Tuple, Union) +from typing import ( + Any, + Callable, + Dict, + Iterable, + Optional, + Sequence, + SupportsFloat, + Tuple, + Union, +) import sympy as sp from sympy.functions.elementary.piecewise import ExprCondPair from sympy.logic.boolalg import BooleanAtom from toposort import toposort -RESERVED_SYMBOLS = ['x', 'k', 'p', 'y', 'w', 'h', 't', 'AMICI_EMPTY_BOLUS'] +RESERVED_SYMBOLS = ["x", "k", "p", "y", "w", "h", "t", "AMICI_EMPTY_BOLUS"] try: import pysb @@ -35,31 +44,31 @@ def __init__(self, data): # error messages. That's convenient for doctests. s = "Circular dependencies exist among these items: {{{}}}".format( ", ".join( - "{!r}:{!r}".format(key, value) for key, value in sorted( - {str(k): v for k, v in data.items()}.items()) + "{!r}:{!r}".format(key, value) + for key, value in sorted({str(k): v for k, v in data.items()}.items()) ) ) super(CircularDependencyError, self).__init__(s) self.data = data -setattr(sys.modules["toposort"], "CircularDependencyError", - CircularDependencyError) +setattr(sys.modules["toposort"], "CircularDependencyError", CircularDependencyError) -sbml_time_symbol = sp.Symbol('time', real=True) -amici_time_symbol = sp.Symbol('t', real=True) +sbml_time_symbol = sp.Symbol("time", real=True) +amici_time_symbol = sp.Symbol("t", real=True) -annotation_namespace = 'https://github.com/AMICI-dev/AMICI' +annotation_namespace = "https://github.com/AMICI-dev/AMICI" class ObservableTransformation(str, enum.Enum): """ Different modes of observable transformation. """ - LOG10 = 'log10' - LOG = 'log' - LIN = 'lin' + + LOG10 = "log10" + LOG = "log" + LIN = "lin" def noise_distribution_to_observable_transformation( @@ -75,16 +84,16 @@ def noise_distribution_to_observable_transformation( observable transformation """ if isinstance(noise_distribution, str): - if noise_distribution.startswith('log-'): + if noise_distribution.startswith("log-"): return ObservableTransformation.LOG - if noise_distribution.startswith('log10-'): + if noise_distribution.startswith("log10-"): return ObservableTransformation.LOG10 return ObservableTransformation.LIN def noise_distribution_to_cost_function( - noise_distribution: Union[str, Callable] + noise_distribution: Union[str, Callable] ) -> Callable[[str], str]: """ Parse noise distribution string to a cost function definition amici can @@ -192,38 +201,46 @@ def noise_distribution_to_cost_function( if isinstance(noise_distribution, Callable): return noise_distribution - if noise_distribution in ['normal', 'lin-normal']: - y_string = '0.5*log(2*pi*{sigma}**2) + 0.5*(({y} - {m}) / {sigma})**2' - elif noise_distribution == 'log-normal': - y_string = '0.5*log(2*pi*{sigma}**2*{m}**2) ' \ - '+ 0.5*((log({y}) - log({m})) / {sigma})**2' - elif noise_distribution == 'log10-normal': - y_string = '0.5*log(2*pi*{sigma}**2*{m}**2*log(10)**2) ' \ - '+ 0.5*((log({y}, 10) - log({m}, 10)) / {sigma})**2' - elif noise_distribution in ['laplace', 'lin-laplace']: - y_string = 'log(2*{sigma}) + Abs({y} - {m}) / {sigma}' - elif noise_distribution == 'log-laplace': - y_string = 'log(2*{sigma}*{m}) + Abs(log({y}) - log({m})) / {sigma}' - elif noise_distribution == 'log10-laplace': - y_string = 'log(2*{sigma}*{m}*log(10)) ' \ - '+ Abs(log({y}, 10) - log({m}, 10)) / {sigma}' - elif noise_distribution in ['binomial', 'lin-binomial']: + if noise_distribution in ["normal", "lin-normal"]: + y_string = "0.5*log(2*pi*{sigma}**2) + 0.5*(({y} - {m}) / {sigma})**2" + elif noise_distribution == "log-normal": + y_string = ( + "0.5*log(2*pi*{sigma}**2*{m}**2) " + "+ 0.5*((log({y}) - log({m})) / {sigma})**2" + ) + elif noise_distribution == "log10-normal": + y_string = ( + "0.5*log(2*pi*{sigma}**2*{m}**2*log(10)**2) " + "+ 0.5*((log({y}, 10) - log({m}, 10)) / {sigma})**2" + ) + elif noise_distribution in ["laplace", "lin-laplace"]: + y_string = "log(2*{sigma}) + Abs({y} - {m}) / {sigma}" + elif noise_distribution == "log-laplace": + y_string = "log(2*{sigma}*{m}) + Abs(log({y}) - log({m})) / {sigma}" + elif noise_distribution == "log10-laplace": + y_string = ( + "log(2*{sigma}*{m}*log(10)) " "+ Abs(log({y}, 10) - log({m}, 10)) / {sigma}" + ) + elif noise_distribution in ["binomial", "lin-binomial"]: # Binomial noise model parameterized via success probability p - y_string = '- log(Heaviside({y} - {m})) - loggamma({y}+1) ' \ - '+ loggamma({m}+1) + loggamma({y}-{m}+1) ' \ - '- {m} * log({sigma}) - ({y} - {m}) * log(1-{sigma})' - elif noise_distribution in ['negative-binomial', 'lin-negative-binomial']: + y_string = ( + "- log(Heaviside({y} - {m})) - loggamma({y}+1) " + "+ loggamma({m}+1) + loggamma({y}-{m}+1) " + "- {m} * log({sigma}) - ({y} - {m}) * log(1-{sigma})" + ) + elif noise_distribution in ["negative-binomial", "lin-negative-binomial"]: # Negative binomial noise model of the number of successes m # (data) before r=(1-sigma)/sigma * y failures occur, # with mean number of successes y (simulation), # parameterized via success probability p = sigma. - r = '{y} * (1-{sigma}) / {sigma}' - y_string = f'- loggamma({{m}}+{r}) + loggamma({{m}}+1) ' \ - f'+ loggamma({r}) - {r} * log(1-{{sigma}}) ' \ - f'- {{m}} * log({{sigma}})' + r = "{y} * (1-{sigma}) / {sigma}" + y_string = ( + f"- loggamma({{m}}+{r}) + loggamma({{m}}+1) " + f"+ loggamma({r}) - {r} * log(1-{{sigma}}) " + f"- {{m}} * log({{sigma}})" + ) else: - raise ValueError( - f"Cost identifier {noise_distribution} not recognized.") + raise ValueError(f"Cost identifier {noise_distribution} not recognized.") def nllh_y_string(str_symbol): y, m, sigma = _get_str_symbol_identifiers(str_symbol) @@ -238,10 +255,9 @@ def _get_str_symbol_identifiers(str_symbol: str) -> tuple: return y, m, sigma -def smart_subs_dict(sym: sp.Expr, - subs: SymbolDef, - field: Optional[str] = None, - reverse: bool = True) -> sp.Expr: +def smart_subs_dict( + sym: sp.Expr, subs: SymbolDef, field: Optional[str] = None, reverse: bool = True +) -> sp.Expr: """ Substitutes expressions completely flattening them out. Requires sorting of expressions with toposort. @@ -263,8 +279,7 @@ def smart_subs_dict(sym: sp.Expr, Substituted symbolic expression """ s = [ - (eid, expr[field] if field is not None else expr) - for eid, expr in subs.items() + (eid, expr[field] if field is not None else expr) for eid, expr in subs.items() ] if reverse: s.reverse() @@ -295,8 +310,7 @@ def smart_subs(element: sp.Expr, old: sp.Symbol, new: sp.Expr) -> sp.Expr: return element.subs(old, new) if element.has(old) else element -def toposort_symbols(symbols: SymbolDef, - field: Optional[str] = None) -> SymbolDef: +def toposort_symbols(symbols: SymbolDef, field: Optional[str] = None) -> SymbolDef: """ Topologically sort symbol definitions according to their interdependency @@ -309,16 +323,18 @@ def toposort_symbols(symbols: SymbolDef, :return: ordered symbol definitions """ - sorted_symbols = toposort({ - identifier: { - s for s in ( - definition[field] if field is not None else definition - ).free_symbols - if s in symbols + sorted_symbols = toposort( + { + identifier: { + s + for s in ( + definition[field] if field is not None else definition + ).free_symbols + if s in symbols + } + for identifier, definition in symbols.items() } - for identifier, definition - in symbols.items() - }) + ) return { s: symbols[s] for symbol_group in sorted_symbols @@ -337,40 +353,42 @@ def _parse_special_functions(sym: sp.Expr, toplevel: bool = True) -> sp.Expr: :param toplevel: as this is called recursively, are we in the top level expression? """ - args = tuple(arg if arg.__class__.__name__ == 'piecewise' - and sym.__class__.__name__ == 'piecewise' - else _parse_special_functions(arg, False) - for arg in sym.args) + args = tuple( + arg + if arg.__class__.__name__ == "piecewise" + and sym.__class__.__name__ == "piecewise" + else _parse_special_functions(arg, False) + for arg in sym.args + ) fun_mappings = { - 'times': sp.Mul, - 'xor': sp.Xor, - 'abs': sp.Abs, - 'min': sp.Min, - 'max': sp.Max, - 'ceil': sp.functions.ceiling, - 'floor': sp.functions.floor, - 'factorial': sp.functions.factorial, - 'arcsin': sp.functions.asin, - 'arccos': sp.functions.acos, - 'arctan': sp.functions.atan, - 'arccot': sp.functions.acot, - 'arcsec': sp.functions.asec, - 'arccsc': sp.functions.acsc, - 'arcsinh': sp.functions.asinh, - 'arccosh': sp.functions.acosh, - 'arctanh': sp.functions.atanh, - 'arccoth': sp.functions.acoth, - 'arcsech': sp.functions.asech, - 'arccsch': sp.functions.acsch, + "times": sp.Mul, + "xor": sp.Xor, + "abs": sp.Abs, + "min": sp.Min, + "max": sp.Max, + "ceil": sp.functions.ceiling, + "floor": sp.functions.floor, + "factorial": sp.functions.factorial, + "arcsin": sp.functions.asin, + "arccos": sp.functions.acos, + "arctan": sp.functions.atan, + "arccot": sp.functions.acot, + "arcsec": sp.functions.asec, + "arccsc": sp.functions.acsc, + "arcsinh": sp.functions.asinh, + "arccosh": sp.functions.acosh, + "arctanh": sp.functions.atanh, + "arccoth": sp.functions.acoth, + "arcsech": sp.functions.asech, + "arccsch": sp.functions.acsch, } if sym.__class__.__name__ in fun_mappings: return fun_mappings[sym.__class__.__name__](*args) - elif sym.__class__.__name__ == 'piecewise' \ - or isinstance(sym, sp.Piecewise): - if isinstance(sym, sp.Piecewise): + elif sym.__class__.__name__ == "piecewise" or isinstance(sym, sp.Piecewise): + if isinstance(sym, sp.Piecewise): # this is sympy piecewise, can't be nested denested_args = args else: @@ -378,7 +396,7 @@ def _parse_special_functions(sym: sp.Expr, toplevel: bool = True) -> sp.Expr: denested_args = _denest_piecewise(args) return _parse_piecewise_to_heaviside(denested_args) - if sym.__class__.__name__ == 'plus' and not sym.args: + if sym.__class__.__name__ == "plus" and not sym.args: return sp.Float(0.0) if isinstance(sym, (sp.Function, sp.Mul, sp.Add, sp.Pow)): @@ -394,7 +412,7 @@ def _parse_special_functions(sym: sp.Expr, toplevel: bool = True) -> sp.Expr: def _denest_piecewise( - args: Sequence[Union[sp.Expr, sp.logic.boolalg.Boolean, bool]] + args: Sequence[Union[sp.Expr, sp.logic.boolalg.Boolean, bool]] ) -> Tuple[Union[sp.Expr, sp.logic.boolalg.Boolean, bool]]: """ Denest piecewise functions that contain piecewise as condition @@ -411,23 +429,19 @@ def _denest_piecewise( # handling of this case is explicitely disabled in # _parse_special_functions as keeping track of coeff/cond # arguments is tricky. Simpler to just parse them out here - if coeff.__class__.__name__ == 'piecewise': + if coeff.__class__.__name__ == "piecewise": coeff = _parse_special_functions(coeff, False) # we can have conditions that are piecewise function # returning True or False - if cond.__class__.__name__ == 'piecewise': + if cond.__class__.__name__ == "piecewise": # this keeps track of conditional that the previous # piece was picked previous_was_picked = sp.false # recursively denest those first - for sub_coeff, sub_cond in grouper( - _denest_piecewise(cond.args), 2, True - ): + for sub_coeff, sub_cond in grouper(_denest_piecewise(cond.args), 2, True): # flatten the individual pieces - pick_this = sp.And( - sp.Not(previous_was_picked), sub_cond - ) + pick_this = sp.And(sp.Not(previous_was_picked), sub_cond) if sub_coeff == sp.true: args_out.extend([coeff, pick_this]) previous_was_picked = pick_this @@ -469,7 +483,7 @@ def _parse_piecewise_to_heaviside(args: Iterable[sp.Expr]) -> sp.Expr: tmp = _parse_heaviside_trigger(trigger) formula += coeff * sp.simplify(not_condition * tmp) - not_condition *= (1-tmp) + not_condition *= 1 - tmp return formula @@ -484,7 +498,7 @@ def _parse_heaviside_trigger(trigger: sp.Expr) -> sp.Expr: """ if trigger.is_Relational: root = trigger.args[0] - trigger.args[1] - _check_unsupported_functions(root, 'sympy.Expression') + _check_unsupported_functions(root, "sympy.Expression") # normalize such that we always implement <, # this ensures that we can correctly evaluate the condition if @@ -506,21 +520,18 @@ def _parse_heaviside_trigger(trigger: sp.Expr) -> sp.Expr: # or(x,y) = not(and(not(x),not(y)) if isinstance(trigger, sp.Or): - return 1-sp.Mul(*[1-_parse_heaviside_trigger(arg) - for arg in trigger.args]) + return 1 - sp.Mul(*[1 - _parse_heaviside_trigger(arg) for arg in trigger.args]) if isinstance(trigger, sp.And): - return sp.Mul(*[_parse_heaviside_trigger(arg) - for arg in trigger.args]) + return sp.Mul(*[_parse_heaviside_trigger(arg) for arg in trigger.args]) raise RuntimeError( - 'AMICI can not parse piecewise/event trigger functions with argument ' - f'{trigger}.' + "AMICI can not parse piecewise/event trigger functions with argument " + f"{trigger}." ) -def grouper(iterable: Iterable, n: int, - fillvalue: Any = None) -> Iterable[Tuple[Any]]: +def grouper(iterable: Iterable, n: int, fillvalue: Any = None) -> Iterable[Tuple[Any]]: """ Collect data into fixed-length chunks or blocks @@ -541,9 +552,9 @@ def grouper(iterable: Iterable, n: int, return itt.zip_longest(*args, fillvalue=fillvalue) -def _check_unsupported_functions(sym: sp.Expr, - expression_type: str, - full_sym: Optional[sp.Expr] = None): +def _check_unsupported_functions( + sym: sp.Expr, expression_type: str, full_sym: Optional[sp.Expr] = None +): """ Recursively checks the symbolic expression for unsupported symbolic functions @@ -564,25 +575,37 @@ def _check_unsupported_functions(sym: sp.Expr, # sp.functions.floor applied to numbers should be simplified out and # thus pass this test unsupported_functions = ( - sp.functions.factorial, sp.functions.ceiling, sp.functions.floor, - sp.functions.sec, sp.functions.csc, sp.functions.cot, - sp.functions.asec, sp.functions.acsc, sp.functions.acot, - sp.functions.acsch, sp.functions.acoth, - sp.Mod, sp.core.function.UndefinedFunction + sp.functions.factorial, + sp.functions.ceiling, + sp.functions.floor, + sp.functions.sec, + sp.functions.csc, + sp.functions.cot, + sp.functions.asec, + sp.functions.acsc, + sp.functions.acot, + sp.functions.acsch, + sp.functions.acoth, + sp.Mod, + sp.core.function.UndefinedFunction, ) - if isinstance(sym.func, unsupported_functions) \ - or isinstance(sym, unsupported_functions): - raise RuntimeError(f'Encountered unsupported expression ' - f'"{sym.func}" of type ' - f'"{type(sym.func)}" as part of a ' - f'{expression_type}: "{full_sym}"!') + if isinstance(sym.func, unsupported_functions) or isinstance( + sym, unsupported_functions + ): + raise RuntimeError( + f"Encountered unsupported expression " + f'"{sym.func}" of type ' + f'"{type(sym.func)}" as part of a ' + f'{expression_type}: "{full_sym}"!' + ) for arg in list(sym.args): _check_unsupported_functions(arg, expression_type) -def cast_to_sym(value: Union[SupportsFloat, sp.Expr, BooleanAtom], - input_name: str) -> sp.Expr: +def cast_to_sym( + value: Union[SupportsFloat, sp.Expr, BooleanAtom], input_name: str +) -> sp.Expr: """ Typecasts the value to :py:class:`sympy.Float` if possible, and ensures the value is a symbolic expression. @@ -602,8 +625,9 @@ def cast_to_sym(value: Union[SupportsFloat, sp.Expr, BooleanAtom], value = sp.Float(float(bool(value))) if not isinstance(value, sp.Expr): - raise TypeError(f"Couldn't cast {input_name} to sympy.Expr, was " - f"{type(value)}") + raise TypeError( + f"Couldn't cast {input_name} to sympy.Expr, was " f"{type(value)}" + ) return value @@ -620,7 +644,7 @@ def generate_measurement_symbol(observable_id: Union[str, sp.Symbol]): """ if not isinstance(observable_id, str): observable_id = strip_pysb(observable_id) - return symbol_with_assumptions(f'm{observable_id}') + return symbol_with_assumptions(f"m{observable_id}") def generate_regularization_symbol(observable_id: Union[str, sp.Symbol]): @@ -635,13 +659,10 @@ def generate_regularization_symbol(observable_id: Union[str, sp.Symbol]): """ if not isinstance(observable_id, str): observable_id = strip_pysb(observable_id) - return symbol_with_assumptions(f'r{observable_id}') + return symbol_with_assumptions(f"r{observable_id}") -def generate_flux_symbol( - reaction_index: int, - name: Optional[str] = None -) -> sp.Symbol: +def generate_flux_symbol(reaction_index: int, name: Optional[str] = None) -> sp.Symbol: """ Generate identifier symbol for a reaction flux. This function will always return the same unique python object for a @@ -657,7 +678,7 @@ def generate_flux_symbol( if name is not None: return symbol_with_assumptions(name) - return symbol_with_assumptions(f'flux_r{reaction_index}') + return symbol_with_assumptions(f"flux_r{reaction_index}") def symbol_with_assumptions(name: str): diff --git a/python/sdist/amici/logging.py b/python/sdist/amici/logging.py index 2c03d4e8e9..bfafffd229 100644 --- a/python/sdist/amici/logging.py +++ b/python/sdist/amici/logging.py @@ -15,23 +15,27 @@ from inspect import getouterframes, currentframe -LOG_LEVEL_ENV_VAR = 'AMICI_LOG' -BASE_LOGGER_NAME = 'amici' +LOG_LEVEL_ENV_VAR = "AMICI_LOG" +BASE_LOGGER_NAME = "amici" # Supported values for LOG_LEVEL_ENV_VAR -NAMED_LOG_LEVELS = {'NOTSET': logging.NOTSET, - 'DEBUG': logging.DEBUG, - 'INFO': logging.INFO, - 'WARNING': logging.WARNING, - 'ERROR': logging.ERROR, - 'CRITICAL': logging.CRITICAL} +NAMED_LOG_LEVELS = { + "NOTSET": logging.NOTSET, + "DEBUG": logging.DEBUG, + "INFO": logging.INFO, + "WARNING": logging.WARNING, + "ERROR": logging.ERROR, + "CRITICAL": logging.CRITICAL, +} from typing import Optional, Callable, Union -def _setup_logger(level: Optional[int] = logging.WARNING, - console_output: Optional[bool] = True, - file_output: Optional[bool] = False, - capture_warnings: Optional[bool] = True) -> logging.Logger: +def _setup_logger( + level: Optional[int] = logging.WARNING, + console_output: Optional[bool] = True, + file_output: Optional[bool] = False, + capture_warnings: Optional[bool] = True, +) -> logging.Logger: """ Set up a new logging.Logger for AMICI logging @@ -67,20 +71,23 @@ def _setup_logger(level: Optional[int] = logging.WARNING, if level_name in NAMED_LOG_LEVELS.keys(): level = NAMED_LOG_LEVELS[level_name] else: - raise ValueError(f'Environment variable {LOG_LEVEL_ENV_VAR} ' - f'contains an invalid value "{level_name}".' - f' If set, its value must be one of ' - f'{", ".join(NAMED_LOG_LEVELS.keys())}' - f' (case-sensitive) or an integer log level.') + raise ValueError( + f"Environment variable {LOG_LEVEL_ENV_VAR} " + f'contains an invalid value "{level_name}".' + f" If set, its value must be one of " + f'{", ".join(NAMED_LOG_LEVELS.keys())}' + f" (case-sensitive) or an integer log level." + ) log.setLevel(level) # Remove default logging handler log.handlers = [] - log_fmt = logging.Formatter('%(asctime)s.%(msecs).3d - %(name)s - ' - '%(levelname)s - %(message)s', - datefmt='%Y-%m-%d %H:%M:%S') + log_fmt = logging.Formatter( + "%(asctime)s.%(msecs).3d - %(name)s - " "%(levelname)s - %(message)s", + datefmt="%Y-%m-%d %H:%M:%S", + ) if console_output: stream_handler = logging.StreamHandler() @@ -92,11 +99,11 @@ def _setup_logger(level: Optional[int] = logging.WARNING, file_handler.setFormatter(log_fmt) log.addHandler(file_handler) - log.info('Logging started on AMICI version %s', amici.__version__) + log.info("Logging started on AMICI version %s", amici.__version__) - log.debug('OS Platform: %s', platform.platform()) - log.debug('Python version: %s', platform.python_version()) - log.debug('Hostname: %s', socket.getfqdn()) + log.debug("OS Platform: %s", platform.platform()) + log.debug("Python version: %s", platform.python_version()) + log.debug("Hostname: %s", socket.getfqdn()) logging.captureWarnings(capture_warnings) @@ -108,17 +115,21 @@ def set_log_level(logger: logging.Logger, log_level: Union[int, bool]) -> None: if isinstance(log_level, bool): log_level = logging.DEBUG elif not isinstance(log_level, int): - raise ValueError('log_level must be a boolean, integer or None') + raise ValueError("log_level must be a boolean, integer or None") if logger.getEffectiveLevel() != log_level: - logger.debug('Changing log_level from %d to %d' % ( - logger.getEffectiveLevel(), log_level)) + logger.debug( + "Changing log_level from %d to %d" + % (logger.getEffectiveLevel(), log_level) + ) logger.setLevel(log_level) -def get_logger(logger_name: Optional[str] = BASE_LOGGER_NAME, - log_level: Optional[int] = None, - **kwargs) -> logging.Logger: +def get_logger( + logger_name: Optional[str] = BASE_LOGGER_NAME, + log_level: Optional[int] = None, + **kwargs, +) -> logging.Logger: """ Returns (if extistant) or creates an AMICI logger @@ -154,8 +165,9 @@ def get_logger(logger_name: Optional[str] = BASE_LOGGER_NAME, if BASE_LOGGER_NAME not in logging.Logger.manager.loggerDict.keys(): _setup_logger(**kwargs) elif kwargs: - warnings.warn('AMICI logger already exists, ignoring keyword ' - 'arguments to setup_logger') + warnings.warn( + "AMICI logger already exists, ignoring keyword " "arguments to setup_logger" + ) logger = logging.getLogger(logger_name) @@ -175,33 +187,41 @@ def log_execution_time(description: str, logger: logging.Logger) -> Callable: :param logger: Logger to which execution timing will be printed """ + def decorator_timer(func): @functools.wraps(func) def wrapper_timer(*args, **kwargs): - # append pluses to indicate recursion level recursion_level = sum( - frame.function == 'wrapper_timer' - and frame.filename == __file__ + frame.function == "wrapper_timer" and frame.filename == __file__ for frame in getouterframes(currentframe(), context=0) ) - recursion = '' + recursion = "" level = logging.INFO - level_length = len('INFO') + level_length = len("INFO") if recursion_level > 1: - recursion = '+' * (recursion_level - 1) + recursion = "+" * (recursion_level - 1) level = logging.DEBUG - level_length = len('DEBUG') + level_length = len("DEBUG") tstart = time.perf_counter() rval = func(*args, **kwargs) tend = time.perf_counter() - spacers = ' ' * max(59 - len(description) - len(logger.name) - - len(recursion) - level_length, 0) + spacers = " " * max( + 59 + - len(description) + - len(logger.name) + - len(recursion) + - level_length, + 0, + ) logger.log( - level, f'Finished {description}{spacers}{recursion} ({(tend - tstart):.2E}s)' + level, + f"Finished {description}{spacers}{recursion} ({(tend - tstart):.2E}s)", ) return rval + return wrapper_timer + return decorator_timer diff --git a/python/sdist/amici/numpy.py b/python/sdist/amici/numpy.py index ea83ac2984..17d8c4d1d0 100644 --- a/python/sdist/amici/numpy.py +++ b/python/sdist/amici/numpy.py @@ -41,23 +41,21 @@ def __getitem__(self, item: str) -> Union[np.ndarray, float]: :return: value """ if self._swigptr is None: - raise NotImplementedError('Cannot get items from abstract class.') + raise NotImplementedError("Cannot get items from abstract class.") - if item == 'ptr': + if item == "ptr": return self._swigptr if item in self._cache: return self._cache[item] - if item == 'id': + if item == "id": return getattr(self._swigptr, item) if item not in self._field_names: self.__missing__(item) - value = _field_as_numpy( - self._field_dimensions, item, self._swigptr - ) + value = _field_as_numpy(self._field_dimensions, item, self._swigptr) self._cache[item] = value return value @@ -67,7 +65,7 @@ def __missing__(self, key: str) -> None: :param key: field name """ - raise KeyError(f'Unknown field name {key}.') + raise KeyError(f"Unknown field name {key}.") def __getattr__(self, item) -> Union[np.ndarray, float]: """ @@ -147,7 +145,7 @@ def __repr__(self): :returns: string representation """ - return f'<{self.__class__.__name__}({self._swigptr})>' + return f"<{self.__class__.__name__}({self._swigptr})>" class ReturnDataView(SwigPtrView): @@ -157,17 +155,60 @@ class ReturnDataView(SwigPtrView): """ _field_names = [ - 'ts', 'x', 'x0', 'x_ss', 'sx', 'sx0', 'sx_ss', 'y', 'sigmay', - 'sy', 'ssigmay', 'z', 'rz', 'sigmaz', 'sz', 'srz', - 'ssigmaz', 'sllh', 's2llh', 'J', 'xdot', 'status', 'llh', - 'chi2', 'res', 'sres', 'FIM', 'w', 'preeq_wrms', 'preeq_t', - 'preeq_numsteps', 'preeq_numstepsB', 'preeq_status', 'preeq_cpu_time', - 'preeq_cpu_timeB', 'posteq_wrms', 'posteq_t', 'posteq_numsteps', - 'posteq_numstepsB', 'posteq_status', 'posteq_cpu_time', - 'posteq_cpu_timeB', 'numsteps', 'numrhsevals', - 'numerrtestfails', 'numnonlinsolvconvfails', 'order', 'cpu_time', - 'numstepsB', 'numrhsevalsB', 'numerrtestfailsB', - 'numnonlinsolvconvfailsB', 'cpu_timeB', 'cpu_time_total' + "ts", + "x", + "x0", + "x_ss", + "sx", + "sx0", + "sx_ss", + "y", + "sigmay", + "sy", + "ssigmay", + "z", + "rz", + "sigmaz", + "sz", + "srz", + "ssigmaz", + "sllh", + "s2llh", + "J", + "xdot", + "status", + "llh", + "chi2", + "res", + "sres", + "FIM", + "w", + "preeq_wrms", + "preeq_t", + "preeq_numsteps", + "preeq_numstepsB", + "preeq_status", + "preeq_cpu_time", + "preeq_cpu_timeB", + "posteq_wrms", + "posteq_t", + "posteq_numsteps", + "posteq_numstepsB", + "posteq_status", + "posteq_cpu_time", + "posteq_cpu_timeB", + "numsteps", + "numrhsevals", + "numerrtestfails", + "numnonlinsolvconvfails", + "order", + "cpu_time", + "numstepsB", + "numrhsevalsB", + "numerrtestfailsB", + "numnonlinsolvconvfailsB", + "cpu_timeB", + "cpu_time_total", ] def __init__(self, rdata: Union[ReturnDataPtr, ReturnData]): @@ -177,65 +218,63 @@ def __init__(self, rdata: Union[ReturnDataPtr, ReturnData]): :param rdata: pointer to the ``ReturnData`` instance """ if not isinstance(rdata, (ReturnDataPtr, ReturnData)): - raise TypeError(f'Unsupported pointer {type(rdata)}, must be' - f'amici.ExpDataPtr!') + raise TypeError( + f"Unsupported pointer {type(rdata)}, must be" f"amici.ExpDataPtr!" + ) self._field_dimensions = { - 'ts': [rdata.nt], - 'x': [rdata.nt, rdata.nx], - 'x0': [rdata.nx], - 'x_ss': [rdata.nx], - 'sx': [rdata.nt, rdata.nplist, rdata.nx], - 'sx0': [rdata.nplist, rdata.nx], - 'sx_ss': [rdata.nplist, rdata.nx], - + "ts": [rdata.nt], + "x": [rdata.nt, rdata.nx], + "x0": [rdata.nx], + "x_ss": [rdata.nx], + "sx": [rdata.nt, rdata.nplist, rdata.nx], + "sx0": [rdata.nplist, rdata.nx], + "sx_ss": [rdata.nplist, rdata.nx], # observables - 'y': [rdata.nt, rdata.ny], - 'sigmay': [rdata.nt, rdata.ny], - 'sy': [rdata.nt, rdata.nplist, rdata.ny], - 'ssigmay': [rdata.nt, rdata.nplist, rdata.ny], - + "y": [rdata.nt, rdata.ny], + "sigmay": [rdata.nt, rdata.ny], + "sy": [rdata.nt, rdata.nplist, rdata.ny], + "ssigmay": [rdata.nt, rdata.nplist, rdata.ny], # event observables - 'z': [rdata.nmaxevent, rdata.nz], - 'rz': [rdata.nmaxevent, rdata.nz], - 'sigmaz': [rdata.nmaxevent, rdata.nz], - 'sz': [rdata.nmaxevent, rdata.nplist, rdata.nz], - 'srz': [rdata.nmaxevent, rdata.nplist, rdata.nz], - 'ssigmaz': [rdata.nmaxevent, rdata.nplist, rdata.nz], - + "z": [rdata.nmaxevent, rdata.nz], + "rz": [rdata.nmaxevent, rdata.nz], + "sigmaz": [rdata.nmaxevent, rdata.nz], + "sz": [rdata.nmaxevent, rdata.nplist, rdata.nz], + "srz": [rdata.nmaxevent, rdata.nplist, rdata.nz], + "ssigmaz": [rdata.nmaxevent, rdata.nplist, rdata.nz], # objective function - 'sllh': [rdata.nplist], - 's2llh': [rdata.np, rdata.nplist], - - 'res': [rdata.nt * rdata.nytrue * - (2 if rdata.sigma_res else 1)], - 'sres': [rdata.nt * rdata.nytrue * - (2 if rdata.sigma_res else 1), rdata.nplist], - 'FIM': [rdata.nplist, rdata.nplist], - + "sllh": [rdata.nplist], + "s2llh": [rdata.np, rdata.nplist], + "res": [rdata.nt * rdata.nytrue * (2 if rdata.sigma_res else 1)], + "sres": [ + rdata.nt * rdata.nytrue * (2 if rdata.sigma_res else 1), + rdata.nplist, + ], + "FIM": [rdata.nplist, rdata.nplist], # diagnosis - 'J': [rdata.nx_solver, rdata.nx_solver], - 'w': [rdata.nt, rdata.nw], - 'xdot': [rdata.nx_solver], - 'preeq_numlinsteps': [rdata.newton_maxsteps, 2], - 'preeq_numsteps': [1, 3], - 'preeq_status': [1, 3], - 'posteq_numlinsteps': [rdata.newton_maxsteps, 2], - 'posteq_numsteps': [1, 3], - 'posteq_status': [1, 3], - 'numsteps': [rdata.nt], - 'numrhsevals': [rdata.nt], - 'numerrtestfails': [rdata.nt], - 'numnonlinsolvconvfails': [rdata.nt], - 'order': [rdata.nt], - 'numstepsB': [rdata.nt], - 'numrhsevalsB': [rdata.nt], - 'numerrtestfailsB': [rdata.nt], - 'numnonlinsolvconvfailsB': [rdata.nt], + "J": [rdata.nx_solver, rdata.nx_solver], + "w": [rdata.nt, rdata.nw], + "xdot": [rdata.nx_solver], + "preeq_numlinsteps": [rdata.newton_maxsteps, 2], + "preeq_numsteps": [1, 3], + "preeq_status": [1, 3], + "posteq_numlinsteps": [rdata.newton_maxsteps, 2], + "posteq_numsteps": [1, 3], + "posteq_status": [1, 3], + "numsteps": [rdata.nt], + "numrhsevals": [rdata.nt], + "numerrtestfails": [rdata.nt], + "numnonlinsolvconvfails": [rdata.nt], + "order": [rdata.nt], + "numstepsB": [rdata.nt], + "numrhsevalsB": [rdata.nt], + "numerrtestfailsB": [rdata.nt], + "numnonlinsolvconvfailsB": [rdata.nt], } super(ReturnDataView, self).__init__(rdata) - def __getitem__(self, item: str) -> Union[np.ndarray, ReturnDataPtr, - ReturnData, float]: + def __getitem__( + self, item: str + ) -> Union[np.ndarray, ReturnDataPtr, ReturnData, float]: """ Access fields by name.s @@ -245,20 +284,15 @@ def __getitem__(self, item: str) -> Union[np.ndarray, ReturnDataPtr, :returns: self[item] """ - if item == 'status': + if item == "status": return int(super().__getitem__(item)) - if item == 't': - item = 'ts' + if item == "t": + item = "ts" return super().__getitem__(item) - def by_id( - self, - entity_id: str, - field: str = None, - model: Model = None - ) -> np.array: + def by_id(self, entity_id: str, field: str = None, model: Model = None) -> np.array: """ Get the value of a given field for a named entity. @@ -273,17 +307,14 @@ def by_id( if field is None: field = _entity_type_from_id(entity_id, self, model) - if field in {'x', 'x0', 'x_ss', 'sx', 'sx0', 'sx_ss'}: + if field in {"x", "x0", "x_ss", "sx", "sx0", "sx_ss"}: ids = (model and model.getStateIds()) or self._swigptr.state_ids - elif field in {'w'}: - ids = (model and model.getExpressionIds()) \ - or self._swigptr.expression_ids - elif field in {'y', 'sy', 'sigmay'}: - ids = (model and model.getObservableIds()) \ - or self._swigptr.observable_ids - elif field in {'sllh'}: - ids = (model and model.getParameterIds()) \ - or self._swigptr.parameter_ids + elif field in {"w"}: + ids = (model and model.getExpressionIds()) or self._swigptr.expression_ids + elif field in {"y", "sy", "sigmay"}: + ids = (model and model.getObservableIds()) or self._swigptr.observable_ids + elif field in {"sllh"}: + ids = (model and model.getParameterIds()) or self._swigptr.parameter_ids else: raise NotImplementedError( f"Subsetting {field} by ID is not implemented or not possible." @@ -299,10 +330,13 @@ class ExpDataView(SwigPtrView): """ _field_names = [ - 'observedData', 'observedDataStdDev', 'observedEvents', - 'observedEventsStdDev', 'fixedParameters', - 'fixedParametersPreequilibration', - 'fixedParametersPresimulation' + "observedData", + "observedDataStdDev", + "observedEvents", + "observedEventsStdDev", + "fixedParameters", + "fixedParametersPreequilibration", + "fixedParametersPresimulation", ] def __init__(self, edata: Union[ExpDataPtr, ExpData]): @@ -312,22 +346,23 @@ def __init__(self, edata: Union[ExpDataPtr, ExpData]): :param edata: pointer to the ExpData instance """ if not isinstance(edata, (ExpDataPtr, ExpData)): - raise TypeError(f'Unsupported pointer {type(edata)}, must be' - f'amici.ExpDataPtr!') + raise TypeError( + f"Unsupported pointer {type(edata)}, must be" f"amici.ExpDataPtr!" + ) self._field_dimensions = { # observables - 'observedData': [edata.nt(), edata.nytrue()], - 'observedDataStdDev': [edata.nt(), edata.nytrue()], - + "observedData": [edata.nt(), edata.nytrue()], + "observedDataStdDev": [edata.nt(), edata.nytrue()], # event observables - 'observedEvents': [edata.nmaxevent(), edata.nztrue()], - 'observedEventsStdDev': [edata.nmaxevent(), edata.nztrue()], - + "observedEvents": [edata.nmaxevent(), edata.nztrue()], + "observedEventsStdDev": [edata.nmaxevent(), edata.nztrue()], # fixed parameters - 'fixedParameters': [len(edata.fixedParameters)], - 'fixedParametersPreequilibration': [ - len(edata.fixedParametersPreequilibration)], - 'fixedParametersPresimulation': [ - len(edata.fixedParametersPreequilibration)], + "fixedParameters": [len(edata.fixedParameters)], + "fixedParametersPreequilibration": [ + len(edata.fixedParametersPreequilibration) + ], + "fixedParametersPresimulation": [ + len(edata.fixedParametersPreequilibration) + ], } edata.observedData = edata.getObservedData() edata.observedDataStdDev = edata.getObservedDataStdDev() @@ -337,8 +372,7 @@ def __init__(self, edata: Union[ExpDataPtr, ExpData]): def _field_as_numpy( - field_dimensions: Dict[str, List[int]], - field: str, data: SwigPtrView + field_dimensions: Dict[str, List[int]], field: str, data: SwigPtrView ) -> Union[np.ndarray, float, None]: """ Convert data object field to numpy array with dimensions according to @@ -359,26 +393,26 @@ def _field_as_numpy( def _entity_type_from_id( - entity_id: str, - rdata: Union[amici.ReturnData, 'amici.ReturnDataView'] = None, - model: amici.Model = None, -) -> Literal['x', 'y', 'w', 'p', 'k']: + entity_id: str, + rdata: Union[amici.ReturnData, "amici.ReturnDataView"] = None, + model: amici.Model = None, +) -> Literal["x", "y", "w", "p", "k"]: """Guess the type of some entity by its ID.""" for entity_type, symbol in ( - ('State', 'x'), - ('Observable', 'y'), - ('Expression', 'w'), - ('Parameter', 'p'), - ('FixedParameter', 'k') + ("State", "x"), + ("Observable", "y"), + ("Expression", "w"), + ("Parameter", "p"), + ("FixedParameter", "k"), ): if model: - if entity_id in getattr(model, f'get{entity_type}Ids')(): + if entity_id in getattr(model, f"get{entity_type}Ids")(): return symbol else: if entity_id in getattr( - rdata if isinstance(rdata, amici.ReturnData) - else rdata._swigptr, - f'{entity_type.lower()}_ids'): + rdata if isinstance(rdata, amici.ReturnData) else rdata._swigptr, + f"{entity_type.lower()}_ids", + ): return symbol raise KeyError(f"Unknown symbol {entity_id}.") diff --git a/python/sdist/amici/pandas.py b/python/sdist/amici/pandas.py index 4842bbec47..b8585c679e 100644 --- a/python/sdist/amici/pandas.py +++ b/python/sdist/amici/pandas.py @@ -15,21 +15,21 @@ import amici __all__ = [ - 'get_expressions_as_dataframe', - 'getEdataFromDataFrame', - 'getDataObservablesAsDataFrame', - 'getSimulationObservablesAsDataFrame', - 'getSimulationStatesAsDataFrame', - 'getResidualsAsDataFrame' + "get_expressions_as_dataframe", + "getEdataFromDataFrame", + "getDataObservablesAsDataFrame", + "getSimulationObservablesAsDataFrame", + "getSimulationStatesAsDataFrame", + "getResidualsAsDataFrame", ] ExpDatas = Union[ - List[amici.amici.ExpData], List[amici.ExpDataPtr], - amici.amici.ExpData, amici.ExpDataPtr -] -ReturnDatas = Union[ - List[amici.ReturnDataView], amici.ReturnDataView + List[amici.amici.ExpData], + List[amici.ExpDataPtr], + amici.amici.ExpData, + amici.ExpDataPtr, ] +ReturnDatas = Union[List[amici.ReturnDataView], amici.ReturnDataView] AmiciModel = Union[amici.ModelPtr, amici.Model] @@ -69,9 +69,8 @@ def _process_rdata_list(rdata_list: ReturnDatas) -> List[amici.ReturnDataView]: def getDataObservablesAsDataFrame( - model: AmiciModel, - edata_list: ExpDatas, - by_id: Optional[bool] = False) -> pd.DataFrame: + model: AmiciModel, edata_list: ExpDatas, by_id: Optional[bool] = False +) -> pd.DataFrame: """ Write Observables from experimental data as DataFrame. @@ -101,16 +100,13 @@ def getDataObservablesAsDataFrame( for edata in edata_list: npdata = ExpDataView(edata) for i_time, timepoint in enumerate(edata.getTimepoints()): - datadict = { - 'time': timepoint, - 'datatype': 'data' - } + datadict = {"time": timepoint, "datatype": "data"} # add observables and noises - for i_obs, obs in enumerate(_get_names_or_ids( - model, 'Observable', by_id=by_id)): - datadict[obs] = npdata['observedData'][i_time, i_obs] - datadict[obs + '_std'] = \ - npdata['observedDataStdDev'][i_time, i_obs] + for i_obs, obs in enumerate( + _get_names_or_ids(model, "Observable", by_id=by_id) + ): + datadict[obs] = npdata["observedData"][i_time, i_obs] + datadict[obs + "_std"] = npdata["observedDataStdDev"][i_time, i_obs] # add conditions _fill_conditions_dict(datadict, model, edata, by_id=by_id) @@ -121,10 +117,10 @@ def getDataObservablesAsDataFrame( def getSimulationObservablesAsDataFrame( - model: amici.Model, - edata_list: ExpDatas, - rdata_list: ReturnDatas, - by_id: Optional[bool] = False + model: amici.Model, + edata_list: ExpDatas, + rdata_list: ReturnDatas, + by_id: Optional[bool] = False, ) -> pd.DataFrame: """ Write Observables from simulation results as DataFrame. @@ -157,16 +153,17 @@ def getSimulationObservablesAsDataFrame( # aggregate records dicts = [] for edata, rdata in zip(edata_list, rdata_list): - for i_time, timepoint in enumerate(rdata['t']): + for i_time, timepoint in enumerate(rdata["t"]): datadict = { - 'time': timepoint, - 'datatype': 'simulation', + "time": timepoint, + "datatype": "simulation", } # append simulations - for i_obs, obs in enumerate(_get_names_or_ids( - model, 'Observable', by_id=by_id)): - datadict[obs] = rdata['y'][i_time, i_obs] - datadict[obs + '_std'] = rdata['sigmay'][i_time, i_obs] + for i_obs, obs in enumerate( + _get_names_or_ids(model, "Observable", by_id=by_id) + ): + datadict[obs] = rdata["y"][i_time, i_obs] + datadict[obs + "_std"] = rdata["sigmay"][i_time, i_obs] # use edata to fill conditions columns _fill_conditions_dict(datadict, model, edata, by_id=by_id) @@ -178,10 +175,11 @@ def getSimulationObservablesAsDataFrame( def getSimulationStatesAsDataFrame( - model: amici.Model, - edata_list: ExpDatas, - rdata_list: ReturnDatas, - by_id: Optional[bool] = False) -> pd.DataFrame: + model: amici.Model, + edata_list: ExpDatas, + rdata_list: ReturnDatas, + by_id: Optional[bool] = False, +) -> pd.DataFrame: """ Get model state according to lists of ReturnData and ExpData. @@ -212,15 +210,16 @@ def getSimulationStatesAsDataFrame( # aggregate records dicts = [] for edata, rdata in zip(edata_list, rdata_list): - for i_time, timepoint in enumerate(rdata['t']): + for i_time, timepoint in enumerate(rdata["t"]): datadict = { - 'time': timepoint, + "time": timepoint, } # append states for i_state, state in enumerate( - _get_names_or_ids(model, 'State', by_id=by_id)): - datadict[state] = rdata['x'][i_time, i_state] + _get_names_or_ids(model, "State", by_id=by_id) + ): + datadict[state] = rdata["x"][i_time, i_state] # use data to fill condition columns _fill_conditions_dict(datadict, model, edata, by_id=by_id) @@ -232,10 +231,11 @@ def getSimulationStatesAsDataFrame( def get_expressions_as_dataframe( - model: amici.Model, - edata_list: ExpDatas, - rdata_list: ReturnDatas, - by_id: Optional[bool] = False) -> pd.DataFrame: + model: amici.Model, + edata_list: ExpDatas, + rdata_list: ReturnDatas, + by_id: Optional[bool] = False, +) -> pd.DataFrame: """ Get values of model expressions from lists of ReturnData as DataFrame. @@ -266,15 +266,16 @@ def get_expressions_as_dataframe( # aggregate records dicts = [] for edata, rdata in zip(edata_list, rdata_list): - for i_time, timepoint in enumerate(rdata['t']): + for i_time, timepoint in enumerate(rdata["t"]): datadict = { - 'time': timepoint, + "time": timepoint, } # append expressions for i_expr, expr in enumerate( - _get_names_or_ids(model, 'Expression', by_id=by_id)): - datadict[expr] = rdata['w'][i_time, i_expr] + _get_names_or_ids(model, "Expression", by_id=by_id) + ): + datadict[expr] = rdata["w"][i_time, i_expr] # use data to fill condition columns _fill_conditions_dict(datadict, model, edata, by_id=by_id) @@ -285,10 +286,12 @@ def get_expressions_as_dataframe( return pd.DataFrame.from_records(dicts, columns=cols) -def getResidualsAsDataFrame(model: amici.Model, - edata_list: ExpDatas, - rdata_list: ReturnDatas, - by_id: Optional[bool] = False) -> pd.DataFrame: +def getResidualsAsDataFrame( + model: amici.Model, + edata_list: ExpDatas, + rdata_list: ReturnDatas, + by_id: Optional[bool] = False, +) -> pd.DataFrame: """ Convert a list of ReturnData and ExpData to pandas DataFrame with residuals. @@ -315,10 +318,10 @@ def getResidualsAsDataFrame(model: amici.Model, rdata_list = _process_rdata_list(rdata_list) # create observable and simulation dataframes - df_edata = getDataObservablesAsDataFrame( - model, edata_list, by_id=by_id) + df_edata = getDataObservablesAsDataFrame(model, edata_list, by_id=by_id) df_rdata = getSimulationObservablesAsDataFrame( - model, edata_list, rdata_list, by_id=by_id) + model, edata_list, rdata_list, by_id=by_id + ) # get all column names using names or ids cols = _get_observable_cols(model, by_id=by_id) @@ -327,23 +330,24 @@ def getResidualsAsDataFrame(model: amici.Model, dicts = [] for row in df_rdata.index: datadict = { - 'time': df_rdata.loc[row]['time'], - 't_presim': df_rdata.loc[row]['t_presim'] + "time": df_rdata.loc[row]["time"], + "t_presim": df_rdata.loc[row]["t_presim"], } # iterate over observables - for obs in _get_names_or_ids(model, 'Observable', by_id=by_id): + for obs in _get_names_or_ids(model, "Observable", by_id=by_id): # compute residual and append to dict datadict[obs] = abs( - (df_edata.loc[row][obs] - df_rdata.loc[row][obs]) / - df_rdata.loc[row][obs + '_std']) + (df_edata.loc[row][obs] - df_rdata.loc[row][obs]) + / df_rdata.loc[row][obs + "_std"] + ) # iterate over fixed parameters - for par in _get_names_or_ids(model, 'FixedParameter', by_id=by_id): + for par in _get_names_or_ids(model, "FixedParameter", by_id=by_id): # fill in conditions datadict[par] = df_rdata.loc[row][par] - datadict[par + '_preeq'] = df_rdata.loc[row][par + '_preeq'] - datadict[par + '_presim'] = df_rdata.loc[row][par + '_presim'] + datadict[par + "_preeq"] = df_rdata.loc[row][par + "_preeq"] + datadict[par + "_presim"] = df_rdata.loc[row][par + "_presim"] # append to dataframe dicts.append(datadict) @@ -351,10 +355,12 @@ def getResidualsAsDataFrame(model: amici.Model, return pd.DataFrame.from_records(dicts, columns=cols) -def _fill_conditions_dict(datadict: Dict[str, float], - model: AmiciModel, - edata: amici.amici.ExpData, - by_id: bool) -> Dict[str, float]: +def _fill_conditions_dict( + datadict: Dict[str, float], + model: AmiciModel, + edata: amici.amici.ExpData, + by_id: bool, +) -> Dict[str, float]: """ Helper function that fills in condition parameters from model and edata. @@ -377,32 +383,30 @@ def _fill_conditions_dict(datadict: Dict[str, float], dictionary with filled condition parameters. """ - datadict['condition_id'] = edata.id - datadict['t_presim'] = edata.t_presim + datadict["condition_id"] = edata.id + datadict["t_presim"] = edata.t_presim for i_par, par in enumerate( - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)): + _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ): if len(edata.fixedParameters): datadict[par] = edata.fixedParameters[i_par] else: datadict[par] = model.getFixedParameters()[i_par] if len(edata.fixedParametersPreequilibration): - datadict[par + '_preeq'] = \ - edata.fixedParametersPreequilibration[i_par] + datadict[par + "_preeq"] = edata.fixedParametersPreequilibration[i_par] else: - datadict[par + '_preeq'] = np.nan + datadict[par + "_preeq"] = np.nan if len(edata.fixedParametersPresimulation): - datadict[par + '_presim'] = \ - edata.fixedParametersPresimulation[i_par] + datadict[par + "_presim"] = edata.fixedParametersPresimulation[i_par] else: - datadict[par + '_presim'] = np.nan + datadict[par + "_presim"] = np.nan return datadict -def _get_extended_observable_cols(model: AmiciModel, - by_id: bool) -> List[str]: +def _get_extended_observable_cols(model: AmiciModel, by_id: bool) -> List[str]: """ Construction helper for extended observable dataframe headers. @@ -416,20 +420,26 @@ def _get_extended_observable_cols(model: AmiciModel, :return: column names as list. """ - return \ - ['condition_id', 'time', 'datatype', 't_presim'] + \ - _get_names_or_ids(model, 'FixedParameter', by_id=by_id) + \ - [name + '_preeq' for name in - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)] + \ - [name + '_presim' for name in - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)] + \ - _get_names_or_ids(model, 'Observable', by_id=by_id) + \ - [name + '_std' for name in - _get_names_or_ids(model, 'Observable', by_id=by_id)] - - -def _get_observable_cols(model: AmiciModel, - by_id: bool) -> List[str]: + return ( + ["condition_id", "time", "datatype", "t_presim"] + + _get_names_or_ids(model, "FixedParameter", by_id=by_id) + + [ + name + "_preeq" + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] + + [ + name + "_presim" + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] + + _get_names_or_ids(model, "Observable", by_id=by_id) + + [ + name + "_std" + for name in _get_names_or_ids(model, "Observable", by_id=by_id) + ] + ) + + +def _get_observable_cols(model: AmiciModel, by_id: bool) -> List[str]: """ Construction helper for observable dataframe headers. @@ -443,18 +453,22 @@ def _get_observable_cols(model: AmiciModel, :return: column names as list. """ - return \ - ['condition_id', 'time', 't_presim'] + \ - _get_names_or_ids(model, 'FixedParameter', by_id=by_id) + \ - [name + '_preeq' for name in - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)] + \ - [name + '_presim' for name in - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)] + \ - _get_names_or_ids(model, 'Observable', by_id=by_id) - - -def _get_state_cols(model: AmiciModel, - by_id: bool) -> List[str]: + return ( + ["condition_id", "time", "t_presim"] + + _get_names_or_ids(model, "FixedParameter", by_id=by_id) + + [ + name + "_preeq" + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] + + [ + name + "_presim" + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] + + _get_names_or_ids(model, "Observable", by_id=by_id) + ) + + +def _get_state_cols(model: AmiciModel, by_id: bool) -> List[str]: """ Construction helper for state dataframe headers. @@ -468,14 +482,19 @@ def _get_state_cols(model: AmiciModel, :return: column names as list. """ - return \ - ['condition_id', 'time', 't_presim'] + \ - _get_names_or_ids(model, 'FixedParameter', by_id=by_id) + \ - [name + '_preeq' for name in - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)] + \ - [name + '_presim' for name in - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)] + \ - _get_names_or_ids(model, 'State', by_id=by_id) + return ( + ["condition_id", "time", "t_presim"] + + _get_names_or_ids(model, "FixedParameter", by_id=by_id) + + [ + name + "_preeq" + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] + + [ + name + "_presim" + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] + + _get_names_or_ids(model, "State", by_id=by_id) + ) def _get_expression_cols(model: AmiciModel, by_id: bool) -> List[str]: @@ -491,19 +510,22 @@ def _get_expression_cols(model: AmiciModel, by_id: bool) -> List[str]: :return: column names as list. """ - return \ - ['condition_id', 'time', 't_presim'] + \ - _get_names_or_ids(model, 'FixedParameter', by_id=by_id) + \ - [name + '_preeq' for name in - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)] + \ - [name + '_presim' for name in - _get_names_or_ids(model, 'FixedParameter', by_id=by_id)] + \ - _get_names_or_ids(model, 'Expression', by_id=by_id) - - -def _get_names_or_ids(model: AmiciModel, - variable: str, - by_id: bool) -> List[str]: + return ( + ["condition_id", "time", "t_presim"] + + _get_names_or_ids(model, "FixedParameter", by_id=by_id) + + [ + name + "_preeq" + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] + + [ + name + "_presim" + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] + + _get_names_or_ids(model, "Expression", by_id=by_id) + ) + + +def _get_names_or_ids(model: AmiciModel, variable: str, by_id: bool) -> List[str]: """ Obtains a unique list of identifiers for the specified variable. First tries model.getVariableNames and then uses model.getVariableIds. @@ -523,18 +545,22 @@ def _get_names_or_ids(model: AmiciModel, """ # check whether variable type permitted variable_options = [ - 'Parameter', 'FixedParameter', 'Observable', 'State', 'Expression' + "Parameter", + "FixedParameter", + "Observable", + "State", + "Expression", ] if variable not in variable_options: - raise ValueError('Variable must be in ' + str(variable_options)) + raise ValueError("Variable must be in " + str(variable_options)) # extract attributes - names = list(getattr(model, f'get{variable}Names')()) - ids = list(getattr(model, f'get{variable}Ids')()) + names = list(getattr(model, f"get{variable}Names")()) + ids = list(getattr(model, f"get{variable}Ids")()) # find out if model has names and ids - has_names = getattr(model, f'has{variable}Names')() - has_ids = getattr(model, f'has{variable}Ids')() + has_names = getattr(model, f"has{variable}Names")() + has_ids = getattr(model, f"has{variable}Ids")() # extract labels if not by_id and has_names and len(set(names)) == len(names): @@ -548,16 +574,18 @@ def _get_names_or_ids(model: AmiciModel, if by_id: msg = f"Model {variable} ids are not set." else: - msg = f"Model {variable} names are not unique and " \ - f"{variable} ids are not set." + msg = ( + f"Model {variable} names are not unique and " + f"{variable} ids are not set." + ) raise ValueError(msg) def _get_specialized_fixed_parameters( - model: AmiciModel, - condition: Union[Dict[str, SupportsFloat], pd.Series], - overwrite: Union[Dict[str, SupportsFloat], pd.Series], - by_id: bool + model: AmiciModel, + condition: Union[Dict[str, SupportsFloat], pd.Series], + overwrite: Union[Dict[str, SupportsFloat], pd.Series], + by_id: bool, ) -> List[float]: """ Copies values in condition and overwrites them according to key @@ -580,15 +608,17 @@ def _get_specialized_fixed_parameters( cond = copy.deepcopy(condition) for field in overwrite: cond[field] = overwrite[field] - return [float(cond[name]) for name in _get_names_or_ids( - model, 'FixedParameter', by_id=by_id)] + return [ + float(cond[name]) + for name in _get_names_or_ids(model, "FixedParameter", by_id=by_id) + ] def constructEdataFromDataFrame( - df: pd.DataFrame, - model: AmiciModel, - condition: pd.Series, - by_id: Optional[bool] = False + df: pd.DataFrame, + model: AmiciModel, + condition: pd.Series, + by_id: Optional[bool] = False, ) -> amici.amici.ExpData: """ Constructs an ExpData instance according to the provided Model @@ -619,68 +649,67 @@ def constructEdataFromDataFrame( edata = amici.ExpData(model.get()) # timepoints - df = df.sort_values(by='time', ascending=True) - edata.setTimepoints(df['time'].values.astype(float)) + df = df.sort_values(by="time", ascending=True) + edata.setTimepoints(df["time"].values.astype(float)) # get fixed parameters from condition overwrite_preeq = {} overwrite_presim = {} - for par in list(_get_names_or_ids(model, 'FixedParameter', by_id=by_id)): - if par + '_preeq' in condition.keys() \ - and not math.isnan(condition[par + '_preeq'].astype(float)): - overwrite_preeq[par] = condition[par + '_preeq'].astype(float) - if par + '_presim' in condition.keys() \ - and not math.isnan(condition[par + '_presim'].astype(float)): - overwrite_presim[par] = condition[par + '_presim'].astype(float) + for par in list(_get_names_or_ids(model, "FixedParameter", by_id=by_id)): + if par + "_preeq" in condition.keys() and not math.isnan( + condition[par + "_preeq"].astype(float) + ): + overwrite_preeq[par] = condition[par + "_preeq"].astype(float) + if par + "_presim" in condition.keys() and not math.isnan( + condition[par + "_presim"].astype(float) + ): + overwrite_presim[par] = condition[par + "_presim"].astype(float) # fill in fixed parameters - edata.fixedParameters = condition[ - _get_names_or_ids(model, 'FixedParameter', by_id=by_id) - ].astype(float).values + edata.fixedParameters = ( + condition[_get_names_or_ids(model, "FixedParameter", by_id=by_id)] + .astype(float) + .values + ) # fill in preequilibration parameters - if any([overwrite_preeq[key] != condition[key] for key in - overwrite_preeq]): - edata.fixedParametersPreequilibration = \ - _get_specialized_fixed_parameters( - model, condition, overwrite_preeq, by_id=by_id) - elif len(overwrite_preeq): - edata.fixedParametersPreequilibration = copy.deepcopy( - edata.fixedParameters + if any([overwrite_preeq[key] != condition[key] for key in overwrite_preeq]): + edata.fixedParametersPreequilibration = _get_specialized_fixed_parameters( + model, condition, overwrite_preeq, by_id=by_id ) + elif len(overwrite_preeq): + edata.fixedParametersPreequilibration = copy.deepcopy(edata.fixedParameters) # fill in presimulation parameters - if any([overwrite_presim[key] != condition[key] for key in - overwrite_presim.keys()]): + if any( + [overwrite_presim[key] != condition[key] for key in overwrite_presim.keys()] + ): edata.fixedParametersPresimulation = _get_specialized_fixed_parameters( model, condition, overwrite_presim, by_id=by_id ) elif len(overwrite_presim.keys()): - edata.fixedParametersPresimulation = copy.deepcopy( - edata.fixedParameters - ) + edata.fixedParametersPresimulation = copy.deepcopy(edata.fixedParameters) # fill in presimulation time - if 't_presim' in condition.keys(): - edata.t_presim = float(condition['t_presim']) + if "t_presim" in condition.keys(): + edata.t_presim = float(condition["t_presim"]) # fill in data and stds for obs_index, obs in enumerate( - _get_names_or_ids(model, 'Observable', by_id=by_id)): + _get_names_or_ids(model, "Observable", by_id=by_id) + ): if obs in df.keys(): edata.setObservedData(df[obs].values.astype(float), obs_index) - if obs + '_std' in df.keys(): + if obs + "_std" in df.keys(): edata.setObservedDataStdDev( - df[obs + '_std'].values.astype(float), obs_index + df[obs + "_std"].values.astype(float), obs_index ) return edata def getEdataFromDataFrame( - model: AmiciModel, - df: pd.DataFrame, - by_id: Optional[bool] = False + model: AmiciModel, df: pd.DataFrame, by_id: Optional[bool] = False ) -> List[amici.amici.ExpData]: """ Constructs a ExpData instances according to the provided Model and @@ -709,17 +738,16 @@ def getEdataFromDataFrame( # aggregate features that define a condition # fixed parameters - condition_parameters = _get_names_or_ids(model, 'FixedParameter', - by_id=by_id) + condition_parameters = _get_names_or_ids(model, "FixedParameter", by_id=by_id) # preeq and presim parameters - for par in _get_names_or_ids(model, 'FixedParameter', by_id=by_id): - if par + '_preeq' in df.columns: - condition_parameters.append(par + '_preeq') - if par + '_presim' in df.columns: - condition_parameters.append(par + '_presim') + for par in _get_names_or_ids(model, "FixedParameter", by_id=by_id): + if par + "_preeq" in df.columns: + condition_parameters.append(par + "_preeq") + if par + "_presim" in df.columns: + condition_parameters.append(par + "_presim") # presimulation time - if 't_presim' in df.columns: - condition_parameters.append('t_presim') + if "t_presim" in df.columns: + condition_parameters.append("t_presim") # drop duplicates to create final conditions conditions = df[condition_parameters].drop_duplicates() @@ -729,9 +757,7 @@ def getEdataFromDataFrame( selected = np.ones((len(df),), dtype=bool) for par_label, par in row.items(): if math.isnan(par): - selected = selected & np.isnan( - df[par_label].astype(float).values - ) + selected = selected & np.isnan(df[par_label].astype(float).values) else: selected = selected & (df[par_label] == par) edata_df = df[selected] diff --git a/python/sdist/amici/parameter_mapping.py b/python/sdist/amici/parameter_mapping.py index ed3eaa2b08..b978a60e30 100644 --- a/python/sdist/amici/parameter_mapping.py +++ b/python/sdist/amici/parameter_mapping.py @@ -61,13 +61,13 @@ class ParameterMappingForCondition: """ def __init__( - self, - map_sim_var: SingleParameterMapping = None, - scale_map_sim_var: SingleScaleMapping = None, - map_preeq_fix: SingleParameterMapping = None, - scale_map_preeq_fix: SingleScaleMapping = None, - map_sim_fix: SingleParameterMapping = None, - scale_map_sim_fix: SingleScaleMapping = None, + self, + map_sim_var: SingleParameterMapping = None, + scale_map_sim_var: SingleScaleMapping = None, + map_preeq_fix: SingleParameterMapping = None, + scale_map_preeq_fix: SingleScaleMapping = None, + map_sim_fix: SingleParameterMapping = None, + scale_map_sim_fix: SingleScaleMapping = None, ): if map_sim_var is None: map_sim_var = {} @@ -94,22 +94,25 @@ def __init__( self.scale_map_sim_fix = scale_map_sim_fix def __repr__(self): - return (f"{self.__class__.__name__}(" - f"map_sim_var={repr(self.map_sim_var)}," - f"scale_map_sim_var={repr(self.scale_map_sim_var)}," - f"map_preeq_fix={repr(self.map_preeq_fix)}," - f"scale_map_preeq_fix={repr(self.scale_map_preeq_fix)}," - f"map_sim_fix={repr(self.map_sim_fix)}," - f"scale_map_sim_fix={repr(self.scale_map_sim_fix)})") + return ( + f"{self.__class__.__name__}(" + f"map_sim_var={repr(self.map_sim_var)}," + f"scale_map_sim_var={repr(self.scale_map_sim_var)}," + f"map_preeq_fix={repr(self.map_preeq_fix)}," + f"scale_map_preeq_fix={repr(self.scale_map_preeq_fix)}," + f"map_sim_fix={repr(self.map_sim_fix)}," + f"scale_map_sim_fix={repr(self.scale_map_sim_fix)})" + ) @property def free_symbols(self) -> Set[str]: """Get IDs of all (symbolic) parameters present in this mapping""" return { - p for p in chain( + p + for p in chain( self.map_sim_var.values(), self.map_preeq_fix.values(), - self.map_sim_fix.values() + self.map_sim_fix.values(), ) if isinstance(p, str) } @@ -124,10 +127,7 @@ class ParameterMapping(Sequence): List of parameter mappings for specific conditions. """ - def __init__( - self, - parameter_mappings: List[ParameterMappingForCondition] = None - ): + def __init__(self, parameter_mappings: List[ParameterMappingForCondition] = None): super().__init__() if parameter_mappings is None: parameter_mappings = [] @@ -137,7 +137,7 @@ def __iter__(self): yield from self.parameter_mappings def __getitem__( - self, item + self, item ) -> Union[ParameterMapping, ParameterMappingForCondition]: result = self.parameter_mappings[item] if isinstance(result, ParameterMappingForCondition): @@ -147,10 +147,7 @@ def __getitem__( def __len__(self): return len(self.parameter_mappings) - def append( - self, - parameter_mapping_for_condition: ParameterMappingForCondition - ): + def append(self, parameter_mapping_for_condition: ParameterMappingForCondition): """Append a condition specific parameter mapping.""" self.parameter_mappings.append(parameter_mapping_for_condition) @@ -164,11 +161,11 @@ def free_symbols(self) -> Set[str]: def fill_in_parameters( - edatas: List[amici.ExpData], - problem_parameters: Dict[str, numbers.Number], - scaled_parameters: bool, - parameter_mapping: ParameterMapping, - amici_model: AmiciModel + edatas: List[amici.ExpData], + problem_parameters: Dict[str, numbers.Number], + scaled_parameters: bool, + parameter_mapping: ParameterMapping, + amici_model: AmiciModel, ) -> None: """Fill fixed and dynamic parameters into the edatas (in-place). @@ -188,23 +185,31 @@ def fill_in_parameters( :param amici_model: AMICI model. """ - if unused_parameters := (set(problem_parameters.keys()) - - parameter_mapping.free_symbols): - warnings.warn("The following problem parameters were not used: " - + str(unused_parameters), RuntimeWarning) + if unused_parameters := ( + set(problem_parameters.keys()) - parameter_mapping.free_symbols + ): + warnings.warn( + "The following problem parameters were not used: " + str(unused_parameters), + RuntimeWarning, + ) for edata, mapping_for_condition in zip(edatas, parameter_mapping): fill_in_parameters_for_condition( - edata, problem_parameters, scaled_parameters, - mapping_for_condition, amici_model) + edata, + problem_parameters, + scaled_parameters, + mapping_for_condition, + amici_model, + ) def fill_in_parameters_for_condition( - edata: amici.ExpData, - problem_parameters: Dict[str, numbers.Number], - scaled_parameters: bool, - parameter_mapping: ParameterMappingForCondition, - amici_model: AmiciModel) -> None: + edata: amici.ExpData, + problem_parameters: Dict[str, numbers.Number], + scaled_parameters: bool, + parameter_mapping: ParameterMappingForCondition, + amici_model: AmiciModel, +) -> None: """Fill fixed and dynamic parameters into the edata for condition (in-place). @@ -254,12 +259,16 @@ def _get_par(model_par, value, mapping): # constant value return value - map_preeq_fix = {key: _get_par(key, val, map_preeq_fix) - for key, val in map_preeq_fix.items()} - map_sim_fix = {key: _get_par(key, val, map_sim_fix) - for key, val in map_sim_fix.items()} - map_sim_var = {key: _get_par(key, val, dict(map_sim_fix, **map_sim_var)) - for key, val in map_sim_var.items()} + map_preeq_fix = { + key: _get_par(key, val, map_preeq_fix) for key, val in map_preeq_fix.items() + } + map_sim_fix = { + key: _get_par(key, val, map_sim_fix) for key, val in map_sim_fix.items() + } + map_sim_var = { + key: _get_par(key, val, dict(map_sim_fix, **map_sim_var)) + for key, val in map_sim_var.items() + } # If necessary, (un)scale parameters if scaled_parameters: @@ -278,16 +287,18 @@ def _get_par(model_par, value, mapping): # variable parameters and parameter scale # parameter list from mapping dict - parameters = [map_sim_var[par_id] - for par_id in amici_model.getParameterIds()] + parameters = [map_sim_var[par_id] for par_id in amici_model.getParameterIds()] # scales list from mapping dict - scales = [petab_to_amici_scale(scale_map_sim_var[par_id]) - for par_id in amici_model.getParameterIds()] + scales = [ + petab_to_amici_scale(scale_map_sim_var[par_id]) + for par_id in amici_model.getParameterIds() + ] # plist plist = [ - ip for ip, par_id in enumerate(amici_model.getParameterIds()) + ip + for ip, par_id in enumerate(amici_model.getParameterIds()) if isinstance(parameter_mapping.map_sim_var[par_id], str) ] @@ -303,15 +314,17 @@ def _get_par(model_par, value, mapping): ########################################################################## # fixed parameters preequilibration if map_preeq_fix: - fixed_pars_preeq = [map_preeq_fix[par_id] - for par_id in amici_model.getFixedParameterIds()] + fixed_pars_preeq = [ + map_preeq_fix[par_id] for par_id in amici_model.getFixedParameterIds() + ] edata.fixedParametersPreequilibration = fixed_pars_preeq ########################################################################## # fixed parameters simulation if map_sim_fix: - fixed_pars_sim = [map_sim_fix[par_id] - for par_id in amici_model.getFixedParameterIds()] + fixed_pars_sim = [ + map_sim_fix[par_id] for par_id in amici_model.getFixedParameterIds() + ] edata.fixedParameters = fixed_pars_sim @@ -337,8 +350,7 @@ def amici_to_petab_scale(amici_scale: int) -> str: raise ValueError(f"AMICI scale not recognized: {amici_scale}") -def scale_parameter(value: numbers.Number, - petab_scale: str) -> numbers.Number: +def scale_parameter(value: numbers.Number, petab_scale: str) -> numbers.Number: """Bring parameter from linear scale to target scale. :param value: @@ -355,12 +367,12 @@ def scale_parameter(value: numbers.Number, return np.log10(value) if petab_scale == LOG: return np.log(value) - raise ValueError(f"Unknown parameter scale {petab_scale}. " - f"Must be from {(LIN, LOG, LOG10)}") + raise ValueError( + f"Unknown parameter scale {petab_scale}. " f"Must be from {(LIN, LOG, LOG10)}" + ) -def unscale_parameter(value: numbers.Number, - petab_scale: str) -> numbers.Number: +def unscale_parameter(value: numbers.Number, petab_scale: str) -> numbers.Number: """Bring parameter from scale to linear scale. :param value: @@ -377,13 +389,14 @@ def unscale_parameter(value: numbers.Number, return np.power(10, value) if petab_scale == LOG: return np.exp(value) - raise ValueError(f"Unknown parameter scale {petab_scale}. " - f"Must be from {(LIN, LOG, LOG10)}") + raise ValueError( + f"Unknown parameter scale {petab_scale}. " f"Must be from {(LIN, LOG, LOG10)}" + ) def scale_parameters_dict( - value_dict: Dict[Any, numbers.Number], - petab_scale_dict: Dict[Any, str]) -> None: + value_dict: Dict[Any, numbers.Number], petab_scale_dict: Dict[Any, str] +) -> None: """ Bring parameters from linear scale to target scale. @@ -405,8 +418,8 @@ def scale_parameters_dict( def unscale_parameters_dict( - value_dict: Dict[Any, numbers.Number], - petab_scale_dict: Dict[Any, str]) -> None: + value_dict: Dict[Any, numbers.Number], petab_scale_dict: Dict[Any, str] +) -> None: """ Bring parameters from target scale to linear scale. diff --git a/python/sdist/amici/petab_import.py b/python/sdist/amici/petab_import.py index 032e3d9ad1..cb4c1377a4 100644 --- a/python/sdist/amici/petab_import.py +++ b/python/sdist/amici/petab_import.py @@ -39,15 +39,17 @@ # ID of model parameter that is to be added to SBML model to indicate # preequilibration -PREEQ_INDICATOR_ID = 'preequilibration_indicator' +PREEQ_INDICATOR_ID = "preequilibration_indicator" -def _add_global_parameter(sbml_model: libsbml.Model, - parameter_id: str, - parameter_name: str = None, - constant: bool = False, - units: str = 'dimensionless', - value: float = 0.0) -> libsbml.Parameter: +def _add_global_parameter( + sbml_model: libsbml.Model, + parameter_id: str, + parameter_name: str = None, + constant: bool = False, + units: str = "dimensionless", + value: float = 0.0, +) -> libsbml.Parameter: """Add new global parameter to SBML model Arguments: @@ -74,8 +76,8 @@ def _add_global_parameter(sbml_model: libsbml.Model, def get_fixed_parameters( - petab_problem: petab.Problem, - non_estimated_parameters_as_constants=True, + petab_problem: petab.Problem, + non_estimated_parameters_as_constants=True, ) -> List[str]: """ Determine, set and return fixed model parameters. @@ -105,13 +107,16 @@ def get_fixed_parameters( # we can't handle them yet compartments = [ - col for col in petab_problem.condition_df + col + for col in petab_problem.condition_df if petab_problem.sbml_model.getCompartment(col) is not None ] if compartments: - raise NotImplementedError("Can't handle initial compartment sizes " - "at the moment. Consider creating an " - f"initial assignment for {compartments}") + raise NotImplementedError( + "Can't handle initial compartment sizes " + "at the moment. Consider creating an " + f"initial assignment for {compartments}" + ) # if we have a parameter table, all parameters that are allowed to be # listed in the parameter table, but are not marked as estimated, can be @@ -130,9 +135,9 @@ def get_fixed_parameters( else pd.DataFrame(columns=petab.MEASUREMENT_DF_REQUIRED_COLS), ) if non_estimated_parameters_as_constants: - estimated_parameters = \ - petab_problem.parameter_df.index.values[ - petab_problem.parameter_df[ESTIMATE] == 1] + estimated_parameters = petab_problem.parameter_df.index.values[ + petab_problem.parameter_df[ESTIMATE] == 1 + ] else: # don't treat parameter table parameters as constants estimated_parameters = petab_problem.parameter_df.index.values @@ -149,22 +154,23 @@ def get_fixed_parameters( # handle parameters in condition table if condition_df is not None: - logger.debug(f'Condition table: {condition_df.shape}') + logger.debug(f"Condition table: {condition_df.shape}") # remove overridden parameters (`object`-type columns) fixed_parameters.update( - p for p in condition_df.columns + p + for p in condition_df.columns # get rid of conditionName column if p != CONDITION_NAME # there is no parametric override # TODO: could check if the final overriding parameter is estimated # or not, but for now, we skip the parameter if there is any kind # of overriding - if condition_df[p].dtype != 'O' - # p is a parameter - and sbml_model.getParameter(p) is not None - # but not a rule target - and sbml_model.getRuleByVariable(p) is None + if condition_df[p].dtype != "O" + # p is a parameter + and sbml_model.getParameter(p) is not None + # but not a rule target + and sbml_model.getRuleByVariable(p) is None ) # Ensure mentioned parameters exist in the model. Remove additional ones @@ -172,23 +178,27 @@ def get_fixed_parameters( for fixed_parameter in fixed_parameters.copy(): # check global parameters if not sbml_model.getParameter(fixed_parameter): - logger.warning(f"Parameter or species '{fixed_parameter}'" - " provided in condition table but not present in" - " model. Ignoring.") + logger.warning( + f"Parameter or species '{fixed_parameter}'" + " provided in condition table but not present in" + " model. Ignoring." + ) fixed_parameters.remove(fixed_parameter) # exclude targets of rules or initial assignments for fixed_parameter in fixed_parameters.copy(): # check global parameters - if sbml_model.getInitialAssignmentBySymbol(fixed_parameter)\ - or sbml_model.getRuleByVariable(fixed_parameter): + if sbml_model.getInitialAssignmentBySymbol( + fixed_parameter + ) or sbml_model.getRuleByVariable(fixed_parameter): fixed_parameters.remove(fixed_parameter) return list(sorted(fixed_parameters)) -def species_to_parameters(species_ids: List[str], - sbml_model: 'libsbml.Model') -> List[str]: +def species_to_parameters( + species_ids: List[str], sbml_model: "libsbml.Model" +) -> List[str]: """ Turn a SBML species into parameters and replace species references inside the model instance. @@ -211,13 +221,15 @@ def species_to_parameters(species_ids: List[str], if species.getHasOnlySubstanceUnits(): logger.warning( f"Ignoring {species.getId()} which has only substance units." - " Conversion not yet implemented.") + " Conversion not yet implemented." + ) continue if math.isnan(species.getInitialConcentration()): logger.warning( f"Ignoring {species.getId()} which has no initial " - "concentration. Amount conversion not yet implemented.") + "concentration. Amount conversion not yet implemented." + ) continue transformables.append(species_id) @@ -250,12 +262,13 @@ def species_to_parameters(species_ids: List[str], def import_petab_problem( - petab_problem: petab.Problem, - model_output_dir: Union[str, Path, None] = None, - model_name: str = None, - force_compile: bool = False, - non_estimated_parameters_as_constants = True, - **kwargs) -> 'amici.Model': + petab_problem: petab.Problem, + model_output_dir: Union[str, Path, None] = None, + model_name: str = None, + force_compile: bool = False, + non_estimated_parameters_as_constants=True, + **kwargs, +) -> "amici.Model": """ Import model from petab problem. @@ -289,8 +302,11 @@ def import_petab_problem( The imported model. """ # extract model name from pysb - if PysbPetabProblem and isinstance(petab_problem, PysbPetabProblem) \ - and model_name is None: + if ( + PysbPetabProblem + and isinstance(petab_problem, PysbPetabProblem) + and model_name is None + ): model_name = petab_problem.pysb_model.name # generate folder and model name if necessary @@ -298,8 +314,9 @@ def import_petab_problem( if PysbPetabProblem and isinstance(petab_problem, PysbPetabProblem): raise ValueError("Parameter `model_output_dir` is required.") - model_output_dir = \ - _create_model_output_dir_name(petab_problem.sbml_model, model_name) + model_output_dir = _create_model_output_dir_name( + petab_problem.sbml_model, model_name + ) else: model_output_dir = os.path.abspath(model_output_dir) @@ -316,7 +333,8 @@ def import_petab_problem( if os.listdir(model_output_dir) and not force_compile: raise ValueError( f"Cannot compile to {model_output_dir}: not empty. " - "Please assign a different target or set `force_compile`.") + "Please assign a different target or set `force_compile`." + ) # remove folder if exists if os.path.exists(model_output_dir): @@ -329,23 +347,23 @@ def import_petab_problem( petab_problem, model_name=model_name, model_output_dir=model_output_dir, - **kwargs) + **kwargs, + ) else: import_model_sbml( petab_problem=petab_problem, model_name=model_name, model_output_dir=model_output_dir, - non_estimated_parameters_as_constants= - non_estimated_parameters_as_constants, - **kwargs) + non_estimated_parameters_as_constants=non_estimated_parameters_as_constants, + **kwargs, + ) # import model model_module = amici.import_model_module(model_name, model_output_dir) model = model_module.getModel() check_model(amici_model=model, petab_problem=petab_problem) - logger.info(f"Successfully loaded model {model_name} " - f"from {model_output_dir}.") + logger.info(f"Successfully loaded model {model_name} " f"from {model_output_dir}.") return model @@ -361,23 +379,25 @@ def check_model( amici_ids_free = set(amici_model.getParameterIds()) amici_ids = amici_ids_free | set(amici_model.getFixedParameterIds()) - petab_ids_free = set(petab_problem.parameter_df.loc[ - petab_problem.parameter_df[ESTIMATE] == 1 - ].index) + petab_ids_free = set( + petab_problem.parameter_df.loc[petab_problem.parameter_df[ESTIMATE] == 1].index + ) amici_ids_free_required = petab_ids_free.intersection(amici_ids) if not amici_ids_free_required.issubset(amici_ids_free): raise ValueError( - 'The available AMICI model does not support estimating the ' - 'following parameters. Please recompile the model and ensure ' - 'that these parameters are not treated as constants. Deleting ' - 'the current model might also resolve this. Parameters: ' - f'{amici_ids_free_required.difference(amici_ids_free)}' + "The available AMICI model does not support estimating the " + "following parameters. Please recompile the model and ensure " + "that these parameters are not treated as constants. Deleting " + "the current model might also resolve this. Parameters: " + f"{amici_ids_free_required.difference(amici_ids_free)}" ) -def _create_model_output_dir_name(sbml_model: 'libsbml.Model', model_name: Optional[str] = None) -> Path: +def _create_model_output_dir_name( + sbml_model: "libsbml.Model", model_name: Optional[str] = None +) -> Path: """ Find a folder for storing the compiled amici model. If possible, use the sbml model id, otherwise create a random folder. @@ -406,10 +426,7 @@ def _create_model_name(folder: Union[str, Path]) -> str: return os.path.split(os.path.normpath(folder))[-1] -def _can_import_model( - model_name: str, - model_output_dir: Union[str, Path] -) -> bool: +def _can_import_model(model_name: str, model_output_dir: Union[str, Path]) -> bool: """ Check whether a module of that name can already be imported. """ @@ -424,22 +441,23 @@ def _can_import_model( return hasattr(model_module, "getModel") -@log_execution_time('Importing PEtab model', logger) +@log_execution_time("Importing PEtab model", logger) def import_model_sbml( - sbml_model: Union[str, Path, 'libsbml.Model'] = None, - condition_table: Optional[Union[str, Path, pd.DataFrame]] = None, - observable_table: Optional[Union[str, Path, pd.DataFrame]] = None, - measurement_table: Optional[Union[str, Path, pd.DataFrame]] = None, - petab_problem: petab.Problem = None, - model_name: Optional[str] = None, - model_output_dir: Optional[Union[str, Path]] = None, - verbose: Optional[Union[bool, int]] = True, - allow_reinit_fixpar_initcond: bool = True, - validate: bool = True, - non_estimated_parameters_as_constants=True, - output_parameter_defaults: Optional[Dict[str, float]] = None, - discard_sbml_annotations: bool = False, - **kwargs) -> amici.SbmlImporter: + sbml_model: Union[str, Path, "libsbml.Model"] = None, + condition_table: Optional[Union[str, Path, pd.DataFrame]] = None, + observable_table: Optional[Union[str, Path, pd.DataFrame]] = None, + measurement_table: Optional[Union[str, Path, pd.DataFrame]] = None, + petab_problem: petab.Problem = None, + model_name: Optional[str] = None, + model_output_dir: Optional[Union[str, Path]] = None, + verbose: Optional[Union[bool, int]] = True, + allow_reinit_fixpar_initcond: bool = True, + validate: bool = True, + non_estimated_parameters_as_constants=True, + output_parameter_defaults: Optional[Dict[str, float]] = None, + discard_sbml_annotations: bool = False, + **kwargs, +) -> amici.SbmlImporter: """ Create AMICI model from PEtab problem @@ -509,15 +527,20 @@ def import_model_sbml( logger.info("Importing model ...") if any([sbml_model, condition_table, observable_table, measurement_table]): - warn("The `sbml_model`, `condition_table`, `observable_table`, and " - "`measurement_table` arguments are deprecated and will be " - "removed in a future version. Use `petab_problem` instead.", - DeprecationWarning, stacklevel=2) + warn( + "The `sbml_model`, `condition_table`, `observable_table`, and " + "`measurement_table` arguments are deprecated and will be " + "removed in a future version. Use `petab_problem` instead.", + DeprecationWarning, + stacklevel=2, + ) if petab_problem: - raise ValueError("Must not pass a `petab_problem` argument in " - "combination with any of `sbml_model`, " - "`condition_table`, `observable_table`, or " - "`measurement_table`.") + raise ValueError( + "Must not pass a `petab_problem` argument in " + "combination with any of `sbml_model`, " + "`condition_table`, `observable_table`, or " + "`measurement_table`." + ) petab_problem = petab.Problem( model=SbmlModel(sbml_model) @@ -528,8 +551,9 @@ def import_model_sbml( ) if petab_problem.observable_df is None: - raise NotImplementedError("PEtab import without observables table " - "is currently not supported.") + raise NotImplementedError( + "PEtab import without observables table " "is currently not supported." + ) assert isinstance(petab_problem.model, SbmlModel) @@ -541,8 +565,10 @@ def import_model_sbml( if model_name is None: if not (model_name := petab_problem.model.sbml_model.getId()): if not isinstance(sbml_model, (str, Path)): - raise ValueError("No `model_name` was provided and no model " - "ID was specified in the SBML model.") + raise ValueError( + "No `model_name` was provided and no model " + "ID was specified in the SBML model." + ) model_name = os.path.splitext(os.path.split(sbml_model)[-1])[0] if model_output_dir is None: @@ -550,8 +576,10 @@ def import_model_sbml( os.getcwd(), f"{model_name}-amici{amici.__version__}" ) - logger.info(f"Model name is '{model_name}'.\n" - f"Writing model code to '{model_output_dir}'.") + logger.info( + f"Model name is '{model_name}'.\n" + f"Writing model code to '{model_output_dir}'." + ) # Create a copy, because it will be modified by SbmlImporter sbml_doc = petab_problem.model.sbml_model.getSBMLDocument().clone() @@ -565,56 +593,63 @@ def import_model_sbml( ) sbml_model = sbml_importer.sbml - allow_n_noise_pars = \ - not petab.lint.observable_table_has_nontrivial_noise_formula( - petab_problem.observable_df + allow_n_noise_pars = not petab.lint.observable_table_has_nontrivial_noise_formula( + petab_problem.observable_df + ) + if ( + petab_problem.measurement_df is not None + and petab.lint.measurement_table_has_timepoint_specific_mappings( + petab_problem.measurement_df, + allow_scalar_numeric_noise_parameters=allow_n_noise_pars, ) - if petab_problem.measurement_df is not None and \ - petab.lint.measurement_table_has_timepoint_specific_mappings( - petab_problem.measurement_df, - allow_scalar_numeric_noise_parameters=allow_n_noise_pars - ): + ): raise ValueError( - 'AMICI does not support importing models with timepoint specific ' - 'mappings for noise or observable parameters. Please flatten ' - 'the problem and try again.' + "AMICI does not support importing models with timepoint specific " + "mappings for noise or observable parameters. Please flatten " + "the problem and try again." ) if petab_problem.observable_df is not None: - observables, noise_distrs, sigmas = \ - get_observation_model(petab_problem.observable_df) + observables, noise_distrs, sigmas = get_observation_model( + petab_problem.observable_df + ) else: observables = noise_distrs = sigmas = None - logger.info(f'Observables: {len(observables)}') - logger.info(f'Sigmas: {len(sigmas)}') + logger.info(f"Observables: {len(observables)}") + logger.info(f"Sigmas: {len(sigmas)}") if len(sigmas) != len(observables): raise AssertionError( - f'Number of provided observables ({len(observables)}) and sigmas ' - f'({len(sigmas)}) do not match.') + f"Number of provided observables ({len(observables)}) and sigmas " + f"({len(sigmas)}) do not match." + ) # TODO: adding extra output parameters is currently not supported, # so we add any output parameters to the SBML model. # this should be changed to something more elegant # - formulas = chain((val['formula'] for val in observables.values()), - sigmas.values()) + formulas = chain((val["formula"] for val in observables.values()), sigmas.values()) output_parameters = OrderedDict() for formula in formulas: # we want reproducible parameter ordering upon repeated import - free_syms = sorted(sp.sympify(formula, locals=_clash).free_symbols, - key=lambda symbol: symbol.name) + free_syms = sorted( + sp.sympify(formula, locals=_clash).free_symbols, + key=lambda symbol: symbol.name, + ) for free_sym in free_syms: sym = str(free_sym) - if sbml_model.getElementBySId(sym) is None and sym != 'time' \ - and sym not in observables: + if ( + sbml_model.getElementBySId(sym) is None + and sym != "time" + and sym not in observables + ): output_parameters[sym] = None - logger.debug("Adding output parameters to model: " - f"{list(output_parameters.keys())}") + logger.debug( + "Adding output parameters to model: " f"{list(output_parameters.keys())}" + ) output_parameter_defaults = output_parameter_defaults or {} - if extra_pars := (set(output_parameter_defaults) - - set(output_parameters.keys())): + if extra_pars := (set(output_parameter_defaults) - set(output_parameters.keys())): raise ValueError( f"Default output parameter values were given for {extra_pars}, " "but they those are not output parameters." @@ -624,7 +659,7 @@ def import_model_sbml( _add_global_parameter( sbml_model=sbml_model, parameter_id=par, - value=output_parameter_defaults.get(par, 0.0) + value=output_parameter_defaults.get(par, 0.0), ) # @@ -634,25 +669,29 @@ def import_model_sbml( # feels dirty and should be changed (see also #924) # - initial_states = [col for col in petab_problem.condition_df - if element_is_state(sbml_model, col)] + initial_states = [ + col for col in petab_problem.condition_df if element_is_state(sbml_model, col) + ] fixed_parameters = [] if initial_states: # add preequilibration indicator variable # NOTE: would only be required if we actually have preequilibration # adding it anyways. can be optimized-out later if sbml_model.getParameter(PREEQ_INDICATOR_ID) is not None: - raise AssertionError("Model already has a parameter with ID " - f"{PREEQ_INDICATOR_ID}. Cannot handle " - "species and compartments in condition table " - "then.") + raise AssertionError( + "Model already has a parameter with ID " + f"{PREEQ_INDICATOR_ID}. Cannot handle " + "species and compartments in condition table " + "then." + ) indicator = sbml_model.createParameter() indicator.setId(PREEQ_INDICATOR_ID) indicator.setName(PREEQ_INDICATOR_ID) # Can only reset parameters after preequilibration if they are fixed. fixed_parameters.append(PREEQ_INDICATOR_ID) - logger.debug("Adding preequilibration indicator " - f"constant {PREEQ_INDICATOR_ID}") + logger.debug( + "Adding preequilibration indicator " f"constant {PREEQ_INDICATOR_ID}" + ) logger.debug(f"Adding initial assignments for {initial_states}") for assignee_id in initial_states: init_par_id_preeq = f"initial_{assignee_id}_preeq" @@ -662,7 +701,8 @@ def import_model_sbml( raise ValueError( "Cannot create parameter for initial assignment " f"for {assignee_id} because an entity named " - f"{init_par_id} exists already in the model.") + f"{init_par_id} exists already in the model." + ) init_par = sbml_model.createParameter() init_par.setId(init_par_id) init_par.setName(init_par_id) @@ -671,14 +711,18 @@ def import_model_sbml( assignment = sbml_model.createInitialAssignment() assignment.setSymbol(assignee_id) else: - logger.debug('The SBML model has an initial assignment defined ' - f'for model entity {assignee_id}, but this entity ' - 'also has an initial value defined in the PEtab ' - 'condition table. The SBML initial assignment will ' - 'be overwritten to handle preequilibration and ' - 'initial values specified by the PEtab problem.') - formula = f'{PREEQ_INDICATOR_ID} * {init_par_id_preeq} ' \ - f'+ (1 - {PREEQ_INDICATOR_ID}) * {init_par_id_sim}' + logger.debug( + "The SBML model has an initial assignment defined " + f"for model entity {assignee_id}, but this entity " + "also has an initial value defined in the PEtab " + "condition table. The SBML initial assignment will " + "be overwritten to handle preequilibration and " + "initial values specified by the PEtab problem." + ) + formula = ( + f"{PREEQ_INDICATOR_ID} * {init_par_id_preeq} " + f"+ (1 - {PREEQ_INDICATOR_ID}) * {init_par_id_sim}" + ) math_ast = libsbml.parseL3Formula(formula) assignment.setMath(math_ast) # @@ -686,15 +730,16 @@ def import_model_sbml( fixed_parameters.extend( get_fixed_parameters( petab_problem=petab_problem, - non_estimated_parameters_as_constants= - non_estimated_parameters_as_constants, - )) + non_estimated_parameters_as_constants=non_estimated_parameters_as_constants, + ) + ) logger.debug(f"Fixed parameters are {fixed_parameters}") logger.info(f"Overall fixed parameters: {len(fixed_parameters)}") - logger.info("Variable parameters: " - + str(len(sbml_model.getListOfParameters()) - - len(fixed_parameters))) + logger.info( + "Variable parameters: " + + str(len(sbml_model.getListOfParameters()) - len(fixed_parameters)) + ) # Create Python module from SBML model sbml_importer.sbml2amici( @@ -706,10 +751,12 @@ def import_model_sbml( allow_reinit_fixpar_initcond=allow_reinit_fixpar_initcond, noise_distributions=noise_distrs, verbose=verbose, - **kwargs) + **kwargs, + ) - if kwargs.get('compile', amici._get_default_argument( - sbml_importer.sbml2amici, 'compile')): + if kwargs.get( + "compile", amici._get_default_argument(sbml_importer.sbml2amici, "compile") + ): # check that the model extension was compiled successfully model_module = amici.import_model_module(model_name, model_output_dir) model = model_module.getModel() @@ -723,9 +770,8 @@ def import_model_sbml( def get_observation_model( - observable_df: pd.DataFrame, -) -> Tuple[Dict[str, Dict[str, str]], Dict[str, str], - Dict[str, Union[str, float]]]: + observable_df: pd.DataFrame, +) -> Tuple[Dict[str, Dict[str, str]], Dict[str, str], Dict[str, Union[str, float]]]: """ Get observables, sigmas, and noise distributions from PEtab observation table in a format suitable for @@ -744,22 +790,21 @@ def get_observation_model( observables = {} sigmas = {} - nan_pat = r'^[nN]a[nN]$' + nan_pat = r"^[nN]a[nN]$" for _, observable in observable_df.iterrows(): oid = str(observable.name) # need to sanitize due to https://github.com/PEtab-dev/PEtab/issues/447 - name = re.sub(nan_pat, '', str(observable.get(OBSERVABLE_NAME, ''))) - formula_obs = re.sub(nan_pat, '', str(observable[OBSERVABLE_FORMULA])) - formula_noise = re.sub(nan_pat, '', str(observable[NOISE_FORMULA])) - observables[oid] = {'name': name, 'formula': formula_obs} + name = re.sub(nan_pat, "", str(observable.get(OBSERVABLE_NAME, ""))) + formula_obs = re.sub(nan_pat, "", str(observable[OBSERVABLE_FORMULA])) + formula_noise = re.sub(nan_pat, "", str(observable[NOISE_FORMULA])) + observables[oid] = {"name": name, "formula": formula_obs} sigmas[oid] = formula_noise # PEtab does currently not allow observables in noiseFormula and AMICI # cannot handle states in sigma expressions. Therefore, where possible, # replace species occurring in error model definition by observableIds. replacements = { - sp.sympify(observable['formula'], locals=_clash): - sp.Symbol(observable_id) + sp.sympify(observable["formula"], locals=_clash): sp.Symbol(observable_id) for observable_id, observable in observables.items() } for observable_id, formula in sigmas.items(): @@ -771,8 +816,7 @@ def get_observation_model( return observables, noise_distrs, sigmas -def petab_noise_distributions_to_amici(observable_df: pd.DataFrame - ) -> Dict[str, str]: +def petab_noise_distributions_to_amici(observable_df: pd.DataFrame) -> Dict[str, str]: """ Map from the petab to the amici format of noise distribution identifiers. @@ -785,19 +829,23 @@ def petab_noise_distributions_to_amici(observable_df: pd.DataFrame """ amici_distrs = {} for _, observable in observable_df.iterrows(): - amici_val = '' - - if OBSERVABLE_TRANSFORMATION in observable \ - and isinstance(observable[OBSERVABLE_TRANSFORMATION], str) \ - and observable[OBSERVABLE_TRANSFORMATION]: - amici_val += observable[OBSERVABLE_TRANSFORMATION] + '-' - - if NOISE_DISTRIBUTION in observable \ - and isinstance(observable[NOISE_DISTRIBUTION], str) \ - and observable[NOISE_DISTRIBUTION]: + amici_val = "" + + if ( + OBSERVABLE_TRANSFORMATION in observable + and isinstance(observable[OBSERVABLE_TRANSFORMATION], str) + and observable[OBSERVABLE_TRANSFORMATION] + ): + amici_val += observable[OBSERVABLE_TRANSFORMATION] + "-" + + if ( + NOISE_DISTRIBUTION in observable + and isinstance(observable[NOISE_DISTRIBUTION], str) + and observable[NOISE_DISTRIBUTION] + ): amici_val += observable[NOISE_DISTRIBUTION] else: - amici_val += 'normal' + amici_val += "normal" amici_distrs[observable.name] = amici_val return amici_distrs @@ -816,24 +864,23 @@ def petab_scale_to_amici_scale(scale_str: str) -> int: raise ValueError(f"Invalid parameter scale {scale_str}") -def show_model_info(sbml_model: 'libsbml.Model'): +def show_model_info(sbml_model: "libsbml.Model"): """Log some model quantities""" - logger.info(f'Species: {len(sbml_model.getListOfSpecies())}') - logger.info('Global parameters: ' - + str(len(sbml_model.getListOfParameters()))) - logger.info(f'Reactions: {len(sbml_model.getListOfReactions())}') + logger.info(f"Species: {len(sbml_model.getListOfSpecies())}") + logger.info("Global parameters: " + str(len(sbml_model.getListOfParameters()))) + logger.info(f"Reactions: {len(sbml_model.getListOfReactions())}") def element_is_state(sbml_model: libsbml.Model, sbml_id: str) -> bool: - """Does the element with ID `sbml_id` correspond to a state variable? - """ + """Does the element with ID `sbml_id` correspond to a state variable?""" if sbml_model.getCompartment(sbml_id) is not None: return True if sbml_model.getSpecies(sbml_id) is not None: return True - if (rule := sbml_model.getRuleByVariable(sbml_id)) is not None \ - and rule.getTypeCode() == libsbml.SBML_RATE_RULE: + if ( + rule := sbml_model.getRuleByVariable(sbml_id) + ) is not None and rule.getTypeCode() == libsbml.SBML_RATE_RULE: return True return False @@ -848,53 +895,88 @@ def _parse_cli_args(): """ parser = argparse.ArgumentParser( - description='Import PEtab-format model into AMICI.') + description="Import PEtab-format model into AMICI." + ) # General options: - parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', - help='More verbose output') - parser.add_argument('-o', '--output-dir', dest='model_output_dir', - help='Name of the model directory to create') - parser.add_argument('--no-compile', action='store_false', - dest='compile', - help='Only generate model code, do not compile') - parser.add_argument('--no-validate', action='store_false', - dest='validate', - help='Skip validation of PEtab files') - parser.add_argument('--flatten', dest='flatten', default=False, - action='store_true', - help='Flatten measurement specific overrides of ' - 'observable and noise parameters') - parser.add_argument('--no-sensitivities', dest='generate_sensitivity_code', - default=True, action='store_false', - help='Skip generation of sensitivity code') + parser.add_argument( + "-v", + "--verbose", + dest="verbose", + action="store_true", + help="More verbose output", + ) + parser.add_argument( + "-o", + "--output-dir", + dest="model_output_dir", + help="Name of the model directory to create", + ) + parser.add_argument( + "--no-compile", + action="store_false", + dest="compile", + help="Only generate model code, do not compile", + ) + parser.add_argument( + "--no-validate", + action="store_false", + dest="validate", + help="Skip validation of PEtab files", + ) + parser.add_argument( + "--flatten", + dest="flatten", + default=False, + action="store_true", + help="Flatten measurement specific overrides of " + "observable and noise parameters", + ) + parser.add_argument( + "--no-sensitivities", + dest="generate_sensitivity_code", + default=True, + action="store_false", + help="Skip generation of sensitivity code", + ) # Call with set of files - parser.add_argument('-s', '--sbml', dest='sbml_file_name', - help='SBML model filename') - parser.add_argument('-m', '--measurements', dest='measurement_file_name', - help='Measurement table') - parser.add_argument('-c', '--conditions', dest='condition_file_name', - help='Conditions table') - parser.add_argument('-p', '--parameters', dest='parameter_file_name', - help='Parameter table') - parser.add_argument('-b', '--observables', dest='observable_file_name', - help='Observable table') - - parser.add_argument('-y', '--yaml', dest='yaml_file_name', - help='PEtab YAML problem filename') - - parser.add_argument('-n', '--model-name', dest='model_name', - help='Name of the python module generated for the ' - 'model') + parser.add_argument( + "-s", "--sbml", dest="sbml_file_name", help="SBML model filename" + ) + parser.add_argument( + "-m", "--measurements", dest="measurement_file_name", help="Measurement table" + ) + parser.add_argument( + "-c", "--conditions", dest="condition_file_name", help="Conditions table" + ) + parser.add_argument( + "-p", "--parameters", dest="parameter_file_name", help="Parameter table" + ) + parser.add_argument( + "-b", "--observables", dest="observable_file_name", help="Observable table" + ) + + parser.add_argument( + "-y", "--yaml", dest="yaml_file_name", help="PEtab YAML problem filename" + ) + + parser.add_argument( + "-n", + "--model-name", + dest="model_name", + help="Name of the python module generated for the " "model", + ) args = parser.parse_args() - if not args.yaml_file_name \ - and not all((args.sbml_file_name, args.condition_file_name, - args.observable_file_name)): - parser.error('When not specifying a model name or YAML file, then ' - 'SBML, condition and observable file must be specified') + if not args.yaml_file_name and not all( + (args.sbml_file_name, args.condition_file_name, args.observable_file_name) + ): + parser.error( + "When not specifying a model name or YAML file, then " + "SBML, condition and observable file must be specified" + ) return args @@ -914,7 +996,8 @@ def _main(): condition_file=args.condition_file_name, measurement_file=args.measurement_file_name, parameter_file=args.parameter_file_name, - observable_files=args.observable_file_name) + observable_files=args.observable_file_name, + ) # Check for valid PEtab before potentially modifying it if args.validate: @@ -923,17 +1006,19 @@ def _main(): if args.flatten: petab.flatten_timepoint_specific_output_overrides(pp) - import_model(model_name=args.model_name, - sbml_model=pp.sbml_model, - condition_table=pp.condition_df, - observable_table=pp.observable_df, - measurement_table=pp.measurement_df, - model_output_dir=args.model_output_dir, - compile=args.compile, - generate_sensitivity_code=args.generate_sensitivity_code, - verbose=args.verbose, - validate=False) + import_model( + model_name=args.model_name, + sbml_model=pp.sbml_model, + condition_table=pp.condition_df, + observable_table=pp.observable_df, + measurement_table=pp.measurement_df, + model_output_dir=args.model_output_dir, + compile=args.compile, + generate_sensitivity_code=args.generate_sensitivity_code, + verbose=args.verbose, + validate=False, + ) -if __name__ == '__main__': +if __name__ == "__main__": _main() diff --git a/python/sdist/amici/petab_import_pysb.py b/python/sdist/amici/petab_import_pysb.py index 4d2f146a74..8ff68c68f1 100644 --- a/python/sdist/amici/petab_import_pysb.py +++ b/python/sdist/amici/petab_import_pysb.py @@ -14,10 +14,18 @@ import petab import pysb import sympy as sp -from petab.C import (CONDITION_FILES, CONDITION_NAME, FORMAT_VERSION, - MEASUREMENT_FILES, NOISE_FORMULA, OBSERVABLE_FILES, - OBSERVABLE_FORMULA, PARAMETER_FILE, SBML_FILES, - VISUALIZATION_FILES) +from petab.C import ( + CONDITION_FILES, + CONDITION_NAME, + FORMAT_VERSION, + MEASUREMENT_FILES, + NOISE_FORMULA, + OBSERVABLE_FILES, + OBSERVABLE_FORMULA, + PARAMETER_FILE, + SBML_FILES, + VISUALIZATION_FILES, +) from petab.models.sbml_model import SbmlModel from .logging import get_logger, log_execution_time, set_log_level @@ -39,7 +47,7 @@ class PysbPetabProblem(petab.Problem): """ - def __init__(self, pysb_model: 'pysb.Model' = None, *args, **kwargs): + def __init__(self, pysb_model: "pysb.Model" = None, *args, **kwargs): """ Constructor @@ -47,32 +55,36 @@ def __init__(self, pysb_model: 'pysb.Model' = None, *args, **kwargs): :param args: See :meth:`petab.Problem.__init__` :param kwargs: See :meth:`petab.Problem.__init__` """ - flatten = kwargs.pop('flatten', False) + flatten = kwargs.pop("flatten", False) super().__init__(*args, **kwargs) if flatten: petab.flatten_timepoint_specific_output_overrides(self) - self.pysb_model: 'pysb.Model' = pysb_model + self.pysb_model: "pysb.Model" = pysb_model self._add_observation_model() if self.pysb_model is not None: - self.model = \ - create_dummy_sbml( - self.pysb_model, - observable_ids=self.observable_df.index.values - if self.observable_df is not None else None - ) + self.model = create_dummy_sbml( + self.pysb_model, + observable_ids=self.observable_df.index.values + if self.observable_df is not None + else None, + ) def _add_observation_model(self): """Extend PySB model by observation model as defined in the PEtab observables table""" # add any required output parameters - local_syms = {sp.Symbol.__str__(comp): comp for comp in - self.pysb_model.components if - isinstance(comp, sp.Symbol)} - for formula in [*self.observable_df[OBSERVABLE_FORMULA], - *self.observable_df[NOISE_FORMULA]]: + local_syms = { + sp.Symbol.__str__(comp): comp + for comp in self.pysb_model.components + if isinstance(comp, sp.Symbol) + } + for formula in [ + *self.observable_df[OBSERVABLE_FORMULA], + *self.observable_df[NOISE_FORMULA], + ]: sym = sp.sympify(formula, locals=local_syms) for s in sym.free_symbols: if not isinstance(s, pysb.Component): @@ -81,43 +93,35 @@ def _add_observation_model(self): local_syms[sp.Symbol.__str__(p)] = p # add observables and sigmas to pysb model - for (observable_id, observable_formula, noise_formula) \ - in zip(self.observable_df.index, - self.observable_df[OBSERVABLE_FORMULA], - self.observable_df[NOISE_FORMULA]): + for observable_id, observable_formula, noise_formula in zip( + self.observable_df.index, + self.observable_df[OBSERVABLE_FORMULA], + self.observable_df[NOISE_FORMULA], + ): obs_symbol = sp.sympify(observable_formula, locals=local_syms) if observable_id in self.pysb_model.expressions.keys(): obs_expr = self.pysb_model.expressions[observable_id] else: - obs_expr = pysb.Expression(observable_id, obs_symbol, - _export=False) + obs_expr = pysb.Expression(observable_id, obs_symbol, _export=False) self.pysb_model.add_component(obs_expr) local_syms[observable_id] = obs_expr sigma_id = f"{observable_id}_sigma" - sigma_symbol = sp.sympify( - noise_formula, - locals=local_syms - ) + sigma_symbol = sp.sympify(noise_formula, locals=local_syms) sigma_expr = pysb.Expression(sigma_id, sigma_symbol, _export=False) self.pysb_model.add_component(sigma_expr) local_syms[sigma_id] = sigma_expr @staticmethod def from_files( - condition_file: - Union[str, Path, Iterable[Union[str, Path]]] = None, - measurement_file: - Union[str, Path, Iterable[Union[str, Path]]] = None, - parameter_file: - Union[str, Path, Iterable[Union[str, Path]]] = None, - visualization_files: - Union[str, Path, Iterable[Union[str, Path]]] = None, - observable_files: - Union[str, Path, Iterable[Union[str, Path]]] = None, - pysb_model_file: Union[str, Path] = None, - flatten: bool = False - ) -> 'PysbPetabProblem': + condition_file: Union[str, Path, Iterable[Union[str, Path]]] = None, + measurement_file: Union[str, Path, Iterable[Union[str, Path]]] = None, + parameter_file: Union[str, Path, Iterable[Union[str, Path]]] = None, + visualization_files: Union[str, Path, Iterable[Union[str, Path]]] = None, + observable_files: Union[str, Path, Iterable[Union[str, Path]]] = None, + pysb_model_file: Union[str, Path] = None, + flatten: bool = False, + ) -> "PysbPetabProblem": """ Factory method to load model and tables from files. @@ -155,7 +159,8 @@ def from_files( if measurement_file: # If there are multiple tables, we will merge them measurement_df = petab.core.concat_tables( - measurement_file, petab.measurements.get_measurement_df) + measurement_file, petab.measurements.get_measurement_df + ) if parameter_file: parameter_df = petab.parameters.get_parameter_df(parameter_file) @@ -163,27 +168,30 @@ def from_files( if visualization_files: # If there are multiple tables, we will merge them visualization_df = petab.core.concat_tables( - visualization_files, petab.core.get_visualization_df) + visualization_files, petab.core.get_visualization_df + ) if observable_files: # If there are multiple tables, we will merge them observable_df = petab.core.concat_tables( - observable_files, petab.observables.get_observable_df) + observable_files, petab.observables.get_observable_df + ) from amici.pysb_import import pysb_model_from_path + return PysbPetabProblem( - pysb_model=pysb_model_from_path( - pysb_model_file=pysb_model_file), + pysb_model=pysb_model_from_path(pysb_model_file=pysb_model_file), condition_df=condition_df, measurement_df=measurement_df, parameter_df=parameter_df, observable_df=observable_df, visualization_df=visualization_df, - flatten=flatten + flatten=flatten, ) @staticmethod - def from_yaml(yaml_config: Union[Dict, Path, str], - flatten: bool = False) -> 'PysbPetabProblem': + def from_yaml( + yaml_config: Union[Dict, Path, str], flatten: bool = False + ) -> "PysbPetabProblem": """ Factory method to load model and tables as specified by YAML file. @@ -199,8 +207,12 @@ def from_yaml(yaml_config: Union[Dict, Path, str], :return: Petab Problem """ - from petab.yaml import (load_yaml, is_composite_problem, - assert_single_condition_and_sbml_file) + from petab.yaml import ( + load_yaml, + is_composite_problem, + assert_single_condition_and_sbml_file, + ) + if isinstance(yaml_config, (str, Path)): path_prefix = os.path.dirname(yaml_config) yaml_config = load_yaml(yaml_config) @@ -208,50 +220,51 @@ def from_yaml(yaml_config: Union[Dict, Path, str], path_prefix = "" if is_composite_problem(yaml_config): - raise ValueError('petab.Problem.from_yaml() can only be used for ' - 'yaml files comprising a single model. ' - 'Consider using ' - 'petab.CompositeProblem.from_yaml() instead.') + raise ValueError( + "petab.Problem.from_yaml() can only be used for " + "yaml files comprising a single model. " + "Consider using " + "petab.CompositeProblem.from_yaml() instead." + ) if yaml_config[FORMAT_VERSION] != petab.__format_version__: - raise ValueError("Provided PEtab files are of unsupported version" - f"{yaml_config[FORMAT_VERSION]}. Expected " - f"{petab.__format_version__}.") + raise ValueError( + "Provided PEtab files are of unsupported version" + f"{yaml_config[FORMAT_VERSION]}. Expected " + f"{petab.__format_version__}." + ) - problem0 = yaml_config['problems'][0] + problem0 = yaml_config["problems"][0] assert_single_condition_and_sbml_file(problem0) if isinstance(yaml_config[PARAMETER_FILE], list): parameter_file = [ - os.path.join(path_prefix, f) - for f in yaml_config[PARAMETER_FILE] + os.path.join(path_prefix, f) for f in yaml_config[PARAMETER_FILE] ] else: - parameter_file = os.path.join( - path_prefix, yaml_config[PARAMETER_FILE]) + parameter_file = os.path.join(path_prefix, yaml_config[PARAMETER_FILE]) return PysbPetabProblem.from_files( - pysb_model_file=os.path.join( - path_prefix, problem0[SBML_FILES][0]), - measurement_file=[os.path.join(path_prefix, f) - for f in problem0[MEASUREMENT_FILES]], - condition_file=os.path.join( - path_prefix, problem0[CONDITION_FILES][0]), + pysb_model_file=os.path.join(path_prefix, problem0[SBML_FILES][0]), + measurement_file=[ + os.path.join(path_prefix, f) for f in problem0[MEASUREMENT_FILES] + ], + condition_file=os.path.join(path_prefix, problem0[CONDITION_FILES][0]), parameter_file=parameter_file, visualization_files=[ os.path.join(path_prefix, f) - for f in problem0.get(VISUALIZATION_FILES, [])], + for f in problem0.get(VISUALIZATION_FILES, []) + ], observable_files=[ - os.path.join(path_prefix, f) - for f in problem0.get(OBSERVABLE_FILES, [])], - flatten=flatten + os.path.join(path_prefix, f) for f in problem0.get(OBSERVABLE_FILES, []) + ], + flatten=flatten, ) def create_dummy_sbml( - pysb_model: 'pysb.Model', - observable_ids: Optional[Iterable[str]] = None + pysb_model: "pysb.Model", observable_ids: Optional[Iterable[str]] = None ) -> SbmlModel: """Create SBML dummy model for to use PySB models with PEtab. @@ -268,11 +281,11 @@ def create_dummy_sbml( dummy_sbml_model = document.createModel() dummy_sbml_model.setTimeUnits("second") dummy_sbml_model.setExtentUnits("mole") - dummy_sbml_model.setSubstanceUnits('mole') + dummy_sbml_model.setSubstanceUnits("mole") # mandatory if there are species c = dummy_sbml_model.createCompartment() - c.setId('dummy_compartment') + c.setId("dummy_compartment") c.setConstant(False) # parameters are required for parameter mapping @@ -301,19 +314,19 @@ def create_dummy_sbml( s.setInitialAmount(0.0) s.setHasOnlySubstanceUnits(False) s.setBoundaryCondition(False) - s.setCompartment('dummy_compartment') + s.setCompartment("dummy_compartment") s.setConstant(False) return SbmlModel(sbml_model=dummy_sbml_model, sbml_document=document) -@log_execution_time('Importing PEtab model', logger) +@log_execution_time("Importing PEtab model", logger) def import_model_pysb( - petab_problem: PysbPetabProblem, - model_output_dir: Optional[Union[str, Path]] = None, - verbose: Optional[Union[bool, int]] = True, - model_name: Optional[str] = None, - **kwargs + petab_problem: PysbPetabProblem, + model_output_dir: Optional[Union[str, Path]] = None, + verbose: Optional[Union[bool, int]] = True, + model_name: Optional[str] = None, + **kwargs, ) -> None: """ Create AMICI model from PySB-PEtab problem @@ -358,9 +371,8 @@ def import_model_pysb( f"Offending column: {x}" ) - from .petab_import import ( - get_fixed_parameters, petab_noise_distributions_to_amici - ) + from .petab_import import get_fixed_parameters, petab_noise_distributions_to_amici + constant_parameters = get_fixed_parameters(petab_problem) if observable_table is None: @@ -368,20 +380,26 @@ def import_model_pysb( sigmas = None noise_distrs = None else: - observables = [expr.name for expr in pysb_model.expressions - if expr.name in observable_table.index] + observables = [ + expr.name + for expr in pysb_model.expressions + if expr.name in observable_table.index + ] sigmas = {obs_id: f"{obs_id}_sigma" for obs_id in observables} noise_distrs = petab_noise_distributions_to_amici(observable_table) from amici.pysb_import import pysb2amici - pysb2amici(model=pysb_model, - output_dir=model_output_dir, - model_name=model_name, - verbose=True, - observables=observables, - sigmas=sigmas, - constant_parameters=constant_parameters, - noise_distributions=noise_distrs, - **kwargs) + + pysb2amici( + model=pysb_model, + output_dir=model_output_dir, + model_name=model_name, + verbose=True, + observables=observables, + sigmas=sigmas, + constant_parameters=constant_parameters, + noise_distributions=noise_distrs, + **kwargs, + ) diff --git a/python/sdist/amici/petab_objective.py b/python/sdist/amici/petab_objective.py index dee9134289..423c8f3ba5 100644 --- a/python/sdist/amici/petab_objective.py +++ b/python/sdist/amici/petab_objective.py @@ -8,8 +8,17 @@ import copy import logging import numbers -from typing import (Any, Collection, Dict, Iterator, List, Optional, Sequence, - Tuple, Union) +from typing import ( + Any, + Collection, + Dict, + Iterator, + List, + Optional, + Sequence, + Tuple, + Union, +) import libsbml import numpy as np @@ -23,8 +32,11 @@ from amici.sbml_import import get_species_initial from . import AmiciExpData, AmiciModel from .logging import get_logger, log_execution_time -from .parameter_mapping import (ParameterMapping, ParameterMappingForCondition, - fill_in_parameters) +from .parameter_mapping import ( + ParameterMapping, + ParameterMappingForCondition, + fill_in_parameters, +) from .petab_import import PREEQ_INDICATOR_ID, element_is_state from .parameter_mapping import ( fill_in_parameters, @@ -36,30 +48,30 @@ # string constant definitions -LLH = 'llh' -SLLH = 'sllh' -FIM = 'fim' -S2LLH = 's2llh' -RES = 'res' -SRES = 'sres' -RDATAS = 'rdatas' -EDATAS = 'edatas' +LLH = "llh" +SLLH = "sllh" +FIM = "fim" +S2LLH = "s2llh" +RES = "res" +SRES = "sres" +RDATAS = "rdatas" +EDATAS = "edatas" -@log_execution_time('Simulating PEtab model', logger) +@log_execution_time("Simulating PEtab model", logger) def simulate_petab( - petab_problem: petab.Problem, - amici_model: AmiciModel, - solver: Optional[amici.Solver] = None, - problem_parameters: Optional[Dict[str, float]] = None, - simulation_conditions: Union[pd.DataFrame, Dict] = None, - edatas: List[AmiciExpData] = None, - parameter_mapping: ParameterMapping = None, - scaled_parameters: Optional[bool] = False, - log_level: int = logging.WARNING, - num_threads: int = 1, - failfast: bool = True, - scaled_gradients: bool = False, + petab_problem: petab.Problem, + amici_model: AmiciModel, + solver: Optional[amici.Solver] = None, + problem_parameters: Optional[Dict[str, float]] = None, + simulation_conditions: Union[pd.DataFrame, Dict] = None, + edatas: List[AmiciExpData] = None, + parameter_mapping: ParameterMapping = None, + scaled_parameters: Optional[bool] = False, + log_level: int = logging.WARNING, + num_threads: int = 1, + failfast: bool = True, + scaled_gradients: bool = False, ) -> Dict[str, Any]: """Simulate PEtab model. @@ -129,10 +141,10 @@ def simulate_petab( # number of amici simulations will be number of unique # (preequilibrationConditionId, simulationConditionId) pairs. # Can be optimized by checking for identical condition vectors. - if simulation_conditions is None and parameter_mapping is None \ - and edatas is None: - simulation_conditions = \ + if simulation_conditions is None and parameter_mapping is None and edatas is None: + simulation_conditions = ( petab_problem.get_simulation_conditions_from_measurement_df() + ) # Get parameter mapping if parameter_mapping is None: @@ -140,7 +152,8 @@ def simulate_petab( petab_problem=petab_problem, simulation_conditions=simulation_conditions, scaled_parameters=scaled_parameters, - amici_model=amici_model) + amici_model=amici_model, + ) # Get edatas if edatas is None: @@ -148,7 +161,8 @@ def simulate_petab( edatas = create_edatas( amici_model=amici_model, petab_problem=petab_problem, - simulation_conditions=simulation_conditions) + simulation_conditions=simulation_conditions, + ) # Fill parameters in ExpDatas (in-place) fill_in_parameters( @@ -156,15 +170,20 @@ def simulate_petab( problem_parameters=problem_parameters, scaled_parameters=scaled_parameters, parameter_mapping=parameter_mapping, - amici_model=amici_model) + amici_model=amici_model, + ) # Simulate rdatas = amici.runAmiciSimulations( - amici_model, solver, edata_list=edatas, - num_threads=num_threads, failfast=failfast) + amici_model, + solver, + edata_list=edatas, + num_threads=num_threads, + failfast=failfast, + ) # Compute total llh - llh = sum(rdata['llh'] for rdata in rdatas) + llh = sum(rdata["llh"] for rdata in rdatas) # Compute total sllh sllh = None if solver.getSensitivityOrder() != amici.SensitivityOrder.none: @@ -207,12 +226,12 @@ def simulate_petab( def aggregate_sllh( - amici_model: AmiciModel, - rdatas: Sequence[amici.ReturnDataView], - parameter_mapping: Optional[ParameterMapping], - edatas: List[AmiciExpData], - petab_scale: bool = True, - petab_problem: petab.Problem = None, + amici_model: AmiciModel, + rdatas: Sequence[amici.ReturnDataView], + parameter_mapping: Optional[ParameterMapping], + edatas: List[AmiciExpData], + petab_scale: bool = True, + petab_problem: petab.Problem = None, ) -> Union[None, Dict[str, float]]: """ Aggregate likelihood gradient for all conditions, according to PEtab @@ -240,8 +259,7 @@ def aggregate_sllh( if petab_scale and petab_problem is None: raise ValueError( - 'Please provide the PEtab problem, when using ' - '`petab_scale=True`.' + "Please provide the PEtab problem, when using " "`petab_scale=True`." ) # Check for issues in all condition simulation results. @@ -252,14 +270,14 @@ def aggregate_sllh( # Condition simulation result does not provide SLLH. if rdata.sllh is None: raise ValueError( - 'The sensitivities of the likelihood for a condition were ' - 'not computed.' + "The sensitivities of the likelihood for a condition were " + "not computed." ) - for condition_parameter_mapping, edata, rdata in \ - zip(parameter_mapping, edatas, rdatas): - for sllh_parameter_index, condition_parameter_sllh in \ - enumerate(rdata.sllh): + for condition_parameter_mapping, edata, rdata in zip( + parameter_mapping, edatas, rdatas + ): + for sllh_parameter_index, condition_parameter_sllh in enumerate(rdata.sllh): # Get PEtab parameter ID # Use ExpData if it provides a parameter list, else default to # Model. @@ -268,10 +286,9 @@ def aggregate_sllh( else: model_parameter_index = amici_model.plist(sllh_parameter_index) model_parameter_id = model_parameter_ids[model_parameter_index] - petab_parameter_id = ( - condition_parameter_mapping - .map_sim_var[model_parameter_id] - ) + petab_parameter_id = condition_parameter_mapping.map_sim_var[ + model_parameter_id + ] # Initialize if petab_parameter_id not in accumulated_sllh: @@ -281,21 +298,18 @@ def aggregate_sllh( if petab_scale: # `ParameterMappingForCondition` objects provide the scale in # terms of `petab.C` constants already, not AMICI equivalents. - model_parameter_scale = ( - condition_parameter_mapping - .scale_map_sim_var[model_parameter_id] - ) - petab_parameter_scale = ( - petab_problem - .parameter_df - .loc[petab_parameter_id, PARAMETER_SCALE] - ) + model_parameter_scale = condition_parameter_mapping.scale_map_sim_var[ + model_parameter_id + ] + petab_parameter_scale = petab_problem.parameter_df.loc[ + petab_parameter_id, PARAMETER_SCALE + ] if model_parameter_scale != petab_parameter_scale: raise ValueError( - f'The scale of the parameter `{petab_parameter_id}` ' - 'differs between the AMICI model ' - f'({model_parameter_scale}) and the PEtab problem ' - f'({petab_parameter_scale}).' + f"The scale of the parameter `{petab_parameter_id}` " + "differs between the AMICI model " + f"({model_parameter_scale}) and the PEtab problem " + f"({petab_parameter_scale})." ) # Accumulate @@ -345,20 +359,18 @@ def rescale_sensitivity( scale[(LOG10, LOG)] = lambda s: scale[(LIN, LOG)](scale[(LOG10, LIN)](s)) if (old_scale, new_scale) not in scale: - raise NotImplementedError( - f"Old scale: {old_scale}. New scale: {new_scale}." - ) + raise NotImplementedError(f"Old scale: {old_scale}. New scale: {new_scale}.") return scale[(old_scale, new_scale)](sensitivity) def create_parameterized_edatas( - amici_model: AmiciModel, - petab_problem: petab.Problem, - problem_parameters: Dict[str, numbers.Number], - scaled_parameters: bool = False, - parameter_mapping: ParameterMapping = None, - simulation_conditions: Union[pd.DataFrame, Dict] = None, + amici_model: AmiciModel, + petab_problem: petab.Problem, + problem_parameters: Dict[str, numbers.Number], + scaled_parameters: bool = False, + parameter_mapping: ParameterMapping = None, + simulation_conditions: Union[pd.DataFrame, Dict] = None, ) -> List[amici.ExpData]: """Create list of :class:amici.ExpData objects with parameters filled in. @@ -389,8 +401,9 @@ def create_parameterized_edatas( # (preequilibrationConditionId, simulationConditionId) pairs. # Can be optimized by checking for identical condition vectors. if simulation_conditions is None: - simulation_conditions = \ + simulation_conditions = ( petab_problem.get_simulation_conditions_from_measurement_df() + ) # Get parameter mapping if parameter_mapping is None: @@ -398,13 +411,15 @@ def create_parameterized_edatas( petab_problem=petab_problem, simulation_conditions=simulation_conditions, scaled_parameters=scaled_parameters, - amici_model=amici_model) + amici_model=amici_model, + ) # Generate ExpData with all condition-specific information edatas = create_edatas( amici_model=amici_model, petab_problem=petab_problem, - simulation_conditions=simulation_conditions) + simulation_conditions=simulation_conditions, + ) # Fill parameters in ExpDatas (in-place) fill_in_parameters( @@ -412,17 +427,18 @@ def create_parameterized_edatas( problem_parameters=problem_parameters, scaled_parameters=scaled_parameters, parameter_mapping=parameter_mapping, - amici_model=amici_model) + amici_model=amici_model, + ) return edatas def create_parameter_mapping( - petab_problem: petab.Problem, - simulation_conditions: Union[pd.DataFrame, List[Dict]], - scaled_parameters: bool, - amici_model: AmiciModel, - **parameter_mapping_kwargs, + petab_problem: petab.Problem, + simulation_conditions: Union[pd.DataFrame, List[Dict]], + scaled_parameters: bool, + amici_model: AmiciModel, + **parameter_mapping_kwargs, ) -> ParameterMapping: """Generate AMICI specific parameter mapping. @@ -446,8 +462,9 @@ def create_parameter_mapping( List of the parameter mappings. """ if simulation_conditions is None: - simulation_conditions = \ + simulation_conditions = ( petab_problem.get_simulation_conditions_from_measurement_df() + ) if isinstance(simulation_conditions, list): simulation_conditions = pd.DataFrame(data=simulation_conditions) @@ -455,51 +472,51 @@ def create_parameter_mapping( # we need to do that here as well to prevent parameter mapping errors # (PEtab does currently not care about SBML LocalParameters) if petab_problem.sbml_document: - converter_config = libsbml.SBMLLocalParameterConverter() \ - .getDefaultProperties() + converter_config = libsbml.SBMLLocalParameterConverter().getDefaultProperties() petab_problem.sbml_document.convert(converter_config) else: - logger.debug("No petab_problem.sbml_document is set. Cannot convert " - "SBML LocalParameters. If the model contains " - "LocalParameters, parameter mapping will fail.") + logger.debug( + "No petab_problem.sbml_document is set. Cannot convert " + "SBML LocalParameters. If the model contains " + "LocalParameters, parameter mapping will fail." + ) default_parameter_mapping_kwargs = { "warn_unmapped": False, "scaled_parameters": scaled_parameters, - "allow_timepoint_specific_numeric_noise_parameters": - not petab.lint.observable_table_has_nontrivial_noise_formula( - petab_problem.observable_df), + "allow_timepoint_specific_numeric_noise_parameters": not petab.lint.observable_table_has_nontrivial_noise_formula( + petab_problem.observable_df + ), } if parameter_mapping_kwargs is None: parameter_mapping_kwargs = {} - prelim_parameter_mapping = \ - petab.get_optimization_to_simulation_parameter_mapping( - condition_df=petab_problem.condition_df, - measurement_df=petab_problem.measurement_df, - parameter_df=petab_problem.parameter_df, - observable_df=petab_problem.observable_df, - model=petab_problem.model, - **dict(default_parameter_mapping_kwargs, - **parameter_mapping_kwargs) - ) + prelim_parameter_mapping = petab.get_optimization_to_simulation_parameter_mapping( + condition_df=petab_problem.condition_df, + measurement_df=petab_problem.measurement_df, + parameter_df=petab_problem.parameter_df, + observable_df=petab_problem.observable_df, + model=petab_problem.model, + **dict(default_parameter_mapping_kwargs, **parameter_mapping_kwargs), + ) parameter_mapping = ParameterMapping() - for (_, condition), prelim_mapping_for_condition in \ - zip(simulation_conditions.iterrows(), prelim_parameter_mapping): + for (_, condition), prelim_mapping_for_condition in zip( + simulation_conditions.iterrows(), prelim_parameter_mapping + ): mapping_for_condition = create_parameter_mapping_for_condition( - prelim_mapping_for_condition, condition, petab_problem, - amici_model) + prelim_mapping_for_condition, condition, petab_problem, amici_model + ) parameter_mapping.append(mapping_for_condition) return parameter_mapping def create_parameter_mapping_for_condition( - parameter_mapping_for_condition: petab.ParMappingDictQuadruple, - condition: Union[pd.Series, Dict], - petab_problem: petab.Problem, - amici_model: AmiciModel + parameter_mapping_for_condition: petab.ParMappingDictQuadruple, + condition: Union[pd.Series, Dict], + petab_problem: petab.Problem, + amici_model: AmiciModel, ) -> ParameterMappingForCondition: """Generate AMICI specific parameter mapping for condition. @@ -518,20 +535,26 @@ def create_parameter_mapping_for_condition( preequilibration, fixed simulation, and variable simulation parameters, and then the respective scalings. """ - (condition_map_preeq, condition_map_sim, condition_scale_map_preeq, - condition_scale_map_sim) = parameter_mapping_for_condition + ( + condition_map_preeq, + condition_map_sim, + condition_scale_map_preeq, + condition_scale_map_sim, + ) = parameter_mapping_for_condition logger.debug(f"PEtab mapping: {parameter_mapping_for_condition}") - if len(condition_map_preeq) != len(condition_scale_map_preeq) \ - or len(condition_map_sim) != len(condition_scale_map_sim): - raise AssertionError("Number of parameters and number of parameter " - "scales do not match.") - if len(condition_map_preeq) \ - and len(condition_map_preeq) != len(condition_map_sim): + if len(condition_map_preeq) != len(condition_scale_map_preeq) or len( + condition_map_sim + ) != len(condition_scale_map_sim): + raise AssertionError( + "Number of parameters and number of parameter " "scales do not match." + ) + if len(condition_map_preeq) and len(condition_map_preeq) != len(condition_map_sim): logger.debug(f"Preequilibration parameter map: {condition_map_preeq}") logger.debug(f"Simulation parameter map: {condition_map_sim}") - raise AssertionError("Number of parameters for preequilbration " - "and simulation do not match.") + raise AssertionError( + "Number of parameters for preequilbration " "and simulation do not match." + ) ########################################################################## # initial states @@ -546,7 +569,8 @@ def create_parameter_mapping_for_condition( # resetting initial states. states_in_condition_table = [ - col for col in petab_problem.condition_df + col + for col in petab_problem.condition_df if element_is_state(petab_problem.sbml_model, col) ] if states_in_condition_table: @@ -560,33 +584,46 @@ def create_parameter_mapping_for_condition( condition_map_sim[PREEQ_INDICATOR_ID] = 0.0 condition_scale_map_sim[PREEQ_INDICATOR_ID] = LIN - def _set_initial_state(condition_id, element_id, init_par_id, - par_map, scale_map): + def _set_initial_state( + condition_id, element_id, init_par_id, par_map, scale_map + ): value = petab.to_float_if_float( - petab_problem.condition_df.loc[condition_id, element_id]) + petab_problem.condition_df.loc[condition_id, element_id] + ) if pd.isna(value): element = petab_problem.sbml_model.getElementBySId(element_id) type_code = element.getTypeCode() - initial_assignment = petab_problem.sbml_model\ - .getInitialAssignmentBySymbol(element_id) + initial_assignment = ( + petab_problem.sbml_model.getInitialAssignmentBySymbol(element_id) + ) if initial_assignment: initial_assignment = sp.sympify( libsbml.formulaToL3String(initial_assignment.getMath()), - locals=_clash + locals=_clash, ) if type_code == libsbml.SBML_SPECIES: - value = get_species_initial(element) \ - if initial_assignment is None else initial_assignment + value = ( + get_species_initial(element) + if initial_assignment is None + else initial_assignment + ) elif type_code == libsbml.SBML_PARAMETER: - value = element.getValue()\ - if initial_assignment is None else initial_assignment + value = ( + element.getValue() + if initial_assignment is None + else initial_assignment + ) elif type_code == libsbml.SBML_COMPARTMENT: - value = element.getSize()\ - if initial_assignment is None else initial_assignment + value = ( + element.getSize() + if initial_assignment is None + else initial_assignment + ) else: raise NotImplementedError( f"Don't know what how to handle {element_id} in " - "condition table.") + "condition table." + ) try: value = float(value) @@ -597,32 +634,39 @@ def _set_initial_state(condition_id, element_id, init_par_id, else: raise NotImplementedError( "Cannot handle non-trivial initial state " - f"expression for {element_id}: {value}") + f"expression for {element_id}: {value}" + ) # this should be a parameter ID value = str(value) - logger.debug(f'The species {element_id} has no initial value ' - f'defined for the condition {condition_id} in ' - 'the PEtab conditions table. The initial value is ' - f'now set to {value}, which is the initial value ' - 'defined in the SBML model.') + logger.debug( + f"The species {element_id} has no initial value " + f"defined for the condition {condition_id} in " + "the PEtab conditions table. The initial value is " + f"now set to {value}, which is the initial value " + "defined in the SBML model." + ) par_map[init_par_id] = value if isinstance(value, float): # numeric initial state scale_map[init_par_id] = petab.LIN else: # parametric initial state - scale_map[init_par_id] = \ - petab_problem.parameter_df[PARAMETER_SCALE]\ - .get(value, petab.LIN) + scale_map[init_par_id] = petab_problem.parameter_df[ + PARAMETER_SCALE + ].get(value, petab.LIN) for element_id in states_in_condition_table: # for preequilibration - init_par_id = f'initial_{element_id}_preeq' + init_par_id = f"initial_{element_id}_preeq" if condition.get(PREEQUILIBRATION_CONDITION_ID): condition_id = condition[PREEQUILIBRATION_CONDITION_ID] _set_initial_state( - condition_id, element_id, init_par_id, condition_map_preeq, - condition_scale_map_preeq) + condition_id, + element_id, + init_par_id, + condition_map_preeq, + condition_scale_map_preeq, + ) else: # need to set dummy value for preeq parameter anyways, as it # is expected below (set to 0, not nan, because will be @@ -632,10 +676,14 @@ def _set_initial_state(condition_id, element_id, init_par_id, # for simulation condition_id = condition[SIMULATION_CONDITION_ID] - init_par_id = f'initial_{element_id}_sim' + init_par_id = f"initial_{element_id}_sim" _set_initial_state( - condition_id, element_id, init_par_id, condition_map_sim, - condition_scale_map_sim) + condition_id, + element_id, + init_par_id, + condition_map_sim, + condition_scale_map_sim, + ) ########################################################################## # separate fixed and variable AMICI parameters, because we may have @@ -646,31 +694,34 @@ def _set_initial_state(condition_id, element_id, init_par_id, variable_par_ids = amici_model.getParameterIds() fixed_par_ids = amici_model.getFixedParameterIds() - condition_map_preeq_var, condition_map_preeq_fix = \ - _subset_dict(condition_map_preeq, variable_par_ids, fixed_par_ids) + condition_map_preeq_var, condition_map_preeq_fix = _subset_dict( + condition_map_preeq, variable_par_ids, fixed_par_ids + ) - condition_scale_map_preeq_var, condition_scale_map_preeq_fix = \ - _subset_dict(condition_scale_map_preeq, variable_par_ids, fixed_par_ids) + condition_scale_map_preeq_var, condition_scale_map_preeq_fix = _subset_dict( + condition_scale_map_preeq, variable_par_ids, fixed_par_ids + ) - condition_map_sim_var, condition_map_sim_fix = \ - _subset_dict(condition_map_sim, variable_par_ids, fixed_par_ids) + condition_map_sim_var, condition_map_sim_fix = _subset_dict( + condition_map_sim, variable_par_ids, fixed_par_ids + ) - condition_scale_map_sim_var, condition_scale_map_sim_fix = \ - _subset_dict(condition_scale_map_sim, variable_par_ids, fixed_par_ids) + condition_scale_map_sim_var, condition_scale_map_sim_fix = _subset_dict( + condition_scale_map_sim, variable_par_ids, fixed_par_ids + ) - logger.debug("Fixed parameters preequilibration: " - f"{condition_map_preeq_fix}") - logger.debug("Fixed parameters simulation: " - f"{condition_map_sim_fix}") - logger.debug("Variable parameters preequilibration: " - f"{condition_map_preeq_var}") - logger.debug("Variable parameters simulation: " - f"{condition_map_sim_var}") + logger.debug("Fixed parameters preequilibration: " f"{condition_map_preeq_fix}") + logger.debug("Fixed parameters simulation: " f"{condition_map_sim_fix}") + logger.debug("Variable parameters preequilibration: " f"{condition_map_preeq_var}") + logger.debug("Variable parameters simulation: " f"{condition_map_sim_var}") petab.merge_preeq_and_sim_pars_condition( - condition_map_preeq_var, condition_map_sim_var, - condition_scale_map_preeq_var, condition_scale_map_sim_var, - condition) + condition_map_preeq_var, + condition_map_sim_var, + condition_scale_map_preeq_var, + condition_scale_map_sim_var, + condition, + ) logger.debug(f"Merged: {condition_map_sim_var}") parameter_mapping_for_condition = ParameterMappingForCondition( @@ -679,16 +730,16 @@ def _set_initial_state(condition_id, element_id, init_par_id, map_sim_var=condition_map_sim_var, scale_map_preeq_fix=condition_scale_map_preeq_fix, scale_map_sim_fix=condition_scale_map_sim_fix, - scale_map_sim_var=condition_scale_map_sim_var + scale_map_sim_var=condition_scale_map_sim_var, ) return parameter_mapping_for_condition def create_edatas( - amici_model: AmiciModel, - petab_problem: petab.Problem, - simulation_conditions: Union[pd.DataFrame, Dict] = None, + amici_model: AmiciModel, + petab_problem: petab.Problem, + simulation_conditions: Union[pd.DataFrame, Dict] = None, ) -> List[amici.ExpData]: """Create list of :class:`amici.amici.ExpData` objects for PEtab problem. @@ -705,19 +756,21 @@ def create_edatas( with filled in timepoints and data. """ if simulation_conditions is None: - simulation_conditions = \ + simulation_conditions = ( petab_problem.get_simulation_conditions_from_measurement_df() + ) observable_ids = amici_model.getObservableIds() - measurement_dfs = dict(list( - petab_problem.measurement_df.groupby( - [petab.SIMULATION_CONDITION_ID, - petab.PREEQUILIBRATION_CONDITION_ID] - if petab.PREEQUILIBRATION_CONDITION_ID in simulation_conditions - else petab.SIMULATION_CONDITION_ID + measurement_dfs = dict( + list( + petab_problem.measurement_df.groupby( + [petab.SIMULATION_CONDITION_ID, petab.PREEQUILIBRATION_CONDITION_ID] + if petab.PREEQUILIBRATION_CONDITION_ID in simulation_conditions + else petab.SIMULATION_CONDITION_ID + ) ) - )) + ) edatas = [] for _, condition in simulation_conditions.iterrows(): @@ -725,7 +778,7 @@ def create_edatas( if petab.PREEQUILIBRATION_CONDITION_ID in condition: measurement_index = ( condition.get(petab.SIMULATION_CONDITION_ID), - condition.get(petab.PREEQUILIBRATION_CONDITION_ID) + condition.get(petab.PREEQUILIBRATION_CONDITION_ID), ) else: measurement_index = condition.get(petab.SIMULATION_CONDITION_ID) @@ -742,11 +795,11 @@ def create_edatas( def create_edata_for_condition( - condition: Union[Dict, pd.Series], - measurement_df: pd.DataFrame, - amici_model: AmiciModel, - petab_problem: petab.Problem, - observable_ids: List[str], + condition: Union[Dict, pd.Series], + measurement_df: pd.DataFrame, + amici_model: AmiciModel, + petab_problem: petab.Problem, + observable_ids: List[str], ) -> amici.ExpData: """Get :class:`amici.amici.ExpData` for the given PEtab condition. @@ -768,8 +821,10 @@ def create_edata_for_condition( ExpData instance. """ if amici_model.nytrue != len(observable_ids): - raise AssertionError("Number of AMICI model observables does not " - "match number of PEtab observables.") + raise AssertionError( + "Number of AMICI model observables does not " + "match number of PEtab observables." + ) # create an ExpData object edata = amici.ExpData(amici_model) @@ -779,43 +834,49 @@ def create_edata_for_condition( ########################################################################## # enable initial parameters reinitialization states_in_condition_table = [ - col for col in petab_problem.condition_df - if not pd.isna(petab_problem.condition_df.loc[ - condition[SIMULATION_CONDITION_ID], col]) - and element_is_state(petab_problem.sbml_model, col) + col + for col in petab_problem.condition_df + if not pd.isna( + petab_problem.condition_df.loc[condition[SIMULATION_CONDITION_ID], col] + ) + and element_is_state(petab_problem.sbml_model, col) ] - if condition.get(PREEQUILIBRATION_CONDITION_ID) \ - and states_in_condition_table: + if condition.get(PREEQUILIBRATION_CONDITION_ID) and states_in_condition_table: state_ids = amici_model.getStateIds() - state_idx_reinitalization = [state_ids.index(s) - for s in states_in_condition_table] + state_idx_reinitalization = [ + state_ids.index(s) for s in states_in_condition_table + ] edata.reinitialization_state_idxs_sim = state_idx_reinitalization - logger.debug("Enabling state reinitialization for condition " - f"{condition.get(PREEQUILIBRATION_CONDITION_ID, '')} - " - f"{condition.get(SIMULATION_CONDITION_ID)} " - f"{states_in_condition_table}") + logger.debug( + "Enabling state reinitialization for condition " + f"{condition.get(PREEQUILIBRATION_CONDITION_ID, '')} - " + f"{condition.get(SIMULATION_CONDITION_ID)} " + f"{states_in_condition_table}" + ) ########################################################################## # timepoints # find replicate numbers of time points - timepoints_w_reps = _get_timepoints_with_replicates( - df_for_condition=measurement_df) + timepoints_w_reps = _get_timepoints_with_replicates(df_for_condition=measurement_df) edata.setTimepoints(timepoints_w_reps) ########################################################################## # measurements and sigmas y, sigma_y = _get_measurements_and_sigmas( - df_for_condition=measurement_df, timepoints_w_reps=timepoints_w_reps, - observable_ids=observable_ids) + df_for_condition=measurement_df, + timepoints_w_reps=timepoints_w_reps, + observable_ids=observable_ids, + ) edata.setObservedData(y.flatten()) edata.setObservedDataStdDev(sigma_y.flatten()) return edata -def _subset_dict(full: Dict[Any, Any], - *args: Collection[Any]) -> Iterator[Dict[Any, Any]]: +def _subset_dict( + full: Dict[Any, Any], *args: Collection[Any] +) -> Iterator[Dict[Any, Any]]: """Get subset of dictionary based on provided keys :param full: @@ -831,7 +892,8 @@ def _subset_dict(full: Dict[Any, Any], def _get_timepoints_with_replicates( - df_for_condition: pd.DataFrame) -> List[numbers.Number]: + df_for_condition: pd.DataFrame, +) -> List[numbers.Number]: """ Get list of timepoints including replicate measurements @@ -849,12 +911,9 @@ def _get_timepoints_with_replicates( timepoints_w_reps = [] for time in timepoints: # subselect for time - df_for_time = df_for_condition[ - df_for_condition.time.astype(float) == time - ] + df_for_time = df_for_condition[df_for_condition.time.astype(float) == time] # rep number is maximum over rep numbers for observables - n_reps = max(df_for_time.groupby( - [OBSERVABLE_ID, TIME]).size()) + n_reps = max(df_for_time.groupby([OBSERVABLE_ID, TIME]).size()) # append time point n_rep times timepoints_w_reps.extend([time] * n_reps) @@ -862,10 +921,10 @@ def _get_timepoints_with_replicates( def _get_measurements_and_sigmas( - df_for_condition: pd.DataFrame, - timepoints_w_reps: Sequence[numbers.Number], - observable_ids: Sequence[str], - ) -> Tuple[np.array, np.array]: + df_for_condition: pd.DataFrame, + timepoints_w_reps: Sequence[numbers.Number], + observable_ids: Sequence[str], +) -> Tuple[np.array, np.array]: """ Get measurements and sigmas @@ -885,8 +944,7 @@ def _get_measurements_and_sigmas( arrays for measurement and sigmas """ # prepare measurement matrix - y = np.full(shape=(len(timepoints_w_reps), len(observable_ids)), - fill_value=np.nan) + y = np.full(shape=(len(timepoints_w_reps), len(observable_ids)), fill_value=np.nan) # prepare sigma matrix sigma_y = y.copy() @@ -912,19 +970,19 @@ def _get_measurements_and_sigmas( time_ix_for_obs_ix[observable_ix] = time_ix_0 # fill observable and possibly noise parameter - y[time_ix_for_obs_ix[observable_ix], - observable_ix] = measurement[MEASUREMENT] - if isinstance(measurement.get(NOISE_PARAMETERS, None), - numbers.Number): - sigma_y[time_ix_for_obs_ix[observable_ix], - observable_ix] = measurement[NOISE_PARAMETERS] + y[time_ix_for_obs_ix[observable_ix], observable_ix] = measurement[ + MEASUREMENT + ] + if isinstance(measurement.get(NOISE_PARAMETERS, None), numbers.Number): + sigma_y[time_ix_for_obs_ix[observable_ix], observable_ix] = measurement[ + NOISE_PARAMETERS + ] return y, sigma_y def rdatas_to_measurement_df( - rdatas: Sequence[amici.ReturnData], - model: AmiciModel, - measurement_df: pd.DataFrame) -> pd.DataFrame: + rdatas: Sequence[amici.ReturnData], model: AmiciModel, measurement_df: pd.DataFrame +) -> pd.DataFrame: """ Create a measurement dataframe in the PEtab format from the passed ``rdatas`` and own information. @@ -942,8 +1000,7 @@ def rdatas_to_measurement_df( :return: A dataframe built from the rdatas in the format of ``measurement_df``. """ - simulation_conditions = petab.get_simulation_conditions( - measurement_df) + simulation_conditions = petab.get_simulation_conditions(measurement_df) observable_ids = model.getObservableIds() rows = [] @@ -955,8 +1012,7 @@ def rdatas_to_measurement_df( t = list(rdata.ts) # extract rows for condition - cur_measurement_df = petab.get_rows_for_condition( - measurement_df, condition) + cur_measurement_df = petab.get_rows_for_condition(measurement_df, condition) # iterate over entries for the given condition # note: this way we only generate a dataframe entry for every @@ -981,17 +1037,17 @@ def rdatas_to_measurement_df( def rdatas_to_simulation_df( - rdatas: Sequence[amici.ReturnData], - model: AmiciModel, - measurement_df: pd.DataFrame) -> pd.DataFrame: + rdatas: Sequence[amici.ReturnData], model: AmiciModel, measurement_df: pd.DataFrame +) -> pd.DataFrame: """Create a PEtab simulation dataframe from :class:`amici.amici.ReturnData` s. See :func:`rdatas_to_measurement_df` for details, only that model outputs will appear in column ``simulation`` instead of ``measurement``.""" - df = rdatas_to_measurement_df(rdatas=rdatas, model=model, - measurement_df=measurement_df) + df = rdatas_to_measurement_df( + rdatas=rdatas, model=model, measurement_df=measurement_df + ) return df.rename(columns={MEASUREMENT: SIMULATION}) @@ -1023,10 +1079,12 @@ def _default_scaled_parameters( The scaled parameter vector. """ if problem_parameters is None: - problem_parameters = dict(zip( - petab_problem.x_ids, - petab_problem.x_nominal_scaled, - )) + problem_parameters = dict( + zip( + petab_problem.x_ids, + petab_problem.x_nominal_scaled, + ) + ) elif not scaled_parameters: problem_parameters = petab_problem.scale_parameters(problem_parameters) return problem_parameters diff --git a/python/sdist/amici/petab_simulate.py b/python/sdist/amici/petab_simulate.py index 09403153b1..d5f5427c9d 100644 --- a/python/sdist/amici/petab_simulate.py +++ b/python/sdist/amici/petab_simulate.py @@ -19,21 +19,20 @@ from amici import SensitivityMethod_none from amici import AmiciModel from amici.petab_import import import_petab_problem -from amici.petab_objective import (simulate_petab, - rdatas_to_measurement_df, - RDATAS) +from amici.petab_objective import simulate_petab, rdatas_to_measurement_df, RDATAS import petab -AMICI_MODEL = 'amici_model' -AMICI_SOLVER = 'solver' -MODEL_NAME = 'model_name' -MODEL_OUTPUT_DIR = 'model_output_dir' +AMICI_MODEL = "amici_model" +AMICI_SOLVER = "solver" +MODEL_NAME = "model_name" +MODEL_OUTPUT_DIR = "model_output_dir" -PETAB_PROBLEM = 'petab_problem' +PETAB_PROBLEM = "petab_problem" class PetabSimulator(petab.simulate.Simulator): """Implementation of the PEtab `Simulator` class that uses AMICI.""" + def __init__(self, *args, amici_model: AmiciModel = None, **kwargs): super().__init__(*args, **kwargs) self.amici_model = amici_model @@ -52,11 +51,13 @@ def simulate_without_noise(self, **kwargs) -> pd.DataFrame: in the Simulator constructor (including the PEtab problem). """ if AMICI_MODEL in {*kwargs, *dir(self)} and ( - any(k in kwargs for k in - inspect.signature(import_petab_problem).parameters)): - print('Arguments related to the PEtab import are unused if ' - f'`{AMICI_MODEL}` is specified, or the ' - '`PetabSimulator.simulate()` method was previously called.') + any(k in kwargs for k in inspect.signature(import_petab_problem).parameters) + ): + print( + "Arguments related to the PEtab import are unused if " + f"`{AMICI_MODEL}` is specified, or the " + "`PetabSimulator.simulate()` method was previously called." + ) kwargs[PETAB_PROBLEM] = self.petab_problem @@ -80,13 +81,12 @@ def simulate_without_noise(self, **kwargs) -> pd.DataFrame: if AMICI_SOLVER not in kwargs: kwargs[AMICI_SOLVER] = self.amici_model.getSolver() - kwargs[AMICI_SOLVER].setSensitivityMethod( - SensitivityMethod_none) + kwargs[AMICI_SOLVER].setSensitivityMethod(SensitivityMethod_none) result = _subset_call(simulate_petab, kwargs) - return rdatas_to_measurement_df(result[RDATAS], - self.amici_model, - self.petab_problem.measurement_df) + return rdatas_to_measurement_df( + result[RDATAS], self.amici_model, self.petab_problem.measurement_df + ) def _subset_call(method: Callable, kwargs: dict): @@ -104,7 +104,5 @@ def _subset_call(method: Callable, kwargs: dict): ``kwargs``. """ method_args = inspect.signature(method).parameters - subset_kwargs = {k: v - for k, v in kwargs.items() - if k in method_args} + subset_kwargs = {k: v for k, v in kwargs.items() if k in method_args} return method(**subset_kwargs) diff --git a/python/sdist/amici/plotting.py b/python/sdist/amici/plotting.py index d21bea6a99..da718c1ec7 100644 --- a/python/sdist/amici/plotting.py +++ b/python/sdist/amici/plotting.py @@ -14,11 +14,11 @@ def plot_state_trajectories( - rdata: ReturnDataView, - state_indices: Optional[Iterable[int]] = None, - ax: Optional[Axes] = None, - model: Model = None, - prefer_names: bool = True, + rdata: ReturnDataView, + state_indices: Optional[Iterable[int]] = None, + ax: Optional[Axes] = None, + model: Model = None, + prefer_names: bool = True, ) -> None: """ Plot state trajectories @@ -42,27 +42,27 @@ def plot_state_trajectories( if not ax: fig, ax = plt.subplots() if not state_indices: - state_indices = range(rdata['x'].shape[1]) + state_indices = range(rdata["x"].shape[1]) for ix in state_indices: if model is None: - label = f'$x_{{{ix}}}$' + label = f"$x_{{{ix}}}$" elif prefer_names and model.getStateNames()[ix]: label = model.getStateNames()[ix] else: label = model.getStateIds()[ix] - ax.plot(rdata['t'], rdata['x'][:, ix], label=label) - ax.set_xlabel('$t$') - ax.set_ylabel('$x(t)$') + ax.plot(rdata["t"], rdata["x"][:, ix], label=label) + ax.set_xlabel("$t$") + ax.set_ylabel("$x(t)$") ax.legend() - ax.set_title('State trajectories') + ax.set_title("State trajectories") def plot_observable_trajectories( - rdata: ReturnDataView, - observable_indices: Optional[Iterable[int]] = None, - ax: Optional[Axes] = None, - model: Model = None, - prefer_names: bool = True, + rdata: ReturnDataView, + observable_indices: Optional[Iterable[int]] = None, + ax: Optional[Axes] = None, + model: Model = None, + prefer_names: bool = True, ) -> None: """ Plot observable trajectories @@ -86,19 +86,19 @@ def plot_observable_trajectories( if not ax: fig, ax = plt.subplots() if not observable_indices: - observable_indices = range(rdata['y'].shape[1]) + observable_indices = range(rdata["y"].shape[1]) for iy in observable_indices: if model is None: - label = f'$y_{{{iy}}}$' + label = f"$y_{{{iy}}}$" elif prefer_names and model.getObservableNames()[iy]: label = model.getObservableNames()[iy] else: label = model.getObservableIds()[iy] - ax.plot(rdata['t'], rdata['y'][:, iy], label=label) - ax.set_xlabel('$t$') - ax.set_ylabel('$y(t)$') + ax.plot(rdata["t"], rdata["y"][:, iy], label=label) + ax.set_xlabel("$t$") + ax.set_ylabel("$y(t)$") ax.legend() - ax.set_title('Observable trajectories') + ax.set_title("Observable trajectories") def plot_jacobian(rdata: ReturnDataView): @@ -111,6 +111,7 @@ def plot_jacobian(rdata: ReturnDataView): sns.heatmap(df, center=0.0) plt.title("Jacobian") + # backwards compatibility plotStateTrajectories = plot_state_trajectories plotObservableTrajectories = plot_observable_trajectories diff --git a/python/sdist/amici/pysb_import.py b/python/sdist/amici/pysb_import.py index 4f92106950..6c0e80cfdb 100644 --- a/python/sdist/amici/pysb_import.py +++ b/python/sdist/amici/pysb_import.py @@ -10,8 +10,7 @@ import os import sys from pathlib import Path -from typing import (Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, - Union) +from typing import Any, Callable, Dict, Iterable, List, Optional, Set, Tuple, Union import numpy as np import pysb @@ -19,15 +18,26 @@ import pysb.pattern import sympy as sp -from .import_utils import (_get_str_symbol_identifiers, - _parse_special_functions, - generate_measurement_symbol, - noise_distribution_to_cost_function, - noise_distribution_to_observable_transformation) +from .import_utils import ( + _get_str_symbol_identifiers, + _parse_special_functions, + generate_measurement_symbol, + noise_distribution_to_cost_function, + noise_distribution_to_observable_transformation, +) from .logging import get_logger, log_execution_time, set_log_level -from .de_export import (Constant, Expression, LogLikelihoodY, DEExporter, - DEModel, Observable, Parameter, SigmaY, DifferentialState, - _default_simplify) +from .de_export import ( + Constant, + Expression, + LogLikelihoodY, + DEExporter, + DEModel, + Observable, + Parameter, + SigmaY, + DifferentialState, + _default_simplify, +) CL_Prototype = Dict[str, Dict[str, Any]] ConservationLaw = Dict[str, Union[Dict, str, sp.Basic]] @@ -36,23 +46,23 @@ def pysb2amici( - model: pysb.Model, - output_dir: Optional[Union[str, Path]] = None, - observables: List[str] = None, - constant_parameters: List[str] = None, - sigmas: Dict[str, str] = None, - noise_distributions: Optional[Dict[str, Union[str, Callable]]] = None, - verbose: Union[int, bool] = False, - assume_pow_positivity: bool = False, - compiler: str = None, - compute_conservation_laws: bool = True, - compile: bool = True, - simplify: Callable = _default_simplify, - # Do not enable by default without testing. - # See https://github.com/AMICI-dev/AMICI/pull/1672 - cache_simplify: bool = False, - generate_sensitivity_code: bool = True, - model_name: Optional[str] = None, + model: pysb.Model, + output_dir: Optional[Union[str, Path]] = None, + observables: List[str] = None, + constant_parameters: List[str] = None, + sigmas: Dict[str, str] = None, + noise_distributions: Optional[Dict[str, Union[str, Callable]]] = None, + verbose: Union[int, bool] = False, + assume_pow_positivity: bool = False, + compiler: str = None, + compute_conservation_laws: bool = True, + compile: bool = True, + simplify: Callable = _default_simplify, + # Do not enable by default without testing. + # See https://github.com/AMICI-dev/AMICI/pull/1672 + cache_simplify: bool = False, + generate_sensitivity_code: bool = True, + model_name: Optional[str] = None, ): r""" Generate AMICI C++ files for the provided model. @@ -143,8 +153,10 @@ def pysb2amici( set_log_level(logger, verbose) ode_model = ode_model_from_pysb_importer( - model, constant_parameters=constant_parameters, - observables=observables, sigmas=sigmas, + model, + constant_parameters=constant_parameters, + observables=observables, + sigmas=sigmas, noise_distributions=noise_distributions, compute_conservation_laws=compute_conservation_laws, simplify=simplify, @@ -158,7 +170,7 @@ def pysb2amici( verbose=verbose, assume_pow_positivity=assume_pow_positivity, compiler=compiler, - generate_sensitivity_code=generate_sensitivity_code + generate_sensitivity_code=generate_sensitivity_code, ) exporter.generate_model_code() @@ -166,19 +178,19 @@ def pysb2amici( exporter.compile_model() -@log_execution_time('creating ODE model', logger) +@log_execution_time("creating ODE model", logger) def ode_model_from_pysb_importer( - model: pysb.Model, - constant_parameters: List[str] = None, - observables: List[str] = None, - sigmas: Dict[str, str] = None, - noise_distributions: Optional[Dict[str, Union[str, Callable]]] = None, - compute_conservation_laws: bool = True, - simplify: Callable = sp.powsimp, - # Do not enable by default without testing. - # See https://github.com/AMICI-dev/AMICI/pull/1672 - cache_simplify: bool = False, - verbose: Union[int, bool] = False, + model: pysb.Model, + constant_parameters: List[str] = None, + observables: List[str] = None, + sigmas: Dict[str, str] = None, + noise_distributions: Optional[Dict[str, Union[str, Callable]]] = None, + compute_conservation_laws: bool = True, + simplify: Callable = sp.powsimp, + # Do not enable by default without testing. + # See https://github.com/AMICI-dev/AMICI/pull/1672 + cache_simplify: bool = False, + verbose: Union[int, bool] = False, ) -> DEModel: """ Creates an :class:`amici.DEModel` instance from a :class:`pysb.Model` @@ -243,12 +255,10 @@ def ode_model_from_pysb_importer( _process_pysb_parameters(model, ode, constant_parameters) if compute_conservation_laws: _process_pysb_conservation_laws(model, ode) - _process_pysb_observables(model, ode, observables, sigmas, - noise_distributions) - _process_pysb_expressions(model, ode, observables, sigmas, - noise_distributions) + _process_pysb_observables(model, ode, observables, sigmas, noise_distributions) + _process_pysb_expressions(model, ode, observables, sigmas, noise_distributions) ode._has_quadratic_nllh = not noise_distributions or all( - noise_distr in ['normal', 'lin-normal', 'log-normal', 'log10-normal'] + noise_distr in ["normal", "lin-normal", "log-normal", "log10-normal"] for noise_distr in noise_distributions.values() ) @@ -259,11 +269,10 @@ def ode_model_from_pysb_importer( return ode -@log_execution_time('processing PySB stoich. matrix', logger) -def _process_stoichiometric_matrix(pysb_model: pysb.Model, - ode_model: DEModel, - constant_parameters: List[str]) -> None: - +@log_execution_time("processing PySB stoich. matrix", logger) +def _process_stoichiometric_matrix( + pysb_model: pysb.Model, ode_model: DEModel, constant_parameters: List[str] +) -> None: """ Exploits the PySB stoichiometric matrix to generate xdot derivatives @@ -277,10 +286,10 @@ def _process_stoichiometric_matrix(pysb_model: pysb.Model, list of constant parameters """ - x = ode_model.sym('x') - w = list(ode_model.sym('w')) - p = list(ode_model.sym('p')) - x_rdata = list(ode_model.sym('x_rdata')) + x = ode_model.sym("x") + w = list(ode_model.sym("w")) + p = list(ode_model.sym("p")) + x_rdata = list(ode_model.sym("x_rdata")) n_x = len(x) n_w = len(w) @@ -305,7 +314,7 @@ def get_cached_index(symbol, sarray, index_cache): return idx for ir, rxn in enumerate(pysb_model.reactions): - for ix in np.unique(rxn['reactants']): + for ix in np.unique(rxn["reactants"]): idx = solver_index.get(ix, None) if idx is not None: # species @@ -315,12 +324,12 @@ def get_cached_index(symbol, sarray, index_cache): idx = get_cached_index(x_rdata[ix], w, wx_idx) values = dflux_dw_dict - values[(ir, idx)] = sp.diff(rxn['rate'], x_rdata[ix]) + values[(ir, idx)] = sp.diff(rxn["rate"], x_rdata[ix]) # typically <= 3 free symbols in rate, we already account for # species above so we only need to account for propensity, which # can only be a parameter or expression - for fs in rxn['rate'].free_symbols: + for fs in rxn["rate"].free_symbols: # dw if isinstance(fs, pysb.Expression): var = w @@ -337,7 +346,7 @@ def get_cached_index(symbol, sarray, index_cache): continue idx = get_cached_index(fs, var, idx_cache) - values[(ir, idx)] = sp.diff(rxn['rate'], fs) + values[(ir, idx)] = sp.diff(rxn["rate"], fs) dflux_dx = sp.ImmutableSparseMatrix(n_r, n_x, dflux_dx_dict) dflux_dw = sp.ImmutableSparseMatrix(n_r, n_w, dflux_dw_dict) @@ -345,20 +354,20 @@ def get_cached_index(symbol, sarray, index_cache): # use dok format to convert numeric csc to sparse symbolic S = sp.ImmutableSparseMatrix( - n_x, n_r, # don't use shape here as we are eliminating rows + n_x, + n_r, # don't use shape here as we are eliminating rows pysb_model.stoichiometry_matrix[ - np.asarray(list(solver_index.keys())),: - ].todok() + np.asarray(list(solver_index.keys())), : + ].todok(), ) # don't use `.dot` since it's awfully slow - ode_model._eqs['dxdotdx_explicit'] = S*dflux_dx - ode_model._eqs['dxdotdw'] = S*dflux_dw - ode_model._eqs['dxdotdp_explicit'] = S*dflux_dp + ode_model._eqs["dxdotdx_explicit"] = S * dflux_dx + ode_model._eqs["dxdotdw"] = S * dflux_dw + ode_model._eqs["dxdotdp_explicit"] = S * dflux_dp -@log_execution_time('processing PySB species', logger) -def _process_pysb_species(pysb_model: pysb.Model, - ode_model: DEModel) -> None: +@log_execution_time("processing PySB species", logger) +def _process_pysb_species(pysb_model: pysb.Model, ode_model: DEModel) -> None: """ Converts pysb Species into States and adds them to the DEModel instance @@ -371,10 +380,9 @@ def _process_pysb_species(pysb_model: pysb.Model, xdot = sp.Matrix(pysb_model.odes) for ix, specie in enumerate(pysb_model.species): - init = sp.sympify('0.0') + init = sp.sympify("0.0") for ic in pysb_model.odes.model.initials: - if pysb.pattern.match_complex_pattern( - ic.pattern, specie, exact=True): + if pysb.pattern.match_complex_pattern(ic.pattern, specie, exact=True): # we don't want to allow expressions in initial conditions if ic.value in pysb_model.expressions: init = pysb_model.expressions[ic.value.name].expand_expr() @@ -382,20 +390,15 @@ def _process_pysb_species(pysb_model: pysb.Model, init = ic.value ode_model.add_component( - DifferentialState( - sp.Symbol(f'__s{ix}'), - f'{specie}', - init, - xdot[ix] - ) + DifferentialState(sp.Symbol(f"__s{ix}"), f"{specie}", init, xdot[ix]) ) - logger.debug(f'Finished Processing PySB species ') + logger.debug(f"Finished Processing PySB species ") -@log_execution_time('processing PySB parameters', logger) -def _process_pysb_parameters(pysb_model: pysb.Model, - ode_model: DEModel, - constant_parameters: List[str]) -> None: +@log_execution_time("processing PySB parameters", logger) +def _process_pysb_parameters( + pysb_model: pysb.Model, ode_model: DEModel, constant_parameters: List[str] +) -> None: """ Converts pysb parameters into Parameters or Constants and adds them to the DEModel instance @@ -415,18 +418,16 @@ def _process_pysb_parameters(pysb_model: pysb.Model, else: comp = Parameter - ode_model.add_component( - comp(par, f'{par.name}', par.value) - ) + ode_model.add_component(comp(par, f"{par.name}", par.value)) -@log_execution_time('processing PySB expressions', logger) +@log_execution_time("processing PySB expressions", logger) def _process_pysb_expressions( - pysb_model: pysb.Model, - ode_model: DEModel, - observables: List[str], - sigmas: Dict[str, str], - noise_distributions: Optional[Dict[str, Union[str, Callable]]] = None, + pysb_model: pysb.Model, + ode_model: DEModel, + observables: List[str], + sigmas: Dict[str, str], + noise_distributions: Optional[Dict[str, Union[str, Callable]]] = None, ) -> None: r""" Converts pysb expressions/observables into Observables (with @@ -459,20 +460,27 @@ def _process_pysb_expressions( # we use _constant and _dynamic functions to get access to derived # expressions that are otherwise only accessible as private attribute - for expr in pysb_model.expressions_constant(include_derived=True)\ - | pysb_model.expressions_dynamic(include_derived=True): + for expr in pysb_model.expressions_constant( + include_derived=True + ) | pysb_model.expressions_dynamic(include_derived=True): if any( - isinstance(symbol, pysb.Tag) - for symbol in expr.expand_expr().free_symbols + isinstance(symbol, pysb.Tag) for symbol in expr.expand_expr().free_symbols ): # we only need explicit instantiations of expressions with tags, # which are defined in the derived expressions. The abstract # expressions are not needed and lead to compilation errors so # we skip them. continue - _add_expression(expr, expr.name, expr.expr, - pysb_model, ode_model, observables, sigmas, - noise_distributions) + _add_expression( + expr, + expr.name, + expr.expr, + pysb_model, + ode_model, + observables, + sigmas, + noise_distributions, + ) def _add_expression( @@ -513,46 +521,37 @@ def _add_expression( :param ode_model: see :py:func:`_process_pysb_expressions` """ - ode_model.add_component( - Expression(sym, name, _parse_special_functions(expr)) - ) + ode_model.add_component(Expression(sym, name, _parse_special_functions(expr))) if name in observables: - noise_dist = noise_distributions.get(name, 'normal') \ - if noise_distributions else 'normal' + noise_dist = ( + noise_distributions.get(name, "normal") if noise_distributions else "normal" + ) - y = sp.Symbol(f'{name}') + y = sp.Symbol(f"{name}") trafo = noise_distribution_to_observable_transformation(noise_dist) obs = Observable(y, name, sym, transformation=trafo) ode_model.add_component(obs) - sigma_name, sigma_value = _get_sigma_name_and_value( - pysb_model, name, sigmas - ) + sigma_name, sigma_value = _get_sigma_name_and_value(pysb_model, name, sigmas) sigma = sp.Symbol(sigma_name) - ode_model.add_component(SigmaY(sigma, f'{sigma_name}', sigma_value)) - + ode_model.add_component(SigmaY(sigma, f"{sigma_name}", sigma_value)) cost_fun_str = noise_distribution_to_cost_function(noise_dist)(name) my = generate_measurement_symbol(obs.get_id()) - cost_fun_expr = sp.sympify(cost_fun_str, - locals=dict(zip( - _get_str_symbol_identifiers(name), - (y, my, sigma)))) + cost_fun_expr = sp.sympify( + cost_fun_str, + locals=dict(zip(_get_str_symbol_identifiers(name), (y, my, sigma))), + ) ode_model.add_component( - LogLikelihoodY( - sp.Symbol(f'llh_{name}'), - f'llh_{name}', - cost_fun_expr - ) + LogLikelihoodY(sp.Symbol(f"llh_{name}"), f"llh_{name}", cost_fun_expr) ) def _get_sigma_name_and_value( - pysb_model: pysb.Model, - obs_name: str, - sigmas: Dict[str, str]) -> Tuple[str, sp.Basic]: + pysb_model: pysb.Model, obs_name: str, sigmas: Dict[str, str] +) -> Tuple[str, sp.Basic]: """ Tries to extract standard deviation symbolic identifier and formula for a given observable name from the pysb model and if no specification is @@ -576,26 +575,26 @@ def _get_sigma_name_and_value( sigma_name = sigmas[obs_name] try: # find corresponding Expression instance - sigma_expr = next(x for x in pysb_model.expressions - if x.name == sigma_name) + sigma_expr = next(x for x in pysb_model.expressions if x.name == sigma_name) except StopIteration: - raise ValueError(f'value of sigma {obs_name} is not a ' - f'valid expression.') + raise ValueError( + f"value of sigma {obs_name} is not a " f"valid expression." + ) sigma_value = sigma_expr.expand_expr() else: - sigma_name = f'sigma_{obs_name}' + sigma_name = f"sigma_{obs_name}" sigma_value = sp.sympify(1.0) return sigma_name, sigma_value -@log_execution_time('processing PySB observables', logger) +@log_execution_time("processing PySB observables", logger) def _process_pysb_observables( - pysb_model: pysb.Model, - ode_model: DEModel, - observables: List[str], - sigmas: Dict[str, str], - noise_distributions: Optional[Dict[str, Union[str, Callable]]] = None, + pysb_model: pysb.Model, + ode_model: DEModel, + observables: List[str], + sigmas: Dict[str, str], + noise_distributions: Optional[Dict[str, Union[str, Callable]]] = None, ) -> None: """ Converts :class:`pysb.core.Observable` into @@ -621,14 +620,20 @@ def _process_pysb_observables( # only add those pysb observables that occur in the added # Observables as expressions for obs in pysb_model.observables: - _add_expression(obs, obs.name, obs.expand_obs(), - pysb_model, ode_model, observables, sigmas, - noise_distributions) + _add_expression( + obs, + obs.name, + obs.expand_obs(), + pysb_model, + ode_model, + observables, + sigmas, + noise_distributions, + ) -@log_execution_time('computing PySB conservation laws', logger) -def _process_pysb_conservation_laws(pysb_model: pysb.Model, - ode_model: DEModel) -> None: +@log_execution_time("computing PySB conservation laws", logger) +def _process_pysb_conservation_laws(pysb_model: pysb.Model, ode_model: DEModel) -> None: """ Removes species according to conservation laws to ensure that the jacobian has full rank @@ -642,11 +647,11 @@ def _process_pysb_conservation_laws(pysb_model: pysb.Model, monomers_without_conservation_law = set() for rule in pysb_model.rules: - monomers_without_conservation_law |= \ - _get_unconserved_monomers(rule, pysb_model) + monomers_without_conservation_law |= _get_unconserved_monomers(rule, pysb_model) - monomers_without_conservation_law |= \ + monomers_without_conservation_law |= ( _compute_monomers_with_fixed_initial_conditions(pysb_model) + ) cl_prototypes = _generate_cl_prototypes( monomers_without_conservation_law, pysb_model, ode_model @@ -662,8 +667,7 @@ def _process_pysb_conservation_laws(pysb_model: pysb.Model, ode_model.add_conservation_law(**cl) -def _compute_monomers_with_fixed_initial_conditions( - pysb_model: pysb.Model) -> Set[str]: +def _compute_monomers_with_fixed_initial_conditions(pysb_model: pysb.Model) -> Set[str]: """ Computes the set of monomers in a model with species that have fixed initial conditions @@ -679,19 +683,21 @@ def _compute_monomers_with_fixed_initial_conditions( # check if monomer has an initial condition that is fixed (means # that corresponding state is constant and all conservation # laws are broken) - if any([ - ic.fixed # true or false - for ic in pysb_model.initials - if monomer.name in extract_monomers(ic.pattern) - ]): + if any( + [ + ic.fixed # true or false + for ic in pysb_model.initials + if monomer.name in extract_monomers(ic.pattern) + ] + ): monomers_with_fixed_initial_conditions |= {monomer.name} return monomers_with_fixed_initial_conditions -def _generate_cl_prototypes(excluded_monomers: Iterable[str], - pysb_model: pysb.Model, - ode_model: DEModel) -> CL_Prototype: +def _generate_cl_prototypes( + excluded_monomers: Iterable[str], pysb_model: pysb.Model, ode_model: DEModel +) -> CL_Prototype: """ Constructs a dict that contains preprocessed information for the construction of conservation laws @@ -711,18 +717,19 @@ def _generate_cl_prototypes(excluded_monomers: Iterable[str], """ cl_prototypes = dict() - _compute_possible_indices(cl_prototypes, pysb_model, ode_model, - excluded_monomers) + _compute_possible_indices(cl_prototypes, pysb_model, ode_model, excluded_monomers) _compute_dependency_idx(cl_prototypes) _compute_target_index(cl_prototypes, ode_model) return cl_prototypes -def _compute_possible_indices(cl_prototypes: CL_Prototype, - pysb_model: pysb.Model, - ode_model: DEModel, - excluded_monomers: Iterable[str]) -> None: +def _compute_possible_indices( + cl_prototypes: CL_Prototype, + pysb_model: pysb.Model, + ode_model: DEModel, + excluded_monomers: Iterable[str], +) -> None: """ Computes viable choices for target_index, ie species that could be removed and replaced by an algebraic expression according to the @@ -752,28 +759,28 @@ def _compute_possible_indices(cl_prototypes: CL_Prototype, ] if len(set(compartments)) > 1: - raise ValueError('Conservation laws involving species in ' - 'multiple compartments are currently not ' - 'supported! Please run pysb2amici with ' - 'compute_conservation_laws=False') + raise ValueError( + "Conservation laws involving species in " + "multiple compartments are currently not " + "supported! Please run pysb2amici with " + "compute_conservation_laws=False" + ) # TODO: implement this, multiply species by the volume of # their respective compartment and allow total_cl to depend # on parameters + constants and update the respective symbolic # derivative accordingly prototype = dict() - prototype['possible_indices'] = [ + prototype["possible_indices"] = [ ix for ix, specie in enumerate(pysb_model.species) if monomer.name in extract_monomers(specie) - and not ode_model.state_is_constant(ix) + and not ode_model.state_is_constant(ix) ] - prototype['species_count'] = len( - prototype['possible_indices'] - ) + prototype["species_count"] = len(prototype["possible_indices"]) - if prototype['possible_indices']: + if prototype["possible_indices"]: cl_prototypes[monomer.name] = prototype @@ -791,35 +798,34 @@ def _compute_dependency_idx(cl_prototypes: CL_Prototype) -> None: """ # for monomer_i, prototype_i in cl_prototypes.items(): - if 'dependency_idx' not in prototype_i: - prototype_i['dependency_idx'] = dict() + if "dependency_idx" not in prototype_i: + prototype_i["dependency_idx"] = dict() for monomer_j, prototype_j in cl_prototypes.items(): if monomer_i == monomer_j: continue - if 'dependency_idx' not in prototype_j: - prototype_j['dependency_idx'] = dict() + if "dependency_idx" not in prototype_j: + prototype_j["dependency_idx"] = dict() - idx_overlap = set(prototype_i['possible_indices']).intersection( - set(prototype_j['possible_indices']) + idx_overlap = set(prototype_i["possible_indices"]).intersection( + set(prototype_j["possible_indices"]) ) if len(idx_overlap) == 0: continue for idx in idx_overlap: - if idx not in prototype_i['dependency_idx']: - prototype_i['dependency_idx'][idx] = set() + if idx not in prototype_i["dependency_idx"]: + prototype_i["dependency_idx"][idx] = set() - if idx not in prototype_j['dependency_idx']: - prototype_j['dependency_idx'][idx] = set() + if idx not in prototype_j["dependency_idx"]: + prototype_j["dependency_idx"][idx] = set() - prototype_i['dependency_idx'][idx] |= {monomer_j} - prototype_j['dependency_idx'][idx] |= {monomer_i} + prototype_i["dependency_idx"][idx] |= {monomer_j} + prototype_j["dependency_idx"][idx] |= {monomer_i} -def _compute_target_index(cl_prototypes: CL_Prototype, - ode_model: DEModel) -> None: +def _compute_target_index(cl_prototypes: CL_Prototype, ode_model: DEModel) -> None: """ Computes the target index for every monomer @@ -829,10 +835,18 @@ def _compute_target_index(cl_prototypes: CL_Prototype, :param ode_model: DEModel instance """ - possible_indices = list(set(list(itertools.chain(*[ - cl_prototypes[monomer]['possible_indices'] - for monomer in cl_prototypes - ])))) + possible_indices = list( + set( + list( + itertools.chain( + *[ + cl_prototypes[monomer]["possible_indices"] + for monomer in cl_prototypes + ] + ) + ) + ) + ) # Note: currently this function is supposed to also count appearances in # expressions. However, expressions are currently still empty as they @@ -848,22 +862,22 @@ def _compute_target_index(cl_prototypes: CL_Prototype, for monomer in cl_prototypes: prototype = cl_prototypes[monomer] # extract monomer specific appearance counts - prototype['appearance_counts'] = \ - [ - appearance_counts[possible_indices.index(idx)] - for idx in prototype['possible_indices'] - ] + prototype["appearance_counts"] = [ + appearance_counts[possible_indices.index(idx)] + for idx in prototype["possible_indices"] + ] # select target index as possible index with minimal appearance count - if len(prototype['appearance_counts']) == 0: - raise RuntimeError(f'Failed to compute conservation law for ' - f'monomer {monomer}') + if len(prototype["appearance_counts"]) == 0: + raise RuntimeError( + f"Failed to compute conservation law for " f"monomer {monomer}" + ) - idx = np.argmin(prototype['appearance_counts']) + idx = np.argmin(prototype["appearance_counts"]) # remove entries from possible indices and appearance counts so we # do not consider them again in later iterations - prototype['target_index'] = prototype['possible_indices'].pop(idx) - prototype['appearance_count'] = prototype['appearance_counts'].pop(idx) + prototype["target_index"] = prototype["possible_indices"].pop(idx) + prototype["appearance_count"] = prototype["appearance_counts"].pop(idx) # this is only an approximation as the effective species count # of other conservation laws may also be affected by the chosen @@ -871,8 +885,7 @@ def _compute_target_index(cl_prototypes: CL_Prototype, # multimers has a low upper bound and the species count does not # vary too much across conservation laws, this approximation # should be fine - prototype['fillin'] = \ - prototype['appearance_count'] * prototype['species_count'] + prototype["fillin"] = prototype["appearance_count"] * prototype["species_count"] # we might end up with the same index for multiple monomers, so loop until # we have a set of unique target indices @@ -893,10 +906,7 @@ def _cl_prototypes_are_valid(cl_prototypes: CL_Prototype) -> bool: if len(cl_prototypes) != len(set(_get_target_indices(cl_prototypes))): return False # conservation law dependencies are cycle free - if any( - _cl_has_cycle(monomer, cl_prototypes) - for monomer in cl_prototypes - ): + if any(_cl_has_cycle(monomer, cl_prototypes) for monomer in cl_prototypes): return False return True @@ -919,28 +929,20 @@ def _cl_has_cycle(monomer: str, cl_prototypes: CL_Prototype) -> bool: prototype = cl_prototypes[monomer] - if prototype['target_index'] not in prototype['dependency_idx']: + if prototype["target_index"] not in prototype["dependency_idx"]: return False visited = [monomer] root = monomer return any( - _is_in_cycle( - connecting_monomer, - cl_prototypes, - visited, - root - ) - for connecting_monomer in prototype['dependency_idx'][ - prototype['target_index'] - ] + _is_in_cycle(connecting_monomer, cl_prototypes, visited, root) + for connecting_monomer in prototype["dependency_idx"][prototype["target_index"]] ) -def _is_in_cycle(monomer: str, - cl_prototypes: CL_Prototype, - visited: List[str], - root: str) -> bool: +def _is_in_cycle( + monomer: str, cl_prototypes: CL_Prototype, visited: List[str], root: str +) -> bool: """ Recursively checks for cycles in conservation law dependencies via Depth First Search @@ -973,19 +975,12 @@ def _is_in_cycle(monomer: str, prototype = cl_prototypes[monomer] - if prototype['target_index'] not in prototype['dependency_idx']: + if prototype["target_index"] not in prototype["dependency_idx"]: return False return any( - _is_in_cycle( - connecting_monomer, - cl_prototypes, - visited, - root - ) - for connecting_monomer in prototype['dependency_idx'][ - prototype['target_index'] - ] + _is_in_cycle(connecting_monomer, cl_prototypes, visited, root) + for connecting_monomer in prototype["dependency_idx"][prototype["target_index"]] ) @@ -1002,8 +997,9 @@ def _greedy_target_index_update(cl_prototypes: CL_Prototype) -> None: target_indices = _get_target_indices(cl_prototypes) for monomer, prototype in cl_prototypes.items(): - if target_indices.count(prototype['target_index']) > 1 or \ - _cl_has_cycle(monomer, cl_prototypes): + if target_indices.count(prototype["target_index"]) > 1 or _cl_has_cycle( + monomer, cl_prototypes + ): # compute how much fillin the next best target_index would yield # we exclude already existing target indices to avoid that @@ -1012,52 +1008,44 @@ def _greedy_target_index_update(cl_prototypes: CL_Prototype) -> None: # solution but prevents infinite loops for target_index in list(set(target_indices)): try: - local_idx = prototype['possible_indices'].index( - target_index - ) + local_idx = prototype["possible_indices"].index(target_index) except ValueError: local_idx = None if local_idx: - del prototype['possible_indices'][local_idx] - del prototype['appearance_counts'][local_idx] + del prototype["possible_indices"][local_idx] + del prototype["appearance_counts"][local_idx] - if len(prototype['possible_indices']) == 0: - prototype['diff_fillin'] = -1 + if len(prototype["possible_indices"]) == 0: + prototype["diff_fillin"] = -1 continue - idx = np.argmin(prototype['appearance_counts']) + idx = np.argmin(prototype["appearance_counts"]) - prototype['local_index'] = idx - prototype['alternate_target_index'] = \ - prototype['possible_indices'][idx] - prototype['alternate_appearance_count'] = \ - prototype['appearance_counts'][idx] + prototype["local_index"] = idx + prototype["alternate_target_index"] = prototype["possible_indices"][idx] + prototype["alternate_appearance_count"] = prototype["appearance_counts"][ + idx + ] - prototype['alternate_fillin'] = \ - prototype['alternate_appearance_count'] \ - * prototype['species_count'] + prototype["alternate_fillin"] = ( + prototype["alternate_appearance_count"] * prototype["species_count"] + ) - prototype['diff_fillin'] = \ - prototype['alternate_fillin'] - prototype['fillin'] + prototype["diff_fillin"] = ( + prototype["alternate_fillin"] - prototype["fillin"] + ) else: - prototype['diff_fillin'] = -1 + prototype["diff_fillin"] = -1 - if all( - prototype['diff_fillin'] == -1 - for prototype in cl_prototypes.values() - ): - raise RuntimeError('Could not compute a valid set of conservation ' - 'laws for this model!') + if all(prototype["diff_fillin"] == -1 for prototype in cl_prototypes.values()): + raise RuntimeError( + "Could not compute a valid set of conservation " "laws for this model!" + ) # this puts prototypes with high diff_fillin last - cl_prototypes = sorted( - cl_prototypes.items(), key=lambda kv: kv[1]['diff_fillin'] - ) - cl_prototypes = { - proto[0]: proto[1] - for proto in cl_prototypes - } + cl_prototypes = sorted(cl_prototypes.items(), key=lambda kv: kv[1]["diff_fillin"]) + cl_prototypes = {proto[0]: proto[1] for proto in cl_prototypes} for monomer in cl_prototypes: prototype = cl_prototypes[monomer] @@ -1069,24 +1057,19 @@ def _greedy_target_index_update(cl_prototypes: CL_Prototype) -> None: # with the highest diff_fillin (note that the target index counts # are recomputed on the fly) - if prototype['diff_fillin'] > -1 \ - and ( - _get_target_indices(cl_prototypes).count( - prototype['target_index'] - ) > 1 - or _cl_has_cycle(monomer, cl_prototypes) + if prototype["diff_fillin"] > -1 and ( + _get_target_indices(cl_prototypes).count(prototype["target_index"]) > 1 + or _cl_has_cycle(monomer, cl_prototypes) ): - prototype['fillin'] = prototype['alternate_fillin'] - prototype['target_index'] = prototype['alternate_target_index'] - prototype['appearance_count'] = \ - prototype['alternate_appearance_count'] + prototype["fillin"] = prototype["alternate_fillin"] + prototype["target_index"] = prototype["alternate_target_index"] + prototype["appearance_count"] = prototype["alternate_appearance_count"] - del prototype['possible_indices'][prototype['local_index']] - del prototype['appearance_counts'][prototype['local_index']] + del prototype["possible_indices"][prototype["local_index"]] + del prototype["appearance_counts"][prototype["local_index"]] -def _get_target_indices( - cl_prototypes: CL_Prototype) -> List[List[int]]: +def _get_target_indices(cl_prototypes: CL_Prototype) -> List[List[int]]: """ Computes the list target indices for the current conservation law prototype @@ -1097,14 +1080,11 @@ def _get_target_indices( :return: List of lists of target indices """ - return [ - prototype['target_index'] for prototype in cl_prototypes.values() - ] + return [prototype["target_index"] for prototype in cl_prototypes.values()] def _construct_conservation_from_prototypes( - cl_prototypes: CL_Prototype, - pysb_model: pysb.Model + cl_prototypes: CL_Prototype, pysb_model: pysb.Model ) -> List[ConservationLaw]: """ Computes the algebraic expression for the total amount of a given @@ -1121,26 +1101,27 @@ def _construct_conservation_from_prototypes( """ conservation_laws = [] for monomer_name in cl_prototypes: - target_index = cl_prototypes[monomer_name]['target_index'] + target_index = cl_prototypes[monomer_name]["target_index"] coefficients = dict() for ix, specie in enumerate(pysb_model.species): count = extract_monomers(specie).count(monomer_name) if count > 0: - coefficients[sp.Symbol(f'__s{ix}')] = count - - conservation_laws.append({ - 'state': sp.Symbol(f'__s{target_index}'), - 'total_abundance': sp.Symbol(f'tcl__s{target_index}'), - 'coefficients': coefficients, - }) + coefficients[sp.Symbol(f"__s{ix}")] = count + + conservation_laws.append( + { + "state": sp.Symbol(f"__s{target_index}"), + "total_abundance": sp.Symbol(f"tcl__s{target_index}"), + "coefficients": coefficients, + } + ) return conservation_laws def _add_conservation_for_constant_species( - ode_model: DEModel, - conservation_laws: List[ConservationLaw] + ode_model: DEModel, conservation_laws: List[ConservationLaw] ) -> None: """ Computes the algebraic expression for the total amount of a given @@ -1156,15 +1137,16 @@ def _add_conservation_for_constant_species( for ix in range(ode_model.num_states_rdata()): if ode_model.state_is_constant(ix): - conservation_laws.append({ - 'state': sp.Symbol(f'__s{ix}'), - 'total_abundance': sp.Symbol(f'tcl__s{ix}'), - 'coefficients': {sp.Symbol(f'__s{ix}'): 1.0} - }) + conservation_laws.append( + { + "state": sp.Symbol(f"__s{ix}"), + "total_abundance": sp.Symbol(f"tcl__s{ix}"), + "coefficients": {sp.Symbol(f"__s{ix}"): 1.0}, + } + ) -def _flatten_conservation_laws( - conservation_laws: List[ConservationLaw]) -> None: +def _flatten_conservation_laws(conservation_laws: List[ConservationLaw]) -> None: """ Flatten the conservation laws such that the state_expr not longer depend on any states that are replaced by conservation laws @@ -1172,22 +1154,20 @@ def _flatten_conservation_laws( :param conservation_laws: see return of :func:`_construct_conservation_from_prototypes` """ - conservation_law_subs = \ - _get_conservation_law_subs(conservation_laws) + conservation_law_subs = _get_conservation_law_subs(conservation_laws) while conservation_law_subs: for cl in conservation_laws: # only update if we changed something if any( - _apply_conseration_law_sub(cl, sub) - for sub in conservation_law_subs + _apply_conseration_law_sub(cl, sub) for sub in conservation_law_subs ): - conservation_law_subs = \ - _get_conservation_law_subs(conservation_laws) + conservation_law_subs = _get_conservation_law_subs(conservation_laws) -def _apply_conseration_law_sub(cl: ConservationLaw, - sub: Tuple[sp.Symbol, ConservationLaw]) -> bool: +def _apply_conseration_law_sub( + cl: ConservationLaw, sub: Tuple[sp.Symbol, ConservationLaw] +) -> bool: """ Applies a substitution to a conservation law by replacing the coefficient of the state of the @@ -1204,26 +1184,24 @@ def _apply_conseration_law_sub(cl: ConservationLaw, if not _state_in_cl_formula(sub[0], cl): return False - coeff = cl['coefficients'].pop(sub[0], 0.0) + coeff = cl["coefficients"].pop(sub[0], 0.0) # x_j = T/b_j - sum_{i≠j}(x_i * b_i) / b_j # don't need to account for totals here as we can simply # absorb that into the new total for k, v in sub[1].items(): if k == sub[0]: continue - update = - coeff * v / sub[1][sub[0]] + update = -coeff * v / sub[1][sub[0]] - if k in cl['coefficients']: - cl['coefficients'][k] += update + if k in cl["coefficients"]: + cl["coefficients"][k] += update else: - cl['coefficients'][k] = update + cl["coefficients"][k] = update return True -def _state_in_cl_formula( - state: sp.Symbol, cl: ConservationLaw -) -> bool: +def _state_in_cl_formula(state: sp.Symbol, cl: ConservationLaw) -> bool: """ Checks whether state appears in the formula the provided cl @@ -1236,14 +1214,14 @@ def _state_in_cl_formula( :return: boolean indicator """ - if cl['state'] == state: + if cl["state"] == state: return False - return cl['coefficients'].get(state, 0.0) != 0.0 + return cl["coefficients"].get(state, 0.0) != 0.0 def _get_conservation_law_subs( - conservation_laws: List[ConservationLaw] + conservation_laws: List[ConservationLaw], ) -> List[Tuple[sp.Symbol, Dict[sp.Symbol, sp.Expr]]]: """ Computes a list of (state, coeffs) tuples for conservation laws that still @@ -1257,16 +1235,18 @@ def _get_conservation_law_subs( subs """ return [ - (cl['state'], cl['coefficients']) for cl in conservation_laws + (cl["state"], cl["coefficients"]) + for cl in conservation_laws if any( - _state_in_cl_formula(cl['state'], other_cl) + _state_in_cl_formula(cl["state"], other_cl) for other_cl in conservation_laws ) ] -def has_fixed_parameter_ic(specie: pysb.core.ComplexPattern, - pysb_model: pysb.Model, - ode_model: DEModel) -> bool: + +def has_fixed_parameter_ic( + specie: pysb.core.ComplexPattern, pysb_model: pysb.Model, ode_model: DEModel +) -> bool: """ Wrapper to interface :meth:`de_export.DEModel.state_has_fixed_parameter_initial_condition` @@ -1291,22 +1271,18 @@ def has_fixed_parameter_ic(specie: pysb.core.ComplexPattern, ( ic for ic, condition in enumerate(pysb_model.initials) - if pysb.pattern.match_complex_pattern(condition[0], - specie, exact=True) + if pysb.pattern.match_complex_pattern(condition[0], specie, exact=True) ), - None + None, ) if ic_index is None: return False else: - return ode_model.state_has_fixed_parameter_initial_condition( - ic_index - ) + return ode_model.state_has_fixed_parameter_initial_condition(ic_index) def extract_monomers( - complex_patterns: Union[pysb.ComplexPattern, - List[pysb.ComplexPattern]] + complex_patterns: Union[pysb.ComplexPattern, List[pysb.ComplexPattern]] ) -> List[str]: """ Constructs a list of monomer names contained in complex patterns. @@ -1328,8 +1304,7 @@ def extract_monomers( ] -def _get_unconserved_monomers(rule: pysb.Rule, - pysb_model: pysb.Model) -> Set[str]: +def _get_unconserved_monomers(rule: pysb.Rule, pysb_model: pysb.Model) -> Set[str]: """ Constructs the set of monomer names for which the specified rule changes the stoichiometry of the monomer in the specified model. @@ -1345,31 +1320,29 @@ def _get_unconserved_monomers(rule: pysb.Rule, """ unconserved_monomers = set() - if not rule.delete_molecules \ - and len(rule.product_pattern.complex_patterns) == 0: + if not rule.delete_molecules and len(rule.product_pattern.complex_patterns) == 0: # if delete_molecules is not True but we have a degradation rule, # we have to actually go through the reactions that are created by # the rule - for reaction in [r for r in pysb_model.reactions - if rule.name in r['rule']]: + for reaction in [r for r in pysb_model.reactions if rule.name in r["rule"]]: unconserved_monomers |= _get_changed_stoichiometries( - [pysb_model.species[ix] for ix in reaction['reactants']], - [pysb_model.species[ix] for ix in reaction['products']] + [pysb_model.species[ix] for ix in reaction["reactants"]], + [pysb_model.species[ix] for ix in reaction["products"]], ) else: # otherwise we can simply extract all information for the rule # itself, which is computationally much more efficient unconserved_monomers |= _get_changed_stoichiometries( rule.reactant_pattern.complex_patterns, - rule.product_pattern.complex_patterns + rule.product_pattern.complex_patterns, ) return unconserved_monomers def _get_changed_stoichiometries( - reactants: Union[pysb.ComplexPattern, List[pysb.ComplexPattern]], - products: Union[pysb.ComplexPattern, List[pysb.ComplexPattern]] + reactants: Union[pysb.ComplexPattern, List[pysb.ComplexPattern]], + products: Union[pysb.ComplexPattern, List[pysb.ComplexPattern]], ) -> Set[str]: """ Constructs the set of monomer names which have different @@ -1386,13 +1359,9 @@ def _get_changed_stoichiometries( changed_stoichiometries = set() - reactant_monomers = extract_monomers( - reactants - ) + reactant_monomers = extract_monomers(reactants) - product_monomers = extract_monomers( - products - ) + product_monomers = extract_monomers(products) for monomer in set(reactant_monomers + product_monomers): if reactant_monomers.count(monomer) != product_monomers.count(monomer): @@ -1408,12 +1377,13 @@ def pysb_model_from_path(pysb_model_file: Union[str, Path]) -> pysb.Model: :return: The pysb Model instance """ - pysb_model_module_name = \ - os.path.splitext(os.path.split(pysb_model_file)[-1])[0] + pysb_model_module_name = os.path.splitext(os.path.split(pysb_model_file)[-1])[0] import importlib.util + spec = importlib.util.spec_from_file_location( - pysb_model_module_name, pysb_model_file) + pysb_model_module_name, pysb_model_file + ) module = importlib.util.module_from_spec(spec) sys.modules[pysb_model_module_name] = module spec.loader.exec_module(module) diff --git a/python/sdist/amici/sbml_import.py b/python/sdist/amici/sbml_import.py index e9c014cccb..c9b8cd7d0a 100644 --- a/python/sdist/amici/sbml_import.py +++ b/python/sdist/amici/sbml_import.py @@ -14,31 +14,48 @@ import warnings import xml.etree.ElementTree as ET from pathlib import Path -from typing import (Any, Callable, Dict, Iterable, List, Optional, Tuple, - Union, Set, List) +from typing import ( + Any, + Callable, + Dict, + Iterable, + List, + Optional, + Tuple, + Union, + Set, + List, +) import libsbml as sbml import sympy as sp from . import has_clibs from .constants import SymbolId -from .import_utils import (RESERVED_SYMBOLS, - annotation_namespace, - amici_time_symbol, - sbml_time_symbol, - _check_unsupported_functions, - _get_str_symbol_identifiers, - _parse_special_functions, - generate_measurement_symbol, - generate_regularization_symbol, - noise_distribution_to_cost_function, - noise_distribution_to_observable_transformation, - smart_subs, smart_subs_dict, toposort_symbols) +from .import_utils import ( + RESERVED_SYMBOLS, + annotation_namespace, + amici_time_symbol, + sbml_time_symbol, + _check_unsupported_functions, + _get_str_symbol_identifiers, + _parse_special_functions, + generate_measurement_symbol, + generate_regularization_symbol, + noise_distribution_to_cost_function, + noise_distribution_to_observable_transformation, + smart_subs, + smart_subs_dict, + toposort_symbols, +) from .sbml_utils import SBMLException, _parse_logical_operators from .logging import get_logger, log_execution_time, set_log_level from .de_export import ( - DEExporter, DEModel, symbol_with_assumptions, _default_simplify, - smart_is_zero_matrix + DEExporter, + DEModel, + symbol_with_assumptions, + _default_simplify, + smart_is_zero_matrix, ) from .splines import AbstractSpline @@ -46,9 +63,7 @@ SymbolicFormula = Dict[sp.Symbol, sp.Expr] -default_symbols = { - symbol: {} for symbol in SymbolId -} +default_symbols = {symbol: {} for symbol in SymbolId} ConservationLaw = Dict[str, Union[str, sp.Expr]] @@ -119,11 +134,13 @@ class SbmlImporter: sets behaviour of SBML Formula parsing """ - def __init__(self, - sbml_source: Union[str, Path, sbml.Model], - show_sbml_warnings: bool = False, - from_file: bool = True, - discard_annotations: bool = False) -> None: + def __init__( + self, + sbml_source: Union[str, Path, sbml.Model], + show_sbml_warnings: bool = False, + from_file: bool = True, + discard_annotations: bool = False, + ) -> None: """ Create a new Model instance. @@ -175,44 +192,51 @@ def __init__(self, # http://sbml.org/Software/libSBML/5.18.0/docs/python-api/classlibsbml_1_1_l3_parser_settings.html#abcfedd34efd3cae2081ba8f42ea43f52 # all defaults except disable unit parsing self.sbml_parser_settings = sbml.L3ParserSettings( - self.sbml, sbml.L3P_PARSE_LOG_AS_LOG10, - sbml.L3P_EXPAND_UNARY_MINUS, sbml.L3P_NO_UNITS, + self.sbml, + sbml.L3P_PARSE_LOG_AS_LOG10, + sbml.L3P_EXPAND_UNARY_MINUS, + sbml.L3P_NO_UNITS, sbml.L3P_AVOGADRO_IS_CSYMBOL, - sbml.L3P_COMPARE_BUILTINS_CASE_INSENSITIVE, None, - sbml.L3P_MODULO_IS_PIECEWISE + sbml.L3P_COMPARE_BUILTINS_CASE_INSENSITIVE, + None, + sbml.L3P_MODULO_IS_PIECEWISE, ) - self._discard_annotations : bool = discard_annotations + self._discard_annotations: bool = discard_annotations - @log_execution_time('loading SBML', logger) + @log_execution_time("loading SBML", logger) def _process_document(self) -> None: """ Validate and simplify document. """ # Ensure we got a valid SBML model, otherwise further processing # might lead to undefined results - log_execution_time('validating SBML', logger)( - self.sbml_doc.validateSBML - )() + log_execution_time("validating SBML", logger)(self.sbml_doc.validateSBML)() _check_lib_sbml_errors(self.sbml_doc, self.show_sbml_warnings) # Flatten "comp" model? Do that before any other converters are run - if any(self.sbml_doc.getPlugin(i_plugin).getPackageName() == 'comp' - for i_plugin in range(self.sbml_doc.getNumPlugins())): + if any( + self.sbml_doc.getPlugin(i_plugin).getPackageName() == "comp" + for i_plugin in range(self.sbml_doc.getNumPlugins()) + ): # see libsbml CompFlatteningConverter for options conversion_properties = sbml.ConversionProperties() conversion_properties.addOption("flatten comp", True) conversion_properties.addOption("leave_ports", False) conversion_properties.addOption("performValidation", False) conversion_properties.addOption("abortIfUnflattenable", "none") - if log_execution_time('converting SBML local parameters', logger)( - self.sbml_doc.convert)(conversion_properties) \ - != sbml.LIBSBML_OPERATION_SUCCESS: + if ( + log_execution_time("converting SBML local parameters", logger)( + self.sbml_doc.convert + )(conversion_properties) + != sbml.LIBSBML_OPERATION_SUCCESS + ): raise SBMLException( - 'Required SBML comp extension is currently not supported ' - 'and flattening the model failed.') + "Required SBML comp extension is currently not supported " + "and flattening the model failed." + ) # check the flattened model is still valid - log_execution_time('re-validating SBML', logger)( + log_execution_time("re-validating SBML", logger)( self.sbml_doc.validateSBML )() _check_lib_sbml_errors(self.sbml_doc, self.show_sbml_warnings) @@ -220,15 +244,15 @@ def _process_document(self) -> None: # apply several model simplifications that make our life substantially # easier if self.sbml_doc.getModel().getNumFunctionDefinitions(): - convert_config = sbml.SBMLFunctionDefinitionConverter()\ - .getDefaultProperties() - log_execution_time('converting SBML functions', logger)( + convert_config = ( + sbml.SBMLFunctionDefinitionConverter().getDefaultProperties() + ) + log_execution_time("converting SBML functions", logger)( self.sbml_doc.convert )(convert_config) - convert_config = sbml.SBMLLocalParameterConverter().\ - getDefaultProperties() - log_execution_time('converting SBML local parameters', logger)( + convert_config = sbml.SBMLLocalParameterConverter().getDefaultProperties() + log_execution_time("converting SBML local parameters", logger)( self.sbml_doc.convert )(convert_config) @@ -248,26 +272,26 @@ def _reset_symbols(self) -> None: self._local_symbols = {} def sbml2amici( - self, - model_name: str, - output_dir: Union[str, Path] = None, - observables: Dict[str, Dict[str, str]] = None, - event_observables: Dict[str, Dict[str, str]] = None, - constant_parameters: Iterable[str] = None, - sigmas: Dict[str, Union[str, float]] = None, - event_sigmas: Dict[str, Union[str, float]] = None, - noise_distributions: Dict[str, Union[str, Callable]] = None, - event_noise_distributions: Dict[str, Union[str, Callable]] = None, - verbose: Union[int, bool] = logging.ERROR, - assume_pow_positivity: bool = False, - compiler: str = None, - allow_reinit_fixpar_initcond: bool = True, - compile: bool = True, - compute_conservation_laws: bool = True, - simplify: Optional[Callable] = _default_simplify, - cache_simplify: bool = False, - log_as_log10: bool = True, - generate_sensitivity_code: bool = True, + self, + model_name: str, + output_dir: Union[str, Path] = None, + observables: Dict[str, Dict[str, str]] = None, + event_observables: Dict[str, Dict[str, str]] = None, + constant_parameters: Iterable[str] = None, + sigmas: Dict[str, Union[str, float]] = None, + event_sigmas: Dict[str, Union[str, float]] = None, + noise_distributions: Dict[str, Union[str, Callable]] = None, + event_noise_distributions: Dict[str, Union[str, Callable]] = None, + verbose: Union[int, bool] = logging.ERROR, + assume_pow_positivity: bool = False, + compiler: str = None, + allow_reinit_fixpar_initcond: bool = True, + compile: bool = True, + compute_conservation_laws: bool = True, + simplify: Optional[Callable] = _default_simplify, + cache_simplify: bool = False, + log_as_log10: bool = True, + generate_sensitivity_code: bool = True, ) -> None: """ Generate and compile AMICI C++ files for the model provided to the @@ -398,37 +422,38 @@ def sbml2amici( assume_pow_positivity=assume_pow_positivity, compiler=compiler, allow_reinit_fixpar_initcond=allow_reinit_fixpar_initcond, - generate_sensitivity_code=generate_sensitivity_code + generate_sensitivity_code=generate_sensitivity_code, ) exporter.generate_model_code() if compile: if not has_clibs: - warnings.warn('AMICI C++ extensions have not been built. ' - 'Generated model code, but unable to compile.') + warnings.warn( + "AMICI C++ extensions have not been built. " + "Generated model code, but unable to compile." + ) exporter.compile_model() def _build_ode_model( - self, - observables: Dict[str, Dict[str, str]] = None, - event_observables: Dict[str, Dict[str, str]] = None, - constant_parameters: Iterable[str] = None, - sigmas: Dict[str, Union[str, float]] = None, - event_sigmas: Dict[str, Union[str, float]] = None, - noise_distributions: Dict[str, Union[str, Callable]] = None, - event_noise_distributions: Dict[str, Union[str, Callable]] = None, - verbose: Union[int, bool] = logging.ERROR, - compute_conservation_laws: bool = True, - simplify: Optional[Callable] = _default_simplify, - cache_simplify: bool = False, - log_as_log10: bool = True, + self, + observables: Dict[str, Dict[str, str]] = None, + event_observables: Dict[str, Dict[str, str]] = None, + constant_parameters: Iterable[str] = None, + sigmas: Dict[str, Union[str, float]] = None, + event_sigmas: Dict[str, Union[str, float]] = None, + noise_distributions: Dict[str, Union[str, Callable]] = None, + event_noise_distributions: Dict[str, Union[str, Callable]] = None, + verbose: Union[int, bool] = logging.ERROR, + compute_conservation_laws: bool = True, + simplify: Optional[Callable] = _default_simplify, + cache_simplify: bool = False, + log_as_log10: bool = True, ) -> DEModel: """Generate an ODEModel from this SBML model. See :py:func:`sbml2amici` for parameters. """ - constant_parameters = list(constant_parameters) \ - if constant_parameters else [] + constant_parameters = list(constant_parameters) if constant_parameters else [] if sigmas is None: sigmas = {} @@ -444,32 +469,29 @@ def _build_ode_model( self._reset_symbols() self.sbml_parser_settings.setParseLog( - sbml.L3P_PARSE_LOG_AS_LOG10 if log_as_log10 else - sbml.L3P_PARSE_LOG_AS_LN + sbml.L3P_PARSE_LOG_AS_LOG10 if log_as_log10 else sbml.L3P_PARSE_LOG_AS_LN ) self._process_sbml(constant_parameters) - if self.symbols.get(SymbolId.EVENT, False) \ - or any(x['value'].has(sp.Heaviside, sp.Piecewise) - for x in self.symbols[SymbolId.EXPRESSION].values())\ - or self.flux_vector.has(sp.Heaviside, sp.Piecewise): + if ( + self.symbols.get(SymbolId.EVENT, False) + or any( + x["value"].has(sp.Heaviside, sp.Piecewise) + for x in self.symbols[SymbolId.EXPRESSION].values() + ) + or self.flux_vector.has(sp.Heaviside, sp.Piecewise) + ): if compute_conservation_laws: logger.warning( - 'Conservation laws are currently not supported for models ' - 'with events, piecewise or Heaviside functions, ' - 'and will be turned off.' + "Conservation laws are currently not supported for models " + "with events, piecewise or Heaviside functions, " + "and will be turned off." ) compute_conservation_laws = False - self._process_observables( - observables, - sigmas, - noise_distributions - ) + self._process_observables(observables, sigmas, noise_distributions) self._process_event_observables( - event_observables, - event_sigmas, - event_noise_distributions + event_observables, event_sigmas, event_noise_distributions ) self._replace_compartments_with_volumes() @@ -481,12 +503,10 @@ def _build_ode_model( simplify=simplify, cache_simplify=cache_simplify, ) - ode_model.import_from_sbml_importer( - self, compute_cls=compute_conservation_laws - ) + ode_model.import_from_sbml_importer(self, compute_cls=compute_conservation_laws) return ode_model - @log_execution_time('importing SBML', logger) + @log_execution_time("importing SBML", logger) def _process_sbml(self, constant_parameters: List[str] = None) -> None: """ Read parameters, species, reactions, and so on from SBML model @@ -515,22 +535,25 @@ def check_support(self) -> None: """ # Check for required but unsupported SBML extensions - if self.sbml_doc.getLevel() != 3 \ - and hasattr(self.sbml, 'all_elements_from_plugins') \ - and self.sbml.all_elements_from_plugins.getSize(): - raise SBMLException('SBML extensions are currently not supported!') + if ( + self.sbml_doc.getLevel() != 3 + and hasattr(self.sbml, "all_elements_from_plugins") + and self.sbml.all_elements_from_plugins.getSize() + ): + raise SBMLException("SBML extensions are currently not supported!") if self.sbml_doc.getLevel() == 3: # the "required" attribute is only available in SBML Level 3 for i_plugin in range(self.sbml.getNumPlugins()): plugin = self.sbml.getPlugin(i_plugin) - if self.sbml_doc.getPkgRequired(plugin.getPackageName()) \ - is False: + if self.sbml_doc.getPkgRequired(plugin.getPackageName()) is False: # if not "required", this has no impact on model # simulation, and we can safely ignore it - if plugin.getPackageName() == "fbc" \ - and plugin.getListOfAllElements(): + if ( + plugin.getPackageName() == "fbc" + and plugin.getListOfAllElements() + ): # fbc is labeled not-required, but in fact it is. # we don't care about the extra attributes of core # elements, such as fbc:chemicalFormula, but we can't @@ -539,9 +562,7 @@ def check_support(self) -> None: raise SBMLException( "The following fbc extension elements are " "currently not supported: " - + ', '.join( - list(map(str, plugin.getListOfAllElements())) - ) + + ", ".join(list(map(str, plugin.getListOfAllElements()))) ) continue @@ -550,18 +571,25 @@ def check_support(self) -> None: # ignore the enabled package if plugin.getListOfAllElements(): raise SBMLException( - f'Required SBML extension {plugin.getPackageName()} ' - f'is currently not supported!') + f"Required SBML extension {plugin.getPackageName()} " + f"is currently not supported!" + ) - if any(rule.isRate() and not isinstance( - self.sbml.getElementBySId(rule.getVariable()), - (sbml.Compartment, sbml.Species, sbml.Parameter) - ) for rule in self.sbml.getListOfRules()): - raise SBMLException('Rate rules are only supported for ' - 'species, compartments, and parameters.') + if any( + rule.isRate() + and not isinstance( + self.sbml.getElementBySId(rule.getVariable()), + (sbml.Compartment, sbml.Species, sbml.Parameter), + ) + for rule in self.sbml.getListOfRules() + ): + raise SBMLException( + "Rate rules are only supported for " + "species, compartments, and parameters." + ) if any(r.getFast() for r in self.sbml.getListOfReactions()): - raise SBMLException('Fast reactions are currently not supported!') + raise SBMLException("Fast reactions are currently not supported!") # Check events for unsupported functionality self.check_event_support() @@ -590,31 +618,38 @@ def check_event_support(self) -> None: # `TypeError` would be raised in the above `float(...)` # if the delay is not a fixed time except (TypeError, ValueError): - raise SBMLException('Events with execution delays are ' - 'currently not supported in AMICI.') + raise SBMLException( + "Events with execution delays are " + "currently not supported in AMICI." + ) # Check for priorities if event.getPriority() is not None: - raise SBMLException(f'Event {event_id} has a priority ' - 'specified. This is currently not ' - 'supported in AMICI.') + raise SBMLException( + f"Event {event_id} has a priority " + "specified. This is currently not " + "supported in AMICI." + ) # check trigger trigger_sbml = event.getTrigger() if trigger_sbml is None: - logger.warning(f'Event {event_id} trigger has no trigger, ' - 'so will be skipped.') + logger.warning( + f"Event {event_id} trigger has no trigger, " "so will be skipped." + ) continue if trigger_sbml.getMath() is None: - logger.warning(f'Event {event_id} trigger has no trigger ' - 'expression, so a dummy trigger will be set.') + logger.warning( + f"Event {event_id} trigger has no trigger " + "expression, so a dummy trigger will be set." + ) if not trigger_sbml.getPersistent(): raise SBMLException( - f'Event {event_id} has a non-persistent trigger.' - 'This is currently not supported in AMICI.' + f"Event {event_id} has a non-persistent trigger." + "This is currently not supported in AMICI." ) - @log_execution_time('gathering local SBML symbols', logger) + @log_execution_time("gathering local SBML symbols", logger) def _gather_locals(self) -> None: """ Populate self.local_symbols with all model entities. @@ -634,21 +669,23 @@ def _gather_base_locals(self): special_symbols_and_funs = { # oo is sympy infinity - 'INF': sp.oo, - 'NaN': sp.nan, - 'rem': sp.Mod, - 'time': symbol_with_assumptions('time'), + "INF": sp.oo, + "NaN": sp.nan, + "rem": sp.Mod, + "time": symbol_with_assumptions("time"), # SBML L3 explicitly defines this value, which is not equal # to the most recent SI definition. - 'avogadro': sp.Float(6.02214179e23), - 'exponentiale': sp.E, + "avogadro": sp.Float(6.02214179e23), + "exponentiale": sp.E, } for s, v in special_symbols_and_funs.items(): self.add_local_symbol(s, v) - for c in itt.chain(self.sbml.getListOfSpecies(), - self.sbml.getListOfParameters(), - self.sbml.getListOfCompartments()): + for c in itt.chain( + self.sbml.getListOfSpecies(), + self.sbml.getListOfParameters(), + self.sbml.getListOfCompartments(), + ): if not c.isSetId(): continue @@ -657,8 +694,7 @@ def _gather_base_locals(self): for x_ref in _get_list_of_species_references(self.sbml): if not x_ref.isSetId(): continue - if x_ref.isSetStoichiometry() and not \ - self.is_assignment_rule_target(x_ref): + if x_ref.isSetStoichiometry() and not self.is_assignment_rule_target(x_ref): value = sp.Float(x_ref.getStoichiometry()) else: value = _get_identifier_symbol(x_ref) @@ -674,12 +710,12 @@ def _gather_base_locals(self): if isinstance(e, sbml.SpeciesReference): continue - if not (e.isSetId() and e.isSetStoichiometry()) or \ - self.is_assignment_rule_target(e): + if not ( + e.isSetId() and e.isSetStoichiometry() + ) or self.is_assignment_rule_target(e): continue - self.add_local_symbol(e.getId(), - sp.Float(e.getStoichiometry())) + self.add_local_symbol(e.getId(), sp.Float(e.getStoichiometry())) def _gather_dependent_locals(self): """ @@ -690,8 +726,7 @@ def _gather_dependent_locals(self): if not r.isSetId(): continue self.add_local_symbol( - r.getId(), - self._sympy_from_sbml_math(r.getKineticLaw() or sp.Float(0)) + r.getId(), self._sympy_from_sbml_math(r.getKineticLaw() or sp.Float(0)) ) def add_local_symbol(self, key: str, value: sp.Expr): @@ -708,22 +743,22 @@ def add_local_symbol(self, key: str, value: sp.Expr): """ if key in self._local_symbols.keys(): raise SBMLException( - f'AMICI tried to add a local symbol {key} with value {value}, ' - f'but {key} was already instantiated with ' - f'{self._local_symbols[key]}. This means that there ' - f'are multiple SBML elements with SId {key}, which is ' - f'invalid SBML. This can be fixed by renaming ' - f'the elements with SId {key}.' + f"AMICI tried to add a local symbol {key} with value {value}, " + f"but {key} was already instantiated with " + f"{self._local_symbols[key]}. This means that there " + f"are multiple SBML elements with SId {key}, which is " + f"invalid SBML. This can be fixed by renaming " + f"the elements with SId {key}." ) - if key in {'True', 'False', 'true', 'false', 'pi'}: + if key in {"True", "False", "true", "false", "pi"}: raise SBMLException( - f'AMICI tried to add a local symbol {key} with value {value}, ' - f'but {key} is a reserved symbol in AMICI. This can be fixed ' - f'by renaming the element with SId {key}.' + f"AMICI tried to add a local symbol {key} with value {value}, " + f"but {key} is a reserved symbol in AMICI. This can be fixed " + f"by renaming the element with SId {key}." ) self._local_symbols[key] = value - @log_execution_time('processing SBML compartments', logger) + @log_execution_time("processing SBML compartments", logger) def _process_compartments(self) -> None: """ Get compartment information, stoichiometric matrix and fluxes from @@ -743,15 +778,13 @@ def _process_compartments(self) -> None: self.compartments[_get_identifier_symbol(comp)] = init - @log_execution_time('processing SBML species', logger) + @log_execution_time("processing SBML species", logger) def _process_species(self) -> None: """ Get species information from SBML model. """ if self.sbml.isSetConversionFactor(): - conversion_factor = symbol_with_assumptions( - self.sbml.getConversionFactor() - ) + conversion_factor = symbol_with_assumptions(self.sbml.getConversionFactor()) else: conversion_factor = 1 @@ -759,23 +792,21 @@ def _process_species(self) -> None: if self.is_assignment_rule_target(s): continue self.symbols[SymbolId.SPECIES][_get_identifier_symbol(s)] = { - 'name': s.getName() if s.isSetName() else s.getId(), - 'compartment': _get_species_compartment_symbol(s), - 'constant': s.getConstant() or s.getBoundaryCondition(), - 'amount': s.getHasOnlySubstanceUnits(), - 'conversion_factor': symbol_with_assumptions( - s.getConversionFactor() - ) + "name": s.getName() if s.isSetName() else s.getId(), + "compartment": _get_species_compartment_symbol(s), + "constant": s.getConstant() or s.getBoundaryCondition(), + "amount": s.getHasOnlySubstanceUnits(), + "conversion_factor": symbol_with_assumptions(s.getConversionFactor()) if s.isSetConversionFactor() else conversion_factor, - 'index': len(self.symbols[SymbolId.SPECIES]), + "index": len(self.symbols[SymbolId.SPECIES]), } self._convert_event_assignment_parameter_targets_to_species() self._process_species_initial() self._process_rate_rules() - @log_execution_time('processing SBML species initials', logger) + @log_execution_time("processing SBML species initials", logger) def _process_species_initial(self): """ Extract initial values and initial assignments from species @@ -790,23 +821,18 @@ def _process_species_initial(self): # targets to have InitialAssignments. species = self.symbols[SymbolId.SPECIES].get(species_id, None) - ia_initial = self._get_element_initial_assignment( - species_variable.getId() - ) + ia_initial = self._get_element_initial_assignment(species_variable.getId()) if ia_initial is not None: initial = ia_initial if species: - species['init'] = initial + species["init"] = initial # don't assign this since they need to stay in order - sorted_species = toposort_symbols(self.symbols[SymbolId.SPECIES], - 'init') + sorted_species = toposort_symbols(self.symbols[SymbolId.SPECIES], "init") for species in self.symbols[SymbolId.SPECIES].values(): - species['init'] = smart_subs_dict(species['init'], - sorted_species, - 'init') + species["init"] = smart_subs_dict(species["init"], sorted_species, "init") - @log_execution_time('processing SBML rate rules', logger) + @log_execution_time("processing SBML rate rules", logger) def _process_rate_rules(self): """ Process rate rules for species, compartments and parameters. @@ -832,7 +858,7 @@ def _process_rate_rules(self): # implemented as species). ia_init = self._get_element_initial_assignment(rule.getVariable()) if variable in self.symbols[SymbolId.SPECIES]: - init = self.symbols[SymbolId.SPECIES][variable]['init'] + init = self.symbols[SymbolId.SPECIES][variable]["init"] name = None if variable in self.compartments: @@ -842,9 +868,9 @@ def _process_rate_rules(self): elif variable in self.symbols[SymbolId.PARAMETER]: init = self._sympy_from_sbml_math( - self.symbols[SymbolId.PARAMETER][variable]['value'], + self.symbols[SymbolId.PARAMETER][variable]["value"], ) - name = self.symbols[SymbolId.PARAMETER][variable]['name'] + name = self.symbols[SymbolId.PARAMETER][variable]["name"] del self.symbols[SymbolId.PARAMETER][variable] # parameter with initial assignment, cannot use @@ -858,11 +884,11 @@ def _process_rate_rules(self): self.add_d_dt(formula, variable, init, name) def add_d_dt( - self, - d_dt: sp.Expr, - variable: sp.Symbol, - variable0: Union[float, sp.Expr], - name: str, + self, + d_dt: sp.Expr, + variable: sp.Symbol, + variable0: Union[float, sp.Expr], + name: str, ) -> None: """ Creates or modifies species, to implement rate rules for @@ -883,28 +909,27 @@ def add_d_dt( """ if variable in self.symbols[SymbolId.SPECIES]: # only update dt if species was already generated - self.symbols[SymbolId.SPECIES][variable]['dt'] = d_dt + self.symbols[SymbolId.SPECIES][variable]["dt"] = d_dt else: # update initial values for species_id, species in self.symbols[SymbolId.SPECIES].items(): - variable0 = smart_subs(variable0, species_id, species['init']) + variable0 = smart_subs(variable0, species_id, species["init"]) for species in self.symbols[SymbolId.SPECIES].values(): - species['init'] = smart_subs(species['init'], - variable, variable0) + species["init"] = smart_subs(species["init"], variable, variable0) # add compartment/parameter species self.symbols[SymbolId.SPECIES][variable] = { - 'name': name, - 'init': variable0, - 'amount': False, - 'conversion_factor': 1.0, - 'constant': False, - 'index': len(self.symbols[SymbolId.SPECIES]), - 'dt': d_dt, + "name": name, + "init": variable0, + "amount": False, + "conversion_factor": 1.0, + "constant": False, + "index": len(self.symbols[SymbolId.SPECIES]), + "dt": d_dt, } - @log_execution_time('processing SBML annotations', logger) + @log_execution_time("processing SBML annotations", logger) def _process_annotations(self) -> None: """ Process annotations that make modifications to the @@ -919,7 +944,7 @@ def _process_annotations(self) -> None: if len(annotation) != 0: annotation = ET.fromstring(annotation) for child in annotation: - if child.tag == f'{{{annotation_namespace}}}discard': + if child.tag == f"{{{annotation_namespace}}}discard": parameter_ids_to_remove.append(p.getIdAttribute()) for parameter_id in parameter_ids_to_remove: # Remove corresponding rules @@ -927,9 +952,8 @@ def _process_annotations(self) -> None: # Remove parameter self.sbml.removeParameter(parameter_id) - @log_execution_time('processing SBML parameters', logger) - def _process_parameters(self, - constant_parameters: List[str] = None) -> None: + @log_execution_time("processing SBML parameters", logger) + def _process_parameters(self, constant_parameters: List[str] = None) -> None: """ Get parameter information from SBML model. @@ -943,8 +967,10 @@ def _process_parameters(self, # Ensure specified constant parameters exist in the model for parameter in constant_parameters: if not self.sbml.getParameter(parameter): - raise KeyError('Cannot make %s a constant parameter: ' - 'Parameter does not exist.' % parameter) + raise KeyError( + "Cannot make %s a constant parameter: " + "Parameter does not exist." % parameter + ) fixed_parameters = [ parameter @@ -952,38 +978,42 @@ def _process_parameters(self, if parameter.getId() in constant_parameters ] for parameter in fixed_parameters: - if self._get_element_initial_assignment(parameter.getId()) is not \ - None or self.is_assignment_rule_target(parameter) or \ - self.is_rate_rule_target(parameter): + if ( + self._get_element_initial_assignment(parameter.getId()) is not None + or self.is_assignment_rule_target(parameter) + or self.is_rate_rule_target(parameter) + ): raise SBMLException( - f'Cannot turn parameter {parameter.getId()} into a ' - 'constant/fixed parameter since it either has an ' - 'initial assignment or is the target of an assignment or ' - 'rate rule.' + f"Cannot turn parameter {parameter.getId()} into a " + "constant/fixed parameter since it either has an " + "initial assignment or is the target of an assignment or " + "rate rule." ) parameters = [ - parameter for parameter - in self.sbml.getListOfParameters() + parameter + for parameter in self.sbml.getListOfParameters() if parameter.getId() not in constant_parameters and self._get_element_initial_assignment(parameter.getId()) is None and not self.is_assignment_rule_target(parameter) ] loop_settings = { - SymbolId.PARAMETER: {'var': parameters, 'name': 'parameter'}, - SymbolId.FIXED_PARAMETER: {'var': fixed_parameters, - 'name': 'fixed_parameter'} + SymbolId.PARAMETER: {"var": parameters, "name": "parameter"}, + SymbolId.FIXED_PARAMETER: { + "var": fixed_parameters, + "name": "fixed_parameter", + }, } for partype, settings in loop_settings.items(): - for par in settings['var']: + for par in settings["var"]: self.symbols[partype][_get_identifier_symbol(par)] = { - 'name': par.getName() if par.isSetName() else par.getId(), - 'value': par.getValue() + "name": par.getName() if par.isSetName() else par.getId(), + "value": par.getValue(), } - @log_execution_time('processing SBML reactions', logger) + @log_execution_time("processing SBML reactions", logger) def _process_reactions(self): """ Get reactions from SBML model. @@ -1001,39 +1031,39 @@ def _process_reactions(self): # level 3 version 2 the ID attribute was not mandatory and may be # unset) self.flux_ids = [ - f"flux_{reaction.getId()}" if reaction.isSetId() + f"flux_{reaction.getId()}" + if reaction.isSetId() else f"flux_r{reaction_idx}" for reaction_idx, reaction in enumerate(reactions) - ] or ['flux_r0'] + ] or ["flux_r0"] reaction_ids = [ - reaction.getId() for reaction in reactions - if reaction.isSetId() + reaction.getId() for reaction in reactions if reaction.isSetId() ] for reaction_index, reaction in enumerate(reactions): - for element_list, sign in [(reaction.getListOfReactants(), -1), - (reaction.getListOfProducts(), 1)]: + for element_list, sign in [ + (reaction.getListOfReactants(), -1), + (reaction.getListOfProducts(), 1), + ]: for element in element_list: - stoichiometry = self._get_element_stoichiometry( - element - ) + stoichiometry = self._get_element_stoichiometry(element) sbml_species = self.sbml.getSpecies(element.getSpecies()) if self.is_assignment_rule_target(sbml_species): continue species_id = _get_identifier_symbol(sbml_species) species = self.symbols[SymbolId.SPECIES][species_id] - if species['constant']: + if species["constant"]: continue # Division by species compartment size (to find the # rate of change in species concentration) now occurs # in the `dx_dt` method in "de_export.py", which also # accounts for possibly variable compartments. - self.stoichiometric_matrix[species['index'], - reaction_index] += \ - sign * stoichiometry * species['conversion_factor'] + self.stoichiometric_matrix[species["index"], reaction_index] += ( + sign * stoichiometry * species["conversion_factor"] + ) if reaction.isSetId(): sym_math = self._local_symbols[reaction.getId()] else: @@ -1047,11 +1077,11 @@ def _process_reactions(self): for symbol in self.flux_vector[reaction_index].free_symbols ): raise SBMLException( - 'Kinetic laws involving reaction ids are currently' - ' not supported!' + "Kinetic laws involving reaction ids are currently" + " not supported!" ) - @log_execution_time('processing SBML rules', logger) + @log_execution_time("processing SBML rules", logger) def _process_rules(self) -> None: """ Process Rules defined in the SBML model. @@ -1066,22 +1096,22 @@ def _process_rules(self) -> None: # not interested in implementing level 2 boundary condition # shenanigans, see test 01787 in the sbml testsuite raise SBMLException( - 'Algebraic rules are only supported in SBML L3+' + "Algebraic rules are only supported in SBML L3+" ) self._process_rule_algebraic(rule) else: self._process_rule_assignment(rule) self.symbols[SymbolId.EXPRESSION] = toposort_symbols( - self.symbols[SymbolId.EXPRESSION], 'value' + self.symbols[SymbolId.EXPRESSION], "value" ) # expressions must not occur in definition of x0 for species in self.symbols[SymbolId.SPECIES].values(): - species['init'] = self._make_initial( - smart_subs_dict(species['init'], - self.symbols[SymbolId.EXPRESSION], - 'value') + species["init"] = self._make_initial( + smart_subs_dict( + species["init"], self.symbols[SymbolId.EXPRESSION], "value" + ) ) def _process_rule_algebraic(self, rule: sbml.AlgebraicRule): @@ -1104,22 +1134,25 @@ def _process_rule_algebraic(self, rule: sbml.AlgebraicRule): continue # and there must also not be a rate rule or assignment # rule for it - if self.is_assignment_rule_target(sbml_var) or \ - self.is_rate_rule_target(sbml_var): + if self.is_assignment_rule_target(sbml_var) or self.is_rate_rule_target( + sbml_var + ): continue # Furthermore, if the entity is a Species object, its value # must not be determined by reactions, which means that it # must either have the attribute boundaryCondition=“false” # or else not be involved in any reaction at all. is_species = isinstance(sbml_var, sbml.Species) - is_boundary_condition = is_species and \ - sbml_var.isSetBoundaryCondition() and \ - sbml_var.getBoundaryCondition() - is_involved_in_reaction = is_species and \ - not smart_is_zero_matrix(self.stoichiometric_matrix[ - list(self.symbols[SymbolId.SPECIES].keys()).index(symbol), - : - ]) + is_boundary_condition = ( + is_species + and sbml_var.isSetBoundaryCondition() + and sbml_var.getBoundaryCondition() + ) + is_involved_in_reaction = is_species and not smart_is_zero_matrix( + self.stoichiometric_matrix[ + list(self.symbols[SymbolId.SPECIES].keys()).index(symbol), : + ] + ) if is_species and not is_boundary_condition and is_involved_in_reaction: continue free_variables.add(symbol) @@ -1129,20 +1162,18 @@ def _process_rule_algebraic(self, rule: sbml.AlgebraicRule): assert len(free_variables) >= 1 self.symbols[SymbolId.ALGEBRAIC_EQUATION][ - f'ae{len(self.symbols[SymbolId.ALGEBRAIC_EQUATION])}' - ] = { - 'value': formula - } + f"ae{len(self.symbols[SymbolId.ALGEBRAIC_EQUATION])}" + ] = {"value": formula} # remove the symbol from the original definition and add to # algebraic symbols (if not already done) for var in free_variables: if var in self.symbols[SymbolId.FIXED_PARAMETER]: raise SBMLException( - 'There are algebraic rules that specify the ' - f'value of {var}, which is also marked as ' - 'fixed parameter. This is currently not supported! ' - f'If {var} is supposed to be a fixed parameter, ' - 'set its SBML attribute `constant` to True.' + "There are algebraic rules that specify the " + f"value of {var}, which is also marked as " + "fixed parameter. This is currently not supported! " + f"If {var} is supposed to be a fixed parameter, " + "set its SBML attribute `constant` to True." ) if var in self.symbols[SymbolId.ALGEBRAIC_STATE]: @@ -1150,49 +1181,51 @@ def _process_rule_algebraic(self, rule: sbml.AlgebraicRule): if var in self.compartments: init = self.compartments[var] symbol = { - 'name': str(var), - 'value': init, + "name": str(var), + "value": init, } - symbol_id = 'compartment' + symbol_id = "compartment" var_ix = np.nan del self.compartments[var] else: symbol_id, source_symbols = next( - ((symbol_id, self.symbols[symbol_id]) - for symbol_id in (SymbolId.PARAMETER, SymbolId.SPECIES) - if var in self.symbols[symbol_id]), + ( + (symbol_id, self.symbols[symbol_id]) + for symbol_id in (SymbolId.PARAMETER, SymbolId.SPECIES) + if var in self.symbols[symbol_id] + ), ) var_ix = list(source_symbols.keys()).index(var) symbol = source_symbols.pop(var) # update symbol and adapt stoichiometric matrix if symbol_id != SymbolId.SPECIES: # parameters have numeric values so we can use Float here - symbol['init'] = sp.Float(symbol.pop('value')) + symbol["init"] = sp.Float(symbol.pop("value")) # if not a species, add a zeros row to the stoichiometric # matrix - if (isinstance(symbol['init'], float) - and np.isnan(symbol['init'])) or \ - (isinstance(symbol['init'], sp.Number) - and symbol['init'] == sp.nan): + if (isinstance(symbol["init"], float) and np.isnan(symbol["init"])) or ( + isinstance(symbol["init"], sp.Number) and symbol["init"] == sp.nan + ): # placeholder, needs to be determined in IC calculation - symbol['init'] = sp.Float(0.0) + symbol["init"] = sp.Float(0.0) self.stoichiometric_matrix = self.stoichiometric_matrix.row_insert( self.stoichiometric_matrix.shape[0], - sp.SparseMatrix([ - [0] * self.stoichiometric_matrix.shape[1] - ]) + sp.SparseMatrix([[0] * self.stoichiometric_matrix.shape[1]]), ) elif var_ix != self.stoichiometric_matrix.shape[0] - 1: # if not the last col, move it to the end # as we reorder state variables - state_ordering = list(range( - len(self.symbols[SymbolId.SPECIES]) + - len(self.symbols[SymbolId.ALGEBRAIC_STATE]) + - 1 - )) + state_ordering = list( + range( + len(self.symbols[SymbolId.SPECIES]) + + len(self.symbols[SymbolId.ALGEBRAIC_STATE]) + + 1 + ) + ) state_ordering.append(state_ordering.pop(var_ix)) - self.stoichiometric_matrix = \ - self.stoichiometric_matrix[state_ordering, :] + self.stoichiometric_matrix = self.stoichiometric_matrix[ + state_ordering, : + ] self.symbols[SymbolId.ALGEBRAIC_STATE][var] = symbol @@ -1206,10 +1239,14 @@ def _process_rule_assignment(self, rule: sbml.AssignmentRule): annotation = AbstractSpline.get_annotation(rule) if annotation is not None: spline = AbstractSpline.from_annotation( - sym_id, annotation, + sym_id, + annotation, locals_=self._local_symbols, ) - if spline.evaluate_at != amici_time_symbol and spline.evaluate_at != sbml_time_symbol: + if ( + spline.evaluate_at != amici_time_symbol + and spline.evaluate_at != sbml_time_symbol + ): raise NotImplementedError( "AMICI at the moment does not support splines " "whose evaluation point is not the model time." @@ -1232,16 +1269,16 @@ def _process_rule_assignment(self, rule: sbml.AssignmentRule): self.parameter_assignment_rules[sym_id] = formula self.symbols[SymbolId.EXPRESSION][sym_id] = { - 'name': str(sym_id), - 'value': formula + "name": str(sym_id), + "value": formula, } def _process_time(self) -> None: """ Convert time_symbol into cpp variable. """ - sbml_time_symbol = symbol_with_assumptions('time') - amici_time_symbol = symbol_with_assumptions('t') + sbml_time_symbol = symbol_with_assumptions("time") + amici_time_symbol = symbol_with_assumptions("t") self.amici_time_symbol = amici_time_symbol self._replace_in_all_expressions(sbml_time_symbol, amici_time_symbol) @@ -1253,17 +1290,16 @@ def _convert_event_assignment_parameter_targets_to_species(self): This is for the convenience of only implementing event assignments for "species". """ - parameter_targets = \ - _collect_event_assignment_parameter_targets(self.sbml) + parameter_targets = _collect_event_assignment_parameter_targets(self.sbml) for parameter_target in parameter_targets: # Parameter rate rules already exist as species. if parameter_target in self.symbols[SymbolId.SPECIES]: continue if parameter_target in self.parameter_assignment_rules: raise SBMLException( - 'AMICI does not currently support models with SBML events ' - 'that affect parameters that are also the target of ' - 'assignment rules.' + "AMICI does not currently support models with SBML events " + "that affect parameters that are also the target of " + "assignment rules." ) parameter_def = None for symbol_id in {SymbolId.PARAMETER, SymbolId.FIXED_PARAMETER}: @@ -1272,36 +1308,33 @@ def _convert_event_assignment_parameter_targets_to_species(self): # `symbol_id` dictionaries. if parameter_def is not None: raise AssertionError( - 'Unexpected error. The parameter target of an ' - 'event assignment was processed twice.' + "Unexpected error. The parameter target of an " + "event assignment was processed twice." ) - parameter_def = \ - self.symbols[symbol_id].pop(parameter_target) + parameter_def = self.symbols[symbol_id].pop(parameter_target) if parameter_def is None: # this happens for parameters that have initial assignments # or are assignment rule targets par = self.sbml.getElementBySId(str(parameter_target)) - ia_init = self._get_element_initial_assignment( - par.getId() - ) + ia_init = self._get_element_initial_assignment(par.getId()) parameter_def = { - 'name': par.getName() if par.isSetName() else par.getId(), - 'value': par.getValue() if ia_init is None else ia_init + "name": par.getName() if par.isSetName() else par.getId(), + "value": par.getValue() if ia_init is None else ia_init, } # Fixed parameters are added as species such that they can be # targets of events. self.symbols[SymbolId.SPECIES][parameter_target] = { - 'name': parameter_def['name'], - 'init': sp.Float(parameter_def['value']), + "name": parameter_def["name"], + "init": sp.Float(parameter_def["value"]), # 'compartment': None, # can ignore for amounts - 'constant': False, - 'amount': True, + "constant": False, + "amount": True, # 'conversion_factor': 1.0, # can be ignored - 'index': len(self.symbols[SymbolId.SPECIES]), - 'dt': sp.Float(0), + "index": len(self.symbols[SymbolId.SPECIES]), + "dt": sp.Float(0), } - @log_execution_time('processing SBML events', logger) + @log_execution_time("processing SBML events", logger) def _process_events(self) -> None: """Process SBML events.""" events = self.sbml.getListOfEvents() @@ -1311,7 +1344,7 @@ def get_empty_bolus_value() -> sp.Float: Used in the event update vector for species that are not affected by the event. """ - return sp.Symbol('AMICI_EMTPY_BOLUS') + return sp.Symbol("AMICI_EMTPY_BOLUS") # Used to update species concentrations when an event affects a # compartment. @@ -1321,20 +1354,21 @@ def get_empty_bolus_value() -> sp.Float: } for species, species_def in self.symbols[SymbolId.SPECIES].items(): if ( - # Species is a concentration - not species_def.get('amount', True) and - # Species has a compartment - 'compartment' in species_def + # Species is a concentration + not species_def.get("amount", True) + and + # Species has a compartment + "compartment" in species_def ): - concentration_species_by_compartment[ - species_def['compartment'] - ].append(species) + concentration_species_by_compartment[species_def["compartment"]].append( + species + ) for ievent, event in enumerate(events): # get the event id (which is optional unfortunately) event_id = event.getId() - if event_id is None or event_id == '': - event_id = f'event_{ievent}' + if event_id is None or event_id == "": + event_id = f"event_{ievent}" event_sym = sp.Symbol(event_id) # get and parse the trigger function @@ -1351,8 +1385,7 @@ def get_empty_bolus_value() -> sp.Float: event_assignments = event.getListOfEventAssignments() compartment_event_assignments = set() for event_assignment in event_assignments: - variable_sym = \ - symbol_with_assumptions(event_assignment.getVariable()) + variable_sym = symbol_with_assumptions(event_assignment.getVariable()) if event_assignment.getMath() is None: # Ignore event assignments with no change in value. continue @@ -1363,10 +1396,10 @@ def get_empty_bolus_value() -> sp.Float: bolus[index] = formula except ValueError: raise SBMLException( - 'Could not process event assignment for ' - f'{str(variable_sym)}. AMICI currently only allows ' - 'event assignments to species; parameters; or, ' - 'compartments with rate rules, at the moment.' + "Could not process event assignment for " + f"{str(variable_sym)}. AMICI currently only allows " + "event assignments to species; parameters; or, " + "compartments with rate rules, at the moment." ) try: # Try working with the formula now to detect errors @@ -1374,15 +1407,14 @@ def get_empty_bolus_value() -> sp.Float: _ = formula - variable_sym except TypeError: raise SBMLException( - 'Could not process event assignment for ' - f'{str(variable_sym)}. AMICI only allows symbolic ' - 'expressions as event assignments.' + "Could not process event assignment for " + f"{str(variable_sym)}. AMICI only allows symbolic " + "expressions as event assignments." ) if variable_sym in concentration_species_by_compartment: compartment_event_assignments.add(variable_sym) - for comp, assignment in \ - self.compartment_assignment_rules.items(): + for comp, assignment in self.compartment_assignment_rules.items(): if variable_sym not in assignment.free_symbols: continue compartment_event_assignments.add(comp) @@ -1391,13 +1423,13 @@ def get_empty_bolus_value() -> sp.Float: # in compartments that were affected by the event assignments. for compartment_sym in compartment_event_assignments: for species_sym in concentration_species_by_compartment[ - compartment_sym + compartment_sym ]: # If the species was not affected by an event assignment # then the old value should be updated. if ( - bolus[state_vector.index(species_sym)] - == get_empty_bolus_value() + bolus[state_vector.index(species_sym)] + == get_empty_bolus_value() ): species_value = species_sym # else the species was affected by an event assignment, @@ -1415,11 +1447,11 @@ def get_empty_bolus_value() -> sp.Float: for index in range(len(bolus)): if bolus[index] != get_empty_bolus_value(): bolus[index] -= state_vector[index] - bolus[index] = bolus[index].subs(get_empty_bolus_value(), - sp.Float(0.0)) + bolus[index] = bolus[index].subs(get_empty_bolus_value(), sp.Float(0.0)) - initial_value = trigger_sbml.getInitialValue() \ - if trigger_sbml is not None else True + initial_value = ( + trigger_sbml.getInitialValue() if trigger_sbml is not None else True + ) if self.symbols[SymbolId.ALGEBRAIC_EQUATION] and not initial_value: # in principle this could be implemented, requires running # IDACalcIc (in solver->setup) before check event initialization @@ -1428,23 +1460,23 @@ def get_empty_bolus_value() -> sp.Float: # (it might not, but this could be checked when someone actually # needs the feature). raise SBMLException( - 'Events with initial values are not supported in models with' - ' algebraic rules.' + "Events with initial values are not supported in models with" + " algebraic rules." ) self.symbols[SymbolId.EVENT][event_sym] = { - 'name': event_id, - 'value': trigger, - 'state_update': sp.MutableDenseMatrix(bolus), - 'initial_value': initial_value, + "name": event_id, + "value": trigger, + "state_update": sp.MutableDenseMatrix(bolus), + "initial_value": initial_value, } - @log_execution_time('processing SBML observables', logger) + @log_execution_time("processing SBML observables", logger) def _process_observables( self, observables: Union[Dict[str, Dict[str, str]], None], sigmas: Dict[str, Union[str, float]], - noise_distributions: Dict[str, str] + noise_distributions: Dict[str, str], ) -> None: """ Perform symbolic computations required for observable and objective @@ -1464,8 +1496,7 @@ def _process_observables( See :py:func:`sbml2amici`. """ - _validate_observables(observables, sigmas, noise_distributions, - events=False) + _validate_observables(observables, sigmas, noise_distributions, events=False) # add user-provided observables or make all species, and compartments # with assignment rules, observable @@ -1476,22 +1507,18 @@ def _process_observables( self.symbols[SymbolId.OBSERVABLE] = { symbol_with_assumptions(obs): { - 'name': definition.get('name', f'y{iobs}'), - 'value': self._sympy_from_sbml_math( - definition['formula'] + "name": definition.get("name", f"y{iobs}"), + "value": self._sympy_from_sbml_math(definition["formula"]), + "transformation": noise_distribution_to_observable_transformation( + noise_distributions.get(obs, "normal") ), - 'transformation': - noise_distribution_to_observable_transformation( - noise_distributions.get(obs, 'normal') - ) } for iobs, (obs, definition) in enumerate(observables.items()) } # check for nesting of observables (unsupported) observable_syms = set(self.symbols[SymbolId.OBSERVABLE].keys()) for obs in self.symbols[SymbolId.OBSERVABLE].values(): - if any(sym in observable_syms - for sym in obs['value'].free_symbols): + if any(sym in observable_syms for sym in obs["value"].free_symbols): raise ValueError( "Nested observables are not supported, " f"but observable `{obs['name']} = {obs['value']}` " @@ -1500,17 +1527,16 @@ def _process_observables( elif observables is None: self._generate_default_observables() - _check_symbol_nesting(self.symbols[SymbolId.OBSERVABLE], - 'eventObservable') + _check_symbol_nesting(self.symbols[SymbolId.OBSERVABLE], "eventObservable") self._process_log_likelihood(sigmas, noise_distributions) - @log_execution_time('processing SBML event observables', logger) + @log_execution_time("processing SBML event observables", logger) def _process_event_observables( - self, - event_observables: Dict[str, Dict[str, str]], - event_sigmas: Dict[str, Union[str, float]], - event_noise_distributions: Dict[str, str] + self, + event_observables: Dict[str, Dict[str, str]], + event_sigmas: Dict[str, Union[str, float]], + event_noise_distributions: Dict[str, str], ) -> None: """ Perform symbolic computations required for observable and objective @@ -1528,54 +1554,54 @@ def _process_event_observables( if event_observables is None: return - _validate_observables(event_observables, event_sigmas, - event_noise_distributions, - events=True) + _validate_observables( + event_observables, event_sigmas, event_noise_distributions, events=True + ) # gather local symbols before parsing observable and sigma formulas for obs, definition in event_observables.items(): self.add_local_symbol(obs, symbol_with_assumptions(obs)) # check corresponding event exists - if sp.Symbol(definition['event']) not in \ - self.symbols[SymbolId.EVENT]: + if sp.Symbol(definition["event"]) not in self.symbols[SymbolId.EVENT]: raise ValueError( - 'Could not find an event with the event identifier ' + "Could not find an event with the event identifier " f'{definition["event"]} for the event observable with name' f'{definition["name"]}.' ) self.symbols[SymbolId.EVENT_OBSERVABLE] = { symbol_with_assumptions(obs): { - 'name': definition.get('name', f'z{iobs}'), - 'value': self._sympy_from_sbml_math( - definition['formula'] + "name": definition.get("name", f"z{iobs}"), + "value": self._sympy_from_sbml_math(definition["formula"]), + "event": sp.Symbol(definition.get("event")), + "transformation": noise_distribution_to_observable_transformation( + event_noise_distributions.get(obs, "normal") ), - 'event': sp.Symbol(definition.get('event')), - 'transformation': - noise_distribution_to_observable_transformation( - event_noise_distributions.get(obs, 'normal') - ) } - for iobs, (obs, definition) in - enumerate(event_observables.items()) + for iobs, (obs, definition) in enumerate(event_observables.items()) } - wrong_t = sp.Symbol('t') + wrong_t = sp.Symbol("t") for eo in self.symbols[SymbolId.EVENT_OBSERVABLE].values(): - if eo['value'].has(wrong_t): - warnings.warn(f'Event observable {eo["name"]} uses `t` in ' - 'it\'s formula which is not the time variable. ' - 'For the time variable, please use `time` ' - 'instead!') + if eo["value"].has(wrong_t): + warnings.warn( + f'Event observable {eo["name"]} uses `t` in ' + "it's formula which is not the time variable. " + "For the time variable, please use `time` " + "instead!" + ) # check for nesting of observables (unsupported) - _check_symbol_nesting(self.symbols[SymbolId.EVENT_OBSERVABLE], - 'eventObservable') + _check_symbol_nesting( + self.symbols[SymbolId.EVENT_OBSERVABLE], "eventObservable" + ) - self._process_log_likelihood(event_sigmas, event_noise_distributions, - events=True) - self._process_log_likelihood(event_sigmas, event_noise_distributions, - events=True, event_reg=True) + self._process_log_likelihood( + event_sigmas, event_noise_distributions, events=True + ) + self._process_log_likelihood( + event_sigmas, event_noise_distributions, events=True, event_reg=True + ) def _generate_default_observables(self): """ @@ -1583,40 +1609,44 @@ def _generate_default_observables(self): (initial) assignment rules. """ self.symbols[SymbolId.OBSERVABLE] = { - symbol_with_assumptions(f'y{state_id}'): { - 'name': state['name'], - 'value': state_id + symbol_with_assumptions(f"y{state_id}"): { + "name": state["name"], + "value": state_id, } - for state_id, state - in { + for state_id, state in { **self.symbols[SymbolId.SPECIES], - **self.symbols[SymbolId.ALGEBRAIC_STATE] + **self.symbols[SymbolId.ALGEBRAIC_STATE], }.items() } for variable, formula in itt.chain( - self.parameter_assignment_rules.items(), - self.initial_assignments.items(), - self.compartment_assignment_rules.items(), - self.species_assignment_rules.items(), - self.compartments.items() + self.parameter_assignment_rules.items(), + self.initial_assignments.items(), + self.compartment_assignment_rules.items(), + self.species_assignment_rules.items(), + self.compartments.items(), ): - symbol = symbol_with_assumptions(f'y{variable}') + symbol = symbol_with_assumptions(f"y{variable}") # Assignment rules take precedence over compartment volume # definitions, so they need to be evaluated first. # Species assignment rules always overwrite. - if symbol in self.symbols[SymbolId.OBSERVABLE] \ - and variable not in self.species_assignment_rules: + if ( + symbol in self.symbols[SymbolId.OBSERVABLE] + and variable not in self.species_assignment_rules + ): continue self.symbols[SymbolId.OBSERVABLE][symbol] = { - 'name': str(variable), 'value': formula + "name": str(variable), + "value": formula, } - def _process_log_likelihood(self, - sigmas: Dict[str, Union[str, float]], - noise_distributions: Dict[str, str], - events: bool = False, - event_reg: bool = False): + def _process_log_likelihood( + self, + sigmas: Dict[str, Union[str, float]], + noise_distributions: Dict[str, str], + events: bool = False, + event_reg: bool = False, + ): """ Perform symbolic computations required for objective function evaluation. @@ -1654,43 +1684,45 @@ def _process_log_likelihood(self, llh_symbol = SymbolId.LLHY for obs_id, obs in self.symbols[obs_symbol].items(): - obs['measurement_symbol'] = generate_measurement_symbol(obs_id) + obs["measurement_symbol"] = generate_measurement_symbol(obs_id) if event_reg: - obs['reg_symbol'] = generate_regularization_symbol(obs_id) + obs["reg_symbol"] = generate_regularization_symbol(obs_id) if not event_reg: self.symbols[sigma_symbol] = { - symbol_with_assumptions(f'sigma_{obs_id}'): { - 'name': f'sigma_{obs["name"]}', - 'value': self._sympy_from_sbml_math( - sigmas.get(str(obs_id), '1.0') - ) + symbol_with_assumptions(f"sigma_{obs_id}"): { + "name": f'sigma_{obs["name"]}', + "value": self._sympy_from_sbml_math(sigmas.get(str(obs_id), "1.0")), } for obs_id, obs in self.symbols[obs_symbol].items() } self.symbols[llh_symbol] = {} for (obs_id, obs), (sigma_id, sigma) in zip( - self.symbols[obs_symbol].items(), - self.symbols[sigma_symbol].items() + self.symbols[obs_symbol].items(), self.symbols[sigma_symbol].items() ): - symbol = symbol_with_assumptions(f'J{obs_id}') - dist = noise_distributions.get(str(obs_id), 'normal') + symbol = symbol_with_assumptions(f"J{obs_id}") + dist = noise_distributions.get(str(obs_id), "normal") cost_fun = noise_distribution_to_cost_function(dist)(obs_id) - value = sp.sympify(cost_fun, locals=dict(zip( - _get_str_symbol_identifiers(obs_id), - (obs_id, obs['measurement_symbol'], sigma_id) - ))) + value = sp.sympify( + cost_fun, + locals=dict( + zip( + _get_str_symbol_identifiers(obs_id), + (obs_id, obs["measurement_symbol"], sigma_id), + ) + ), + ) if event_reg: - value = value.subs(obs['measurement_symbol'], 0.0) - value = value.subs(obs_id, obs['reg_symbol']) + value = value.subs(obs["measurement_symbol"], 0.0) + value = value.subs(obs_id, obs["reg_symbol"]) self.symbols[llh_symbol][symbol] = { - 'name': f'J{obs["name"]}', - 'value': value, - 'dist': dist, - } + "name": f'J{obs["name"]}', + "value": value, + "dist": dist, + } - @log_execution_time('processing SBML initial assignments', logger) + @log_execution_time("processing SBML initial assignments", logger) def _process_initial_assignments(self): """ Accounts for initial assignments of parameters and species @@ -1700,17 +1732,18 @@ def _process_initial_assignments(self): """ for ia in self.sbml.getListOfInitialAssignments(): identifier = _get_identifier_symbol(ia) - if identifier in itt.chain(self.symbols[SymbolId.SPECIES], - self.compartments): + if identifier in itt.chain( + self.symbols[SymbolId.SPECIES], self.compartments + ): continue sym_math = self._get_element_initial_assignment(ia.getId()) if sym_math is None: continue - sym_math = self._make_initial(smart_subs_dict( - sym_math, self.symbols[SymbolId.EXPRESSION], 'value' - )) + sym_math = self._make_initial( + smart_subs_dict(sym_math, self.symbols[SymbolId.EXPRESSION], "value") + ) self.initial_assignments[_get_identifier_symbol(ia)] = sym_math # sort and flatten @@ -1723,7 +1756,7 @@ def _process_initial_assignments(self): for identifier, sym_math in list(self.initial_assignments.items()): self._replace_in_all_expressions(identifier, sym_math) - @log_execution_time('processing SBML species references', logger) + @log_execution_time("processing SBML species references", logger) def _process_species_references(self): """ Replaces species references that define anything but stoichiometries. @@ -1734,21 +1767,26 @@ def _process_species_references(self): # doesnt look like there is a better way to get hold of those lists: species_references = _get_list_of_species_references(self.sbml) for species_reference in species_references: - if hasattr(species_reference, 'getStoichiometryMath') and \ - species_reference.getStoichiometryMath() is not None: - raise SBMLException('StoichiometryMath is currently not ' - 'supported for species references.') - if species_reference.getId() == '': + if ( + hasattr(species_reference, "getStoichiometryMath") + and species_reference.getStoichiometryMath() is not None + ): + raise SBMLException( + "StoichiometryMath is currently not " + "supported for species references." + ) + if species_reference.getId() == "": continue stoich = self._get_element_stoichiometry(species_reference) self._replace_in_all_expressions( _get_identifier_symbol(species_reference), - self._sympy_from_sbml_math(stoich) + self._sympy_from_sbml_math(stoich), ) - def _make_initial(self, sym_math: Union[sp.Expr, None, float] - ) -> Union[sp.Expr, None, float]: + def _make_initial( + self, sym_math: Union[sp.Expr, None, float] + ) -> Union[sp.Expr, None, float]: """ Transforms an expression to its value at the initial time point by replacing species by their initial values. @@ -1763,11 +1801,10 @@ def _make_initial(self, sym_math: Union[sp.Expr, None, float] return sym_math for species_id, species in self.symbols[SymbolId.SPECIES].items(): - if 'init' in species: - sym_math = smart_subs(sym_math, species_id, species['init']) + if "init" in species: + sym_math = smart_subs(sym_math, species_id, species["init"]) - sym_math = smart_subs(sym_math, self._local_symbols['time'], - sp.Float(0)) + sym_math = smart_subs(sym_math, self._local_symbols["time"], sp.Float(0)) return sym_math @@ -1785,11 +1822,18 @@ def process_conservation_laws(self, ode_model) -> None: ode_model, conservation_laws ) # Non-constant species processed here - if "AMICI_EXPERIMENTAL_SBML_NONCONST_CLS" in os.environ \ - or "GITHUB_ACTIONS" in os.environ: - species_solver = list(set( - self._add_conservation_for_non_constant_species( - ode_model, conservation_laws)) & set(species_solver)) + if ( + "AMICI_EXPERIMENTAL_SBML_NONCONST_CLS" in os.environ + or "GITHUB_ACTIONS" in os.environ + ): + species_solver = list( + set( + self._add_conservation_for_non_constant_species( + ode_model, conservation_laws + ) + ) + & set(species_solver) + ) # add algebraic variables to species_solver as they were ignored above ndifferential = len(ode_model._differential_states) @@ -1804,16 +1848,15 @@ def process_conservation_laws(self, ode_model) -> None: species_solver = list(range(ode_model.num_states_rdata())) # prune out species from stoichiometry and - self.stoichiometric_matrix = \ - self.stoichiometric_matrix[species_solver, :] + self.stoichiometric_matrix = self.stoichiometric_matrix[species_solver, :] # add the found CLs to the ode_model for cl in conservation_laws: ode_model.add_conservation_law(**cl) def _get_conservation_laws_demartino( - self, - ode_model: DEModel, + self, + ode_model: DEModel, ) -> List[Tuple[int, List[int], List[float]]]: """Identify conservation laws based on algorithm by DeMartino et al. (see conserved_moieties.py). @@ -1825,32 +1868,32 @@ def _get_conservation_laws_demartino( quantity (including the eliminated one) (2) coefficients for the species in (1) """ - from .conserved_quantities_demartino \ - import compute_moiety_conservation_laws + from .conserved_quantities_demartino import compute_moiety_conservation_laws - sm = self.stoichiometric_matrix[:len(self.symbols[SymbolId.SPECIES]), :] + sm = self.stoichiometric_matrix[: len(self.symbols[SymbolId.SPECIES]), :] try: - stoichiometric_list = [ - float(entry) for entry in sm.T.flat() - ] + stoichiometric_list = [float(entry) for entry in sm.T.flat()] except TypeError: # Due to the numerical algorithm currently used to identify # conserved quantities, we can't have symbols in the # stoichiometric matrix - warnings.warn("Conservation laws for non-constant species in " - "combination with parameterized stoichiometric " - "coefficients are not currently supported " - "and will be turned off.") + warnings.warn( + "Conservation laws for non-constant species in " + "combination with parameterized stoichiometric " + "coefficients are not currently supported " + "and will be turned off." + ) return [] if not _non_const_conservation_laws_supported(self.sbml): return [] cls_state_idxs, cls_coefficients = compute_moiety_conservation_laws( - stoichiometric_list, *sm.shape, + stoichiometric_list, + *sm.shape, rng_seed=32, - species_names=[str(x.get_id()) for x in ode_model._differential_states] + species_names=[str(x.get_id()) for x in ode_model._differential_states], ) # Sparsify conserved quantities @@ -1863,8 +1906,9 @@ def _get_conservation_laws_demartino( # pivot species are the ones to be eliminated. The resulting state # expressions are sparse and void of any circular dependencies. A = sp.zeros(len(cls_coefficients), len(ode_model._differential_states)) - for i_cl, (cl, coefficients) in enumerate(zip(cls_state_idxs, - cls_coefficients)): + for i_cl, (cl, coefficients) in enumerate( + zip(cls_state_idxs, cls_coefficients) + ): for i, c in zip(cl, coefficients): A[i_cl, i] = sp.Rational(c) rref, pivots = A.rref() @@ -1872,16 +1916,14 @@ def _get_conservation_laws_demartino( raw_cls = [] for i_cl, target_state_model_idx in enumerate(pivots): # collect values for species engaged in the current CL - state_idxs = [i for i, coeff in enumerate(rref[i_cl, :]) - if coeff] + state_idxs = [i for i, coeff in enumerate(rref[i_cl, :]) if coeff] coefficients = [coeff for coeff in rref[i_cl, :] if coeff] - raw_cls.append((target_state_model_idx, state_idxs, - coefficients),) + raw_cls.append( + (target_state_model_idx, state_idxs, coefficients), + ) return raw_cls - def _get_conservation_laws_rref( - self - ) -> List[Tuple[int, List[int], List[float]]]: + def _get_conservation_laws_rref(self) -> List[Tuple[int, List[int], List[float]]]: """Identify conservation laws based on left nullspace of the stoichiometric matrix, computed through (numeric) Gaussian elimination @@ -1897,18 +1939,19 @@ def _get_conservation_laws_rref( try: S = np.asarray( - self.stoichiometric_matrix[ - :len(self.symbols[SymbolId.SPECIES]), : - ], dtype=float + self.stoichiometric_matrix[: len(self.symbols[SymbolId.SPECIES]), :], + dtype=float, ) except TypeError: # Due to the numerical algorithm currently used to identify # conserved quantities, we can't have symbols in the # stoichiometric matrix - warnings.warn("Conservation laws for non-constant species in " - "combination with parameterized stoichiometric " - "coefficients are not currently supported " - "and will be turned off.") + warnings.warn( + "Conservation laws for non-constant species in " + "combination with parameterized stoichiometric " + "coefficients are not currently supported " + "and will be turned off." + ) return [] if not _non_const_conservation_laws_supported(self.sbml): @@ -1933,14 +1976,14 @@ def _get_conservation_laws_rref( for row in kernel: state_idxs = [i for i, coeff in enumerate(row) if coeff] coefficients = [coeff for coeff in row if coeff] - raw_cls.append((state_idxs[0], state_idxs, coefficients),) + raw_cls.append( + (state_idxs[0], state_idxs, coefficients), + ) return raw_cls def _add_conservation_for_non_constant_species( - self, - model: DEModel, - conservation_laws: List[ConservationLaw] + self, model: DEModel, conservation_laws: List[ConservationLaw] ) -> List[int]: """Add non-constant species to conservation laws @@ -1969,23 +2012,21 @@ def _add_conservation_for_non_constant_species( # keep new conservations laws separate until we know everything worked new_conservation_laws = [] # previously removed constant species - eliminated_state_ids = {cl['state'] for cl in conservation_laws} + eliminated_state_ids = {cl["state"] for cl in conservation_laws} all_state_ids = [x.get_id() for x in model.states()] all_compartment_sizes = [] for state_id in all_state_ids: symbol = { **self.symbols[SymbolId.SPECIES], - **self.symbols[SymbolId.ALGEBRAIC_STATE] + **self.symbols[SymbolId.ALGEBRAIC_STATE], }[state_id] - if 'amount' not in symbol: + if "amount" not in symbol: continue # not a species - if symbol['amount']: + if symbol["amount"]: compartment_size = sp.Integer(1) else: - compartment_size = self.compartments[ - symbol['compartment'] - ] + compartment_size = self.compartments[symbol["compartment"]] all_compartment_sizes.append(compartment_size) # iterate over list of conservation laws, create symbolic expressions, @@ -1998,17 +2039,20 @@ def _add_conservation_for_non_constant_species( compartment_sizes = [all_compartment_sizes[i] for i in state_idxs] target_state_id = all_state_ids[target_state_model_idx] - total_abundance = symbol_with_assumptions(f'tcl_{target_state_id}') - - new_conservation_laws.append({ - 'state': target_state_id, - 'total_abundance': total_abundance, - 'coefficients': { - state_id: coeff * compartment - for state_id, coeff, compartment - in zip(state_ids, coefficients, compartment_sizes) - }, - }) + total_abundance = symbol_with_assumptions(f"tcl_{target_state_id}") + + new_conservation_laws.append( + { + "state": target_state_id, + "total_abundance": total_abundance, + "coefficients": { + state_id: coeff * compartment + for state_id, coeff, compartment in zip( + state_ids, coefficients, compartment_sizes + ) + }, + } + ) species_to_be_removed.add(target_state_model_idx) conservation_laws.extend(new_conservation_laws) @@ -2022,23 +2066,23 @@ def _replace_compartments_with_volumes(self): (possibly variable) volumes. """ for comp, vol in self.compartments.items(): - if comp in self.symbols[SymbolId.SPECIES] \ - or comp in self.symbols[SymbolId.ALGEBRAIC_STATE]: + if ( + comp in self.symbols[SymbolId.SPECIES] + or comp in self.symbols[SymbolId.ALGEBRAIC_STATE] + ): # for comps with rate rules volume is only initial for state in { **self.symbols[SymbolId.SPECIES], - **self.symbols[SymbolId.ALGEBRAIC_STATE] + **self.symbols[SymbolId.ALGEBRAIC_STATE], }.values(): - if isinstance(state['init'], sp.Expr): - state['init'] = smart_subs(state['init'], - comp, vol) + if isinstance(state["init"], sp.Expr): + state["init"] = smart_subs(state["init"], comp, vol) continue self._replace_in_all_expressions(comp, vol) - def _replace_in_all_expressions(self, - old: sp.Symbol, - new: sp.Expr, - replace_identifiers=False) -> None: + def _replace_in_all_expressions( + self, old: sp.Symbol, new: sp.Expr, replace_identifiers=False + ) -> None: """ Replace 'old' by 'new' in all symbolic expressions. @@ -2049,17 +2093,19 @@ def _replace_in_all_expressions(self, replacement symbolic variables """ fields = [ - 'stoichiometric_matrix', 'flux_vector', + "stoichiometric_matrix", + "flux_vector", ] for field in fields: if field in dir(self): - self.__setattr__(field, smart_subs( - self.__getattribute__(field), old, new - )) + self.__setattr__( + field, smart_subs(self.__getattribute__(field), old, new) + ) dictfields = [ - 'compartment_assignment_rules', 'parameter_assignment_rules', - 'initial_assignments' + "compartment_assignment_rules", + "parameter_assignment_rules", + "initial_assignments", ] for dictfield in dictfields: d = getattr(self, dictfield) @@ -2069,7 +2115,7 @@ def _replace_in_all_expressions(self, d[new] = d[old] del d[old] - if dictfield == 'initial_assignments': + if dictfield == "initial_assignments": tmp_new = self._make_initial(new) else: tmp_new = new @@ -2080,55 +2126,65 @@ def _replace_in_all_expressions(self, # replace in identifiers if replace_identifiers: - for symbol in [SymbolId.EXPRESSION, SymbolId.SPECIES, - SymbolId.ALGEBRAIC_STATE]: + for symbol in [ + SymbolId.EXPRESSION, + SymbolId.SPECIES, + SymbolId.ALGEBRAIC_STATE, + ]: # completely recreate the dict to keep ordering consistent if old not in self.symbols[symbol]: continue self.symbols[symbol] = { - smart_subs(k, old, new): v - for k, v in self.symbols[symbol].items() + smart_subs(k, old, new): v for k, v in self.symbols[symbol].items() } - for symbol in [SymbolId.OBSERVABLE, SymbolId.LLHY, - SymbolId.SIGMAY]: + for symbol in [SymbolId.OBSERVABLE, SymbolId.LLHY, SymbolId.SIGMAY]: if old not in self.symbols[symbol]: continue self.symbols[symbol][new] = self.symbols[symbol][old] del self.symbols[symbol][old] # replace in values - for symbol in [SymbolId.OBSERVABLE, SymbolId.LLHY, SymbolId.LLHZ, - SymbolId.SIGMAY, SymbolId.SIGMAZ, SymbolId.EXPRESSION, - SymbolId.EVENT, SymbolId.EVENT_OBSERVABLE, - SymbolId.ALGEBRAIC_EQUATION]: + for symbol in [ + SymbolId.OBSERVABLE, + SymbolId.LLHY, + SymbolId.LLHZ, + SymbolId.SIGMAY, + SymbolId.SIGMAZ, + SymbolId.EXPRESSION, + SymbolId.EVENT, + SymbolId.EVENT_OBSERVABLE, + SymbolId.ALGEBRAIC_EQUATION, + ]: for element in self.symbols[symbol].values(): - element['value'] = smart_subs(element['value'], old, new) + element["value"] = smart_subs(element["value"], old, new) # replace in event state updates (boluses) if self.symbols.get(SymbolId.EVENT, False): for event in self.symbols[SymbolId.EVENT].values(): - for index in range(len(event['state_update'])): - event['state_update'][index] = \ - smart_subs(event['state_update'][index], old, new) + for index in range(len(event["state_update"])): + event["state_update"][index] = smart_subs( + event["state_update"][index], old, new + ) for state in { **self.symbols[SymbolId.SPECIES], - **self.symbols[SymbolId.ALGEBRAIC_STATE] + **self.symbols[SymbolId.ALGEBRAIC_STATE], }.values(): - state['init'] = smart_subs(state['init'], - old, self._make_initial(new)) + state["init"] = smart_subs(state["init"], old, self._make_initial(new)) - if 'dt' in state: - state['dt'] = smart_subs(state['dt'], old, new) + if "dt" in state: + state["dt"] = smart_subs(state["dt"], old, new) # Initial compartment volume may also be specified with an assignment # rule (at the end of the _process_species method), hence needs to be # processed here too. - self.compartments = {smart_subs(c, old, new) if replace_identifiers - else c: - smart_subs(v, old, self._make_initial(new)) - for c, v in self.compartments.items()} + self.compartments = { + smart_subs(c, old, new) + if replace_identifiers + else c: smart_subs(v, old, self._make_initial(new)) + for c, v in self.compartments.items() + } # Substitute inside spline definitions for spline in self.splines: @@ -2140,9 +2196,10 @@ def _clean_reserved_symbols(self) -> None: """ for sym in RESERVED_SYMBOLS: old_symbol = symbol_with_assumptions(sym) - new_symbol = symbol_with_assumptions(f'amici_{sym}') - self._replace_in_all_expressions(old_symbol, new_symbol, - replace_identifiers=True) + new_symbol = symbol_with_assumptions(f"amici_{sym}") + self._replace_in_all_expressions( + old_symbol, new_symbol, replace_identifiers=True + ) for symbols_ids, symbols in self.symbols.items(): if old_symbol in symbols: # reconstitute the whole dict in order to keep the ordering @@ -2152,7 +2209,7 @@ def _clean_reserved_symbols(self) -> None: } def _sympy_from_sbml_math( - self, var_or_math: [sbml.SBase, str] + self, var_or_math: [sbml.SBase, str] ) -> Union[sp.Expr, float, None]: """ Sympify Math of SBML variables with all sanity checks and @@ -2165,41 +2222,43 @@ def _sympy_from_sbml_math( """ if isinstance(var_or_math, sbml.SBase): math_string = sbml.formulaToL3StringWithSettings( - var_or_math.getMath(), - self.sbml_parser_settings + var_or_math.getMath(), self.sbml_parser_settings ) ele_name = var_or_math.element_name else: math_string = var_or_math - ele_name = 'string' + ele_name = "string" math_string = replace_logx(math_string) try: try: - formula = sp.sympify(_parse_logical_operators( - math_string - ), locals=self._local_symbols) + formula = sp.sympify( + _parse_logical_operators(math_string), locals=self._local_symbols + ) except TypeError as err: - if str(err) == 'BooleanAtom not allowed in this context.': - formula = sp.sympify(_parse_logical_operators( - math_string - ), locals={'true': sp.Float(1.0), 'false': sp.Float(0.0), - **self._local_symbols}) + if str(err) == "BooleanAtom not allowed in this context.": + formula = sp.sympify( + _parse_logical_operators(math_string), + locals={ + "true": sp.Float(1.0), + "false": sp.Float(0.0), + **self._local_symbols, + }, + ) else: raise except (sp.SympifyError, TypeError, ZeroDivisionError) as err: - raise SBMLException(f'{ele_name} "{math_string}" ' - 'contains an unsupported expression: ' - f'{err}.') + raise SBMLException( + f'{ele_name} "{math_string}" ' + "contains an unsupported expression: " + f"{err}." + ) if isinstance(formula, sp.Expr): formula = _parse_special_functions_sbml(formula) - _check_unsupported_functions_sbml(formula, - expression_type=ele_name) + _check_unsupported_functions_sbml(formula, expression_type=ele_name) return formula - def _get_element_initial_assignment(self, - element_id: str) -> Union[sp.Expr, - None]: + def _get_element_initial_assignment(self, element_id: str) -> Union[sp.Expr, None]: """ Extract value of sbml variable according to its initial assignment @@ -2208,9 +2267,7 @@ def _get_element_initial_assignment(self, :return: """ - assignment = self.sbml.getInitialAssignment( - element_id - ) + assignment = self.sbml.getInitialAssignment(element_id) if assignment is None: return None sym = self._sympy_from_sbml_math(assignment) @@ -2219,8 +2276,7 @@ def _get_element_initial_assignment(self, sym = self._make_initial(sym) return sym - def _get_element_stoichiometry(self, - ele: sbml.SBase) -> sp.Expr: + def _get_element_stoichiometry(self, ele: sbml.SBase) -> sp.Expr: """ Computes the stoichiometry of a reactant or product of a reaction @@ -2239,8 +2295,11 @@ def _get_element_stoichiometry(self, if ele.isSetStoichiometry(): stoichiometry: float = ele.getStoichiometry() - return sp.Integer(stoichiometry) if stoichiometry.is_integer() \ + return ( + sp.Integer(stoichiometry) + if stoichiometry.is_integer() else sp.Float(stoichiometry) + ) return sp.Integer(1) @@ -2273,8 +2332,9 @@ def is_rate_rule_target(self, element: sbml.SBase) -> bool: return a is not None and self._sympy_from_sbml_math(a) is not None -def _check_lib_sbml_errors(sbml_doc: sbml.SBMLDocument, - show_warnings: bool = False) -> None: +def _check_lib_sbml_errors( + sbml_doc: sbml.SBMLDocument, show_warnings: bool = False +) -> None: """ Checks the error log in the current self.sbml_doc. @@ -2292,17 +2352,17 @@ def _check_lib_sbml_errors(sbml_doc: sbml.SBMLDocument, for i_error in range(sbml_doc.getNumErrors()): error = sbml_doc.getError(i_error) # we ignore any info messages for now - if error.getSeverity() >= sbml.LIBSBML_SEV_ERROR \ - or (show_warnings and - error.getSeverity() >= sbml.LIBSBML_SEV_WARNING): - logger.error(f'libSBML {error.getCategoryAsString()} ' - f'({error.getSeverityAsString()}):' - f' {error.getMessage()}') + if error.getSeverity() >= sbml.LIBSBML_SEV_ERROR or ( + show_warnings and error.getSeverity() >= sbml.LIBSBML_SEV_WARNING + ): + logger.error( + f"libSBML {error.getCategoryAsString()} " + f"({error.getSeverityAsString()}):" + f" {error.getMessage()}" + ) if num_error + num_fatal: - raise SBMLException( - 'SBML Document failed to load (see error messages above)' - ) + raise SBMLException("SBML Document failed to load (see error messages above)") def _parse_event_trigger(trigger: sp.Expr) -> sp.Expr: @@ -2319,15 +2379,18 @@ def _parse_event_trigger(trigger: sp.Expr) -> sp.Expr: return sp.Float(1.0) if trigger.is_Relational: root = trigger.args[0] - trigger.args[1] - _check_unsupported_functions_sbml(root, 'sympy.Expression') + _check_unsupported_functions_sbml(root, "sympy.Expression") # convert relational expressions into trigger functions - if isinstance(trigger, (sp.core.relational.LessThan, - sp.core.relational.StrictLessThan)): + if isinstance( + trigger, (sp.core.relational.LessThan, sp.core.relational.StrictLessThan) + ): # y < x or y <= x return -root - if isinstance(trigger, (sp.core.relational.GreaterThan, - sp.core.relational.StrictGreaterThan)): + if isinstance( + trigger, + (sp.core.relational.GreaterThan, sp.core.relational.StrictGreaterThan), + ): # y >= x or y > x return root @@ -2339,13 +2402,14 @@ def _parse_event_trigger(trigger: sp.Expr) -> sp.Expr: return sp.Min(*[_parse_event_trigger(arg) for arg in trigger.args]) raise SBMLException( - 'AMICI can not parse piecewise/event trigger functions with argument ' - f'{trigger}.' + "AMICI can not parse piecewise/event trigger functions with argument " + f"{trigger}." ) -def assignmentRules2observables(sbml_model: sbml.Model, - filter_function: Callable = lambda *_: True): +def assignmentRules2observables( + sbml_model: sbml.Model, filter_function: Callable = lambda *_: True +): """ Turn assignment rules into observables. @@ -2368,13 +2432,12 @@ def assignmentRules2observables(sbml_model: sbml.Model, if rule.getTypeCode() != sbml.SBML_ASSIGNMENT_RULE: continue parameter_id = rule.getVariable() - if (p := sbml_model.getParameter(parameter_id)) \ - and filter_function(p): + if (p := sbml_model.getParameter(parameter_id)) and filter_function(p): observables[parameter_id] = { - 'name': p.getName() if p.isSetName() else parameter_id, - 'formula': sbml_model.getAssignmentRuleByVariable( + "name": p.getName() if p.isSetName() else parameter_id, + "formula": sbml_model.getAssignmentRuleByVariable( parameter_id - ).getFormula() + ).getFormula(), } for parameter_id in observables: @@ -2385,8 +2448,7 @@ def assignmentRules2observables(sbml_model: sbml.Model, def _add_conservation_for_constant_species( - ode_model: DEModel, - conservation_laws: List[ConservationLaw] + ode_model: DEModel, conservation_laws: List[ConservationLaw] ) -> List[int]: """ Adds constant species to conservations laws @@ -2410,12 +2472,14 @@ def _add_conservation_for_constant_species( # dont use sym('x') here since conservation laws need to be # added before symbols are generated target_state = ode_model._differential_states[ix].get_id() - total_abundance = symbol_with_assumptions(f'tcl_{target_state}') - conservation_laws.append({ - 'state': target_state, - 'total_abundance': total_abundance, - 'coefficients': {target_state: 1.0}, - }) + total_abundance = symbol_with_assumptions(f"tcl_{target_state}") + conservation_laws.append( + { + "state": target_state, + "total_abundance": total_abundance, + "coefficients": {target_state: 1.0}, + } + ) # mark species to delete from stoichiometric matrix species_solver.pop(ix) @@ -2480,8 +2544,9 @@ def get_species_initial(species: sbml.Species) -> sp.Expr: return sp.Float(0.0) -def _get_list_of_species_references(sbml_model: sbml.Model) \ - -> List[sbml.SpeciesReference]: +def _get_list_of_species_references( + sbml_model: sbml.Model, +) -> List[sbml.SpeciesReference]: """ Extracts list of species references as SBML doesn't provide a native function for this. @@ -2513,9 +2578,7 @@ def replace_logx(math_str: Union[str, float, None]) -> Union[str, float, None]: if not isinstance(math_str, str): return math_str - return re.sub( - r'(^|\W)log(\d+)\(', r'\g<1>1/ln(\2)*ln(', math_str - ) + return re.sub(r"(^|\W)log(\d+)\(", r"\g<1>1/ln(\2)*ln(", math_str) def _collect_event_assignment_parameter_targets(sbml_model: sbml.Model): @@ -2526,23 +2589,24 @@ def _collect_event_assignment_parameter_targets(sbml_model: sbml.Model): for event_assignment in event.getListOfEventAssignments(): target_id = event_assignment.getVariable() if target_id in sbml_parameter_ids: - targets.add(_get_identifier_symbol( - sbml_parameters[sbml_parameter_ids.index(target_id)] - )) + targets.add( + _get_identifier_symbol( + sbml_parameters[sbml_parameter_ids.index(target_id)] + ) + ) return targets -def _check_unsupported_functions_sbml(sym: sp.Expr, - expression_type: str, - full_sym: Optional[sp.Expr] = None): +def _check_unsupported_functions_sbml( + sym: sp.Expr, expression_type: str, full_sym: Optional[sp.Expr] = None +): try: _check_unsupported_functions(sym, expression_type, full_sym) except RuntimeError as err: raise SBMLException(str(err)) -def _parse_special_functions_sbml(sym: sp.Expr, - toplevel: bool = True) -> sp.Expr: +def _parse_special_functions_sbml(sym: sp.Expr, toplevel: bool = True) -> sp.Expr: try: return _parse_special_functions(sym, toplevel) except RuntimeError as err: @@ -2553,9 +2617,8 @@ def _validate_observables( observables: Union[Dict[str, Dict[str, str]], None], sigmas: Dict[str, Union[str, float]], noise_distributions: Dict[str, str], - events: bool = False + events: bool = False, ) -> None: - if observables is None or not observables: return @@ -2566,25 +2629,26 @@ def _validate_observables( raise ValueError( f"Sigma provided for unknown " f"{'eventO' if events else 'o'}bservableIds: " - f"{unknown_ids}.") + f"{unknown_ids}." + ) # Ensure no non-existing observableIds have been specified # (no problem here, but usually an upstream bug) - unknown_ids = set(noise_distributions.keys()) - \ - set(observables.keys()) + unknown_ids = set(noise_distributions.keys()) - set(observables.keys()) if unknown_ids: raise ValueError( f"Noise distribution provided for unknown " f"{'eventO' if events else 'o'}bservableIds: " - f"{unknown_ids}.") + f"{unknown_ids}." + ) -def _check_symbol_nesting(symbols: Dict[sp.Symbol, Dict[str, sp.Expr]], - symbol_type: str): +def _check_symbol_nesting( + symbols: Dict[sp.Symbol, Dict[str, sp.Expr]], symbol_type: str +): observable_syms = set(symbols.keys()) for obs in symbols.values(): - if any(sym in observable_syms - for sym in obs['value'].free_symbols): + if any(sym in observable_syms for sym in obs["value"].free_symbols): raise ValueError( "Nested observables are not supported, " f"but {symbol_type} `{obs['name']} = {obs['value']}` " @@ -2595,20 +2659,28 @@ def _check_symbol_nesting(symbols: Dict[sp.Symbol, Dict[str, sp.Expr]], def _non_const_conservation_laws_supported(sbml_model: sbml.Model) -> bool: """Check whether non-constant conservation laws can be handled for the given model.""" - if any(rule.getTypeCode() == sbml.SBML_RATE_RULE - for rule in sbml_model.getListOfRules()): + if any( + rule.getTypeCode() == sbml.SBML_RATE_RULE + for rule in sbml_model.getListOfRules() + ): # see SBML semantic test suite, case 33 for an example - warnings.warn("Conservation laws for non-constant species in " - "models with RateRules are currently not supported " - "and will be turned off.") + warnings.warn( + "Conservation laws for non-constant species in " + "models with RateRules are currently not supported " + "and will be turned off." + ) return False - if any(rule.getTypeCode() == sbml.SBML_ASSIGNMENT_RULE and - sbml_model.getSpecies(rule.getVariable()) - for rule in sbml_model.getListOfRules()): - warnings.warn("Conservation laws for non-constant species in " - "models with Species-AssignmentRules are currently not " - "supported and will be turned off.") + if any( + rule.getTypeCode() == sbml.SBML_ASSIGNMENT_RULE + and sbml_model.getSpecies(rule.getVariable()) + for rule in sbml_model.getListOfRules() + ): + warnings.warn( + "Conservation laws for non-constant species in " + "models with Species-AssignmentRules are currently not " + "supported and will be turned off." + ) return False return True diff --git a/python/sdist/amici/sbml_utils.py b/python/sdist/amici/sbml_utils.py index c98f8b32ff..8202869678 100644 --- a/python/sdist/amici/sbml_utils.py +++ b/python/sdist/amici/sbml_utils.py @@ -8,8 +8,10 @@ from typing import TYPE_CHECKING import sympy as sp + if TYPE_CHECKING: from typing import Optional, Union, Tuple, Dict, Any + SbmlID = Union[str, sp.Symbol] from .import_utils import ( @@ -17,7 +19,7 @@ amici_time_symbol, _parse_special_functions, _check_unsupported_functions, - SBMLException + SBMLException, ) import xml.dom.minidom import libsbml @@ -46,8 +48,9 @@ class SbmlAnnotationError(SBMLException): pass -def create_sbml_model(model_id: str, level: int = 2, version: int = 5) \ - -> Tuple[libsbml.SBMLDocument, libsbml.Model]: +def create_sbml_model( + model_id: str, level: int = 2, version: int = 5 +) -> Tuple[libsbml.SBMLDocument, libsbml.Model]: """Helper for creating an empty SBML model. :param model_id: @@ -96,12 +99,12 @@ def add_compartment( # if other types of objects (e.g., parameter) have the same ID if model.getCompartment(compartment_id): raise SbmlDuplicateComponentIdError( - f'A compartment with ID {compartment_id} has already been defined' + f"A compartment with ID {compartment_id} has already been defined" ) cmp = model.createCompartment() if cmp.setId(compartment_id) != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlInvalidIdSyntax(f'{compartment_id} is not a valid SBML ID') + raise SbmlInvalidIdSyntax(f"{compartment_id} is not a valid SBML ID") cmp.setSize(size) return cmp @@ -144,25 +147,23 @@ def add_species( # Check whether an element with the same ID already exists if model.getElementBySId(species_id): raise SbmlDuplicateComponentIdError( - f'An element with ID {species_id} has already been defined.' + f"An element with ID {species_id} has already been defined." ) if compartment_id is None: compartments = model.getListOfCompartments() if len(compartments) != 1: raise ValueError( - 'Compartment auto-selection is possible ' - 'only if there is one and only one compartment.' + "Compartment auto-selection is possible " + "only if there is one and only one compartment." ) compartment_id = compartments[0].getId() elif not model.getCompartment(compartment_id): - raise SbmlMissingComponentIdError( - f'No compartment with ID {compartment_id}.' - ) + raise SbmlMissingComponentIdError(f"No compartment with ID {compartment_id}.") sp = model.createSpecies() if sp.setIdAttribute(species_id) != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlInvalidIdSyntax(f'{species_id} is not a valid SBML ID.') + raise SbmlInvalidIdSyntax(f"{species_id} is not a valid SBML ID.") sp.setCompartment(compartment_id) sp.setInitialAmount(float(initial_amount)) if units is not None: @@ -212,12 +213,12 @@ def add_parameter( # Check whether an element with the same ID already exists if model.getElementBySId(parameter_id): raise SbmlDuplicateComponentIdError( - f'An element with ID {parameter_id} has already been defined.' + f"An element with ID {parameter_id} has already been defined." ) par = model.createParameter() if par.setIdAttribute(parameter_id) != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlInvalidIdSyntax(f'{parameter_id} is not a valid SBML ID.') + raise SbmlInvalidIdSyntax(f"{parameter_id} is not a valid SBML ID.") if units is not None: par.setUnits(str(units)) if constant is not None: @@ -256,23 +257,23 @@ def add_assignment_rule( """ variable_id = str(variable_id) if rule_id is None: - rule_id = 'assignment_' + variable_id + rule_id = "assignment_" + variable_id # Check whether rules exists for this parameter or with the same name if model.getRuleByVariable(variable_id): raise SbmlDuplicateComponentIdError( - f'A rule for parameter {variable_id} has already been defined.' + f"A rule for parameter {variable_id} has already been defined." ) if model.getElementBySId(rule_id): raise SbmlDuplicateComponentIdError( - f'An element with SBML ID {rule_id} has already been defined.' + f"An element with SBML ID {rule_id} has already been defined." ) rule = model.createAssignmentRule() if rule.setVariable(variable_id) != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlInvalidIdSyntax(f'{variable_id} is not a valid SBML ID.') + raise SbmlInvalidIdSyntax(f"{variable_id} is not a valid SBML ID.") if rule.setIdAttribute(rule_id) != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlInvalidIdSyntax(f'{rule_id} is not a valid SBML ID.') + raise SbmlInvalidIdSyntax(f"{rule_id} is not a valid SBML ID.") set_sbml_math(rule, formula) return rule @@ -305,23 +306,23 @@ def add_rate_rule( """ variable_id = str(variable_id) if rule_id is None: - rule_id = 'rate_' + variable_id + rule_id = "rate_" + variable_id # Check whether rules exists for this parameter or with the same name if model.getRuleByVariable(variable_id): raise SbmlDuplicateComponentIdError( - f'A rule for parameter {variable_id} has already been defined.' + f"A rule for parameter {variable_id} has already been defined." ) if model.getElementBySId(rule_id): raise SbmlDuplicateComponentIdError( - f'An element with SBML ID {rule_id} has already been defined.' + f"An element with SBML ID {rule_id} has already been defined." ) rule = model.createRateRule() if rule.setVariable(variable_id) != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlInvalidIdSyntax(f'{variable_id} is not a valid SBML ID.') + raise SbmlInvalidIdSyntax(f"{variable_id} is not a valid SBML ID.") if rule.setIdAttribute(rule_id) != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlInvalidIdSyntax(f'{rule_id} is not a valid SBML ID.') + raise SbmlInvalidIdSyntax(f"{rule_id} is not a valid SBML ID.") set_sbml_math(rule, formula) return rule @@ -337,16 +338,16 @@ def add_inflow( ) -> libsbml.Reaction: species_id = str(species_id) if reaction_id is None: - reaction_id = f'inflow_of_{species_id}' + reaction_id = f"inflow_of_{species_id}" if model.getElementBySId(reaction_id): raise SbmlDuplicateComponentIdError( - f'An element with SBML ID {reaction_id} has already been defined.' + f"An element with SBML ID {reaction_id} has already been defined." ) reaction = model.createReaction() if reaction.setId(reaction_id) != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlInvalidIdSyntax(f'{reaction_id} is not a valid SBML ID.') + raise SbmlInvalidIdSyntax(f"{reaction_id} is not a valid SBML ID.") reaction.setReversible(reversible) spr = reaction.createProduct() @@ -359,8 +360,9 @@ def add_inflow( return reaction -def get_sbml_units(model: libsbml.Model, x: Union[SbmlID, sp.Basic]) \ - -> Union[None, str]: +def get_sbml_units( + model: libsbml.Model, x: Union[SbmlID, sp.Basic] +) -> Union[None, str]: """Try to get the units for expression `x`. :param model: @@ -382,7 +384,7 @@ def get_sbml_units(model: libsbml.Model, x: Union[SbmlID, sp.Basic]) \ if par is None: return None units = par.getUnits() - if units == '': + if units == "": return None return units @@ -392,7 +394,7 @@ def pretty_xml(ugly_xml: str) -> str: dom = xml.dom.minidom.parseString(ugly_xml) pretty_xml = dom.toprettyxml() # We must delete the first line (xml header) - return pretty_xml[pretty_xml.index('\n') + 1:] + return pretty_xml[pretty_xml.index("\n") + 1 :] class MathMLSbmlPrinter(MathMLContentPrinter): @@ -411,11 +413,11 @@ def _print_Symbol(self, sym: sp.Symbol) -> xml.dom.minidom.Element: def doprint(self, expr, *, pretty: bool = False) -> str: mathml = '' mathml += super().doprint(expr) - mathml += '' + mathml += "" mathml = mathml.replace( - 'time', + "time", ' time ' + '"http://www.sbml.org/sbml/symbols/time"> time ', ) return pretty_xml(mathml) if pretty else mathml @@ -454,9 +456,9 @@ def sbml_math_ast(expr, **kwargs) -> libsbml.ASTNode: ast = libsbml.readMathMLFromString(mathml) if ast is None: raise SbmlMathError( - f'error while converting the following expression to SBML AST.\n' - f'expression:\n{expr}\n' - f'MathML:\n{pretty_xml(mathml)}' + f"error while converting the following expression to SBML AST.\n" + f"expression:\n{expr}\n" + f"MathML:\n{pretty_xml(mathml)}" ) return ast @@ -477,9 +479,9 @@ def set_sbml_math(obj: libsbml.SBase, expr, **kwargs) -> None: mathml = sbml_math_ast(expr, **kwargs) if obj.setMath(mathml) != libsbml.LIBSBML_OPERATION_SUCCESS: raise SbmlMathError( - f'Could not set math attribute of SBML object {obj}\n' - f'expression:\n{expr}\n' - f'MathML:\n{pretty_xml(mathml)}' + f"Could not set math attribute of SBML object {obj}\n" + f"expression:\n{expr}\n" + f"MathML:\n{pretty_xml(mathml)}" ) @@ -488,12 +490,12 @@ def mathml2sympy( *, evaluate: bool = False, locals: Optional[Dict[str, Any]] = None, - expression_type: str = 'mathml2sympy', + expression_type: str = "mathml2sympy", ) -> sp.Basic: ast = libsbml.readMathMLFromString(mathml) if ast is None: raise ValueError( - f'libSBML could not parse MathML string:\n{pretty_xml(mathml)}' + f"libSBML could not parse MathML string:\n{pretty_xml(mathml)}" ) formula = _parse_logical_operators(libsbml.formulaToL3String(ast)) @@ -512,8 +514,9 @@ def mathml2sympy( return expr -def _parse_logical_operators(math_str: Union[str, float, None] - ) -> Union[str, float, None]: +def _parse_logical_operators( + math_str: Union[str, float, None] +) -> Union[str, float, None]: """ Parses a math string in order to replace logical operators by a form parsable for sympy @@ -526,8 +529,7 @@ def _parse_logical_operators(math_str: Union[str, float, None] if not isinstance(math_str, str): return math_str - if ' xor(' in math_str or ' Xor(' in math_str: - raise SBMLException('Xor is currently not supported as logical ' - 'operation.') + if " xor(" in math_str or " Xor(" in math_str: + raise SBMLException("Xor is currently not supported as logical " "operation.") - return (math_str.replace('&&', '&')).replace('||', '|') + return (math_str.replace("&&", "&")).replace("||", "|") diff --git a/python/sdist/amici/setup.template.py b/python/sdist/amici/setup.template.py index 4eb7eca14a..08cef133a3 100644 --- a/python/sdist/amici/setup.template.py +++ b/python/sdist/amici/setup.template.py @@ -18,15 +18,15 @@ def get_extension() -> CMakeExtension: # handle parallel building # Note: can be empty to use all hardware threads - if (parallel_jobs := os.environ.get('AMICI_PARALLEL_COMPILE')) is not None: - os.environ['CMAKE_BUILD_PARALLEL_LEVEL'] = parallel_jobs + if (parallel_jobs := os.environ.get("AMICI_PARALLEL_COMPILE")) is not None: + os.environ["CMAKE_BUILD_PARALLEL_LEVEL"] = parallel_jobs else: - os.environ['CMAKE_BUILD_PARALLEL_LEVEL'] = "1" + os.environ["CMAKE_BUILD_PARALLEL_LEVEL"] = "1" return CMakeExtension( - name='model_ext', + name="model_ext", source_dir=os.getcwd(), - install_prefix='TPL_MODELNAME', + install_prefix="TPL_MODELNAME", cmake_configure_options=[ "-DCMAKE_VERBOSE_MAKEFILE=ON", "-DCMAKE_MODULE_PATH=" @@ -44,34 +44,34 @@ def get_extension() -> CMakeExtension: MODEL_EXT = get_extension() CLASSIFIERS = [ - 'Development Status :: 3 - Alpha', - 'Intended Audience :: Science/Research', - 'Operating System :: POSIX :: Linux', - 'Operating System :: MacOS :: MacOS X', - 'Programming Language :: Python', - 'Programming Language :: C++', - 'Topic :: Scientific/Engineering :: Bio-Informatics', + "Development Status :: 3 - Alpha", + "Intended Audience :: Science/Research", + "Operating System :: POSIX :: Linux", + "Operating System :: MacOS :: MacOS X", + "Programming Language :: Python", + "Programming Language :: C++", + "Topic :: Scientific/Engineering :: Bio-Informatics", ] CMDCLASS = { # for CMake-based builds - 'build_ext': AmiciBuildCMakeExtension, + "build_ext": AmiciBuildCMakeExtension, } # Install setup( - name='TPL_MODELNAME', + name="TPL_MODELNAME", cmdclass=CMDCLASS, - version='TPL_PACKAGE_VERSION', - description='AMICI-generated module for model TPL_MODELNAME', - url='https://github.com/AMICI-dev/AMICI', - author='model-author-todo', - author_email='model-author-todo', + version="TPL_PACKAGE_VERSION", + description="AMICI-generated module for model TPL_MODELNAME", + url="https://github.com/AMICI-dev/AMICI", + author="model-author-todo", + author_email="model-author-todo", ext_modules=[MODEL_EXT], packages=find_namespace_packages(), - install_requires=['amici==TPL_AMICI_VERSION'], - extras_require={'wurlitzer': ['wurlitzer']}, - python_requires='>=3.9', + install_requires=["amici==TPL_AMICI_VERSION"], + extras_require={"wurlitzer": ["wurlitzer"]}, + python_requires=">=3.9", package_data={}, zip_safe=False, classifiers=CLASSIFIERS, diff --git a/python/sdist/amici/splines.py b/python/sdist/amici/splines.py index dabd3c0323..bf416ed375 100644 --- a/python/sdist/amici/splines.py +++ b/python/sdist/amici/splines.py @@ -24,10 +24,7 @@ Callable, ) - BClike = Union[ - None, str, - Tuple[Union[None, str], Union[None, str]] - ] + BClike = Union[None, str, Tuple[Union[None, str], Union[None, str]]] NormalizedBC = Tuple[Union[None, str], Union[None, str]] @@ -94,7 +91,7 @@ def __init__( step: Optional[Union[Real, sp.Basic]] = None, *, number_of_nodes: Optional[Integral] = None, - always_include_stop: bool = True + always_include_stop: bool = True, ): """Create a new ``UniformGrid``. @@ -135,12 +132,10 @@ def __init__( step = sp.nsimplify(sp.sympify(step)) if start > stop: - raise ValueError( - f'Start point {start} greater than stop point {stop}!' - ) + raise ValueError(f"Start point {start} greater than stop point {stop}!") if step <= 0: - raise ValueError(f'Step size {step} must be strictly positive!') + raise ValueError(f"Step size {step} must be strictly positive!") xx = [] for i in count(): @@ -193,8 +188,9 @@ def __array__(self, dtype=None) -> np.ndarray: return np.array(self._xx, dtype=dtype) def __repr__(self) -> str: - return (f"UniformGrid(start={self.start}, stop={self.stop}, " - f"step={self.step})") + return ( + f"UniformGrid(start={self.start}, stop={self.stop}, " f"step={self.step})" + ) ############################################################################### @@ -213,15 +209,15 @@ class AbstractSpline(ABC): """ def __init__( - self, - sbml_id: Union[str, sp.Symbol], - nodes: Sequence, - values_at_nodes: Sequence, - *, - evaluate_at: Optional[Union[str, sp.Basic]] = None, - bc: BClike = None, - extrapolate: BClike = None, - logarithmic_parametrization: bool = False + self, + sbml_id: Union[str, sp.Symbol], + nodes: Sequence, + values_at_nodes: Sequence, + *, + evaluate_at: Optional[Union[str, sp.Basic]] = None, + bc: BClike = None, + extrapolate: BClike = None, + logarithmic_parametrization: bool = False, ): """Base constructor for ``AbstractSpline`` objects. @@ -302,8 +298,8 @@ def __init__( sbml_id = symbol_with_assumptions(sbml_id) elif not isinstance(sbml_id, sp.Symbol): raise TypeError( - 'sbml_id must be either a string or a SymPy symbol, ' - f'got {sbml_id} of type {type(sbml_id)} instead!' + "sbml_id must be either a string or a SymPy symbol, " + f"got {sbml_id} of type {type(sbml_id)} instead!" ) if evaluate_at is None: @@ -312,7 +308,7 @@ def __init__( evaluate_at = sympify_noeval(evaluate_at) if not isinstance(evaluate_at, sp.Basic): # It may still be e.g. a list! - raise ValueError(f'Invalid evaluate_at = {evaluate_at}!') + raise ValueError(f"Invalid evaluate_at = {evaluate_at}!") if evaluate_at != amici_time_symbol and evaluate_at != sbml_time_symbol: logger.warning( "At the moment AMICI only supports evaluate_at = (model time). " @@ -327,28 +323,28 @@ def __init__( if len(nodes) != len(values_at_nodes): raise ValueError( - 'Length of nodes and values_at_nodes must be the same ' - f'(instead len(nodes) = {len(nodes)} and len(values_at_nodes) = {len(values_at_nodes)})!' + "Length of nodes and values_at_nodes must be the same " + f"(instead len(nodes) = {len(nodes)} and len(values_at_nodes) = {len(values_at_nodes)})!" ) if all(x.is_Number for x in nodes) and not np.all(np.diff(nodes) >= 0): - raise ValueError('nodes should be strictly increasing!') + raise ValueError("nodes should be strictly increasing!") if ( - logarithmic_parametrization - and all(y.is_Number for y in values_at_nodes) - and any(y <= 0 for y in values_at_nodes) + logarithmic_parametrization + and all(y.is_Number for y in values_at_nodes) + and any(y <= 0 for y in values_at_nodes) ): raise ValueError( - 'When interpolation is done in log-scale, ' - 'values_at_nodes should be strictly positive!' + "When interpolation is done in log-scale, " + "values_at_nodes should be strictly positive!" ) bc, extrapolate = self._normalize_bc_and_extrapolate(bc, extrapolate) - if bc == ('periodic', 'periodic') and values_at_nodes[0] != values_at_nodes[-1]: + if bc == ("periodic", "periodic") and values_at_nodes[0] != values_at_nodes[-1]: raise ValueError( - 'If the spline is to be periodic, ' - 'the first and last elements of values_at_nodes must be equal!' + "If the spline is to be periodic, " + "the first and last elements of values_at_nodes must be equal!" ) self._sbml_id: sp.Symbol = sbml_id @@ -372,39 +368,40 @@ def _normalize_bc(bc: BClike) -> NormalizedBC: if not isinstance(bc, tuple): bc = (bc, bc) elif len(bc) != 2: - raise TypeError(f'bc should be a 2-tuple, got {bc} instead!') + raise TypeError(f"bc should be a 2-tuple, got {bc} instead!") bc = list(bc) valid_bc = ( - 'periodic', - 'zeroderivative', - 'zeroderivative+natural', - 'natural', - 'no_bc', - 'auto', - None + "periodic", + "zeroderivative", + "zeroderivative+natural", + "natural", + "no_bc", + "auto", + None, ) for i in (0, 1): if bc[i] not in valid_bc: raise ValueError( - f'Unsupported bc = {bc[i]}! ' - f'The currently supported bc methods are: {valid_bc}' + f"Unsupported bc = {bc[i]}! " + f"The currently supported bc methods are: {valid_bc}" ) - elif bc[i] == 'no_bc': + elif bc[i] == "no_bc": bc[i] = None - if (bc[0] == 'periodic' or bc[1] == 'periodic') and bc[0] != bc[1]: + if (bc[0] == "periodic" or bc[1] == "periodic") and bc[0] != bc[1]: raise ValueError( - 'If the bc on one side is periodic, ' - 'then the bc on the other side must be periodic too!' + "If the bc on one side is periodic, " + "then the bc on the other side must be periodic too!" ) return bc[0], bc[1] - def _normalize_extrapolate(self, bc: NormalizedBC, extrapolate: BClike) \ - -> Tuple[NormalizedBC, NormalizedBC]: + def _normalize_extrapolate( + self, bc: NormalizedBC, extrapolate: BClike + ) -> Tuple[NormalizedBC, NormalizedBC]: """ Preprocess `extrapolate` to a standard form and perform consistency checks @@ -413,91 +410,91 @@ def _normalize_extrapolate(self, bc: NormalizedBC, extrapolate: BClike) \ extrapolate = (extrapolate, extrapolate) elif len(extrapolate) != 2: raise TypeError( - f'extrapolate should be a 2-tuple, got {extrapolate} instead!' + f"extrapolate should be a 2-tuple, got {extrapolate} instead!" ) extrapolate = list(extrapolate) if not isinstance(bc, tuple) or len(bc) != 2: - raise TypeError(f'bc should be a 2-tuple, got {bc} instead!') + raise TypeError(f"bc should be a 2-tuple, got {bc} instead!") bc = list(bc) valid_extrapolate = ( - 'no_extrapolation', - 'constant', - 'linear', - 'polynomial', - 'periodic', - None + "no_extrapolation", + "constant", + "linear", + "polynomial", + "periodic", + None, ) for i in (0, 1): if extrapolate[i] not in valid_extrapolate: raise ValueError( - f'Unsupported extrapolate= {extrapolate[i]}!' + - ' The currently supported extrapolation methods are: ' + - str(valid_extrapolate) + f"Unsupported extrapolate= {extrapolate[i]}!" + + " The currently supported extrapolation methods are: " + + str(valid_extrapolate) ) - if extrapolate[i] == 'no_extrapolation': + if extrapolate[i] == "no_extrapolation": extrapolate[i] = None - if extrapolate[i] == 'periodic': - if bc[0] == 'auto': - bc[0] = 'periodic' - if bc[1] == 'auto': - bc[1] = 'periodic' - if not (bc[0] == bc[1] == 'periodic'): + if extrapolate[i] == "periodic": + if bc[0] == "auto": + bc[0] = "periodic" + if bc[1] == "auto": + bc[1] = "periodic" + if not (bc[0] == bc[1] == "periodic"): raise ValueError( - 'The spline must satisfy periodic boundary conditions ' - 'on both sides of the base interval ' - 'in order for periodic extrapolation to be used!' + "The spline must satisfy periodic boundary conditions " + "on both sides of the base interval " + "in order for periodic extrapolation to be used!" ) - elif extrapolate[i] == 'constant': + elif extrapolate[i] == "constant": assert self.smoothness > 0 if self.smoothness == 1: - if bc[i] == 'auto': - bc[i] = 'zeroderivative' - elif bc[i] != 'zeroderivative': + if bc[i] == "auto": + bc[i] = "zeroderivative" + elif bc[i] != "zeroderivative": raise ValueError( - 'The spline must satisfy zero-derivative bc ' - 'in order for constant extrapolation to be used!' + "The spline must satisfy zero-derivative bc " + "in order for constant extrapolation to be used!" ) - elif bc[i] == 'auto': - bc[i] = 'zeroderivative+natural' - elif bc[i] != 'zeroderivative+natural': + elif bc[i] == "auto": + bc[i] = "zeroderivative+natural" + elif bc[i] != "zeroderivative+natural": raise ValueError( - 'The spline must satisfy zero-derivative and natural' - ' bc in order for constant extrapolation to be used!' + "The spline must satisfy zero-derivative and natural" + " bc in order for constant extrapolation to be used!" ) - elif extrapolate[i] == 'linear': + elif extrapolate[i] == "linear": assert self.smoothness > 0 if self.smoothness > 1: - if bc[i] == 'auto': - bc[i] = 'natural' - elif bc[i] != 'natural': + if bc[i] == "auto": + bc[i] = "natural" + elif bc[i] != "natural": raise ValueError( - 'The spline must satisfy natural bc ' - 'in order for linear extrapolation to be used!' + "The spline must satisfy natural bc " + "in order for linear extrapolation to be used!" ) - elif bc[i] == 'auto': + elif bc[i] == "auto": bc[i] = None - elif bc[i] == 'auto': + elif bc[i] == "auto": bc[i] = None if ( - (extrapolate[0] == 'periodic' or extrapolate[1] == 'periodic') - and extrapolate[0] != extrapolate[1] - and extrapolate[0] is not None - and extrapolate[1] is not None + (extrapolate[0] == "periodic" or extrapolate[1] == "periodic") + and extrapolate[0] != extrapolate[1] + and extrapolate[0] is not None + and extrapolate[1] is not None ): raise NotImplementedError( - 'At the moment if periodic extrapolation is applied ' - 'to one side, the extrapolation at the other side ' - 'must either be periodic or not be applied ' - '(in which case it will be periodic anyway).' + "At the moment if periodic extrapolation is applied " + "to one side, the extrapolation at the other side " + "must either be periodic or not be applied " + "(in which case it will be periodic anyway)." ) return (bc[0], bc[1]), (extrapolate[0], extrapolate[1]) @@ -561,23 +558,22 @@ def check_if_valid(self, importer: sbml_import.SbmlImporter) -> None: # If found, they should be checked for here # until (if at all) they are accounted for. from .de_export import SymbolId + fixed_parameters: List[sp.Symbol] = list( - importer.symbols[SymbolId.FIXED_PARAMETER].keys()) - species: List[sp.Symbol] = list( - importer.symbols[SymbolId.SPECIES].keys()) + importer.symbols[SymbolId.FIXED_PARAMETER].keys() + ) + species: List[sp.Symbol] = list(importer.symbols[SymbolId.SPECIES].keys()) for x in self.nodes: if not x.free_symbols.issubset(fixed_parameters): - raise ValueError( - 'nodes should only depend on constant parameters!' - ) + raise ValueError("nodes should only depend on constant parameters!") for y in self.values_at_nodes: if y.free_symbols.intersection(species): - raise ValueError('values_at_nodes should not depend on model species!') + raise ValueError("values_at_nodes should not depend on model species!") fixed_parameters_values = [ - importer.symbols[SymbolId.FIXED_PARAMETER][fp]['value'] + importer.symbols[SymbolId.FIXED_PARAMETER][fp]["value"] for fp in fixed_parameters ] subs = dict(zip(fixed_parameters, fixed_parameters_values)) @@ -585,7 +581,7 @@ def check_if_valid(self, importer: sbml_import.SbmlImporter) -> None: for x in nodes_values: assert x.is_Number if not np.all(np.diff(nodes_values) >= 0): - raise ValueError('nodes should be strictly increasing!') + raise ValueError("nodes should be strictly increasing!") def poly(self, i: Integral, *, x: Union[Real, sp.Basic] = None) -> sp.Basic: """ @@ -599,13 +595,13 @@ def poly(self, i: Integral, *, x: Union[Real, sp.Basic] = None) -> sp.Basic: i += len(self.nodes) - 1 if not 0 <= i < len(self.nodes) - 1: - raise ValueError(f'Interval index {i} is out of bounds!') + raise ValueError(f"Interval index {i} is out of bounds!") if x is None: x = self.evaluate_at # Compute polynomial in Horner form for the scaled variable - t = sp.Dummy('t') + t = sp.Dummy("t") poly = self._poly(t, i).expand().as_poly(wrt=t, domain=sp.RR) # Rewrite in Horner form @@ -631,7 +627,7 @@ def poly_variable(self, x: Union[Real, sp.Basic], i: Integral) -> sp.Basic: in which the polynomial on the ``i``-th interval is expressed. """ if not 0 <= i < len(self.nodes) - 1: - raise ValueError(f'Interval index {i} is out of bounds!') + raise ValueError(f"Interval index {i} is out of bounds!") return self._poly_variable(x, i) @abstractmethod @@ -648,7 +644,9 @@ def _poly(self, t: Union[Real, sp.Basic], i: Integral) -> sp.Basic: """ raise NotImplementedError() - def segment_formula(self, i: Integral, *, x: Union[Real, sp.Basic] = None) -> sp.Basic: + def segment_formula( + self, i: Integral, *, x: Union[Real, sp.Basic] = None + ) -> sp.Basic: """ Return the formula for the actual value of the spline expression on the ``(nodes[i], nodes[i+1])`` interval. @@ -673,8 +671,9 @@ def y_scaled(self, i: Integral): return self.values_at_nodes[i] @property - def extrapolation_formulas(self) \ - -> Tuple[Union[None, sp.Basic], Union[None, sp.Basic]]: + def extrapolation_formulas( + self, + ) -> Tuple[Union[None, sp.Basic], Union[None, sp.Basic]]: """ Returns the extrapolation formulas on the left and right side of the interval ``(nodes[0], nodes[-1])``. @@ -683,7 +682,8 @@ def extrapolation_formulas(self) \ return self._extrapolation_formulas(self.evaluate_at) def _extrapolation_formulas( - self, x: Union[Real, sp.Basic], + self, + x: Union[Real, sp.Basic], extrapolate: Optional[NormalizedBC] = None, ) -> Tuple[Union[None, sp.Expr], Union[None, sp.Expr]]: if extrapolate is None: @@ -691,24 +691,24 @@ def _extrapolation_formulas( else: extr_left, extr_right = extrapolate - if extr_left == 'constant': + if extr_left == "constant": extr_left = self.values_at_nodes[0] - elif extr_left == 'linear': + elif extr_left == "linear": dx = x - self.nodes[0] dydx = self.derivative(self.nodes[0], extrapolate=None) extr_left = self.values_at_nodes[0] + dydx * dx - elif extr_left == 'polynomial': + elif extr_left == "polynomial": extr_left = None else: assert extr_left is None - if extr_right == 'constant': + if extr_right == "constant": extr_right = self.values_at_nodes[-1] - elif extr_right == 'linear': + elif extr_right == "linear": dx = x - self.nodes[-1] dydx = self.derivative(self.nodes[-1], extrapolate=None) extr_right = self.values_at_nodes[-1] + dydx * dx - elif extr_right == 'polynomial': + elif extr_right == "polynomial": extr_right = None else: assert extr_right is None @@ -741,18 +741,18 @@ def mathml_formula(self) -> sp.Piecewise: return self._formula(sbml_syms=True, sbml_ops=True) def _formula( - self, - *, - x: Union[Real, sp.Basic] = None, - sbml_syms: bool = False, - sbml_ops: bool = False, - cache: bool = True, - **kwargs + self, + *, + x: Union[Real, sp.Basic] = None, + sbml_syms: bool = False, + sbml_ops: bool = False, + cache: bool = True, + **kwargs, ) -> sp.Piecewise: # Cache formulas in the case they are reused if cache: - if 'extrapolate' in kwargs.keys(): - key = (x, sbml_syms, sbml_ops, kwargs['extrapolate']) + if "extrapolate" in kwargs.keys(): + key = (x, sbml_syms, sbml_ops, kwargs["extrapolate"]) else: key = (x, sbml_syms, sbml_ops) if key in self._formula_cache.keys(): @@ -760,9 +760,9 @@ def _formula( if x is None: x = self.evaluate_at - if 'extrapolate' in kwargs.keys(): + if "extrapolate" in kwargs.keys(): _bc, extrapolate = self._normalize_extrapolate( - self.bc, kwargs['extrapolate'] + self.bc, kwargs["extrapolate"] ) assert self.bc == _bc else: @@ -770,11 +770,11 @@ def _formula( pieces = [] - if extrapolate[0] == 'periodic' or extrapolate[1] == 'periodic': + if extrapolate[0] == "periodic" or extrapolate[1] == "periodic": if sbml_ops: # NB mod is not supported in SBML x = symbol_with_assumptions( - self.sbml_id.name + '_x_in_fundamental_period' + self.sbml_id.name + "_x_in_fundamental_period" ) # NB we will do the parameter substitution in SBML # because the formula for x will be a piecewise @@ -784,8 +784,7 @@ def _formula( x = self._to_base_interval(x) extr_left, extr_right = None, None else: - extr_left, extr_right = self._extrapolation_formulas(x, - extrapolate) + extr_left, extr_right = self._extrapolation_formulas(x, extrapolate) if extr_left is not None: pieces.append((extr_left, x < self.nodes[0])) @@ -804,7 +803,7 @@ def _formula( pieces = [ ( p.subs(amici_time_symbol, sbml_time_symbol), - c.subs(amici_time_symbol, sbml_time_symbol) + c.subs(amici_time_symbol, sbml_time_symbol), ) for (p, c) in pieces ] @@ -816,20 +815,18 @@ def _formula( @property def period(self) -> Union[sp.Basic, None]: - """Period of a periodic spline. `None` if the spline is not periodic. - """ - if self.bc == ('periodic', 'periodic'): + """Period of a periodic spline. `None` if the spline is not periodic.""" + if self.bc == ("periodic", "periodic"): return self.nodes[-1] - self.nodes[0] return None - def _to_base_interval(self, x: Union[Real, sp.Basic], *, with_interval_number: bool = False) \ - -> Union[sp.Basic, Tuple[sp.core.numbers.Integer, sp.Basic]]: + def _to_base_interval( + self, x: Union[Real, sp.Basic], *, with_interval_number: bool = False + ) -> Union[sp.Basic, Tuple[sp.core.numbers.Integer, sp.Basic]]: """For periodic splines, maps the real point `x` to the reference period.""" - if self.bc != ('periodic', 'periodic'): - raise ValueError( - '_to_base_interval makes no sense with non-periodic bc' - ) + if self.bc != ("periodic", "periodic"): + raise ValueError("_to_base_interval makes no sense with non-periodic bc") xA = self.nodes[0] xB = self.nodes[-1] @@ -846,19 +843,19 @@ def _to_base_interval(self, x: Union[Real, sp.Basic], *, with_interval_number: b def evaluate(self, x: Union[Real, sp.Basic]) -> sp.Basic: """Evaluate the spline at the point `x`.""" - _x = sp.Dummy('x') + _x = sp.Dummy("x") return self._formula(x=_x, cache=False).subs(_x, x) def derivative(self, x: Union[Real, sp.Basic], **kwargs) -> sp.Expr: """Evaluate the spline derivative at the point `x`.""" # NB kwargs are used to pass on extrapolate=None # when called from .extrapolation_formulas() - _x = sp.Dummy('x') + _x = sp.Dummy("x") return self._formula(x=_x, cache=False, **kwargs).diff(_x).subs(_x, x) def second_derivative(self, x: Union[Real, sp.Basic]) -> sp.Basic: """Evaluate the spline second derivative at the point `x`.""" - _x = sp.Dummy('x') + _x = sp.Dummy("x") return self._formula(x=_x, cache=False).diff(_x).diff(_x).subs(_x, x) def squared_L2_norm_of_curvature(self) -> sp.Basic: @@ -868,25 +865,27 @@ def squared_L2_norm_of_curvature(self) -> sp.Basic: This is always computed in the spline native scale (i.e., in log-scale for positivity enforcing splines). """ - x = sp.Dummy('x') + x = sp.Dummy("x") integral = sp.sympify(0) for i in range(len(self.nodes) - 1): - formula = self.poly(i, x=x).diff(x, 2)**2 - integral += sp.integrate(formula, (x, self.nodes[i], self.nodes[i+1])) + formula = self.poly(i, x=x).diff(x, 2) ** 2 + integral += sp.integrate(formula, (x, self.nodes[i], self.nodes[i + 1])) return sp.simplify(integral) - def integrate(self, x0: Union[Real, sp.Basic], x1: Union[Real, sp.Basic]) -> sp.Basic: + def integrate( + self, x0: Union[Real, sp.Basic], x1: Union[Real, sp.Basic] + ) -> sp.Basic: """Integrate the spline between the points `x0` and `x1`.""" - x = sp.Dummy('x') + x = sp.Dummy("x") x0, x1 = sp.sympify((x0, x1)) if x0 > x1: - raise ValueError('x0 > x1') + raise ValueError("x0 > x1") if x0 == x1: return sp.sympify(0) - if self.extrapolate != ('periodic', 'periodic'): + if self.extrapolate != ("periodic", "periodic"): return self._formula(x=x, cache=False).integrate((x, x0, x1)) formula = self._formula(x=x, cache=False, extrapolate=None) @@ -901,61 +900,62 @@ def integrate(self, x0: Union[Real, sp.Basic], x1: Union[Real, sp.Basic]) -> sp. return formula.integrate((x, z0, z1)) if k0 + 1 == k1: - return formula.integrate((x, z0, xB)) + \ - formula.integrate((x, xA, z1)) + return formula.integrate((x, z0, xB)) + formula.integrate((x, xA, z1)) - return formula.integrate((x, z0, xB)) + \ - (k1 - k0 - 1) * formula.integrate((x, xA, xB)) + \ - formula.integrate((x, xA, z1)) + return ( + formula.integrate((x, z0, xB)) + + (k1 - k0 - 1) * formula.integrate((x, xA, xB)) + + formula.integrate((x, xA, z1)) + ) @property def amici_annotation(self) -> str: """An SBML annotation describing the spline.""" annotation = f'' + annotation += f"" for gc in grandchildren: annotation += gc - annotation += f'' + annotation += f"" - annotation += '' + annotation += "" # Check XML and prettify return pretty_xml(annotation) def _annotation_attributes(self) -> Dict[str, Any]: - attributes = {'spline_method': self.method} + attributes = {"spline_method": self.method} if self.bc[0] == self.bc[1]: if self.bc[0] is not None: - attributes['spline_bc'] = self.bc[0] + attributes["spline_bc"] = self.bc[0] else: bc1, bc2 = self.bc - bc1 = 'no_bc' if bc1 is None else bc1 - bc2 = 'no_bc' if bc2 is None else bc2 - attributes['spline_bc'] = f'({bc1}, {bc2})' + bc1 = "no_bc" if bc1 is None else bc1 + bc2 = "no_bc" if bc2 is None else bc2 + attributes["spline_bc"] = f"({bc1}, {bc2})" if self.extrapolate[0] == self.extrapolate[1]: extr = None if self.extrapolate is None else self.extrapolate[0] else: extr1, extr2 = self.extrapolate - extr1 = 'no_extrapolation' if extr1 is None else extr1 - extr2 = 'no_extrapolation' if extr2 is None else extr2 - extr = f'({extr1}, {extr2})' + extr1 = "no_extrapolation" if extr1 is None else extr1 + extr2 = "no_extrapolation" if extr2 is None else extr2 + extr = f"({extr1}, {extr2})" if extr is not None: - attributes['spline_extrapolate'] = extr + attributes["spline_extrapolate"] = extr if self.logarithmic_parametrization: - attributes['spline_logarithmic_parametrization'] = True + attributes["spline_logarithmic_parametrization"] = True return attributes @@ -964,10 +964,10 @@ def _annotation_children(self) -> Dict[str, Union[str, List[str]]]: with evaluate(False): x = self.evaluate_at.subs(amici_time_symbol, sbml_time_symbol) - children['spline_evaluation_point'] = sbml_mathml(x) + children["spline_evaluation_point"] = sbml_mathml(x) if isinstance(self.nodes, UniformGrid): - children['spline_uniform_grid'] = [ + children["spline_uniform_grid"] = [ sbml_mathml(self.nodes.start), sbml_mathml(self.nodes.stop), sbml_mathml(self.nodes.step), @@ -975,22 +975,22 @@ def _annotation_children(self) -> Dict[str, Union[str, List[str]]]: else: for x in self.nodes: assert amici_time_symbol not in x.free_symbols - children['spline_grid'] = [sbml_mathml(x) for x in self.nodes] + children["spline_grid"] = [sbml_mathml(x) for x in self.nodes] - children['spline_values'] = [sbml_mathml(y) for y in self.values_at_nodes] + children["spline_values"] = [sbml_mathml(y) for y in self.values_at_nodes] return children def add_to_sbml_model( - self, - model: libsbml.Model, - *, - auto_add: Union[bool, str] = False, - x_nominal: Optional[Sequence[float]] = None, - y_nominal: Optional[Union[Sequence[float], float]] = None, - x_units: Optional[str] = None, - y_units: Optional[str] = None, - y_constant: Optional[Union[Sequence[bool], bool]] = None, + self, + model: libsbml.Model, + *, + auto_add: Union[bool, str] = False, + x_nominal: Optional[Sequence[float]] = None, + y_nominal: Optional[Union[Sequence[float], float]] = None, + x_units: Optional[str] = None, + y_units: Optional[str] = None, + y_constant: Optional[Union[Sequence[bool], bool]] = None, ) -> None: """ Function to add the spline to an SBML model using an assignment rule @@ -1039,61 +1039,62 @@ def add_to_sbml_model( break # Autoadd parameters - if auto_add is True or auto_add == 'spline': - if not model.getParameter(str(self.sbml_id)) and not model.getSpecies(str(self.sbml_id)): - add_parameter(model, self.sbml_id, constant=False, - units=y_units) + if auto_add is True or auto_add == "spline": + if not model.getParameter(str(self.sbml_id)) and not model.getSpecies( + str(self.sbml_id) + ): + add_parameter(model, self.sbml_id, constant=False, units=y_units) if auto_add is True: if isinstance(x_nominal, collections.abc.Sequence): if len(x_nominal) != len(self.nodes): raise ValueError( - 'If x_nominal is a list, then it must have ' - 'the same length as the spline grid!' + "If x_nominal is a list, then it must have " + "the same length as the spline grid!" ) for i in range(len(x_nominal) - 1): - if x[i] >= x[i+1]: - raise ValueError( - 'x_nominal must be strictly increasing!' - ) + if x[i] >= x[i + 1]: + raise ValueError("x_nominal must be strictly increasing!") elif x_nominal is None: x_nominal = len(self.nodes) * [None] else: # It makes no sense to give a single nominal value: # grid values must all be different - raise TypeError('x_nominal must be a Sequence!') - for (_x, _val) in zip(self.nodes, x_nominal): + raise TypeError("x_nominal must be a Sequence!") + for _x, _val in zip(self.nodes, x_nominal): if _x.is_Symbol and not model.getParameter(_x.name): - add_parameter(model, _x.name, value=_val, - units=x_units) + add_parameter(model, _x.name, value=_val, units=x_units) if isinstance(y_nominal, collections.abc.Sequence): if len(y_nominal) != len(self.values_at_nodes): raise ValueError( - 'If y_nominal is a list, then it must have ' - 'the same length as the spline values!' + "If y_nominal is a list, then it must have " + "the same length as the spline values!" ) else: y_nominal = len(self.values_at_nodes) * [y_nominal] if isinstance(y_constant, collections.abc.Sequence): if len(y_constant) != len(self.values_at_nodes): raise ValueError( - 'If y_constant is a list, then it must have ' - 'the same length as the spline values!' + "If y_constant is a list, then it must have " + "the same length as the spline values!" ) else: y_constant = len(self.values_at_nodes) * [y_constant] - for (_y, _val, _const) in zip(self.values_at_nodes, y_nominal, y_constant): + for _y, _val, _const in zip( + self.values_at_nodes, y_nominal, y_constant + ): if _y.is_Symbol and not model.getParameter(_y.name): add_parameter( - model, _y.name, + model, + _y.name, value=_val, constant=_const, units=y_units, ) elif auto_add is not False: - raise ValueError(f'Invalid value {auto_add} for auto_add!') + raise ValueError(f"Invalid value {auto_add} for auto_add!") # Create assignment rule for spline rule = add_assignment_rule(model, self.sbml_id, self.mathml_formula) @@ -1101,33 +1102,30 @@ def add_to_sbml_model( # Add annotation specifying spline method retcode = rule.setAnnotation(self.amici_annotation) if retcode != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlAnnotationError('Could not set SBML annotation!') + raise SbmlAnnotationError("Could not set SBML annotation!") # Create additional assignment rule for periodic extrapolation # TODO is supported in SBML Level 3 (but not in Level 2). # Consider simplifying the formulas using it # (after checking it actually works as expected), # checking what level the input SBML model is. - if any(extr == 'periodic' for extr in self.extrapolate): - parameter_id = self.sbml_id.name + '_x_in_fundamental_period' + if any(extr == "periodic" for extr in self.extrapolate): + parameter_id = self.sbml_id.name + "_x_in_fundamental_period" T = self.nodes[-1] - self.nodes[0] x0 = self.nodes[0] s = 2 * sp.pi * ((x - x0) / T - sp.sympify(1) / 4) k = sp.Piecewise((3, sp.cos(s) < 0), (1, True)) formula = x0 + T * (sp.atan(sp.tan(s)) / (2 * sp.pi) + k / 4) assert amici_time_symbol not in formula.free_symbols - par = add_parameter( - model, parameter_id, constant=False, units=x_units - ) + par = add_parameter(model, parameter_id, constant=False, units=x_units) retcode = par.setAnnotation( f'' ) if retcode != libsbml.LIBSBML_OPERATION_SUCCESS: - raise SbmlAnnotationError('Could not set SBML annotation!') + raise SbmlAnnotationError("Could not set SBML annotation!") add_assignment_rule(model, parameter_id, formula) - def _replace_in_all_expressions(self, old: sp.Symbol, new: sp.Symbol) \ - -> None: + def _replace_in_all_expressions(self, old: sp.Symbol, new: sp.Symbol) -> None: if self.sbml_id == old: self._sbml_id = new self._x = self.evaluate_at.subs(old, new) @@ -1145,20 +1143,18 @@ def is_spline(rule: libsbml.AssignmentRule) -> bool: return AbstractSpline.get_annotation(rule) is not None @staticmethod - def get_annotation( - rule: libsbml.AssignmentRule - ) -> Union[ET.Element, None]: + def get_annotation(rule: libsbml.AssignmentRule) -> Union[ET.Element, None]: """ Extract AMICI spline annotation from an SBML assignment rule (given as a :py:class:`libsbml.AssignmentRule` object). Return ``None`` if any such annotation could not be found. """ if not isinstance(rule, libsbml.AssignmentRule): - raise TypeError('Rule must be an AssignmentRule!') + raise TypeError("Rule must be an AssignmentRule!") if rule.isSetAnnotation(): annotation = ET.fromstring(rule.getAnnotationString()) for child in annotation: - if child.tag == f'{{{annotation_namespace}}}spline': + if child.tag == f"{{{annotation_namespace}}}spline": return child return None @@ -1178,62 +1174,56 @@ def from_annotation( However, the mapping between method strings and subclasses must be hard-coded into this function here (at the moment). """ - if annotation.tag != f'{{{annotation_namespace}}}spline': - raise ValueError( - 'The given annotation is not an AMICI spline annotation!' - ) + if annotation.tag != f"{{{annotation_namespace}}}spline": + raise ValueError("The given annotation is not an AMICI spline annotation!") attributes = {} for key, value in annotation.items(): - if not key.startswith(f'{{{annotation_namespace}}}'): + if not key.startswith(f"{{{annotation_namespace}}}"): raise ValueError( - f'Unexpected attribute {key} inside spline annotation!' + f"Unexpected attribute {key} inside spline annotation!" ) - key = key[len(annotation_namespace) + 2:] - if value == 'true': + key = key[len(annotation_namespace) + 2 :] + if value == "true": value = True - elif value == 'false': + elif value == "false": value = False attributes[key] = value children = {} for child in annotation: - if not child.tag.startswith(f'{{{annotation_namespace}}}'): + if not child.tag.startswith(f"{{{annotation_namespace}}}"): raise ValueError( - f'Unexpected node {child.tag} inside spline annotation!' + f"Unexpected node {child.tag} inside spline annotation!" ) - key = child.tag[len(annotation_namespace) + 2:] + key = child.tag[len(annotation_namespace) + 2 :] value = [ mathml2sympy( ET.tostring(gc).decode(), evaluate=False, locals=locals_, - expression_type='Rule' + expression_type="Rule", ) for gc in child ] children[key] = value - if attributes['spline_method'] == 'cubic_hermite': + if attributes["spline_method"] == "cubic_hermite": cls = CubicHermiteSpline else: - raise ValueError( - f"Unknown spline method {attributes['spline_method']}!" - ) + raise ValueError(f"Unknown spline method {attributes['spline_method']}!") - del attributes['spline_method'] + del attributes["spline_method"] kwargs = cls._from_annotation(attributes, children) if attributes: raise ValueError( - 'Unprocessed attributes in spline annotation!\n' + - str(attributes) + "Unprocessed attributes in spline annotation!\n" + str(attributes) ) if children: raise ValueError( - 'Unprocessed children in spline annotation!\n' + - str(children) + "Unprocessed children in spline annotation!\n" + str(children) ) return cls(sbml_id, **kwargs) @@ -1251,63 +1241,63 @@ def _from_annotation( """ kwargs = {} - bc = attributes.pop('spline_bc', None) - if isinstance(bc, str) and bc.startswith('('): - if not bc.endswith(')'): - raise ValueError(f'Ill-formatted bc {bc}!') - bc_cmps = bc[1:-1].split(',') + bc = attributes.pop("spline_bc", None) + if isinstance(bc, str) and bc.startswith("("): + if not bc.endswith(")"): + raise ValueError(f"Ill-formatted bc {bc}!") + bc_cmps = bc[1:-1].split(",") if len(bc_cmps) != 2: - raise ValueError(f'Ill-formatted bc {bc}!') + raise ValueError(f"Ill-formatted bc {bc}!") bc = (bc_cmps[0].strip(), bc_cmps[1].strip()) - kwargs['bc'] = bc + kwargs["bc"] = bc - extr = attributes.pop('spline_extrapolate', None) - if isinstance(extr, str) and extr.startswith('('): - if not extr.endswith(')'): - raise ValueError(f'Ill-formatted extrapolation {extr}!') - extr_cmps = extr[1:-1].split(',') + extr = attributes.pop("spline_extrapolate", None) + if isinstance(extr, str) and extr.startswith("("): + if not extr.endswith(")"): + raise ValueError(f"Ill-formatted extrapolation {extr}!") + extr_cmps = extr[1:-1].split(",") if len(extr_cmps) != 2: - raise ValueError(f'Ill-formatted extrapolation {extr}!') + raise ValueError(f"Ill-formatted extrapolation {extr}!") extr = (extr_cmps[0].strip(), extr_cmps[1].strip()) - kwargs['extrapolate'] = extr + kwargs["extrapolate"] = extr - kwargs['logarithmic_parametrization'] = \ - attributes.pop('spline_logarithmic_parametrization', False) + kwargs["logarithmic_parametrization"] = attributes.pop( + "spline_logarithmic_parametrization", False + ) - if 'spline_evaluation_point' not in children.keys(): + if "spline_evaluation_point" not in children.keys(): raise ValueError( "Required spline annotation 'spline_evaluation_point' missing!" ) - x = children.pop('spline_evaluation_point') + x = children.pop("spline_evaluation_point") if len(x) != 1: raise ValueError( "Ill-formatted spline annotation 'spline_evaluation_point' " "(more than one children is present)!" ) - kwargs['evaluate_at'] = x[0] + kwargs["evaluate_at"] = x[0] - if 'spline_uniform_grid' in children: - start, stop, step = children.pop('spline_uniform_grid') - kwargs['nodes'] = UniformGrid(start, stop, step) - elif 'spline_grid' in children: - kwargs['nodes'] = children.pop('spline_grid') + if "spline_uniform_grid" in children: + start, stop, step = children.pop("spline_uniform_grid") + kwargs["nodes"] = UniformGrid(start, stop, step) + elif "spline_grid" in children: + kwargs["nodes"] = children.pop("spline_grid") else: raise ValueError( "Spline annotation requires either " "'spline_grid' or 'spline_uniform_grid' to be specified!" ) - if 'spline_values' not in children: - raise ValueError( - "Required spline annotation 'spline_values' missing!" - ) - kwargs['values_at_nodes'] = children.pop('spline_values') + if "spline_values" not in children: + raise ValueError("Required spline annotation 'spline_values' missing!") + kwargs["values_at_nodes"] = children.pop("spline_values") return kwargs def parameters(self, importer: sbml_import.SbmlImporter) -> Set[sp.Symbol]: """Returns the SBML parameters used by this spline""" from .de_export import SymbolId + return self._parameters().intersection( set(importer.symbols[SymbolId.PARAMETER].keys()) ) @@ -1318,10 +1308,7 @@ def _parameters(self) -> Set[sp.Symbol]: parameters.update(y.free_symbols) return parameters - def ode_model_symbol( - self, - importer: sbml_import.SbmlImporter - ) -> sp.Function: + def ode_model_symbol(self, importer: sbml_import.SbmlImporter) -> sp.Function: """ Returns the `sympy` object to be used by :py:class:`amici.de_export.ODEModel`. @@ -1346,6 +1333,7 @@ def fdiff(self, argindex=1): return sp.Integer(0) if argindex == 2: + class AmiciSplineDerivative(sp.Function): # Spline derivative # AmiciSplineDerivative(splineId, x, *parameters) @@ -1357,8 +1345,8 @@ def eval(cls, *args): def fdiff(self, argindex=1): return NotImplementedError( - 'Second order derivatives for spline ' - 'are not implemented yet.' + "Second order derivatives for spline " + "are not implemented yet." ) def _eval_is_real(self): @@ -1380,18 +1368,15 @@ def eval(cls, *args): def fdiff(self, argindex=1): return NotImplementedError( - 'Second order derivatives for spline ' - 'are not implemented yet.' + "Second order derivatives for spline " + "are not implemented yet." ) def _eval_is_real(self): return True return AmiciSplineSensitivity( - self.args[0], - self.args[1], - parameters[pindex], - *self.args[2:] + self.args[0], self.args[1], parameters[pindex], *self.args[2:] ) def _eval_is_real(self): @@ -1414,13 +1399,16 @@ def plot( parameters = {} if ax is None: from matplotlib import pyplot as plt + fig, ax = plt.subplots() if xlim is None: nodes = np.asarray(self.nodes) xlim = (float(nodes[0]), float(nodes[-1])) nodes = np.linspace(*xlim, npoints) ax.plot(nodes, [float(self.evaluate(x).subs(parameters)) for x in nodes]) - ax.plot(self.nodes, [float(y.subs(parameters)) for y in self.values_at_nodes], 'o') + ax.plot( + self.nodes, [float(y.subs(parameters)) for y in self.values_at_nodes], "o" + ) if xlabel is not None: ax.set_xlabel(xlabel) if ylabel is not None: @@ -1429,8 +1417,8 @@ def plot( def spline_user_functions( - splines: List[AbstractSpline], - p_index: Dict[sp.Symbol, int], + splines: List[AbstractSpline], + p_index: Dict[sp.Symbol, int], ) -> Dict[str, List[Tuple[Callable[..., bool], Callable[..., str]]]]: """ Custom user functions to be used in `ODEExporter` @@ -1438,34 +1426,39 @@ def spline_user_functions( """ spline_ids = [spline.sbml_id.name for spline in splines] return { - 'AmiciSpline': [( - lambda *args: True, - lambda spline_id, x, *p: f"spl_{spline_ids.index(spline_id)}" - )], - 'AmiciSplineDerivative': [( - lambda *args: True, - lambda spline_id, x, *p: f"dspl_{spline_ids.index(spline_id)}" - )], - 'AmiciSplineSensitivity': [( - lambda *args: True, - lambda spline_id, x, param_id, *p: - f"sspl_{spline_ids.index(spline_id)}_{p_index[param_id]}" - )], + "AmiciSpline": [ + ( + lambda *args: True, + lambda spline_id, x, *p: f"spl_{spline_ids.index(spline_id)}", + ) + ], + "AmiciSplineDerivative": [ + ( + lambda *args: True, + lambda spline_id, x, *p: f"dspl_{spline_ids.index(spline_id)}", + ) + ], + "AmiciSplineSensitivity": [ + ( + lambda *args: True, + lambda spline_id, x, param_id, *p: f"sspl_{spline_ids.index(spline_id)}_{p_index[param_id]}", + ) + ], } class CubicHermiteSpline(AbstractSpline): def __init__( - self, - sbml_id: Union[str, sp.Symbol], - nodes: Sequence, - values_at_nodes: Sequence, - derivatives_at_nodes: Sequence = None, - *, - evaluate_at: Optional[Union[str, sp.Basic]] = None, - bc: BClike = 'auto', - extrapolate: BClike = None, - logarithmic_parametrization: bool = False + self, + sbml_id: Union[str, sp.Symbol], + nodes: Sequence, + values_at_nodes: Sequence, + derivatives_at_nodes: Sequence = None, + *, + evaluate_at: Optional[Union[str, sp.Basic]] = None, + bc: BClike = "auto", + extrapolate: BClike = None, + logarithmic_parametrization: bool = False, ): """ Constructor for `CubicHermiteSpline` objects. @@ -1522,42 +1515,48 @@ def __init__( # however, we check it now so that an informative message # can be printed (otherwise finite difference computation fails) raise ValueError( - 'Length of nodes and values_at_nodes must be the same ' - f'(instead len(nodes) = {len(nodes)} and len(values_at_nodes) = {len(values_at_nodes)})!' + "Length of nodes and values_at_nodes must be the same " + f"(instead len(nodes) = {len(nodes)} and len(values_at_nodes) = {len(values_at_nodes)})!" ) bc, extrapolate = self._normalize_bc_and_extrapolate(bc, extrapolate) - if bc[0] == 'zeroderivative+natural' \ - or bc[1] == 'zeroderivative+natural': - raise ValueError("zeroderivative+natural bc not supported by " - "CubicHermiteSplines!") + if bc[0] == "zeroderivative+natural" or bc[1] == "zeroderivative+natural": + raise ValueError( + "zeroderivative+natural bc not supported by " "CubicHermiteSplines!" + ) if derivatives_at_nodes is None: derivatives_at_nodes = _finite_differences(nodes, values_at_nodes, bc) self._derivatives_by_fd = True else: - derivatives_at_nodes = np.asarray([sympify_noeval(d) for d in derivatives_at_nodes]) + derivatives_at_nodes = np.asarray( + [sympify_noeval(d) for d in derivatives_at_nodes] + ) self._derivatives_by_fd = False if len(nodes) != len(derivatives_at_nodes): raise ValueError( - 'Length of nodes and derivatives_at_nodes must be the same ' - f'(instead len(nodes) = {len(nodes)} and len(derivatives_at_nodes) = {len(derivatives_at_nodes)})!' + "Length of nodes and derivatives_at_nodes must be the same " + f"(instead len(nodes) = {len(nodes)} and len(derivatives_at_nodes) = {len(derivatives_at_nodes)})!" ) - if bc == ('periodic', 'periodic') \ - and (values_at_nodes[0] != values_at_nodes[-1] or derivatives_at_nodes[0] != derivatives_at_nodes[-1]): + if bc == ("periodic", "periodic") and ( + values_at_nodes[0] != values_at_nodes[-1] + or derivatives_at_nodes[0] != derivatives_at_nodes[-1] + ): raise ValueError( - 'bc=periodic but given values_at_nodes and derivatives_at_nodes do not satisfy ' - 'periodic boundary conditions!' + "bc=periodic but given values_at_nodes and derivatives_at_nodes do not satisfy " + "periodic boundary conditions!" ) super().__init__( - sbml_id, nodes, values_at_nodes, + sbml_id, + nodes, + values_at_nodes, evaluate_at=evaluate_at, bc=bc, extrapolate=extrapolate, - logarithmic_parametrization=logarithmic_parametrization + logarithmic_parametrization=logarithmic_parametrization, ) self._derivatives_at_nodes = derivatives_at_nodes @@ -1578,7 +1577,7 @@ def smoothness(self) -> int: @property def method(self) -> str: """Spline method (cubic Hermite spline)""" - return 'cubic_hermite' + return "cubic_hermite" @property def derivatives_by_fd(self) -> bool: @@ -1592,10 +1591,13 @@ def check_if_valid(self, importer: sbml_import.SbmlImporter) -> None: """ # TODO this is very much a draft from .de_export import SymbolId + species: List[sp.Symbol] = list(importer.symbols[SymbolId.SPECIES]) for d in self.derivatives_at_nodes: if len(d.free_symbols.intersection(species)) != 0: - raise ValueError('derivatives_at_nodes should not depend on model species') + raise ValueError( + "derivatives_at_nodes should not depend on model species" + ) super().check_if_valid(importer) @@ -1624,10 +1626,10 @@ def _poly(self, t: Union[Real, sp.Basic], i: Integral) -> sp.Basic: dx = self.nodes[i + 1] - self.nodes[i] - h00 = 2 * t ** 3 - 3 * t ** 2 + 1 - h10 = t ** 3 - 2 * t ** 2 + t - h01 = -2 * t ** 3 + 3 * t ** 2 - h11 = t ** 3 - t ** 2 + h00 = 2 * t**3 - 3 * t**2 + 1 + h10 = t**3 - 2 * t**2 + t + h01 = -2 * t**3 + 3 * t**2 + h11 = t**3 - t**2 y0 = self.y_scaled(i) y1 = self.y_scaled(i + 1) @@ -1640,7 +1642,9 @@ def _poly(self, t: Union[Real, sp.Basic], i: Integral) -> sp.Basic: def _annotation_children(self) -> Dict[str, Union[str, List[str]]]: children = super()._annotation_children() if not self._derivatives_by_fd: - children['spline_derivatives'] = [sbml_mathml(d) for d in self.derivatives_at_nodes] + children["spline_derivatives"] = [ + sbml_mathml(d) for d in self.derivatives_at_nodes + ] return children def _parameters(self) -> Set[sp.Symbol]: @@ -1649,52 +1653,53 @@ def _parameters(self) -> Set[sp.Symbol]: parameters.update(d.free_symbols) return parameters - def _replace_in_all_expressions( - self, old: sp.Symbol, new: sp.Symbol - ) -> None: + def _replace_in_all_expressions(self, old: sp.Symbol, new: sp.Symbol) -> None: super()._replace_in_all_expressions(old, new) - self._derivatives_at_nodes = [d.subs(old, new) for d in self.derivatives_at_nodes] + self._derivatives_at_nodes = [ + d.subs(old, new) for d in self.derivatives_at_nodes + ] @classmethod def _from_annotation(cls, attributes, children) -> Dict[str, Any]: kwargs = super()._from_annotation(attributes, children) - if 'spline_derivatives' in children.keys(): - kwargs['derivatives_at_nodes'] = children.pop('spline_derivatives') + if "spline_derivatives" in children.keys(): + kwargs["derivatives_at_nodes"] = children.pop("spline_derivatives") return kwargs def __str__(self) -> str: - s = 'HermiteCubicSpline ' + \ - f'on ({self.nodes[0]}, {self.nodes[-1]}) with {len(self.nodes)} points' + s = ( + "HermiteCubicSpline " + + f"on ({self.nodes[0]}, {self.nodes[-1]}) with {len(self.nodes)} points" + ) cmps = [] if self.bc != (None, None): - if self.bc == ('periodic', 'periodic'): - cmps.append('periodic') + if self.bc == ("periodic", "periodic"): + cmps.append("periodic") else: - cmps.append(f'bc = {self.bc}') + cmps.append(f"bc = {self.bc}") if self.derivatives_by_fd: - cmps.append('finite differences') + cmps.append("finite differences") if self.extrapolate != (None, None): - cmps.append(f'extrapolate = {self.extrapolate}') + cmps.append(f"extrapolate = {self.extrapolate}") if not cmps: return s - return s + ' [' + ', '.join(cmps) + ']' + return s + " [" + ", ".join(cmps) + "]" -def _finite_differences(xx: np.ndarray, yy: np.ndarray, bc: NormalizedBC) \ - -> np.ndarray: +def _finite_differences(xx: np.ndarray, yy: np.ndarray, bc: NormalizedBC) -> np.ndarray: dd = [] - if bc[0] == 'periodic': + if bc[0] == "periodic": fd = _centered_fd(yy[-2], yy[0], yy[1], xx[-1] - xx[-2], xx[1] - xx[0]) - elif bc[0] == 'zeroderivative': + elif bc[0] == "zeroderivative": fd = sp.Integer(0) - elif bc[0] == 'natural': + elif bc[0] == "natural": if len(xx) < 3: raise ValueError( - 'At least 3 nodes are needed ' - 'for computing finite differences with natural bc!' + "At least 3 nodes are needed " + "for computing finite differences with natural bc!" ) fd = _natural_fd(yy[0], xx[1] - xx[0], yy[1], xx[2] - xx[1], yy[2]) else: @@ -1703,22 +1708,22 @@ def _finite_differences(xx: np.ndarray, yy: np.ndarray, bc: NormalizedBC) \ for i in range(1, len(xx) - 1): dd.append( - _centered_fd(yy[i - 1], yy[i], yy[i + 1], xx[i] - xx[i - 1], - xx[i + 1] - xx[i]) + _centered_fd( + yy[i - 1], yy[i], yy[i + 1], xx[i] - xx[i - 1], xx[i + 1] - xx[i] + ) ) - if bc[1] == 'periodic': + if bc[1] == "periodic": fd = dd[0] - elif bc[1] == 'zeroderivative': + elif bc[1] == "zeroderivative": fd = sp.Integer(0) - elif bc[1] == 'natural': + elif bc[1] == "natural": if len(xx) < 3: raise ValueError( - 'At least 3 nodes are needed ' - 'for computing finite differences with natural bc!' + "At least 3 nodes are needed " + "for computing finite differences with natural bc!" ) - fd = _natural_fd(yy[-1], xx[-2] - xx[-1], yy[-2], xx[-3] - xx[-2], - yy[-3]) + fd = _natural_fd(yy[-1], xx[-2] - xx[-1], yy[-2], xx[-3] - xx[-2], yy[-3]) else: fd = _onesided_fd(yy[-2], yy[-1], xx[-1] - xx[-2]) dd.append(fd) diff --git a/python/sdist/amici/swig.py b/python/sdist/amici/swig.py index c7b12a370f..bfb2964a3a 100644 --- a/python/sdist/amici/swig.py +++ b/python/sdist/amici/swig.py @@ -8,43 +8,40 @@ class TypeHintFixer(ast.NodeTransformer): """Replaces SWIG-generated C++ typehints by corresponding Python types""" mapping = { - 'void': None, - 'double': ast.Name('float'), - 'int': ast.Name('int'), - 'long': ast.Name('int'), - 'ptrdiff_t': ast.Name('int'), - 'size_t': ast.Name('int'), - 'bool': ast.Name('bool'), - 'std::unique_ptr< amici::Solver >': ast.Constant('Solver'), - 'amici::InternalSensitivityMethod': - ast.Constant('InternalSensitivityMethod'), - 'amici::InterpolationType': ast.Constant('InterpolationType'), - 'amici::LinearMultistepMethod': ast.Constant('LinearMultistepMethod'), - 'amici::LinearSolver': ast.Constant('LinearSolver'), - 'amici::Model *': ast.Constant('Model'), - 'amici::Model const *': ast.Constant('Model'), - 'amici::NewtonDampingFactorMode': - ast.Constant('NewtonDampingFactorMode'), - 'amici::NonlinearSolverIteration': - ast.Constant('NonlinearSolverIteration'), - 'amici::ObservableScaling': ast.Constant('ObservableScaling'), - 'amici::ParameterScaling': ast.Constant('ParameterScaling'), - 'amici::RDataReporting': ast.Constant('RDataReporting'), - 'amici::SensitivityMethod': ast.Constant('SensitivityMethod'), - 'amici::SensitivityOrder': ast.Constant('SensitivityOrder'), - 'amici::Solver *': ast.Constant('Solver'), - 'amici::SteadyStateSensitivityMode': - ast.Constant('SteadyStateSensitivityMode'), - 'amici::realtype': ast.Name('float'), - 'DoubleVector': ast.Constant('Sequence[float]'), - 'IntVector': ast.Name('Sequence[int]'), - 'std::string': ast.Name('str'), - 'std::string const &': ast.Name('str'), - 'std::unique_ptr< amici::ExpData >': ast.Constant('ExpData'), - 'std::unique_ptr< amici::ReturnData >': ast.Constant('ReturnData'), - 'std::vector< amici::ParameterScaling,' - 'std::allocator< amici::ParameterScaling > > const &': - ast.Constant('ParameterScalingVector') + "void": None, + "double": ast.Name("float"), + "int": ast.Name("int"), + "long": ast.Name("int"), + "ptrdiff_t": ast.Name("int"), + "size_t": ast.Name("int"), + "bool": ast.Name("bool"), + "std::unique_ptr< amici::Solver >": ast.Constant("Solver"), + "amici::InternalSensitivityMethod": ast.Constant("InternalSensitivityMethod"), + "amici::InterpolationType": ast.Constant("InterpolationType"), + "amici::LinearMultistepMethod": ast.Constant("LinearMultistepMethod"), + "amici::LinearSolver": ast.Constant("LinearSolver"), + "amici::Model *": ast.Constant("Model"), + "amici::Model const *": ast.Constant("Model"), + "amici::NewtonDampingFactorMode": ast.Constant("NewtonDampingFactorMode"), + "amici::NonlinearSolverIteration": ast.Constant("NonlinearSolverIteration"), + "amici::ObservableScaling": ast.Constant("ObservableScaling"), + "amici::ParameterScaling": ast.Constant("ParameterScaling"), + "amici::RDataReporting": ast.Constant("RDataReporting"), + "amici::SensitivityMethod": ast.Constant("SensitivityMethod"), + "amici::SensitivityOrder": ast.Constant("SensitivityOrder"), + "amici::Solver *": ast.Constant("Solver"), + "amici::SteadyStateSensitivityMode": ast.Constant("SteadyStateSensitivityMode"), + "amici::realtype": ast.Name("float"), + "DoubleVector": ast.Constant("Sequence[float]"), + "IntVector": ast.Name("Sequence[int]"), + "std::string": ast.Name("str"), + "std::string const &": ast.Name("str"), + "std::unique_ptr< amici::ExpData >": ast.Constant("ExpData"), + "std::unique_ptr< amici::ReturnData >": ast.Constant("ReturnData"), + "std::vector< amici::ParameterScaling," + "std::allocator< amici::ParameterScaling > > const &": ast.Constant( + "ParameterScalingVector" + ), } def visit_FunctionDef(self, node): @@ -73,15 +70,21 @@ def _new_annot(self, old_annot: str): return ast.Name("int") # std::vector value type - if (value_type := re.sub( - r'std::vector< (.*) >::value_type(?: const &)?', - r'\1', old_annot)) in self.mapping: + if ( + value_type := re.sub( + r"std::vector< (.*) >::value_type(?: const &)?", r"\1", old_annot + ) + ) in self.mapping: return self.mapping[value_type] # std::vector - if (value_type := re.sub( - r'std::vector< (.*),std::allocator< \1 > >(?: const &)?', - r'\1', old_annot)) in self.mapping: + if ( + value_type := re.sub( + r"std::vector< (.*),std::allocator< \1 > >(?: const &)?", + r"\1", + old_annot, + ) + ) in self.mapping: value_type_annot = self.mapping[value_type] if isinstance(value_type_annot, ast.Constant): return ast.Name(f"Tuple['{value_type_annot.value}']") @@ -94,11 +97,11 @@ def _new_annot(self, old_annot: str): def fix_typehints(infilename, outfilename): """Change SWIG-generated C++ typehints to Python typehints""" # Only available from Python3.9 - if not getattr(ast, 'unparse', None): + if not getattr(ast, "unparse", None): return # file -> AST - with open(infilename, 'r') as f: + with open(infilename, "r") as f: source = f.read() parsed_source = ast.parse(source) @@ -107,5 +110,5 @@ def fix_typehints(infilename, outfilename): parsed_source = fixer.visit(parsed_source) # AST -> file - with open(outfilename, 'w') as f: + with open(outfilename, "w") as f: f.write(ast.unparse(parsed_source)) diff --git a/python/sdist/amici/swig_wrappers.py b/python/sdist/amici/swig_wrappers.py index 8601b97c0f..17490a2028 100644 --- a/python/sdist/amici/swig_wrappers.py +++ b/python/sdist/amici/swig_wrappers.py @@ -13,18 +13,25 @@ __all__ = [ - 'runAmiciSimulation', 'runAmiciSimulations', 'ExpData', - 'readSolverSettingsFromHDF5', 'writeSolverSettingsToHDF5', - 'set_model_settings', 'get_model_settings', - 'AmiciModel', 'AmiciSolver', 'AmiciExpData', 'AmiciReturnData', - 'AmiciExpDataVector' + "runAmiciSimulation", + "runAmiciSimulations", + "ExpData", + "readSolverSettingsFromHDF5", + "writeSolverSettingsToHDF5", + "set_model_settings", + "get_model_settings", + "AmiciModel", + "AmiciSolver", + "AmiciExpData", + "AmiciReturnData", + "AmiciExpDataVector", ] -AmiciModel = Union['amici.Model', 'amici.ModelPtr'] -AmiciSolver = Union['amici.Solver', 'amici.SolverPtr'] -AmiciExpData = Union['amici.ExpData', 'amici.ExpDataPtr'] -AmiciReturnData = Union['amici.ReturnData', 'amici.ReturnDataPtr'] -AmiciExpDataVector = Union['amici.ExpDataPtrVector', Sequence[AmiciExpData]] +AmiciModel = Union["amici.Model", "amici.ModelPtr"] +AmiciSolver = Union["amici.Solver", "amici.SolverPtr"] +AmiciExpData = Union["amici.ExpData", "amici.ExpDataPtr"] +AmiciReturnData = Union["amici.ReturnData", "amici.ReturnDataPtr"] +AmiciExpDataVector = Union["amici.ExpDataPtrVector", Sequence[AmiciExpData]] try: @@ -45,9 +52,13 @@ def _capture_cstdout(): def _get_ptr( - obj: Union[AmiciModel, AmiciExpData, AmiciSolver, AmiciReturnData] -) -> Union['amici_swig.Model', 'amici_swig.ExpData', - 'amici_swig.Solver', 'amici_swig.ReturnData']: + obj: Union[AmiciModel, AmiciExpData, AmiciSolver, AmiciReturnData] +) -> Union[ + "amici_swig.Model", + "amici_swig.ExpData", + "amici_swig.Solver", + "amici_swig.ReturnData", +]: """ Convenience wrapper that returns the smart pointer pointee, if applicable @@ -57,17 +68,22 @@ def _get_ptr( :returns: Non-smart pointer """ - if isinstance(obj, (amici_swig.ModelPtr, amici_swig.ExpDataPtr, - amici_swig.SolverPtr, amici_swig.ReturnDataPtr)): + if isinstance( + obj, + ( + amici_swig.ModelPtr, + amici_swig.ExpDataPtr, + amici_swig.SolverPtr, + amici_swig.ReturnDataPtr, + ), + ): return obj.get() return obj def runAmiciSimulation( - model: AmiciModel, - solver: AmiciSolver, - edata: Optional[AmiciExpData] = None -) -> 'numpy.ReturnDataView': + model: AmiciModel, solver: AmiciSolver, edata: Optional[AmiciExpData] = None +) -> "numpy.ReturnDataView": """ Convenience wrapper around :py:func:`amici.amici.runAmiciSimulation` (generated by swig) @@ -87,14 +103,15 @@ def runAmiciSimulation( """ with _capture_cstdout(): rdata = amici_swig.runAmiciSimulation( - _get_ptr(solver), _get_ptr(edata), _get_ptr(model)) + _get_ptr(solver), _get_ptr(edata), _get_ptr(model) + ) _log_simulation(rdata) if solver.getReturnDataReportingMode() == amici.RDataReporting.full: _ids_and_names_to_rdata(rdata, model) return numpy.ReturnDataView(rdata) -def ExpData(*args) -> 'amici_swig.ExpData': +def ExpData(*args) -> "amici_swig.ExpData": """ Convenience wrapper for :py:class:`amici.amici.ExpData` constructors @@ -103,7 +120,7 @@ def ExpData(*args) -> 'amici_swig.ExpData': :returns: ExpData Instance """ if isinstance(args[0], numpy.ReturnDataView): - return amici_swig.ExpData(_get_ptr(args[0]['ptr']), *args[1:]) + return amici_swig.ExpData(_get_ptr(args[0]["ptr"]), *args[1:]) elif isinstance(args[0], (amici_swig.ExpData, amici_swig.ExpDataPtr)): # the *args[:1] should be empty, but by the time you read this, # the constructor signature may have changed, and you are glad this @@ -116,12 +133,12 @@ def ExpData(*args) -> 'amici_swig.ExpData': def runAmiciSimulations( - model: AmiciModel, - solver: AmiciSolver, - edata_list: AmiciExpDataVector, - failfast: bool = True, - num_threads: int = 1, -) -> List['numpy.ReturnDataView']: + model: AmiciModel, + solver: AmiciSolver, + edata_list: AmiciExpDataVector, + failfast: bool = True, + num_threads: int = 1, +) -> List["numpy.ReturnDataView"]: """ Convenience wrapper for loops of amici.runAmiciSimulation @@ -137,11 +154,7 @@ def runAmiciSimulations( with _capture_cstdout(): edata_ptr_vector = amici_swig.ExpDataPtrVector(edata_list) rdata_ptr_list = amici_swig.runAmiciSimulations( - _get_ptr(solver), - edata_ptr_vector, - _get_ptr(model), - failfast, - num_threads + _get_ptr(solver), edata_ptr_vector, _get_ptr(model), failfast, num_threads ) for rdata in rdata_ptr_list: _log_simulation(rdata) @@ -152,9 +165,7 @@ def runAmiciSimulations( def readSolverSettingsFromHDF5( - file: str, - solver: AmiciSolver, - location: Optional[str] = 'solverSettings' + file: str, solver: AmiciSolver, location: Optional[str] = "solverSettings" ) -> None: """ Convenience wrapper for :py:func:`amici.readSolverSettingsFromHDF5` @@ -167,9 +178,9 @@ def readSolverSettingsFromHDF5( def writeSolverSettingsToHDF5( - solver: AmiciSolver, - file: Union[str, object], - location: Optional[str] = 'solverSettings' + solver: AmiciSolver, + file: Union[str, object], + location: Optional[str] = "solverSettings", ) -> None: """ Convenience wrapper for :py:func:`amici.amici.writeSolverSettingsToHDF5` @@ -189,27 +200,27 @@ def writeSolverSettingsToHDF5( model_instance_settings = [ # `setParameter{List,Scale}` will clear initial state sensitivities, so # `setParameter{List,Scale}` has to be called first. - 'ParameterList', - 'ParameterScale', # getter returns a SWIG object - 'AddSigmaResiduals', - 'AlwaysCheckFinite', - 'FixedParameters', - 'InitialStates', - ('getInitialStateSensitivities', 'setUnscaledInitialStateSensitivities'), - 'MinimumSigmaResiduals', - ('nMaxEvent', 'setNMaxEvent'), - 'Parameters', - 'ReinitializationStateIdxs', - 'ReinitializeFixedParameterInitialStates', - 'StateIsNonNegative', - 'SteadyStateSensitivityMode', - ('t0', 'setT0'), - 'Timepoints', + "ParameterList", + "ParameterScale", # getter returns a SWIG object + "AddSigmaResiduals", + "AlwaysCheckFinite", + "FixedParameters", + "InitialStates", + ("getInitialStateSensitivities", "setUnscaledInitialStateSensitivities"), + "MinimumSigmaResiduals", + ("nMaxEvent", "setNMaxEvent"), + "Parameters", + "ReinitializationStateIdxs", + "ReinitializeFixedParameterInitialStates", + "StateIsNonNegative", + "SteadyStateSensitivityMode", + ("t0", "setT0"), + "Timepoints", ] def get_model_settings( - model: AmiciModel, + model: AmiciModel, ) -> Dict[str, Any]: """Get model settings that are set independently of the compiled model. @@ -219,27 +230,29 @@ def get_model_settings( """ settings = {} for setting in model_instance_settings: - getter = setting[0] if isinstance(setting, tuple) else f'get{setting}' + getter = setting[0] if isinstance(setting, tuple) else f"get{setting}" - if getter == 'getInitialStates' and not model.hasCustomInitialStates(): + if getter == "getInitialStates" and not model.hasCustomInitialStates(): settings[setting] = [] continue - if getter == 'getInitialStateSensitivities' \ - and not model.hasCustomInitialStateSensitivities(): + if ( + getter == "getInitialStateSensitivities" + and not model.hasCustomInitialStateSensitivities() + ): settings[setting] = [] continue settings[setting] = getattr(model, getter)() # TODO `amici.Model.getParameterScale` returns a SWIG object instead # of a Python list/tuple. - if setting == 'ParameterScale': + if setting == "ParameterScale": settings[setting] = tuple(settings[setting]) return settings def set_model_settings( - model: AmiciModel, - settings: Dict[str, Any], + model: AmiciModel, + settings: Dict[str, Any], ) -> None: """Set model settings. @@ -248,7 +261,7 @@ def set_model_settings( values are provided to the setters. """ for setting, value in settings.items(): - setter = setting[1] if isinstance(setting, tuple) else f'set{setting}' + setter = setting[1] if isinstance(setting, tuple) else f"set{setting}" getattr(model, setter)(value) @@ -263,23 +276,21 @@ def _log_simulation(rdata: amici_swig.ReturnData): condition = f"[{rdata.id}]" if rdata.id else "" logger.log( amici_severity_to_logging[msg.severity], - f"{condition}[{msg.identifier}] {msg.message}" + f"{condition}[{msg.identifier}] {msg.message}", ) -def _ids_and_names_to_rdata( - rdata: amici_swig.ReturnData, - model: amici_swig.Model -): +def _ids_and_names_to_rdata(rdata: amici_swig.ReturnData, model: amici_swig.Model): """Copy entity IDs and names from a Model to ReturnData.""" - for entity_type in ('State', 'Observable', 'Expression', - 'Parameter', 'FixedParameter'): - for name_or_id in ('Ids', 'Names'): - names_or_ids = getattr(model, f'get{entity_type}{name_or_id}')() - setattr( - rdata, - f"{entity_type.lower()}_{name_or_id.lower()}", - names_or_ids - ) + for entity_type in ( + "State", + "Observable", + "Expression", + "Parameter", + "FixedParameter", + ): + for name_or_id in ("Ids", "Names"): + names_or_ids = getattr(model, f"get{entity_type}{name_or_id}")() + setattr(rdata, f"{entity_type.lower()}_{name_or_id.lower()}", names_or_ids) rdata.state_ids_solver = model.getStateIdsSolver() rdata.state_names_solver = model.getStateNamesSolver() diff --git a/python/sdist/amici/testing.py b/python/sdist/amici/testing.py index de1f69a1cc..cdee80b1f0 100644 --- a/python/sdist/amici/testing.py +++ b/python/sdist/amici/testing.py @@ -9,16 +9,19 @@ # see also https://stackoverflow.com/a/62364698 ON_VALGRIND = any( needle in haystack - for needle in ('valgrind', 'vgpreload') - for haystack in (os.getenv("LD_PRELOAD", ""), - os.getenv("DYLD_INSERT_LIBRARIES", "")) + for needle in ("valgrind", "vgpreload") + for haystack in ( + os.getenv("LD_PRELOAD", ""), + os.getenv("DYLD_INSERT_LIBRARIES", ""), + ) ) # Decorator to skip certain tests when we are under valgrind # (those that are independent of the AMICI C++ parts, or that take too long, # or that test performance) skip_on_valgrind = pytest.mark.skipif( - ON_VALGRIND, reason="Takes too long or is meaningless under valgrind") + ON_VALGRIND, reason="Takes too long or is meaningless under valgrind" +) class TemporaryDirectoryWinSafe(TemporaryDirectory): @@ -28,11 +31,12 @@ class TemporaryDirectoryWinSafe(TemporaryDirectory): otherwise fail on Windows with a ``PermissionError``. This class ignores such failures. """ + def cleanup(self): try: super().cleanup() except PermissionError as e: - if sys.platform not in {'win32', 'cygwin'}: + if sys.platform not in {"win32", "cygwin"}: raise e except NotADirectoryError: # Ignore exception on Windows for pyd files: diff --git a/python/sdist/setup.py b/python/sdist/setup.py index ede595f4cb..dcfcb34a3d 100755 --- a/python/sdist/setup.py +++ b/python/sdist/setup.py @@ -21,9 +21,13 @@ sys.path.insert(0, os.path.dirname(__file__)) from amici.custom_commands import ( - AmiciInstall, AmiciDevelop, - AmiciInstallLib, AmiciSDist, AmiciBuildPy, - AmiciBuildCMakeExtension) + AmiciInstall, + AmiciDevelop, + AmiciInstallLib, + AmiciSDist, + AmiciBuildPy, + AmiciBuildCMakeExtension, +) def get_extensions(): @@ -41,63 +45,63 @@ def get_extensions(): # SuiteSparse Config suitesparse_config = CMakeExtension( - name='SuiteSparse_config', - install_prefix='amici', - source_dir='amici/ThirdParty/SuiteSparse/SuiteSparse_config', + name="SuiteSparse_config", + install_prefix="amici", + source_dir="amici/ThirdParty/SuiteSparse/SuiteSparse_config", cmake_configure_options=[ *global_cmake_configure_options, "-DBLA_VENDOR=All", "-DENABLE_CUDA=FALSE", "-DNFORTRAN=TRUE", - ] + ], ) # SuiteSparse AMD amd = CMakeExtension( - name='amd', - install_prefix='amici', - source_dir='amici/ThirdParty/SuiteSparse/AMD', + name="amd", + install_prefix="amici", + source_dir="amici/ThirdParty/SuiteSparse/AMD", cmake_configure_options=[ *global_cmake_configure_options, "-DNFORTRAN=TRUE", - ] + ], ) # SuiteSparse BTF btf = CMakeExtension( - name='btf', - install_prefix='amici', - source_dir='amici/ThirdParty/SuiteSparse/BTF', + name="btf", + install_prefix="amici", + source_dir="amici/ThirdParty/SuiteSparse/BTF", cmake_configure_options=[ *global_cmake_configure_options, "-DNFORTRAN=TRUE", - ] + ], ) # SuiteSparse COLAMD colamd = CMakeExtension( - name='colamd', - install_prefix='amici', - source_dir='amici/ThirdParty/SuiteSparse/COLAMD', + name="colamd", + install_prefix="amici", + source_dir="amici/ThirdParty/SuiteSparse/COLAMD", cmake_configure_options=[ *global_cmake_configure_options, "-DNFORTRAN=TRUE", - ] + ], ) # SuiteSparse KLU klu = CMakeExtension( - name='klu', - install_prefix='amici', - source_dir='amici/ThirdParty/SuiteSparse/KLU', + name="klu", + install_prefix="amici", + source_dir="amici/ThirdParty/SuiteSparse/KLU", cmake_configure_options=[ *global_cmake_configure_options, "-DNCHOLMOD=ON", "-DENABLE_CUDA=FALSE", "-DNFORTRAN=TRUE", - ] + ], ) # SUNDIALS sundials = CMakeExtension( - name='sundials', - install_prefix='amici', - source_dir='amici/ThirdParty/sundials', + name="sundials", + install_prefix="amici", + source_dir="amici/ThirdParty/sundials", cmake_configure_options=[ *global_cmake_configure_options, "-DBUILD_ARKODE=OFF", @@ -117,18 +121,18 @@ def get_extensions(): # before being passed to CMake. "-DKLU_LIBRARY_DIR='${build_dir}/amici/lib'", "-DKLU_INCLUDE_DIR='${build_dir}/amici/include'", - ] + ], ) # AMICI amici_ext = CMakeExtension( - name='amici', - install_prefix='amici', - source_dir='amici', + name="amici", + install_prefix="amici", + source_dir="amici", cmake_configure_options=[ *global_cmake_configure_options, - '-DAMICI_PYTHON_BUILD_EXT_ONLY=ON', - f'-DPython3_EXECUTABLE={Path(sys.executable).as_posix()}', - ] + "-DAMICI_PYTHON_BUILD_EXT_ONLY=ON", + f"-DPython3_EXECUTABLE={Path(sys.executable).as_posix()}", + ], ) # Order matters! return [suitesparse_config, amd, btf, colamd, klu, sundials, amici_ext] @@ -137,28 +141,29 @@ def get_extensions(): def main(): # Readme as long package description to go on PyPi # (https://pypi.org/project/amici/) - with open(os.path.join(os.path.dirname(__file__), "README.md"), - "r", encoding="utf-8") as fh: + with open( + os.path.join(os.path.dirname(__file__), "README.md"), "r", encoding="utf-8" + ) as fh: long_description = fh.read() ext_modules = get_extensions() # handle parallel building # Note: can be empty to use all hardware threads - if (parallel_jobs := os.environ.get('AMICI_PARALLEL_COMPILE')) is not None: - os.environ['CMAKE_BUILD_PARALLEL_LEVEL'] = parallel_jobs + if (parallel_jobs := os.environ.get("AMICI_PARALLEL_COMPILE")) is not None: + os.environ["CMAKE_BUILD_PARALLEL_LEVEL"] = parallel_jobs else: - os.environ['CMAKE_BUILD_PARALLEL_LEVEL'] = "1" + os.environ["CMAKE_BUILD_PARALLEL_LEVEL"] = "1" # Install setup( cmdclass={ - 'install': AmiciInstall, - 'sdist': AmiciSDist, - 'build_ext': AmiciBuildCMakeExtension, - 'install_lib': AmiciInstallLib, - 'develop': AmiciDevelop, - 'build_py': AmiciBuildPy, + "install": AmiciInstall, + "sdist": AmiciSDist, + "build_ext": AmiciBuildCMakeExtension, + "install_lib": AmiciInstallLib, + "develop": AmiciDevelop, + "build_py": AmiciBuildPy, }, long_description=long_description, long_description_content_type="text/markdown", @@ -166,5 +171,5 @@ def main(): ) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/python/tests/conftest.py b/python/tests/conftest.py index adfe6b93c8..b849c2d7c2 100644 --- a/python/tests/conftest.py +++ b/python/tests/conftest.py @@ -15,19 +15,23 @@ def sbml_example_presimulation_module(): """SBML example_presimulation model module fixture""" - sbml_file = os.path.join(os.path.dirname(__file__), '..', - 'examples', 'example_presimulation', - 'model_presimulation.xml') + sbml_file = os.path.join( + os.path.dirname(__file__), + "..", + "examples", + "example_presimulation", + "model_presimulation.xml", + ) sbml_importer = amici.SbmlImporter(sbml_file) - constant_parameters = ['DRUG_0', 'KIN_0'] + constant_parameters = ["DRUG_0", "KIN_0"] observables = amici.assignmentRules2observables( sbml_importer.sbml, # the libsbml model object - filter_function=lambda variable: variable.getName() == 'pPROT_obs' + filter_function=lambda variable: variable.getName() == "pPROT_obs", ) - module_name = 'test_model_presimulation' + module_name = "test_model_presimulation" with TemporaryDirectoryWinSafe(prefix=module_name) as outdir: sbml_importer.sbml2amici( @@ -35,12 +39,11 @@ def sbml_example_presimulation_module(): output_dir=outdir, verbose=False, observables=observables, - constant_parameters=constant_parameters) - - yield amici.import_model_module( - module_name=module_name, module_path=outdir + constant_parameters=constant_parameters, ) + yield amici.import_model_module(module_name=module_name, module_path=outdir) + @pytest.fixture(scope="session") def pysb_example_presimulation_module(): @@ -48,27 +51,32 @@ def pysb_example_presimulation_module(): pysb = pytest.importorskip("pysb") from amici.pysb_import import pysb2amici - constant_parameters = ['DRUG_0', 'KIN_0'] + constant_parameters = ["DRUG_0", "KIN_0"] pysb.SelfExporter.cleanup() # reset pysb pysb.SelfExporter.do_export = True - model_path = os.path.join(os.path.dirname(__file__), '..', - 'examples', 'example_presimulation') + model_path = os.path.join( + os.path.dirname(__file__), "..", "examples", "example_presimulation" + ) with amici.add_path(model_path): - if 'createModelPresimulation' in sys.modules: - importlib.reload(sys.modules['createModelPresimulation']) - model_module = sys.modules['createModelPresimulation'] + if "createModelPresimulation" in sys.modules: + importlib.reload(sys.modules["createModelPresimulation"]) + model_module = sys.modules["createModelPresimulation"] else: - model_module = importlib.import_module('createModelPresimulation') + model_module = importlib.import_module("createModelPresimulation") model = copy.deepcopy(model_module.model) - model.name = 'test_model_presimulation_pysb' + model.name = "test_model_presimulation_pysb" with TemporaryDirectoryWinSafe(prefix=model.name) as outdir: - pysb2amici(model, outdir, verbose=True, - observables=['pPROT_obs'], - constant_parameters=constant_parameters) + pysb2amici( + model, + outdir, + verbose=True, + observables=["pPROT_obs"], + constant_parameters=constant_parameters, + ) yield amici.import_model_module(model.name, outdir) diff --git a/python/tests/petab_test_problems/lotka_volterra/model/lotka_volterra.yaml b/python/tests/petab_test_problems/lotka_volterra/model/lotka_volterra.yaml index 27fd6ec29a..b6f756cb33 100644 --- a/python/tests/petab_test_problems/lotka_volterra/model/lotka_volterra.yaml +++ b/python/tests/petab_test_problems/lotka_volterra/model/lotka_volterra.yaml @@ -7,14 +7,14 @@ odes: rightHandSide: delta * prey * predator - gamma * predator + arrival_predator * prey initialValue: 2 -parameters: +parameters: - parameterId: alpha nominalValue: 2 parameterScale: log10 lowerBound: 0.1 upperBound: 10 estimate: 1 - + - parameterId: beta nominalValue: 4 parameterScale: log10 @@ -26,17 +26,17 @@ parameters: lowerBound: 0.1 upperBound: 10 estimate: 1 - + - parameterId: delta nominalValue: 3 parameterScale: log10 estimate: 0 - + - parameterId: departure_prey nominalValue: 3 parameterScale: log10 estimate: 0 - + - parameterId: arrival_predator nominalValue: 3 parameterScale: log10 diff --git a/python/tests/petab_test_problems/lotka_volterra/model/writer.py b/python/tests/petab_test_problems/lotka_volterra/model/writer.py index 680cbe43e6..d65969d165 100644 --- a/python/tests/petab_test_problems/lotka_volterra/model/writer.py +++ b/python/tests/petab_test_problems/lotka_volterra/model/writer.py @@ -4,16 +4,16 @@ import yaml2sbml -yaml2sbml_yaml = 'lotka_volterra.yaml' -petab_path = Path(__file__).parent.parent / 'petab' -petab_yaml = 'problem.yaml' -measurements_tsv = 'measurements.tsv' -model_name = 'lotka_volterra' +yaml2sbml_yaml = "lotka_volterra.yaml" +petab_path = Path(__file__).parent.parent / "petab" +petab_yaml = "problem.yaml" +measurements_tsv = "measurements.tsv" +model_name = "lotka_volterra" yaml2sbml.yaml2petab( yaml_dir=yaml2sbml_yaml, output_dir=str(petab_path), - sbml_name=model_name, + sbml_name=model_name, petab_yaml_name=petab_yaml, measurement_table_name=measurements_tsv, ) diff --git a/python/tests/pysb_test_models/bngwiki_egfr_simple_deletemolecules.py b/python/tests/pysb_test_models/bngwiki_egfr_simple_deletemolecules.py index aa6f409c02..ae2181e71b 100644 --- a/python/tests/pysb_test_models/bngwiki_egfr_simple_deletemolecules.py +++ b/python/tests/pysb_test_models/bngwiki_egfr_simple_deletemolecules.py @@ -8,94 +8,108 @@ Model() -Parameter('NA', 6.02e23) # Avogadro's number (molecules/mol) -Parameter('f', 1) # Fraction of the cell to simulate -Expression('Vo', f*1.0e-10) # Extracellular volume=1/cell_density (L) -Expression('V', f*3.0e-12) # Cytoplasmic volume (L) +Parameter("NA", 6.02e23) # Avogadro's number (molecules/mol) +Parameter("f", 1) # Fraction of the cell to simulate +Expression("Vo", f * 1.0e-10) # Extracellular volume=1/cell_density (L) +Expression("V", f * 3.0e-12) # Cytoplasmic volume (L) # Initial amount of ligand (20 nM) converted to copies per cell -Expression('EGF_init', 20*1e-9*NA*Vo) +Expression("EGF_init", 20 * 1e-9 * NA * Vo) # Initial amounts of cellular components (copies per cell) -Expression('EGFR_init', f*1.8e5) -Expression('Grb2_init', f*1.5e5) -Expression('Sos1_init', f*6.2e4) +Expression("EGFR_init", f * 1.8e5) +Expression("Grb2_init", f * 1.5e5) +Expression("Sos1_init", f * 6.2e4) # Rate constants # Divide by NA*V to convert bimolecular rate constants from /M/sec to # /(molecule/cell)/sec -Expression('kp1', 9.0e7/(NA*Vo)) # ligand-monomer binding -Parameter('km1', 0.06) # ligand-monomer dissociation -Expression('kp2', 1.0e7/(NA*V)) # aggregation of bound monomers -Parameter('km2', 0.1) # dissociation of bound monomers -Parameter('kp3', 0.5) # dimer transphosphorylation -Parameter('km3', 4.505) # dimer dephosphorylation -Expression('kp4', 1.5e6/(NA*V)) # binding of Grb2 to receptor -Parameter('km4', 0.05) # dissociation of Grb2 from receptor -Expression('kp5', 1.0e7/(NA*V)) # binding of Grb2 to Sos1 -Parameter('km5', 0.06) # dissociation of Grb2 from Sos1 -Parameter('deg', 0.01) # degradation of receptor dimers +Expression("kp1", 9.0e7 / (NA * Vo)) # ligand-monomer binding +Parameter("km1", 0.06) # ligand-monomer dissociation +Expression("kp2", 1.0e7 / (NA * V)) # aggregation of bound monomers +Parameter("km2", 0.1) # dissociation of bound monomers +Parameter("kp3", 0.5) # dimer transphosphorylation +Parameter("km3", 4.505) # dimer dephosphorylation +Expression("kp4", 1.5e6 / (NA * V)) # binding of Grb2 to receptor +Parameter("km4", 0.05) # dissociation of Grb2 from receptor +Expression("kp5", 1.0e7 / (NA * V)) # binding of Grb2 to Sos1 +Parameter("km5", 0.06) # dissociation of Grb2 from Sos1 +Parameter("deg", 0.01) # degradation of receptor dimers -Monomer('EGF', ['R']) -Monomer('EGFR', ['L','CR1','Y1068'], {'Y1068':('U','P')}) -Monomer('Grb2', ['SH2','SH3']) -Monomer('Sos1', ['PxxP']) +Monomer("EGF", ["R"]) +Monomer("EGFR", ["L", "CR1", "Y1068"], {"Y1068": ("U", "P")}) +Monomer("Grb2", ["SH2", "SH3"]) +Monomer("Sos1", ["PxxP"]) Initial(EGF(R=None), EGF_init) -Initial(EGFR(L=None, CR1=None, Y1068='U'), EGFR_init) +Initial(EGFR(L=None, CR1=None, Y1068="U"), EGFR_init) Initial(Grb2(SH2=None, SH3=None), Grb2_init) Initial(Sos1(PxxP=None), Sos1_init) -Observable('EGFR_tot', EGFR()) -Observable('Lig_free', EGF(R=None)) -Observable('Dim', EGFR(CR1=ANY), match='species') -Observable('RP', EGFR(Y1068=('P',WILD))) -Observable('Grb2Sos1', Grb2(SH2=None, SH3=1) % Sos1(PxxP=1)) -Observable('Sos1_act', EGFR(Y1068=1) % Grb2(SH2=1, SH3=2) % Sos1(PxxP=2)) +Observable("EGFR_tot", EGFR()) +Observable("Lig_free", EGF(R=None)) +Observable("Dim", EGFR(CR1=ANY), match="species") +Observable("RP", EGFR(Y1068=("P", WILD))) +Observable("Grb2Sos1", Grb2(SH2=None, SH3=1) % Sos1(PxxP=1)) +Observable("Sos1_act", EGFR(Y1068=1) % Grb2(SH2=1, SH3=2) % Sos1(PxxP=2)) # Ligand-receptor binding -Rule('egf_bind_egfr', - EGFR(L=None, CR1=None) + EGF(R=None) | EGFR(L=1, CR1=None) % EGF(R=1), - kp1, km1) +Rule( + "egf_bind_egfr", + EGFR(L=None, CR1=None) + EGF(R=None) | EGFR(L=1, CR1=None) % EGF(R=1), + kp1, + km1, +) # Receptor-aggregation -Rule('egfr_dimerize', - EGFR(L=ANY, CR1=None) + EGFR(L=ANY, CR1=None) | - EGFR(L=ANY, CR1=1) % EGFR(L=ANY, CR1=1), - kp2, km2) +Rule( + "egfr_dimerize", + EGFR(L=ANY, CR1=None) + EGFR(L=ANY, CR1=None) + | EGFR(L=ANY, CR1=1) % EGFR(L=ANY, CR1=1), + kp2, + km2, +) # Transphosphorylation of EGFR by RTK -Rule('egfr_transphos', - EGFR(CR1=ANY, Y1068='U') >> EGFR(CR1=ANY, Y1068='P'), kp3) +Rule("egfr_transphos", EGFR(CR1=ANY, Y1068="U") >> EGFR(CR1=ANY, Y1068="P"), kp3) # Dephosphorylation -Rule('egfr_dephos', - EGFR(Y1068='P') >> EGFR(Y1068='U'), km3) +Rule("egfr_dephos", EGFR(Y1068="P") >> EGFR(Y1068="U"), km3) # Grb2 binding to pY1068 -Rule('grb2_bind_egfr', - EGFR(Y1068='P') + Grb2(SH2=None) | EGFR(Y1068=('P',1)) % Grb2(SH2=1), - kp4, km4) +Rule( + "grb2_bind_egfr", + EGFR(Y1068="P") + Grb2(SH2=None) | EGFR(Y1068=("P", 1)) % Grb2(SH2=1), + kp4, + km4, +) # Grb2 binding to Sos1 -Rule('sos1_bind_grb2', - Grb2(SH3=None) + Sos1(PxxP=None) | Grb2(SH3=1) % Sos1(PxxP=1), - kp5, km5) +Rule( + "sos1_bind_grb2", + Grb2(SH3=None) + Sos1(PxxP=None) | Grb2(SH3=1) % Sos1(PxxP=1), + kp5, + km5, +) # Receptor dimer internalization/degradation -Rule('egfr_dimer_degrade', - EGF(R=1) % EGF(R=2) % EGFR(L=1, CR1=3) % EGFR(L=2, CR1=3) >> None, - deg, - delete_molecules=True) +Rule( + "egfr_dimer_degrade", + EGF(R=1) % EGF(R=2) % EGFR(L=1, CR1=3) % EGFR(L=2, CR1=3) >> None, + deg, + delete_molecules=True, +) -if __name__ == '__main__': +if __name__ == "__main__": print(__doc__, "\n", model) - print(""" + print( + """ NOTE: This model code is designed to be imported and programatically manipulated, not executed directly. The above output is merely a -diagnostic aid.""") \ No newline at end of file +diagnostic aid.""" + ) diff --git a/python/tests/splines_utils.py b/python/tests/splines_utils.py index 4ecc95c605..f1ed6bd0d6 100644 --- a/python/tests/splines_utils.py +++ b/python/tests/splines_utils.py @@ -19,19 +19,22 @@ import amici from amici.gradient_check import _check_results from amici.petab_import import import_petab_problem -from amici.petab_objective import (LLH, RDATAS, SLLH, EDATAS, simulate_petab) -from amici.sbml_utils import (add_compartment, add_inflow, add_parameter, - add_rate_rule, add_species, amici_time_symbol, - create_sbml_model) +from amici.petab_objective import LLH, RDATAS, SLLH, EDATAS, simulate_petab +from amici.sbml_utils import ( + add_compartment, + add_inflow, + add_parameter, + add_rate_rule, + add_species, + amici_time_symbol, + create_sbml_model, +) from amici.splines import AbstractSpline, CubicHermiteSpline, UniformGrid from amici.testing import TemporaryDirectoryWinSafe as TemporaryDirectory def evaluate_spline( - spline: AbstractSpline, - params: dict, - tt: Sequence[float], - **kwargs + spline: AbstractSpline, params: dict, tt: Sequence[float], **kwargs ): """ Evaluate the `AbstractSpline` `spline` at timepoints `tt` @@ -45,7 +48,7 @@ def integrate_spline( params: Union[Dict, None], tt: Sequence[float], initial_value: float = 0, - **kwargs + **kwargs, ): """ Integrate the `AbstractSpline` `spline` at timepoints `tt` @@ -59,44 +62,44 @@ def integrate_spline( def create_condition_table() -> pd.DataFrame: """Create a PEtab condition table.""" - condition_df = pd.DataFrame({'conditionId': ['condition1']}) - condition_df.set_index(['conditionId'], inplace=True) + condition_df = pd.DataFrame({"conditionId": ["condition1"]}) + condition_df.set_index(["conditionId"], inplace=True) return condition_df def create_parameter_table(**columns) -> pd.DataFrame: """Create a PEtab parameter table.""" - if isinstance(columns['parameterId'], str): - columns['parameterId'] = [columns['parameterId']] - columns.setdefault('parameterScale', 'lin') - columns.setdefault('estimate', 1) + if isinstance(columns["parameterId"], str): + columns["parameterId"] = [columns["parameterId"]] + columns.setdefault("parameterScale", "lin") + columns.setdefault("estimate", 1) parameter_df = pd.DataFrame(columns) - parameter_df.set_index(['parameterId'], inplace=True) + parameter_df.set_index(["parameterId"], inplace=True) return parameter_df def create_observable_table(**columns) -> pd.DataFrame: """Create a PEtab observable table.""" - if isinstance(columns['observableId'], str): - columns['observableId'] = [columns['observableId']] - columns.setdefault('observableTransformation', 'lin') - columns.setdefault('noiseDistribution', 'normal') + if isinstance(columns["observableId"], str): + columns["observableId"] = [columns["observableId"]] + columns.setdefault("observableTransformation", "lin") + columns.setdefault("noiseDistribution", "normal") observable_df = pd.DataFrame(columns) - observable_df.set_index(['observableId'], inplace=True) + observable_df.set_index(["observableId"], inplace=True) return observable_df def create_measurement_table(**columns) -> pd.DataFrame: """Create a PEtab measurement table.""" - if isinstance(columns['observableId'], str): - columns['observableId'] = [columns['observableId']] - columns.setdefault('simulationConditionId', 'condition1') + if isinstance(columns["observableId"], str): + columns["observableId"] = [columns["observableId"]] + columns.setdefault("simulationConditionId", "condition1") return pd.DataFrame(columns) def species(i) -> str: """Name to use for the `i`-th species.""" - return f'z{i}' + return f"z{i}" def observable(i) -> str: @@ -105,25 +108,25 @@ def observable(i) -> str: i.e., the observable associated to the `i`-th species. """ - return f'{species(i)}_obs' + return f"{species(i)}_obs" def species_to_index(name) -> int: """Get the species index from a species name.""" - assert name[0] == 'z' + assert name[0] == "z" return int(name[1:]) def create_petab_problem( - splines: List[AbstractSpline], - params_true: Dict, - initial_values: Optional[np.ndarray] = None, - use_reactions: bool = False, - measure_upsample: int = 6, - sigma: float = 1.0, - t_extrapolate: float = 0.25, - folder: Optional[str] = None, - model_name: str = 'test_splines', + splines: List[AbstractSpline], + params_true: Dict, + initial_values: Optional[np.ndarray] = None, + use_reactions: bool = False, + measure_upsample: int = 6, + sigma: float = 1.0, + t_extrapolate: float = 0.25, + folder: Optional[str] = None, + model_name: str = "test_splines", ): """ Given a list of `AbstractSplines`, create a PEtab problem for the system of @@ -164,7 +167,7 @@ def create_petab_problem( for spline in splines: if spline.evaluate_at != amici_time_symbol: raise ValueError( - 'the given splines must be evaluated at the simulation time' + "the given splines must be evaluated at the simulation time" ) if initial_values is None: @@ -172,15 +175,15 @@ def create_petab_problem( # Create SBML document doc, model = create_sbml_model(model_name) - add_compartment(model, 'compartment') - for (i, spline) in enumerate(splines): + add_compartment(model, "compartment") + for i, spline in enumerate(splines): spline.add_to_sbml_model(model) add_species(model, species(i), initial_amount=initial_values[i]) if use_reactions: add_inflow(model, species(i), splines[i].sbml_id) else: add_rate_rule(model, species(i), splines[i].sbml_id) - for (parId, value) in params_true.items(): + for parId, value in params_true.items(): add_parameter(model, parId, value=value, constant=True) for spline in splines: add_parameter(model, spline.sbml_id, constant=False) @@ -192,12 +195,15 @@ def create_petab_problem( for spline in splines: if spline.extrapolate[0] is None and spline.nodes[0] > 0: raise ValueError( - 'if no left-extrapolation is defined for a spline, ' - 'its interval of definition should contain zero' + "if no left-extrapolation is defined for a spline, " + "its interval of definition should contain zero" ) if spline.extrapolate[1] is not None: - f = t_extrapolate if spline.extrapolate[ - 1] != 'periodic' else 1 + t_extrapolate + f = ( + t_extrapolate + if spline.extrapolate[1] != "periodic" + else 1 + t_extrapolate + ) DT = f * (spline.nodes[-1] - spline.nodes[0]) else: DT = 0 @@ -232,7 +238,8 @@ def create_petab_problem( ) measurement_df = create_measurement_table( observableId=np.concatenate( - [len(tt_obs) * [observable(i)] for i in range(len(splines))]), + [len(tt_obs) * [observable(i)] for i in range(len(splines))] + ), time=len(splines) * list(tt_obs), measurement=np.concatenate(zz_obs), ) @@ -249,7 +256,7 @@ def create_petab_problem( observable_df=observable_df, ) if petab.lint_problem(problem): - raise RuntimeError('PEtab lint failed') + raise RuntimeError("PEtab lint failed") # Write PEtab problem to disk if folder is None: @@ -257,35 +264,33 @@ def create_petab_problem( folder = os.path.abspath(folder) os.makedirs(folder, exist_ok=True) problem.to_files( - sbml_file=os.path.join(folder, f'{model_name}_model.xml'), - condition_file=os.path.join(folder, f'{model_name}_conditions.tsv'), - measurement_file=os.path.join(folder, - f'{model_name}_measurements.tsv'), - parameter_file=os.path.join(folder, f'{model_name}_parameters.tsv'), - observable_file=os.path.join(folder, - f'{model_name}_observables.tsv'), - yaml_file=os.path.join(folder, f'{model_name}.yaml'), + sbml_file=os.path.join(folder, f"{model_name}_model.xml"), + condition_file=os.path.join(folder, f"{model_name}_conditions.tsv"), + measurement_file=os.path.join(folder, f"{model_name}_measurements.tsv"), + parameter_file=os.path.join(folder, f"{model_name}_parameters.tsv"), + observable_file=os.path.join(folder, f"{model_name}_observables.tsv"), + yaml_file=os.path.join(folder, f"{model_name}.yaml"), ) - return os.path.join(folder, f'{model_name}.yaml'), initial_values, T + return os.path.join(folder, f"{model_name}.yaml"), initial_values, T def simulate_splines( - splines, - params_true, - initial_values=None, - *, - folder: Optional[str] = None, - keep_temporary: bool = False, - benchmark: Union[bool, int] = False, - rtol: float = 1e-12, - atol: float = 1e-12, - maxsteps: int = 500_000, - discard_annotations: bool = False, - use_adjoint: bool = False, - skip_sensitivity: bool = False, - petab_problem=None, - amici_model=None, - **kwargs + splines, + params_true, + initial_values=None, + *, + folder: Optional[str] = None, + keep_temporary: bool = False, + benchmark: Union[bool, int] = False, + rtol: float = 1e-12, + atol: float = 1e-12, + maxsteps: int = 500_000, + discard_annotations: bool = False, + use_adjoint: bool = False, + skip_sensitivity: bool = False, + petab_problem=None, + amici_model=None, + **kwargs, ): """ Create a PEtab problem using `create_petab_problem` and simulate it with @@ -347,40 +352,43 @@ def simulate_splines( else: with TemporaryDirectory() as folder: return simulate_splines( - splines, params_true, initial_values, folder=folder, - benchmark=benchmark, rtol=rtol, atol=atol, - maxsteps=maxsteps, discard_annotations=discard_annotations, - use_adjoint=use_adjoint, skip_sensitivity=skip_sensitivity, - petab_problem=petab_problem, amici_model=amici_model, - **kwargs + splines, + params_true, + initial_values, + folder=folder, + benchmark=benchmark, + rtol=rtol, + atol=atol, + maxsteps=maxsteps, + discard_annotations=discard_annotations, + use_adjoint=use_adjoint, + skip_sensitivity=skip_sensitivity, + petab_problem=petab_problem, + amici_model=amici_model, + **kwargs, ) if petab_problem is None and amici_model is not None: - raise ValueError( - "if amici_model is given, petab_problem must be given too" - ) + raise ValueError("if amici_model is given, petab_problem must be given too") if petab_problem is not None and initial_values is None: - raise ValueError( - "if petab_problem is given, initial_values must be given too" - ) + raise ValueError("if petab_problem is given, initial_values must be given too") if petab_problem is None: # Create PEtab problem path, initial_values, T = create_petab_problem( - splines, params_true, initial_values, - sigma=0.0, folder=folder, **kwargs + splines, params_true, initial_values, sigma=0.0, folder=folder, **kwargs ) petab_problem = petab.Problem.from_yaml(path) if amici_model is None: # Create and compile AMICI model - model_id = uuid.uuid4().hex[-5:] # to prevent folder/module collisions + model_id = uuid.uuid4().hex[-5:] # to prevent folder/module collisions amici_model = import_petab_problem( petab_problem, discard_sbml_annotations=discard_annotations, - model_output_dir=os.path.join(folder, f'amici_models_{model_id}'), - model_name=f'splinetest_{model_id}', + model_output_dir=os.path.join(folder, f"amici_models_{model_id}"), + model_name=f"splinetest_{model_id}", ) # Set solver options @@ -420,11 +428,23 @@ def simulate_splines( state_ids = amici_model.getStateIds() param_ids = amici_model.getParameterIds() - return initial_values, petab_problem, amici_model, solver, llh, sllh, rdata, edata, state_ids, param_ids + return ( + initial_values, + petab_problem, + amici_model, + solver, + llh, + sllh, + rdata, + edata, + state_ids, + param_ids, + ) if benchmark is True: benchmark = 50 import time + runtimes = [] for _ in range(int(benchmark)): t0 = time.perf_counter() @@ -442,42 +462,44 @@ def simulate_splines( def compute_ground_truth(splines, initial_values, times, params_true, params_sorted): - x_true_sym = sp.Matrix([ - integrate_spline(spline, None, times, iv) - for (spline, iv) in zip(splines, initial_values) - ]).transpose() - groundtruth = {'x_true': np.asarray(x_true_sym.subs(params_true), dtype=float)} + x_true_sym = sp.Matrix( + [ + integrate_spline(spline, None, times, iv) + for (spline, iv) in zip(splines, initial_values) + ] + ).transpose() + groundtruth = {"x_true": np.asarray(x_true_sym.subs(params_true), dtype=float)} sx_by_state = [ x_true_sym[:, i].jacobian(params_sorted).subs(params_true) for i in range(x_true_sym.shape[1]) ] sx_by_state = [np.asarray(sx, dtype=float) for sx in sx_by_state] - groundtruth['sx_true'] = np.concatenate([ - sx[:, :, np.newaxis] for sx in sx_by_state - ], axis=2) + groundtruth["sx_true"] = np.concatenate( + [sx[:, :, np.newaxis] for sx in sx_by_state], axis=2 + ) return groundtruth def check_splines( - splines, - params_true, - initial_values=None, - *, - discard_annotations: bool = False, - use_adjoint: bool = False, - skip_sensitivity: bool = False, - debug: Union[bool, str] = False, - parameter_lists: Optional[Sequence[Sequence[int]]] = None, - llh_rtol: float = 1e-8, - sllh_atol: float = 1e-8, - x_rtol: float = 1e-11, - x_atol: float = 1e-11, - w_rtol: float = 1e-11, - w_atol: float = 1e-11, - sx_rtol: float = 1e-10, - sx_atol: float = 1e-10, - groundtruth: Optional[Union[str, Dict[str, Any]]] = None, - **kwargs + splines, + params_true, + initial_values=None, + *, + discard_annotations: bool = False, + use_adjoint: bool = False, + skip_sensitivity: bool = False, + debug: Union[bool, str] = False, + parameter_lists: Optional[Sequence[Sequence[int]]] = None, + llh_rtol: float = 1e-8, + sllh_atol: float = 1e-8, + x_rtol: float = 1e-11, + x_atol: float = 1e-11, + w_rtol: float = 1e-11, + w_atol: float = 1e-11, + sx_rtol: float = 1e-10, + sx_atol: float = 1e-10, + groundtruth: Optional[Union[str, Dict[str, Any]]] = None, + **kwargs, ): """ Create a PEtab problem using `create_petab_problem`, @@ -519,20 +541,32 @@ def check_splines( splines = [splines] # Simulate PEtab problem - initial_values, petab_problem, amici_model, amici_solver, llh, sllh, rdata, edata, state_ids, param_ids = simulate_splines( - splines, params_true, initial_values, + ( + initial_values, + petab_problem, + amici_model, + amici_solver, + llh, + sllh, + rdata, + edata, + state_ids, + param_ids, + ) = simulate_splines( + splines, + params_true, + initial_values, discard_annotations=discard_annotations, skip_sensitivity=skip_sensitivity, use_adjoint=use_adjoint, - **kwargs + **kwargs, ) - tt = rdata['ts'] + tt = rdata["ts"] # Sort splines/ics/parameters as in the AMICI model splines = [splines[species_to_index(name)] for name in state_ids] - initial_values = [initial_values[species_to_index(name)] for name in - state_ids] + initial_values = [initial_values[species_to_index(name)] for name in state_ids] def param_by_name(id): for p in params_true.keys(): @@ -543,27 +577,27 @@ def param_by_name(id): params_sorted = [param_by_name(id) for id in param_ids] # Check states - if groundtruth == 'compute': - groundtruth = compute_ground_truth(splines, initial_values, tt, params_true, params_sorted) + if groundtruth == "compute": + groundtruth = compute_ground_truth( + splines, initial_values, tt, params_true, params_sorted + ) if groundtruth is None: - x_true_sym = sp.Matrix([ - integrate_spline(spline, None, tt, iv) - for (spline, iv) in zip(splines, initial_values) - ]).transpose() + x_true_sym = sp.Matrix( + [ + integrate_spline(spline, None, tt, iv) + for (spline, iv) in zip(splines, initial_values) + ] + ).transpose() x_true = np.asarray(x_true_sym.subs(params_true), dtype=float) else: - x_true = groundtruth['x_true'] + x_true = groundtruth["x_true"] if not debug: assert rdata.x.shape == x_true.shape - _check_results(rdata, 'x', x_true, atol=x_atol, rtol=x_rtol) - elif debug == 'print': - x_err_abs = abs(rdata['x'] - x_true) - x_err_rel = np.where( - x_err_abs == 0, - 0, - x_err_abs / abs(x_true) - ) - print(f'x_atol={x_atol} x_rtol={x_rtol}') + _check_results(rdata, "x", x_true, atol=x_atol, rtol=x_rtol) + elif debug == "print": + x_err_abs = abs(rdata["x"] - x_true) + x_err_rel = np.where(x_err_abs == 0, 0, x_err_abs / abs(x_true)) + print(f"x_atol={x_atol} x_rtol={x_rtol}") print("x_err_abs:") print(np.squeeze(x_err_abs)) print("x_err_abs (maximum):") @@ -576,24 +610,25 @@ def param_by_name(id): # Check spline evaluations # TODO can we know how the splines are ordered inside w? if False and discard_annotations and len(splines) == 1: - assert rdata['w'].shape[1] == 1 - w_true = np.column_stack([ - evaluate_spline(spline, params_true, tt, dtype=float) - for spline in splines - ]) + assert rdata["w"].shape[1] == 1 + w_true = np.column_stack( + [ + evaluate_spline(spline, params_true, tt, dtype=float) + for spline in splines + ] + ) if not debug: _check_results( - rdata, 'w', w_true, - atol=w_atol, rtol=w_rtol, + rdata, + "w", + w_true, + atol=w_atol, + rtol=w_rtol, ) - elif debug == 'print': - w_err_abs = abs(rdata['w'] - w_true) - w_err_rel = np.where( - w_err_abs == 0, - 0, - w_err_abs / abs(w_true) - ) - print(f'w_atol={w_atol} w_rtol={w_rtol}') + elif debug == "print": + w_err_abs = abs(rdata["w"] - w_true) + w_err_rel = np.where(w_err_abs == 0, 0, w_err_abs / abs(w_true)) + print(f"w_atol={w_atol} w_rtol={w_rtol}") print("w_err_abs:") print(np.squeeze(w_err_abs)) print("w_err_abs (maximum):") @@ -615,25 +650,24 @@ def param_by_name(id): for i in range(x_true_sym.shape[1]) ] sx_by_state = [np.asarray(sx, dtype=float) for sx in sx_by_state] - sx_true = np.concatenate([ - sx[:, :, np.newaxis] for sx in sx_by_state - ], axis=2) + sx_true = np.concatenate( + [sx[:, :, np.newaxis] for sx in sx_by_state], axis=2 + ) else: - sx_true = groundtruth['sx_true'] + sx_true = groundtruth["sx_true"] if not debug: assert rdata.sx.shape == sx_true.shape _check_results( - rdata, 'sx', sx_true, - atol=sx_atol, rtol=sx_rtol, + rdata, + "sx", + sx_true, + atol=sx_atol, + rtol=sx_rtol, ) - elif debug == 'print': - sx_err_abs = abs(rdata['sx'] - sx_true) - sx_err_rel = np.where( - sx_err_abs == 0, - 0, - sx_err_abs / abs(sx_true) - ) - print(f'sx_atol={sx_atol} sx_rtol={sx_rtol}') + elif debug == "print": + sx_err_abs = abs(rdata["sx"] - sx_true) + sx_err_rel = np.where(sx_err_abs == 0, 0, sx_err_abs / abs(sx_true)) + print(f"sx_atol={sx_atol} sx_rtol={sx_rtol}") print("sx_err_abs:") print(np.squeeze(sx_err_abs)) print("sx_err_abs (maximum):") @@ -643,14 +677,14 @@ def param_by_name(id): print("sx_err_rel (maximum):") print(sx_err_rel.max()) else: - assert rdata['sx'] is None + assert rdata["sx"] is None # Check log-likelihood - llh_true = - 0.5 * rdata['y'].size * np.log(2 * np.pi) + llh_true = -0.5 * rdata["y"].size * np.log(2 * np.pi) llh_error_rel = abs(llh - llh_true) / abs(llh_true) - if (llh_error_rel > llh_rtol and debug is not True) or debug == 'print': - print(f'{llh_rtol=}') - print(f'{llh_error_rel=}') + if (llh_error_rel > llh_rtol and debug is not True) or debug == "print": + print(f"{llh_rtol=}") + print(f"{llh_error_rel=}") if not debug: assert llh_error_rel <= llh_rtol @@ -661,9 +695,9 @@ def param_by_name(id): if sllh_atol is None: sllh_atol = np.finfo(float).eps sllh_err_abs = abs(sllh).max() - if (sllh_err_abs > sllh_atol and debug is not True) or debug == 'print': - print(f'sllh_atol={sllh_atol}') - print(f'sllh_err_abs = {sllh_err_abs}') + if (sllh_err_abs > sllh_atol and debug is not True) or debug == "print": + print(f"sllh_atol={sllh_atol}") + print(f"sllh_err_abs = {sllh_err_abs}") if not debug: assert sllh_err_abs <= sllh_atol else: @@ -703,14 +737,17 @@ def param_by_name(id): def check_splines_full( - splines, params, tols, *args, + splines, + params, + tols, + *args, check_piecewise: bool = True, check_forward: bool = True, check_adjoint: bool = True, folder: Optional[str] = None, - groundtruth: Optional[Union[dict, str]] = 'compute', + groundtruth: Optional[Union[dict, str]] = "compute", return_groundtruth: bool = False, - **kwargs + **kwargs, ): """ Check example PEtab problem with `check_splines` @@ -720,14 +757,17 @@ def check_splines_full( if folder is None: with TemporaryDirectory() as folder: return check_splines_full( - splines, params, tols, *args, + splines, + params, + tols, + *args, check_piecewise=check_piecewise, check_forward=check_forward, check_adjoint=check_adjoint, folder=folder, groundtruth=groundtruth, return_groundtruth=return_groundtruth, - **kwargs + **kwargs, ) if isinstance(tols, dict): @@ -739,8 +779,7 @@ def check_splines_full( splines = [splines] contains_periodic = any( - spline.extrapolate == ('periodic', 'periodic') - for spline in splines + spline.extrapolate == ("periodic", "periodic") for spline in splines ) # Amortize creation of PEtab and AMICI objects @@ -751,7 +790,11 @@ def check_splines_full( if check_piecewise and not contains_periodic: results = check_splines( - splines, params, *args, **kwargs, **tols1, + splines, + params, + *args, + **kwargs, + **tols1, folder=folder, discard_annotations=True, use_adjoint=False, @@ -763,7 +806,11 @@ def check_splines_full( if check_forward: results = check_splines( - splines, params, *args, **kwargs, **tols2, + splines, + params, + *args, + **kwargs, + **tols2, initial_values=initial_values, folder=folder, petab_problem=petab_problem, @@ -777,13 +824,19 @@ def check_splines_full( if check_adjoint: results = check_splines( - splines, params, *args, **kwargs, **tols3, + splines, + params, + *args, + **kwargs, + **tols3, initial_values=initial_values, folder=folder, petab_problem=petab_problem, amici_model=amici_model, use_adjoint=True, - groundtruth=(None if groundtruth == 'compute' else groundtruth), # do not compute sensitivities if possible + groundtruth=( + None if groundtruth == "compute" else groundtruth + ), # do not compute sensitivities if possible ) if return_groundtruth: @@ -792,31 +845,32 @@ def check_splines_full( elif results is None: return None else: - return results['groundtruth'] + return results["groundtruth"] def example_spline_1( - idx: int = 0, - offset: float = 0, - scale: float = 1, - num_nodes: int = 9, - fixed_values=None, # a list of indices or 'all' - extrapolate=None, + idx: int = 0, + offset: float = 0, + scale: float = 1, + num_nodes: int = 9, + fixed_values=None, # a list of indices or 'all' + extrapolate=None, ): """A simple spline with no extrapolation.""" yy_true = np.asarray( - [0.0, 2.0, 5.0, 6.0, 5.0, 4.0, 2.0, 3.0, 4.0, 6.0, 7.0, 7.5, 6.5, 4.0]) + [0.0, 2.0, 5.0, 6.0, 5.0, 4.0, 2.0, 3.0, 4.0, 6.0, 7.0, 7.5, 6.5, 4.0] + ) if num_nodes is not None: assert 1 < num_nodes <= len(yy_true) yy_true = yy_true[:num_nodes] yy_true = scale * yy_true + offset xx = UniformGrid(0, 25, number_of_nodes=len(yy_true)) - yy = list(sp.symbols(f'y{idx}_0:{len(yy_true)}')) + yy = list(sp.symbols(f"y{idx}_0:{len(yy_true)}")) if fixed_values is None: params = dict(zip(yy, yy_true)) - elif fixed_values == 'all': + elif fixed_values == "all": params = {} for i in range(len(yy_true)): yy[i] = yy_true[i] @@ -829,13 +883,10 @@ def example_spline_1( params[yy[i]] = yy_true[i] spline = CubicHermiteSpline( - f'y{idx}', - nodes=xx, - values_at_nodes=yy, - bc=None, extrapolate=extrapolate + f"y{idx}", nodes=xx, values_at_nodes=yy, bc=None, extrapolate=extrapolate ) - if os.name == 'nt': + if os.name == "nt": tols = ( dict(llh_rtol=1e-15, x_rtol=1e-8, x_atol=1e-7), dict(llh_rtol=1e-15, x_rtol=1e-8, x_atol=1e-7), @@ -855,19 +906,16 @@ def example_spline_2(idx: int = 0): """A simple periodic spline.""" yy_true = [0.0, 2.0, 3.0, 4.0, 1.0, -0.5, -1, -1.5, 0.5, 0.0] xx = UniformGrid(0, 25, number_of_nodes=len(yy_true)) - yy = list(sp.symbols(f'y{idx}_0:{len(yy_true) - 1}')) + yy = list(sp.symbols(f"y{idx}_0:{len(yy_true) - 1}")) yy.append(yy[0]) params = dict(zip(yy, yy_true)) spline = CubicHermiteSpline( - f'y{idx}', - nodes=xx, - values_at_nodes=yy, - bc='periodic', extrapolate='periodic' + f"y{idx}", nodes=xx, values_at_nodes=yy, bc="periodic", extrapolate="periodic" ) tols = ( dict(llh_rtol=1e-15), dict(llh_rtol=1e-15), - dict(llh_rtol=1e-15, sllh_atol=5e-8, x_rtol=1e-10, x_atol=5e-10) + dict(llh_rtol=1e-15, sllh_atol=5e-8, x_rtol=1e-10, x_atol=5e-10), ) return spline, params, tols @@ -876,13 +924,14 @@ def example_spline_3(idx: int = 0): """A simple spline with extrapolation on the right side.""" yy_true = [0.0, 2.0, 5.0, 6.0, 5.0, 4.0, 2.0, 3.0, 4.0, 6.0] xx = UniformGrid(0, 25, number_of_nodes=len(yy_true)) - yy = list(sp.symbols(f'y{idx}_0:{len(yy_true)}')) + yy = list(sp.symbols(f"y{idx}_0:{len(yy_true)}")) params = dict(zip(yy, yy_true)) spline = CubicHermiteSpline( - f'y{idx}', + f"y{idx}", nodes=xx, values_at_nodes=yy, - bc=(None, 'zeroderivative'), extrapolate=(None, 'constant') + bc=(None, "zeroderivative"), + extrapolate=(None, "constant"), ) tols = {} return spline, params, tols diff --git a/python/tests/test_bngl.py b/python/tests/test_bngl.py index b1b0c117e5..4da7308282 100644 --- a/python/tests/test_bngl.py +++ b/python/tests/test_bngl.py @@ -14,25 +14,45 @@ tests = [ - 'CaOscillate_Func', 'deleteMolecules', 'empty_compartments_block', - 'gene_expr', 'gene_expr_func', 'gene_expr_simple', 'isomerization', - 'Motivating_example_cBNGL', 'motor', 'simple_system', - 'test_compartment_XML', 'test_setconc', 'test_synthesis_cBNGL_simple', - 'test_synthesis_complex', 'test_synthesis_complex_0_cBNGL', - 'test_synthesis_complex_source_cBNGL', 'test_synthesis_simple', - 'univ_synth', 'Repressilator', 'test_paramname', 'tlmr' + "CaOscillate_Func", + "deleteMolecules", + "empty_compartments_block", + "gene_expr", + "gene_expr_func", + "gene_expr_simple", + "isomerization", + "Motivating_example_cBNGL", + "motor", + "simple_system", + "test_compartment_XML", + "test_setconc", + "test_synthesis_cBNGL_simple", + "test_synthesis_complex", + "test_synthesis_complex_0_cBNGL", + "test_synthesis_complex_source_cBNGL", + "test_synthesis_simple", + "univ_synth", + "Repressilator", + "test_paramname", + "tlmr", ] @skip_on_valgrind -@pytest.mark.parametrize('example', tests) +@pytest.mark.parametrize("example", tests) def test_compare_to_pysb_simulation(example): atol = 1e-8 rtol = 1e-8 - model_file = os.path.join(os.path.dirname(__file__), '..', '..', - 'ThirdParty', 'BioNetGen-2.7.0', 'Validate', - f'{example}.bngl') + model_file = os.path.join( + os.path.dirname(__file__), + "..", + "..", + "ThirdParty", + "BioNetGen-2.7.0", + "Validate", + f"{example}.bngl", + ) pysb_model = model_from_bngl(model_file) @@ -41,17 +61,17 @@ def test_compare_to_pysb_simulation(example): sim = ScipyOdeSimulator( pysb_model, tspan=tspan, - integrator_options={'rtol': rtol, 'atol': atol}, - compiler='python' + integrator_options={"rtol": rtol, "atol": atol}, + compiler="python", ) pysb_simres = sim.run() # amici part - cl = example not in ['Motivating_example_cBNGL', 'univ_synth'] + cl = example not in ["Motivating_example_cBNGL", "univ_synth"] kwargs = { - 'compute_conservation_laws': cl, - 'observables': list(pysb_model.observables.keys()) + "compute_conservation_laws": cl, + "observables": list(pysb_model.observables.keys()), } with TemporaryDirectoryWinSafe(prefix=pysb_model.name) as outdir: @@ -59,15 +79,14 @@ def test_compare_to_pysb_simulation(example): with pytest.raises(ValueError, match="Conservation laws"): bngl2amici(model_file, outdir, compute_conservation_laws=True) - if example in ['empty_compartments_block', 'motor']: + if example in ["empty_compartments_block", "motor"]: with pytest.raises(ValueError, match="Cannot add"): bngl2amici(model_file, outdir, **kwargs) return else: bngl2amici(model_file, outdir, **kwargs) - amici_model_module = amici.import_model_module(pysb_model.name, - outdir) + amici_model_module = amici.import_model_module(pysb_model.name, outdir) model_amici = amici_model_module.getModel() diff --git a/python/tests/test_compare_conservation_laws_sbml.py b/python/tests/test_compare_conservation_laws_sbml.py index 92dc319bd4..b2d14640c5 100644 --- a/python/tests/test_compare_conservation_laws_sbml.py +++ b/python/tests/test_compare_conservation_laws_sbml.py @@ -11,27 +11,29 @@ @pytest.fixture def edata_fixture(): """edata is generated to test pre- and postequilibration""" - edata_pre = amici.ExpData(2, 0, 0, - np.array([0., 0.1, 0.2, 0.5, 1., 2., 5., 10.])) + edata_pre = amici.ExpData( + 2, 0, 0, np.array([0.0, 0.1, 0.2, 0.5, 1.0, 2.0, 5.0, 10.0]) + ) edata_pre.setObservedData([1.5] * 16) - edata_pre.fixedParameters = np.array([5., 20.]) - edata_pre.fixedParametersPreequilibration = np.array([0., 10.]) + edata_pre.fixedParameters = np.array([5.0, 20.0]) + edata_pre.fixedParametersPreequilibration = np.array([0.0, 10.0]) edata_pre.reinitializeFixedParameterInitialStates = True # edata for postequilibration - edata_post = amici.ExpData(2, 0, 0, - np.array([float('inf')] * 3)) + edata_post = amici.ExpData(2, 0, 0, np.array([float("inf")] * 3)) edata_post.setObservedData([0.75] * 6) - edata_post.fixedParameters = np.array([7.5, 30.]) + edata_post.fixedParameters = np.array([7.5, 30.0]) # edata with both equilibrations - edata_full = amici.ExpData(2, 0, 0, - np.array( - [0., 0., 0., 1., 2., 2., 4., float('inf'), - float('inf')])) + edata_full = amici.ExpData( + 2, + 0, + 0, + np.array([0.0, 0.0, 0.0, 1.0, 2.0, 2.0, 4.0, float("inf"), float("inf")]), + ) edata_full.setObservedData([3.14] * 18) - edata_full.fixedParameters = np.array([1., 2.]) - edata_full.fixedParametersPreequilibration = np.array([3., 4.]) + edata_full.fixedParameters = np.array([1.0, 2.0]) + edata_full.fixedParametersPreequilibration = np.array([3.0, 4.0]) edata_full.reinitializeFixedParameterInitialStates = True return edata_pre, edata_post, edata_full @@ -40,43 +42,51 @@ def edata_fixture(): @pytest.fixture(scope="session") def models(): # SBML model we want to import - sbml_file = os.path.join(os.path.dirname(__file__), '..', - 'examples', 'example_constant_species', - 'model_constant_species.xml') + sbml_file = os.path.join( + os.path.dirname(__file__), + "..", + "examples", + "example_constant_species", + "model_constant_species.xml", + ) sbml_importer = amici.SbmlImporter(sbml_file) # Name of the model that will also be the name of the python module - model_name = model_output_dir = 'model_constant_species' - model_name_cl = model_output_dir_cl = 'model_constant_species_cl' + model_name = model_output_dir = "model_constant_species" + model_name_cl = model_output_dir_cl = "model_constant_species_cl" # Define constants, observables, sigmas - constant_parameters = ['synthesis_substrate', 'init_enzyme'] + constant_parameters = ["synthesis_substrate", "init_enzyme"] observables = { - 'observable_product': {'name': '', 'formula': 'product'}, - 'observable_substrate': {'name': '', 'formula': 'substrate'}, + "observable_product": {"name": "", "formula": "product"}, + "observable_substrate": {"name": "", "formula": "substrate"}, } - sigmas = {'observable_product': 1.0, 'observable_substrate': 1.0} + sigmas = {"observable_product": 1.0, "observable_substrate": 1.0} # wrap models with and without conservations laws - sbml_importer.sbml2amici(model_name_cl, - model_output_dir_cl, - observables=observables, - constant_parameters=constant_parameters, - sigmas=sigmas) - sbml_importer.sbml2amici(model_name, - model_output_dir, - observables=observables, - constant_parameters=constant_parameters, - sigmas=sigmas, - compute_conservation_laws=False) + sbml_importer.sbml2amici( + model_name_cl, + model_output_dir_cl, + observables=observables, + constant_parameters=constant_parameters, + sigmas=sigmas, + ) + sbml_importer.sbml2amici( + model_name, + model_output_dir, + observables=observables, + constant_parameters=constant_parameters, + sigmas=sigmas, + compute_conservation_laws=False, + ) # load both models model_without_cl_module = amici.import_model_module( - model_name, - module_path=os.path.abspath(model_name)) + model_name, module_path=os.path.abspath(model_name) + ) model_with_cl_module = amici.import_model_module( - model_name_cl, - module_path=os.path.abspath(model_name_cl)) + model_name_cl, module_path=os.path.abspath(model_name_cl) + ) # get the models and return model_without_cl = model_without_cl_module.getModel() @@ -84,11 +94,15 @@ def models(): return model_with_cl, model_without_cl -def get_results(model, edata=None, sensi_order=0, - sensi_meth=amici.SensitivityMethod.forward, - sensi_meth_preeq=amici.SensitivityMethod.forward, - stst_sensi_mode=amici.SteadyStateSensitivityMode.newtonOnly, - reinitialize_states=False): +def get_results( + model, + edata=None, + sensi_order=0, + sensi_meth=amici.SensitivityMethod.forward, + sensi_meth_preeq=amici.SensitivityMethod.forward, + stst_sensi_mode=amici.SteadyStateSensitivityMode.newtonOnly, + reinitialize_states=False, +): # set model and data properties model.setReinitializeFixedParameterInitialStates(reinitialize_states) @@ -116,69 +130,82 @@ def test_compare_conservation_laws_sbml(models, edata_fixture): assert model_without_cl.nx_rdata == model_with_cl.nx_rdata assert model_with_cl.nx_solver < model_without_cl.nx_solver assert len(model_with_cl.getStateIdsSolver()) == model_with_cl.nx_solver - assert len(model_without_cl.getStateIdsSolver()) \ - == model_without_cl.nx_solver + assert len(model_without_cl.getStateIdsSolver()) == model_without_cl.nx_solver # ----- compare simulations wo edata, sensi = 0, states ------------------ # run simulations rdata_cl = get_results(model_with_cl) - assert rdata_cl['status'] == amici.AMICI_SUCCESS + assert rdata_cl["status"] == amici.AMICI_SUCCESS rdata = get_results(model_without_cl) - assert rdata['status'] == amici.AMICI_SUCCESS + assert rdata["status"] == amici.AMICI_SUCCESS # compare state trajectories - assert_allclose(rdata['x'], rdata_cl['x'], - rtol=1.e-5, atol=1.e-8, - err_msg="rdata.x mismatch") + assert_allclose( + rdata["x"], rdata_cl["x"], rtol=1.0e-5, atol=1.0e-8, err_msg="rdata.x mismatch" + ) # ----- compare simulations wo edata, sensi = 1, states and sensis ------- # run simulations rdata_cl = get_results(model_with_cl, sensi_order=1) - assert rdata_cl['status'] == amici.AMICI_SUCCESS + assert rdata_cl["status"] == amici.AMICI_SUCCESS rdata = get_results(model_without_cl, sensi_order=1) - assert rdata['status'] == amici.AMICI_SUCCESS + assert rdata["status"] == amici.AMICI_SUCCESS # compare state trajectories - for field in ['x', 'sx']: - assert_allclose(rdata[field], rdata_cl[field], - rtol=1.e-5, atol=1.e-8, - err_msg=f"rdata.{field} mismatch") + for field in ["x", "sx"]: + assert_allclose( + rdata[field], + rdata_cl[field], + rtol=1.0e-5, + atol=1.0e-8, + err_msg=f"rdata.{field} mismatch", + ) # ----- compare simulations wo edata, sensi = 0, states and sensis ------- # run simulations edata, _, _ = edata_fixture rdata_cl = get_results(model_with_cl, edata=edata) - assert rdata_cl['status'] == amici.AMICI_SUCCESS + assert rdata_cl["status"] == amici.AMICI_SUCCESS rdata = get_results(model_without_cl, edata=edata) - assert rdata['status'] == amici.AMICI_SUCCESS + assert rdata["status"] == amici.AMICI_SUCCESS # compare preequilibrated states - for field in ['x', 'x_ss', 'llh']: - assert_allclose(rdata[field], rdata_cl[field], - rtol=1.e-5, atol=1.e-8, - err_msg=f"rdata.{field} mismatch") + for field in ["x", "x_ss", "llh"]: + assert_allclose( + rdata[field], + rdata_cl[field], + rtol=1.0e-5, + atol=1.0e-8, + err_msg=f"rdata.{field} mismatch", + ) # ----- compare simulations wo edata, sensi = 1, states and sensis ------- # run simulations rdata_cl = get_results(model_with_cl, edata=edata, sensi_order=1) - assert rdata_cl['status'] == amici.AMICI_SUCCESS + assert rdata_cl["status"] == amici.AMICI_SUCCESS rdata = get_results( - model_without_cl, edata=edata, sensi_order=1, - stst_sensi_mode=amici.SteadyStateSensitivityMode.integrateIfNewtonFails + model_without_cl, + edata=edata, + sensi_order=1, + stst_sensi_mode=amici.SteadyStateSensitivityMode.integrateIfNewtonFails, ) - assert rdata['status'] == amici.AMICI_SUCCESS + assert rdata["status"] == amici.AMICI_SUCCESS # check that steady state computation succeeded only by sim in full model - assert_array_equal(rdata['preeq_status'], np.array([[-3, 1, 0]])) + assert_array_equal(rdata["preeq_status"], np.array([[-3, 1, 0]])) # check that steady state computation succeeded by Newton in reduced model - assert_array_equal(rdata_cl['preeq_status'], np.array([[1, 0, 0]])) + assert_array_equal(rdata_cl["preeq_status"], np.array([[1, 0, 0]])) # compare state sensitivities with edata and preequilibration - for field in ['x', 'x_ss', 'sx', 'llh', 'sllh']: - assert_allclose(rdata[field], rdata_cl[field], - rtol=1.e-5, atol=1.e-8, - err_msg=f"rdata.{field} mismatch") + for field in ["x", "x_ss", "sx", "llh", "sllh"]: + assert_allclose( + rdata[field], + rdata_cl[field], + rtol=1.0e-5, + atol=1.0e-8, + err_msg=f"rdata.{field} mismatch", + ) # ----- check failure st.st. sensi computation if run wo CLs ------------- # check failure of steady state sensitivity computation if run wo CLs @@ -188,7 +215,7 @@ def test_compare_conservation_laws_sbml(models, edata_fixture): with warnings.catch_warnings(): warnings.filterwarnings("ignore") rdata = get_results(model_without_cl, edata=edata, sensi_order=1) - assert rdata['status'] == amici.AMICI_ERROR + assert rdata["status"] == amici.AMICI_ERROR def test_adjoint_pre_and_post_equilibration(models, edata_fixture): @@ -201,42 +228,51 @@ def test_adjoint_pre_and_post_equilibration(models, edata_fixture): # compare different ways of preequilibration, full rank Jacobian # forward preequilibration, forward simulation rff_cl = get_results( - model_cl, edata=edata, sensi_order=1, + model_cl, + edata=edata, + sensi_order=1, sensi_meth=amici.SensitivityMethod.forward, sensi_meth_preeq=amici.SensitivityMethod.forward, - reinitialize_states=reinit) + reinitialize_states=reinit, + ) # forward preequilibration, adjoint simulation rfa_cl = get_results( - model_cl, edata=edata, sensi_order=1, + model_cl, + edata=edata, + sensi_order=1, sensi_meth=amici.SensitivityMethod.adjoint, sensi_meth_preeq=amici.SensitivityMethod.forward, - reinitialize_states=reinit) + reinitialize_states=reinit, + ) # adjoint preequilibration, adjoint simulation raa_cl = get_results( - model_cl, edata=edata, sensi_order=1, + model_cl, + edata=edata, + sensi_order=1, sensi_meth=amici.SensitivityMethod.adjoint, sensi_meth_preeq=amici.SensitivityMethod.adjoint, - reinitialize_states=reinit) + reinitialize_states=reinit, + ) # assert all are close - assert_allclose(rff_cl['sllh'], rfa_cl['sllh'], - rtol=1.e-5, atol=1.e-8) - assert_allclose(rfa_cl['sllh'], raa_cl['sllh'], - rtol=1.e-5, atol=1.e-8) - assert_allclose(raa_cl['sllh'], rff_cl['sllh'], - rtol=1.e-5, atol=1.e-8) + assert_allclose(rff_cl["sllh"], rfa_cl["sllh"], rtol=1.0e-5, atol=1.0e-8) + assert_allclose(rfa_cl["sllh"], raa_cl["sllh"], rtol=1.0e-5, atol=1.0e-8) + assert_allclose(raa_cl["sllh"], rff_cl["sllh"], rtol=1.0e-5, atol=1.0e-8) # compare fully adjoint approach to simulation with singular # Jacobian raa = get_results( - model, edata=edata, sensi_order=1, + model, + edata=edata, + sensi_order=1, sensi_meth=amici.SensitivityMethod.adjoint, sensi_meth_preeq=amici.SensitivityMethod.adjoint, stst_sensi_mode=amici.SteadyStateSensitivityMode.integrateIfNewtonFails, - reinitialize_states=reinit) + reinitialize_states=reinit, + ) # assert gradients are close (quadrature tolerances are laxer) - assert_allclose(raa_cl['sllh'], raa['sllh'], 1e-5, 1e-5) + assert_allclose(raa_cl["sllh"], raa["sllh"], 1e-5, 1e-5) def test_get_set_model_settings(models): diff --git a/python/tests/test_conserved_quantities_demartino.py b/python/tests/test_conserved_quantities_demartino.py index b25d09df5a..955a0221d6 100644 --- a/python/tests/test_conserved_quantities_demartino.py +++ b/python/tests/test_conserved_quantities_demartino.py @@ -7,9 +7,10 @@ import sympy as sp from amici.conserved_quantities_demartino import ( - _fill, _kernel, + _fill, + _kernel, _output as output, - compute_moiety_conservation_laws + compute_moiety_conservation_laws, ) from amici.logging import get_logger, log_execution_time from amici.testing import skip_on_valgrind @@ -19,16 +20,137 @@ # reference data for `engaged_species` after kernel() demartino2014_kernel_engaged_species = [ - 179, 181, 185, 186, 187, 190, 191, 194, 195, 197, 198, 200, 208, 209, 210, - 211, 214, 215, 218, 219, 221, 222, 224, 277, 292, 340, 422, 467, 468, 490, - 491, 598, 613, 966, 968, 1074, 1171, 1221, 1223, 1234, 1266, 1478, 1479, - 1480, 1481, 1496, 1497, 1498, 1501, 1526, 1527, 1528, 1529, 394, 1066, 398, - 465, 466, 594, 671, 429, 990, 652, 655, 662, 663, 664, 665, 666, 667, 668, - 669, 759, 760, 920, 921, 569, 1491, 1055, 1546, 276, 1333, 1421, 1429, - 1430, 1438, 1551, 1428, 1439, 1552, 1513, 1553, 1520, 1523, 1530, 1531, - 384, 1536, 440, 1537, 447, 1538, 456, 1539, 582, 1540, 876, 1541, 885, - 1542, 911, 1543, 978, 1544, 1010, 1545, 1070, 1547, 761, 1127, 1548, 1324, - 1549, 1370, 1550, 1554, 1560, 1555, 1580, 1556, 1644 + 179, + 181, + 185, + 186, + 187, + 190, + 191, + 194, + 195, + 197, + 198, + 200, + 208, + 209, + 210, + 211, + 214, + 215, + 218, + 219, + 221, + 222, + 224, + 277, + 292, + 340, + 422, + 467, + 468, + 490, + 491, + 598, + 613, + 966, + 968, + 1074, + 1171, + 1221, + 1223, + 1234, + 1266, + 1478, + 1479, + 1480, + 1481, + 1496, + 1497, + 1498, + 1501, + 1526, + 1527, + 1528, + 1529, + 394, + 1066, + 398, + 465, + 466, + 594, + 671, + 429, + 990, + 652, + 655, + 662, + 663, + 664, + 665, + 666, + 667, + 668, + 669, + 759, + 760, + 920, + 921, + 569, + 1491, + 1055, + 1546, + 276, + 1333, + 1421, + 1429, + 1430, + 1438, + 1551, + 1428, + 1439, + 1552, + 1513, + 1553, + 1520, + 1523, + 1530, + 1531, + 384, + 1536, + 440, + 1537, + 447, + 1538, + 456, + 1539, + 582, + 1540, + 876, + 1541, + 885, + 1542, + 911, + 1543, + 978, + 1544, + 1010, + 1545, + 1070, + 1547, + 761, + 1127, + 1548, + 1324, + 1549, + 1370, + 1550, + 1554, + 1560, + 1555, + 1580, + 1556, + 1644, ] @@ -41,21 +163,24 @@ def data_demartino2014(): # stoichiometric matrix response = urllib.request.urlopen( - r'https://github.com/AMICI-dev/AMICI/files/11430971/DeMartinoDe2014_test-ecoli.dat.gz', - timeout=10 + r"https://github.com/AMICI-dev/AMICI/files/11430971/DeMartinoDe2014_test-ecoli.dat.gz", + timeout=10, ) data = gzip.GzipFile(fileobj=io.BytesIO(response.read())) - S = [int(item) for sl in - [entry.decode('ascii').strip().split('\t') - for entry in data.readlines()] for item in sl] + S = [ + int(item) + for sl in [ + entry.decode("ascii").strip().split("\t") for entry in data.readlines() + ] + for item in sl + ] # metabolite / row names response = urllib.request.urlopen( - r'https://github.com/AMICI-dev/AMICI/files/11430970/test-ecoli-met.txt', - timeout=10 + r"https://github.com/AMICI-dev/AMICI/files/11430970/test-ecoli-met.txt", + timeout=10, ) - row_names = [entry.decode('ascii').strip() - for entry in io.BytesIO(response.read())] + row_names = [entry.decode("ascii").strip() for entry in io.BytesIO(response.read())] return S, row_names @@ -67,34 +192,46 @@ def test_kernel_demartino2014(data_demartino2014, quiet=True): stoichiometric_list, row_names = data_demartino2014 num_species = 1668 num_reactions = 2381 - assert len(stoichiometric_list) == num_species * num_reactions, \ - "Unexpected dimension of stoichiometric matrix" + assert ( + len(stoichiometric_list) == num_species * num_reactions + ), "Unexpected dimension of stoichiometric matrix" # Expected number of metabolites per conservation law found after kernel() - expected_num_species = \ - [53] + [2] * 11 + [6] + [3] * 2 + [2] * 15 + [3] + [2] * 5 + expected_num_species = [53] + [2] * 11 + [6] + [3] * 2 + [2] * 15 + [3] + [2] * 5 - (kernel_dim, engaged_species, int_kernel_dim, conserved_moieties, - cls_species_idxs, cls_coefficients) = _kernel( - stoichiometric_list, num_species, num_reactions) + ( + kernel_dim, + engaged_species, + int_kernel_dim, + conserved_moieties, + cls_species_idxs, + cls_coefficients, + ) = _kernel(stoichiometric_list, num_species, num_reactions) if not quiet: - output(int_kernel_dim, kernel_dim, engaged_species, cls_species_idxs, - cls_coefficients, row_names) + output( + int_kernel_dim, + kernel_dim, + engaged_species, + cls_species_idxs, + cls_coefficients, + row_names, + ) # There are 38 conservation laws, engaging 131 metabolites # 36 are integers (conserved moieties), engaging 128 metabolites (from C++) assert kernel_dim == 38, "Not all conservation laws found" assert int_kernel_dim == 36, "Not all conserved moiety laws found" - assert engaged_species == demartino2014_kernel_engaged_species, \ - "Wrong engaged metabolites reported" - assert len(conserved_moieties) == 128, \ - "Wrong number of conserved moieties reported" + assert ( + engaged_species == demartino2014_kernel_engaged_species + ), "Wrong engaged metabolites reported" + assert len(conserved_moieties) == 128, "Wrong number of conserved moieties reported" # Assert that each conserved moiety has the correct number of metabolites for i in range(int_kernel_dim - 2): - assert (len(cls_species_idxs[i]) == expected_num_species[i]), \ - f"Moiety #{i + 1} failed for test case (De Martino et al.)" + assert ( + len(cls_species_idxs[i]) == expected_num_species[i] + ), f"Moiety #{i + 1} failed for test case (De Martino et al.)" @skip_on_valgrind @@ -102,111 +239,554 @@ def test_fill_demartino2014(data_demartino2014): """Test creation of interaction matrix""" stoichiometric_list, row_names = data_demartino2014 num_species = 1668 - J, J2, fields = _fill(stoichiometric_list, - demartino2014_kernel_engaged_species, num_species) + J, J2, fields = _fill( + stoichiometric_list, demartino2014_kernel_engaged_species, num_species + ) ref_for_J = [ - [25, 27], [12, 42], [13, 43], [14, 44], [15, 41], [16, 45], - [17, 47], [18, 48], [19, 23, 49], [20, 50], [21, 51], [22, 52], - [1, 23, 30, 35], [2, 23, 29, 35], [3, 23, 35, 46], - [4, 23, 33, 35], [5, 23, 31, 35], [6, 23, 35, 37], - [7, 23, 28, 35], [8, 23, 32, 35], [9, 23, 34, 35], - [10, 23, 35, 40], [11, 23, 35, 36], - [8, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 28, - 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 46], [23, 25], - [0, 23, 24, 35], [23], [0, 28], [18, 23, 27, 35], - [13, 23, 35, 42], [12, 23, 35, 47], [16, 23, 35, 47], - [19, 23, 35, 45], [15, 23, 35, 44], [20, 23, 35, 48], - [12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 25, 28, 29, - 30, 31, 32, 33, 34, 36, 37, 40, 46], [22, 23, 35, 49], - [17, 23, 35, 50], [23, 51], [23, 41], [21, 23, 35, 52], - [4, 39], [1, 29], [2, 46], [3, 33], [5, 32], [14, 23, 35, 43], - [6, 30, 31], [7, 34], [8, 36], [9, 37], [10, 38], [11, 40], - [54], [53], [58, 80], [57, 59, 82], [56], [55, 59, 80], - [56, 58, 82], [61], [60], [63], [62], [65], [64], [67, 68, 69], - [66, 68, 69], [66, 67, 69, 70, 71, 94, 95], - [66, 67, 68, 70, 71, 94, 95], [68, 69, 71], [68, 69, 70], [73], - [72], [75], [74], [77], [76], [79], [78], [55, 58, 81], [80], - [56, 59], [84], [83, 85, 87], [84, 86, 87], [85], [84, 85], - [89], [88], [91], [90], [93], [92], [68, 69, 95], [68, 69, 94], - [97], [96], [99], [98], [101], [100], [103], [102], [105], - [104], [107], [106], [109], [108], [111], [110], [113], [112], - [115], [114], [117], [116], [119], [118, 120], [119], [122], - [121], [124], [123], [126], [125], [128], [127], [130], [129] + [25, 27], + [12, 42], + [13, 43], + [14, 44], + [15, 41], + [16, 45], + [17, 47], + [18, 48], + [19, 23, 49], + [20, 50], + [21, 51], + [22, 52], + [1, 23, 30, 35], + [2, 23, 29, 35], + [3, 23, 35, 46], + [4, 23, 33, 35], + [5, 23, 31, 35], + [6, 23, 35, 37], + [7, 23, 28, 35], + [8, 23, 32, 35], + [9, 23, 34, 35], + [10, 23, 35, 40], + [11, 23, 35, 36], + [ + 8, + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 24, + 25, + 26, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 35, + 36, + 37, + 38, + 39, + 40, + 46, + ], + [23, 25], + [0, 23, 24, 35], + [23], + [0, 28], + [18, 23, 27, 35], + [13, 23, 35, 42], + [12, 23, 35, 47], + [16, 23, 35, 47], + [19, 23, 35, 45], + [15, 23, 35, 44], + [20, 23, 35, 48], + [ + 12, + 13, + 14, + 15, + 16, + 17, + 18, + 19, + 20, + 21, + 22, + 23, + 25, + 28, + 29, + 30, + 31, + 32, + 33, + 34, + 36, + 37, + 40, + 46, + ], + [22, 23, 35, 49], + [17, 23, 35, 50], + [23, 51], + [23, 41], + [21, 23, 35, 52], + [4, 39], + [1, 29], + [2, 46], + [3, 33], + [5, 32], + [14, 23, 35, 43], + [6, 30, 31], + [7, 34], + [8, 36], + [9, 37], + [10, 38], + [11, 40], + [54], + [53], + [58, 80], + [57, 59, 82], + [56], + [55, 59, 80], + [56, 58, 82], + [61], + [60], + [63], + [62], + [65], + [64], + [67, 68, 69], + [66, 68, 69], + [66, 67, 69, 70, 71, 94, 95], + [66, 67, 68, 70, 71, 94, 95], + [68, 69, 71], + [68, 69, 70], + [73], + [72], + [75], + [74], + [77], + [76], + [79], + [78], + [55, 58, 81], + [80], + [56, 59], + [84], + [83, 85, 87], + [84, 86, 87], + [85], + [84, 85], + [89], + [88], + [91], + [90], + [93], + [92], + [68, 69, 95], + [68, 69, 94], + [97], + [96], + [99], + [98], + [101], + [100], + [103], + [102], + [105], + [104], + [107], + [106], + [109], + [108], + [111], + [110], + [113], + [112], + [115], + [114], + [117], + [116], + [119], + [118, 120], + [119], + [122], + [121], + [124], + [123], + [126], + [125], + [128], + [127], + [130], + [129], ] ref_for_J2 = [ - [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], [-1, -1], - [-1, -1], [-1, -1], [-1, -2, -1], [-1, -1], [-1, -1], - [-1, -1], [-1, 1, -1, -1], [-1, 1, -1, -1], [-1, 1, -1, -1], - [-1, 1, -1, -1], [-1, 1, -1, -1], [-1, 1, -1, -1], - [-1, 1, -1, -1], [-1, 1, -1, -1], [-1, 1, -1, -1], - [-1, 1, -1, -1], [-1, 1, -1, -1], - [-2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -2, 1, -1, -1, -1, -1, - -3, -6, -6, -1, -13, -7, -3, -3, -3, -5, -5], [-2, -1], - [-1, 1, -1, -2], [-1], [-1, -2], [-1, -1, -2, 1], - [-1, -1, 1, -2], [-1, -1, 1, -1], [-1, -3, 1, -2], - [-1, -6, 1, -2], [-1, -6, 1, -2], [-1, -1, 1, -2], - [-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -13, -2, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1], [-1, -7, 1, -2], [-1, -3, 1, -2], - [-3, -2], [-3, -2], [-1, -5, 1, -2], [-1, -2], [-1, -2], - [-1, -2], [-1, -2], [-1, -2], [-1, -5, 1, -2], [-1, -1, -2], - [-1, -2], [-1, -2], [-1, -2], [-1, -2], [-1, -2], [-2], [-2], - [1, -1], [-2, -1, -1], [-2], [1, -1, -1], [-1, -1, 1], [-1], - [-1], [-2], [-2], [-2], [-2], [-2, -1, 1], [-2, 1, -1], - [-1, 1, -3, -1, 1, -1, 1], [1, -1, -3, 1, -1, 1, -1], - [-1, 1, -2], [1, -1, -2], [-5], [-5], [-6], [-6], [-2], [-2], - [-1], [-1], [-1, -1, -1], [-1], [-1, 1], [-1], [-1, 1, -1], - [1, -1, -1], [-1], [-1, -1], [-1], [-1], [-1], [-1], [-2], - [-2], [-1, 1, -10], [1, -1, -10], [-1], [-1], [-1], [-1], - [-1], [-1], [-1], [-1], [-1], [-1], [-1], [-1], [-2], [-2], - [-1], [-1], [-1], [-1], [-1], [-1], [-1], [-1], [-1], - [-1, -1], [-1], [-1], [-1], [-1], [-1], [-1], [-1], [-1], - [-1], [-1], [-1] + [-1, -1], + [-1, -1], + [-1, -1], + [-1, -1], + [-1, -1], + [-1, -1], + [-1, -1], + [-1, -1], + [-1, -2, -1], + [-1, -1], + [-1, -1], + [-1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [-1, 1, -1, -1], + [ + -2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + -2, + 1, + -1, + -1, + -1, + -1, + -3, + -6, + -6, + -1, + -13, + -7, + -3, + -3, + -3, + -5, + -5, + ], + [-2, -1], + [-1, 1, -1, -2], + [-1], + [-1, -2], + [-1, -1, -2, 1], + [-1, -1, 1, -2], + [-1, -1, 1, -1], + [-1, -3, 1, -2], + [-1, -6, 1, -2], + [-1, -6, 1, -2], + [-1, -1, 1, -2], + [ + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -1, + -13, + -2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + ], + [-1, -7, 1, -2], + [-1, -3, 1, -2], + [-3, -2], + [-3, -2], + [-1, -5, 1, -2], + [-1, -2], + [-1, -2], + [-1, -2], + [-1, -2], + [-1, -2], + [-1, -5, 1, -2], + [-1, -1, -2], + [-1, -2], + [-1, -2], + [-1, -2], + [-1, -2], + [-1, -2], + [-2], + [-2], + [1, -1], + [-2, -1, -1], + [-2], + [1, -1, -1], + [-1, -1, 1], + [-1], + [-1], + [-2], + [-2], + [-2], + [-2], + [-2, -1, 1], + [-2, 1, -1], + [-1, 1, -3, -1, 1, -1, 1], + [1, -1, -3, 1, -1, 1, -1], + [-1, 1, -2], + [1, -1, -2], + [-5], + [-5], + [-6], + [-6], + [-2], + [-2], + [-1], + [-1], + [-1, -1, -1], + [-1], + [-1, 1], + [-1], + [-1, 1, -1], + [1, -1, -1], + [-1], + [-1, -1], + [-1], + [-1], + [-1], + [-1], + [-2], + [-2], + [-1, 1, -10], + [1, -1, -10], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-2], + [-2], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1, -1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], + [-1], ] ref_for_fields = [ - 2, 2, 2, 2, 2, 2, 2, 2, 4, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 51, 3, 3, 1, 3, 3, 3, 2, 5, 8, 8, 3, 15, 9, 5, - 5, 5, 7, 3, 3, 3, 3, 3, 7, 4, 3, 3, 3, 3, 3, 2, 2, 1, 3, - 2, 2, 2, 1, 1, 2, 2, 2, 2, 2, 2, 3, 3, 2, 2, 5, 5, 6, 6, - 2, 2, 1, 1, 2, 1, 1, 1, 2, 2, 1, 1, 1, 1, 1, 1, 2, 2, 10, - 10, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 4, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 2, + 51, + 3, + 3, + 1, + 3, + 3, + 3, + 2, + 5, + 8, + 8, + 3, + 15, + 9, + 5, + 5, + 5, + 7, + 3, + 3, + 3, + 3, + 3, + 7, + 4, + 3, + 3, + 3, + 3, + 3, + 2, + 2, + 1, + 3, + 2, + 2, + 2, + 1, + 1, + 2, + 2, + 2, + 2, + 2, + 2, + 3, + 3, + 2, + 2, + 5, + 5, + 6, + 6, + 2, + 2, + 1, + 1, + 2, + 1, + 1, + 1, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 10, + 10, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 2, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, ] # compare J from Python with reference from C++ for i in range(len(ref_for_J)): - assert J[i] == ref_for_J[i], \ - f"J_{i} ({J[i]}) does not match J_{i}_ref ({ref_for_J[i]})" - assert not any(J[len(ref_for_J):]) + assert ( + J[i] == ref_for_J[i] + ), f"J_{i} ({J[i]}) does not match J_{i}_ref ({ref_for_J[i]})" + assert not any(J[len(ref_for_J) :]) # compare J2 from Python with reference from C++ for i in range(len(ref_for_J2)): - assert J2[i] == ref_for_J2[i], \ - f"J_{i} ({J2[i]}) does not match J_{i}_ref ({ref_for_J2[i]})" - assert not any(J2[len(ref_for_J2):]) + assert ( + J2[i] == ref_for_J2[i] + ), f"J_{i} ({J2[i]}) does not match J_{i}_ref ({ref_for_J2[i]})" + assert not any(J2[len(ref_for_J2) :]) # compare fields from Python with reference from C++ for i in range(len(ref_for_fields)): - assert fields[i] == ref_for_fields[i], \ - f"J_{i} ({fields[i]}) does not match J_{i}_ref ({ref_for_fields[i]})" - assert not any(fields[len(ref_for_fields):]) + assert ( + fields[i] == ref_for_fields[i] + ), f"J_{i} ({fields[i]}) does not match J_{i}_ref ({ref_for_fields[i]})" + assert not any(fields[len(ref_for_fields) :]) -def compute_moiety_conservation_laws_demartino2014( - data_demartino2014, quiet=False -): +def compute_moiety_conservation_laws_demartino2014(data_demartino2014, quiet=False): """Compute conserved quantities for De Martino's published results for E. coli network""" stoichiometric_list, row_names = data_demartino2014 num_species = 1668 num_reactions = 2381 - assert len(stoichiometric_list) == num_species * num_reactions, \ - "Unexpected dimension of stoichiometric matrix" + assert ( + len(stoichiometric_list) == num_species * num_reactions + ), "Unexpected dimension of stoichiometric matrix" start = perf_counter() cls_state_idxs, cls_coefficients = compute_moiety_conservation_laws( - stoichiometric_list, - num_species=num_species, - num_reactions=num_reactions + stoichiometric_list, num_species=num_species, num_reactions=num_reactions ) runtime = perf_counter() - start if not quiet: @@ -220,9 +800,7 @@ def compute_moiety_conservation_laws_demartino2014( def test_compute_moiety_conservation_laws_demartino2014(data_demartino2014): """Invoke test case and benchmarking for De Martino's published results for E. coli network""" - compute_moiety_conservation_laws_demartino2014( - data_demartino2014, quiet=False - ) + compute_moiety_conservation_laws_demartino2014(data_demartino2014, quiet=False) @skip_on_valgrind @@ -239,7 +817,8 @@ def test_cl_detect_execution_time(data_demartino2014): for _ in range(max_tries): runtime = compute_moiety_conservation_laws_demartino2014( - data_demartino2014, quiet=True) + data_demartino2014, quiet=True + ) if runtime < max_time_seconds: break assert runtime < max_time_seconds, "Took too long" @@ -248,29 +827,29 @@ def test_cl_detect_execution_time(data_demartino2014): @skip_on_valgrind def test_compute_moiety_conservation_laws_simple(): """Test a simple example, ensure the conservation laws are identified - reliably. Requires the Monte Carlo to identify all.""" - stoichiometric_matrix = sp.Matrix([ - [-1.0, 1.0], - [-1.0, 1.0], - [1.0, -1.0], - [1.0, -1.0]] + reliably. Requires the Monte Carlo to identify all.""" + stoichiometric_matrix = sp.Matrix( + [[-1.0, 1.0], [-1.0, 1.0], [1.0, -1.0], [1.0, -1.0]] ) - stoichiometric_list = [ - float(entry) for entry in stoichiometric_matrix.T.flat() - ] + stoichiometric_list = [float(entry) for entry in stoichiometric_matrix.T.flat()] num_tries = 1000 found_all_n_times = 0 for _ in range(num_tries): cls_state_idxs, cls_coefficients = compute_moiety_conservation_laws( - stoichiometric_list, *stoichiometric_matrix.shape) - - assert cls_state_idxs in ([[0, 3], [1, 2], [1, 3]], - [[0, 3], [1, 2], [0, 2]], - # should happen rarely - [[0, 3], [1, 2]]) - assert cls_coefficients in ([[1.0, 1.0], [1.0, 1.0], [1.0, 1.0]], - [[1.0, 1.0], [1.0, 1.0]]) + stoichiometric_list, *stoichiometric_matrix.shape + ) + + assert cls_state_idxs in ( + [[0, 3], [1, 2], [1, 3]], + [[0, 3], [1, 2], [0, 2]], + # should happen rarely + [[0, 3], [1, 2]], + ) + assert cls_coefficients in ( + [[1.0, 1.0], [1.0, 1.0], [1.0, 1.0]], + [[1.0, 1.0], [1.0, 1.0]], + ) num_cls_found = len(cls_state_idxs) if num_cls_found == 3: diff --git a/python/tests/test_conserved_quantities_rref.py b/python/tests/test_conserved_quantities_rref.py index 2368b06b8a..4442ee4015 100644 --- a/python/tests/test_conserved_quantities_rref.py +++ b/python/tests/test_conserved_quantities_rref.py @@ -39,7 +39,10 @@ def test_nullspace_by_rref(mat): assert np.allclose(mat.dot(actual.T), 0) expected = sp.Matrix(mat).nullspace() - expected = np.hstack(np.asarray(expected, dtype=float)).T \ - if len(expected) else np.array([]) + expected = ( + np.hstack(np.asarray(expected, dtype=float)).T + if len(expected) + else np.array([]) + ) assert np.allclose(actual, expected, rtol=1e-8) diff --git a/python/tests/test_edata.py b/python/tests/test_edata.py index 7df799d4d9..b6fb65d52c 100644 --- a/python/tests/test_edata.py +++ b/python/tests/test_edata.py @@ -6,6 +6,7 @@ from test_sbml_import import model_units_module + @skip_on_valgrind def test_edata_sensi_unscaling(model_units_module): """ @@ -17,10 +18,10 @@ def test_edata_sensi_unscaling(model_units_module): sx0 = (3, 3, 3, 3) - parameter_scales_log10 = \ - [amici.ParameterScaling.log10.value]*len(parameters0) - amici_parameter_scales_log10 = \ - amici.parameterScalingFromIntVector(parameter_scales_log10) + parameter_scales_log10 = [amici.ParameterScaling.log10.value] * len(parameters0) + amici_parameter_scales_log10 = amici.parameterScalingFromIntVector( + parameter_scales_log10 + ) model = model_units_module.getModel() model.setTimepoints(np.linspace(0, 1, 3)) diff --git a/python/tests/test_events.py b/python/tests/test_events.py index a6f5334b83..3a543190ff 100644 --- a/python/tests/test_events.py +++ b/python/tests/test_events.py @@ -4,16 +4,21 @@ import numpy as np import pytest -from util import (check_trajectories_with_forward_sensitivities, - check_trajectories_without_sensitivities, create_amici_model, - create_sbml_model) +from util import ( + check_trajectories_with_forward_sensitivities, + check_trajectories_without_sensitivities, + create_amici_model, + create_sbml_model, +) from amici.testing import skip_on_valgrind -@pytest.fixture(params=[ - pytest.param('events_plus_heavisides', marks=skip_on_valgrind), - 'nested_events', -]) +@pytest.fixture( + params=[ + pytest.param("events_plus_heavisides", marks=skip_on_valgrind), + "nested_events", + ] +) def model(request): """Returns the requested AMICI model and analytical expressions.""" ( @@ -24,7 +29,7 @@ def model(request): events, timepoints, x_pected, - sx_pected + sx_pected, ) = get_model_definition(request.param) # SBML model @@ -49,14 +54,12 @@ def model(request): def get_model_definition(model_name): - if model_name == 'events_plus_heavisides': - return model_definition_events_plus_heavisides() - if model_name == 'nested_events': + if model_name == "events_plus_heavisides": + return model_definition_events_plus_heavisides() + if model_name == "nested_events": return model_definition_nested_events() else: - raise NotImplementedError( - f'Model with name {model_name} is not implemented.' - ) + raise NotImplementedError(f"Model with name {model_name} is not implemented.") def model_definition_events_plus_heavisides(): @@ -87,39 +90,39 @@ def model_definition_events_plus_heavisides(): [ zeta / 3]] """ # Model components - species = ['x_1', 'x_2', 'x_3'] + species = ["x_1", "x_2", "x_3"] initial_assignments = { - 'x_1': 'k1', - 'x_2': 'k2', - 'x_3': 'k3', + "x_1": "k1", + "x_2": "k2", + "x_3": "k3", } rate_rules = { - 'x_1': 'piecewise( -alpha * x_1, time >= delta, 0)', - 'x_2': 'beta * x_1 - gamma * x_2', - 'x_3': '-eta * x_3 + piecewise( 1, time >= zeta, 0)', + "x_1": "piecewise( -alpha * x_1, time >= delta, 0)", + "x_2": "beta * x_1 - gamma * x_2", + "x_3": "-eta * x_3 + piecewise( 1, time >= zeta, 0)", } parameters = { - 'k1': 2, - 'k2': 0.01, - 'k3': 5, - 'alpha': 2, - 'beta': 3, - 'gamma': 2, - 'delta': 3, - 'eta': 1, - 'zeta': 5, + "k1": 2, + "k2": 0.01, + "k3": 5, + "alpha": 2, + "beta": 3, + "gamma": 2, + "delta": 3, + "eta": 1, + "zeta": 5, } events = { - 'event_1': { - 'trigger': 'x_3 < k1', - 'target': 'x_1', - 'assignment': 'x_1 - x_3 / 2' + "event_1": { + "trigger": "x_3 < k1", + "target": "x_1", + "assignment": "x_1 - x_3 / 2", + }, + "event_2": { + "trigger": "time >= zeta", + "target": "x_3", + "assignment": "x_3 + zeta / 3", }, - 'event_2': { - 'trigger': 'time >= zeta', - 'target': 'x_3', - 'assignment': 'x_3 + zeta / 3' - } } timepoints = np.linspace(0, 8, 400) @@ -138,17 +141,13 @@ def get_early_x(t): # compute dynamics if t < event_1_time: # Define A - A = np.array([[0, 0, 0], - [beta, -gamma, 0], - [0, 0, -eta]]) + A = np.array([[0, 0, 0], [beta, -gamma, 0], [0, 0, -eta]]) tmp_x = expm(t * A) return np.matmul(tmp_x, x0) elif t <= event_2_time: # "simulate" until first event - A = np.array([[0, 0, 0], - [beta, -gamma, 0], - [0, 0, -eta]]) + A = np.array([[0, 0, 0], [beta, -gamma, 0], [0, 0, -eta]]) tmp_x = expm(event_1_time * A) x1 = np.matmul(tmp_x, x0) # apply bolus @@ -163,26 +162,24 @@ def get_early_x(t): elif t < event_3_time: x2 = get_early_x(event_2_time) - A = np.array([[-alpha, 0, 0], - [beta, -gamma, 0], - [0, 0, -eta]]) + A = np.array([[-alpha, 0, 0], [beta, -gamma, 0], [0, 0, -eta]]) tmp_x = expm((t - event_2_time) * A) x = np.matmul(tmp_x, x2).flatten() else: x2 = get_early_x(event_2_time) - A = np.array([[-alpha, 0, 0], - [beta, -gamma, 0], - [0, 0, -eta]]) + A = np.array([[-alpha, 0, 0], [beta, -gamma, 0], [0, 0, -eta]]) tmp_x = expm((event_3_time - event_2_time) * A) x3 = np.matmul(tmp_x, x2) # apply bolus x3 += np.array([[0], [0], [zeta / 3]]) hom_x = np.matmul(expm((t - event_3_time) * A), x3) - inhom_x = [[0], [0], - [-np.exp(-eta * (t - event_3_time)) / (eta) - + 1 / (eta)]] + inhom_x = [ + [0], + [0], + [-np.exp(-eta * (t - event_3_time)) / (eta) + 1 / (eta)], + ] x = (hom_x + inhom_x).flatten() @@ -197,7 +194,7 @@ def sx_pected(t, parameters): perturbed_params = deepcopy(parameters) perturbed_params[ip] += eps sx_p = x_pected(t, **perturbed_params) - perturbed_params[ip] -= 2*eps + perturbed_params[ip] -= 2 * eps sx_m = x_pected(t, **perturbed_params) sx.append((sx_p - sx_m) / (2 * eps)) @@ -211,7 +208,7 @@ def sx_pected(t, parameters): events, timepoints, x_pected, - sx_pected + sx_pected, ) @@ -237,34 +234,34 @@ def model_definition_nested_events(): [ bolus]] """ # Model components - species = ['x_1', 'x_2'] + species = ["x_1", "x_2"] initial_assignments = { - 'x_1': 'k1', - 'x_2': 'k2', + "x_1": "k1", + "x_2": "k2", } rate_rules = { - 'x_1': 'inflow_1 - decay_1 * x_1', - 'x_2': '- decay_2 * x_2', + "x_1": "inflow_1 - decay_1 * x_1", + "x_2": "- decay_2 * x_2", } parameters = { - 'k1': 0, - 'k2': 0, - 'inflow_1': 4, - 'decay_1': 2, - 'decay_2': 5, - 'bolus': 0, # for bolus != 0, nested event sensitivities are off! + "k1": 0, + "k2": 0, + "inflow_1": 4, + "decay_1": 2, + "decay_2": 5, + "bolus": 0, # for bolus != 0, nested event sensitivities are off! } events = { - 'event_1': { - 'trigger': 'x_1 > inflow_1 / decay_2', - 'target': 'x_2', - 'assignment': 'x_2 - 1 / time' + "event_1": { + "trigger": "x_1 > inflow_1 / decay_2", + "target": "x_2", + "assignment": "x_2 - 1 / time", + }, + "event_2": { + "trigger": "x_2 < - 0.5", + "target": ["x_1", "x_2"], + "assignment": ["x_1 + bolus", "x_2 + bolus"], }, - 'event_2': { - 'trigger': 'x_2 < - 0.5', - 'target': ['x_1', 'x_2'], - 'assignment': ['x_1 + bolus', 'x_2 + bolus'], - } } timepoints = np.linspace(0, 1, 101) @@ -275,11 +272,11 @@ def x_pected(t, k1, k2, inflow_1, decay_1, decay_2, bolus): equil = inflow_1 / decay_1 tmp1 = inflow_1 / decay_2 - inflow_1 / decay_1 tmp2 = k1 - inflow_1 / decay_1 - event_time = (- 1 / decay_1) * np.log( tmp1 / tmp2) + event_time = (-1 / decay_1) * np.log(tmp1 / tmp2) def get_early_x(t): # compute dynamics before event - x_1 = equil * (1 - np.exp(-decay_1 * t)) + k1*np.exp(-decay_1 * t) + x_1 = equil * (1 - np.exp(-decay_1 * t)) + k1 * np.exp(-decay_1 * t) x_2 = k2 * np.exp(-decay_2 * t) return np.array([[x_1], [x_2]]) @@ -293,8 +290,9 @@ def get_early_x(t): # compute dynamics after event inhom = np.exp(decay_1 * event_time) * tau_x1 - x_1 = equil * (1 - np.exp(decay_1 * (event_time - t))) + \ - inhom * np.exp(- decay_1 * t) + x_1 = equil * (1 - np.exp(decay_1 * (event_time - t))) + inhom * np.exp( + -decay_1 * t + ) x_2 = tau_x2 * np.exp(decay_2 * event_time) * np.exp(-decay_2 * t) x = np.array([[x_1], [x_2]]) @@ -310,7 +308,7 @@ def sx_pected(t, parameters): perturbed_params = deepcopy(parameters) perturbed_params[ip] += eps sx_p = x_pected(t, **perturbed_params) - perturbed_params[ip] -= 2*eps + perturbed_params[ip] -= 2 * eps sx_m = x_pected(t, **perturbed_params) sx.append((sx_p - sx_m) / (2 * eps)) @@ -324,28 +322,21 @@ def sx_pected(t, parameters): events, timepoints, x_pected, - sx_pected + sx_pected, ) def test_models(model): amici_model, parameters, timepoints, x_pected, sx_pected = model - result_expected_x = np.array([ - x_pected(t, **parameters) - for t in timepoints - ]) - result_expected_sx = np.array([ - sx_pected(t, parameters) - for t in timepoints - ]) + result_expected_x = np.array([x_pected(t, **parameters) for t in timepoints]) + result_expected_sx = np.array([sx_pected(t, parameters) for t in timepoints]) # assert correctness of trajectories - check_trajectories_without_sensitivities(amici_model, - result_expected_x) - check_trajectories_with_forward_sensitivities(amici_model, - result_expected_x, - result_expected_sx) + check_trajectories_without_sensitivities(amici_model, result_expected_x) + check_trajectories_with_forward_sensitivities( + amici_model, result_expected_x, result_expected_sx + ) def expm(x): @@ -354,4 +345,5 @@ def expm(x): Uses ``expm`` from ``mpmath``. *Something* changed in scipy's ``expm`` in version 1.9.0 breaking these tests""" from mpmath import expm + return np.array(expm(x).tolist()).astype(float) diff --git a/python/tests/test_hdf5.py b/python/tests/test_hdf5.py index 40098f9212..c47d8653eb 100644 --- a/python/tests/test_hdf5.py +++ b/python/tests/test_hdf5.py @@ -10,18 +10,18 @@ def _modify_solver_attrs(solver): # change to non-default values for attr in dir(solver): - if not attr.startswith('set'): + if not attr.startswith("set"): continue - val = getattr(solver, attr.replace('set', 'get'))() + val = getattr(solver, attr.replace("set", "get"))() if isinstance(val, bool): cval = not val - elif attr == 'setStabilityLimitFlag': + elif attr == "setStabilityLimitFlag": cval = 0 - elif attr == 'setReturnDataReportingMode': + elif attr == "setReturnDataReportingMode": cval = amici.RDataReporting.likelihood - elif attr == 'setMaxTime': + elif attr == "setMaxTime": # default value is the maximum, must not add to that cval = random.random() elif isinstance(val, int): @@ -32,8 +32,7 @@ def _modify_solver_attrs(solver): getattr(solver, attr)(cval) -@pytest.mark.skipif(not amici.hdf5_enabled, - reason='AMICI was compiled without HDF5') +@pytest.mark.skipif(not amici.hdf5_enabled, reason="AMICI was compiled without HDF5") def test_solver_hdf5_roundtrip(sbml_example_presimulation_module): """TestCase class for AMICI HDF5 I/O""" @@ -41,29 +40,31 @@ def test_solver_hdf5_roundtrip(sbml_example_presimulation_module): solver = model.getSolver() _modify_solver_attrs(solver) - hdf5file = 'solverSettings.hdf5' + hdf5file = "solverSettings.hdf5" - amici.writeSolverSettingsToHDF5(solver, hdf5file, 'ssettings') + amici.writeSolverSettingsToHDF5(solver, hdf5file, "ssettings") new_solver = model.getSolver() # check that we changed everything for attr in dir(solver): - if not attr.startswith('set'): + if not attr.startswith("set"): continue - assert getattr(solver, attr.replace('set', 'get'))() \ - != getattr(new_solver, attr.replace('set', 'get'))(), attr + assert ( + getattr(solver, attr.replace("set", "get"))() + != getattr(new_solver, attr.replace("set", "get"))() + ), attr - amici.readSolverSettingsFromHDF5(hdf5file, new_solver, 'ssettings') + amici.readSolverSettingsFromHDF5(hdf5file, new_solver, "ssettings") # check that reading in settings worked for attr in dir(solver): - if not attr.startswith('set'): + if not attr.startswith("set"): continue - assert getattr(solver, attr.replace('set', 'get'))() \ - == pytest.approx( - getattr(new_solver, attr.replace('set', 'get'))()), attr + assert getattr(solver, attr.replace("set", "get"))() == pytest.approx( + getattr(new_solver, attr.replace("set", "get"))() + ), attr os.remove(hdf5file) diff --git a/python/tests/test_heavisides.py b/python/tests/test_heavisides.py index 3d1a3564b4..05d3dd5987 100644 --- a/python/tests/test_heavisides.py +++ b/python/tests/test_heavisides.py @@ -10,11 +10,14 @@ check_trajectories_with_forward_sensitivities, ) -@pytest.fixture(params=[ - 'state_and_param_dep_heavisides', - 'piecewise_with_boolean_operations', - 'piecewise_many_conditions', -]) + +@pytest.fixture( + params=[ + "state_and_param_dep_heavisides", + "piecewise_with_boolean_operations", + "piecewise_many_conditions", + ] +) def model(request): """Returns the requested AMICI model and analytical expressions.""" ( @@ -25,7 +28,7 @@ def model(request): events, timepoints, x_pected, - sx_pected + sx_pected, ) = get_model_definition(request.param) # SBML model @@ -52,34 +55,25 @@ def model(request): def test_models(model): amici_model, parameters, timepoints, x_pected, sx_pected = model - result_expected_x = np.array([ - x_pected(t, **parameters) - for t in timepoints - ]) - result_expected_sx = np.array([ - sx_pected(t, **parameters) - for t in timepoints - ]) + result_expected_x = np.array([x_pected(t, **parameters) for t in timepoints]) + result_expected_sx = np.array([sx_pected(t, **parameters) for t in timepoints]) # Does the AMICI simulation match the analytical solution? - check_trajectories_without_sensitivities(amici_model, - result_expected_x) - check_trajectories_with_forward_sensitivities(amici_model, - result_expected_x, - result_expected_sx) + check_trajectories_without_sensitivities(amici_model, result_expected_x) + check_trajectories_with_forward_sensitivities( + amici_model, result_expected_x, result_expected_sx + ) def get_model_definition(model_name): - if model_name == 'state_and_param_dep_heavisides': + if model_name == "state_and_param_dep_heavisides": return model_definition_state_and_parameter_dependent_heavisides() - elif model_name == 'piecewise_with_boolean_operations': + elif model_name == "piecewise_with_boolean_operations": return model_definition_piecewise_with_boolean_operations() - elif model_name == 'piecewise_many_conditions': + elif model_name == "piecewise_many_conditions": return model_definition_piecewise_many_conditions() else: - raise NotImplementedError( - f'Model with name {model_name} is not implemented.' - ) + raise NotImplementedError(f"Model with name {model_name} is not implemented.") def model_definition_state_and_parameter_dependent_heavisides(): @@ -95,21 +89,21 @@ def model_definition_state_and_parameter_dependent_heavisides(): - { eta, t >= delta """ # Model components - species = ['x_1', 'x_2'] + species = ["x_1", "x_2"] initial_assignments = { - 'x_1': 'zeta', + "x_1": "zeta", } rate_rules = { - 'x_1': 'piecewise( alpha * x_1, time < x_2, -beta * x_1 )', - 'x_2': 'piecewise( gamma * x_2, time < delta, eta )', + "x_1": "piecewise( alpha * x_1, time < x_2, -beta * x_1 )", + "x_2": "piecewise( gamma * x_2, time < delta, eta )", } parameters = { - 'alpha': float(np.log(2)), - 'beta': float(np.log(4)), - 'gamma': float(np.log(3)), - 'delta': 1, - 'eta': 0.5, - 'zeta': 0.25, + "alpha": float(np.log(2)), + "beta": float(np.log(4)), + "gamma": float(np.log(3)), + "delta": 1, + "eta": 0.5, + "zeta": 0.25, } timepoints = np.linspace(0, 10, 100) events = {} @@ -121,14 +115,14 @@ def x_pected(t, alpha, beta, gamma, delta, eta, zeta): if t < tau_1: x_1 = zeta * np.exp(alpha * t) else: - x_1 = zeta * np.exp(alpha * tau_1 - beta*(t - tau_1)) + x_1 = zeta * np.exp(alpha * tau_1 - beta * (t - tau_1)) # get x_2 tau_2 = delta if t < tau_2: - x_2 = np.exp(gamma*t) + x_2 = np.exp(gamma * t) else: - x_2 = np.exp(gamma*delta) + eta*(t-delta) + x_2 = np.exp(gamma * delta) + eta * (t - delta) return (x_1, x_2) @@ -144,53 +138,51 @@ def sx_pected(t, alpha, beta, gamma, delta, eta, zeta): sx_1_zeta = np.exp(alpha * t) else: # Never trust Wolfram Alpha... - sx_1_alpha = ( - zeta * tau_1 * np.exp(alpha * tau_1 - beta*(t - tau_1)) - ) - sx_1_beta = ( - zeta * (tau_1 - t) - * np.exp(alpha * tau_1 - beta*(t - tau_1)) - ) + sx_1_alpha = zeta * tau_1 * np.exp(alpha * tau_1 - beta * (t - tau_1)) + sx_1_beta = zeta * (tau_1 - t) * np.exp(alpha * tau_1 - beta * (t - tau_1)) sx_1_gamma = ( - zeta * (alpha + beta) * delta * np.exp(gamma * delta) + zeta + * (alpha + beta) + * delta + * np.exp(gamma * delta) / (1 - eta) - * np.exp(alpha * tau_1 - beta*(t - tau_1)) + * np.exp(alpha * tau_1 - beta * (t - tau_1)) ) sx_1_delta = ( - zeta * (alpha + beta) - * np.exp(alpha * tau_1 - beta*(t - tau_1)) + zeta + * (alpha + beta) + * np.exp(alpha * tau_1 - beta * (t - tau_1)) * (gamma * np.exp(gamma * delta) - eta) / (1 - eta) ) sx_1_eta = ( - zeta * (alpha + beta) - * (-delta * (1-eta) + np.exp(gamma * delta) - delta * eta) - / (1 - eta)**2 - * np.exp(alpha * tau_1 - beta*(t - tau_1)) + zeta + * (alpha + beta) + * (-delta * (1 - eta) + np.exp(gamma * delta) - delta * eta) + / (1 - eta) ** 2 + * np.exp(alpha * tau_1 - beta * (t - tau_1)) ) - sx_1_zeta = np.exp(alpha * tau_1 - beta*(t - tau_1)) + sx_1_zeta = np.exp(alpha * tau_1 - beta * (t - tau_1)) # get sx_2, w.r.t. parameters tau_2 = delta if t < tau_2: sx_2_alpha = 0 sx_2_beta = 0 - sx_2_gamma = t * np.exp(gamma*t) + sx_2_gamma = t * np.exp(gamma * t) sx_2_delta = 0 sx_2_eta = 0 sx_2_zeta = 0 else: sx_2_alpha = 0 sx_2_beta = 0 - sx_2_gamma = delta * np.exp(gamma*delta) - sx_2_delta = gamma*np.exp(gamma*delta) - eta + sx_2_gamma = delta * np.exp(gamma * delta) + sx_2_delta = gamma * np.exp(gamma * delta) - eta sx_2_eta = t - delta sx_2_zeta = 0 - sx_1 = (sx_1_alpha, sx_1_beta, sx_1_gamma, - sx_1_delta, sx_1_eta, sx_1_zeta) - sx_2 = (sx_2_alpha, sx_2_beta, sx_2_gamma, - sx_2_delta, sx_2_eta, sx_2_zeta) + sx_1 = (sx_1_alpha, sx_1_beta, sx_1_gamma, sx_1_delta, sx_1_eta, sx_1_zeta) + sx_2 = (sx_2_alpha, sx_2_beta, sx_2_gamma, sx_2_delta, sx_2_eta, sx_2_zeta) return np.array((sx_1, sx_2)).transpose() @@ -202,7 +194,7 @@ def sx_pected(t, alpha, beta, gamma, delta, eta, zeta): events, timepoints, x_pected, - sx_pected + sx_pected, ) @@ -216,24 +208,24 @@ def model_definition_piecewise_with_boolean_operations(): - { 0, otherwise """ # Model components - species = ['x_1'] - initial_assignments = {'x_1': 'x_1_0'} + species = ["x_1"] + initial_assignments = {"x_1": "x_1_0"} rate_rules = { - 'x_1': ( - 'piecewise(' - '1, ' # noqa - '(alpha <= time && time < beta) || ' # noqa - '(gamma <= time && time < delta), ' - '0' - ')' + "x_1": ( + "piecewise(" + "1, " # noqa + "(alpha <= time && time < beta) || " # noqa + "(gamma <= time && time < delta), " + "0" + ")" ), } parameters = { - 'alpha': 1, - 'beta': 2, - 'gamma': 3, - 'delta': 4, - 'x_1_0': 1, + "alpha": 1, + "beta": 2, + "gamma": 3, + "delta": 4, + "x_1_0": 1, } timepoints = np.linspace(0, 5, 100) events = {} @@ -249,7 +241,7 @@ def x_pected(t, x_1_0, alpha, beta, gamma, delta): elif gamma <= t < delta: return (x_1_0 + (beta - alpha) + (t - gamma),) else: - return (x_1_0 + (beta - alpha) + (delta - gamma), ) + return (x_1_0 + (beta - alpha) + (delta - gamma),) def sx_pected(t, x_1_0, alpha, beta, gamma, delta): # x0 is very simple... @@ -280,7 +272,7 @@ def sx_pected(t, x_1_0, alpha, beta, gamma, delta): events, timepoints, x_pected, - sx_pected + sx_pected, ) @@ -294,23 +286,25 @@ def model_definition_piecewise_many_conditions(): - { 0, otherwise """ # Model components - species = ['x_1'] - initial_assignments = {'x_1': 'x_1_0'} + species = ["x_1"] + initial_assignments = {"x_1": "x_1_0"} t_final = 5 - pieces = 'piecewise(' + pieces = "piecewise(" for t in range(t_final): if t > 0: - pieces += ', ' + pieces += ", " if t % 2 == 1: - pieces += f'1, time < {t + 1}' + pieces += f"1, time < {t + 1}" else: - pieces += f'0, time < {t + 1}' - pieces += ', 0)' - rate_rules = {'x_1': pieces, } + pieces += f"0, time < {t + 1}" + pieces += ", 0)" + rate_rules = { + "x_1": pieces, + } parameters = { - 'x_1_0': 1, + "x_1_0": 1, } timepoints = np.linspace(0, t_final, 100) events = {} @@ -318,12 +312,18 @@ def model_definition_piecewise_many_conditions(): # Analytical solution def x_pected(t, x_1_0): if np.floor(t) % 2 == 1: - return (x_1_0 + (np.floor(t)-1)/2 + (t-np.floor(t)), ) + return (x_1_0 + (np.floor(t) - 1) / 2 + (t - np.floor(t)),) else: - return (x_1_0 + np.floor(t)/2, ) + return (x_1_0 + np.floor(t) / 2,) def sx_pected(t, x_1_0): - return np.array([[1, ], ]) + return np.array( + [ + [ + 1, + ], + ] + ) return ( initial_assignments, @@ -333,5 +333,5 @@ def sx_pected(t, x_1_0): events, timepoints, x_pected, - sx_pected + sx_pected, ) diff --git a/python/tests/test_misc.py b/python/tests/test_misc.py index 2e70c4c6e3..d2820adbbd 100644 --- a/python/tests/test_misc.py +++ b/python/tests/test_misc.py @@ -7,8 +7,7 @@ import sympy as sp import amici -from amici.de_export import _custom_pow_eval_derivative, _monkeypatched, \ - smart_subs_dict +from amici.de_export import _custom_pow_eval_derivative, _monkeypatched, smart_subs_dict from amici.testing import skip_on_valgrind @@ -19,38 +18,42 @@ def test_parameter_scaling_from_int_vector(): [ amici.ParameterScaling.log10, amici.ParameterScaling.ln, - amici.ParameterScaling.none - ]) + amici.ParameterScaling.none, + ] + ) assert scale_vector[0] == amici.ParameterScaling.log10 assert scale_vector[1] == amici.ParameterScaling.ln assert scale_vector[2] == amici.ParameterScaling.none + @skip_on_valgrind def test_hill_function_dwdx(): """Kinetic laws with Hill functions, may lead to NaNs in the Jacobian if involved states are zero if not properly arranged symbolically. Test that what we are applying the right sympy simplification.""" - w = sp.Matrix([[sp.sympify('Pow(x1, p1) / (Pow(x1, p1) + a)')]]) - dwdx = w.diff(sp.Symbol('x1')) + w = sp.Matrix([[sp.sympify("Pow(x1, p1) / (Pow(x1, p1) + a)")]]) + dwdx = w.diff(sp.Symbol("x1")) # Verify that without simplification we fail with pytest.raises(ZeroDivisionError): with sp.evaluate(False): - res = dwdx.subs({'x1': 0.0}) + res = dwdx.subs({"x1": 0.0}) _ = str(res) # Test that powsimp does the job dwdx = dwdx.applyfunc(lambda x: sp.powsimp(x, deep=True)) with sp.evaluate(False): - res = dwdx.subs({'x1': 0.0}) + res = dwdx.subs({"x1": 0.0}) _ = str(res) @skip_on_valgrind -@pytest.mark.skipif(os.environ.get('AMICI_SKIP_CMAKE_TESTS', '') == 'TRUE', - reason='skipping cmake based test') +@pytest.mark.skipif( + os.environ.get("AMICI_SKIP_CMAKE_TESTS", "") == "TRUE", + reason="skipping cmake based test", +) def test_cmake_compilation(sbml_example_presimulation_module): """Check that CMake build succeeds for one of the models generated during Python tests""" @@ -59,14 +62,17 @@ def test_cmake_compilation(sbml_example_presimulation_module): build_dir = f"{source_dir}/build" # path hint for amici base installation, in case CMake configuration has # not been exported - amici_dir = (Path(__file__).parents[2] / 'build').absolute() - cmd = f"set -e; " \ - f"cmake -S {source_dir} -B '{build_dir}' -DAmici_DIR={amici_dir}; " \ - f"cmake --build '{build_dir}'" + amici_dir = (Path(__file__).parents[2] / "build").absolute() + cmd = ( + f"set -e; " + f"cmake -S {source_dir} -B '{build_dir}' -DAmici_DIR={amici_dir}; " + f"cmake --build '{build_dir}'" + ) try: - subprocess.run(cmd, shell=True, check=True, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + subprocess.run( + cmd, shell=True, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE + ) except subprocess.CalledProcessError as e: print(e.stdout.decode()) print(e.stderr.decode()) @@ -75,13 +81,13 @@ def test_cmake_compilation(sbml_example_presimulation_module): @skip_on_valgrind def test_smart_subs_dict(): - expr_str = 'c + d' + expr_str = "c + d" subs_dict = { - 'c': 'a + b', - 'd': 'c + a', + "c": "a + b", + "d": "c + a", } - expected_default_str = '3*a + 2*b' - expected_reverse_str = '2*a + b + c' + expected_default_str = "3*a + 2*b" + expected_reverse_str = "2*a + b + c" expr_sym = sp.sympify(expr_str) subs_sym = {sp.sympify(k): sp.sympify(v) for k, v in subs_dict.items()} @@ -97,32 +103,30 @@ def test_smart_subs_dict(): @skip_on_valgrind def test_monkeypatch(): - t = sp.Symbol('t') - n = sp.Symbol('n') - vals = [(t, 0), - (n, 1)] + t = sp.Symbol("t") + n = sp.Symbol("n") + vals = [(t, 0), (n, 1)] # check that the removable singularity still exists assert (t**n).diff(t).subs(vals) is sp.nan # check that we can monkeypatch it out - with _monkeypatched(sp.Pow, '_eval_derivative', - _custom_pow_eval_derivative): - assert (t ** n).diff(t).subs(vals) is not sp.nan + with _monkeypatched(sp.Pow, "_eval_derivative", _custom_pow_eval_derivative): + assert (t**n).diff(t).subs(vals) is not sp.nan # check that the monkeypatch is transient - assert (t ** n).diff(t).subs(vals) is sp.nan + assert (t**n).diff(t).subs(vals) is sp.nan @skip_on_valgrind def test_get_default_argument(): # no default with pytest.raises(ValueError): - amici._get_default_argument(lambda x: x, 'x') + amici._get_default_argument(lambda x: x, "x") # non-existant parameter with pytest.raises(KeyError): - amici._get_default_argument(lambda x: x, 'y') + amici._get_default_argument(lambda x: x, "y") # okay - assert amici._get_default_argument(lambda x=1: x, 'x') == 1 + assert amici._get_default_argument(lambda x=1: x, "x") == 1 diff --git a/python/tests/test_observable_events.py b/python/tests/test_observable_events.py index 8cd932a248..98de4881f0 100644 --- a/python/tests/test_observable_events.py +++ b/python/tests/test_observable_events.py @@ -4,8 +4,10 @@ from util import create_sbml_model, create_amici_model from test_pregenerated_models import ( - options_file, expected_results, expected_results_file, - verify_simulation_results + options_file, + expected_results, + expected_results_file, + verify_simulation_results, ) @@ -28,45 +30,39 @@ def model_neuron_def(): observable: t """ # Model components - species = ['v', 'u'] + species = ["v", "u"] initial_assignments = { - 'v': 'v0', - 'u': 'b*v0', + "v": "v0", + "u": "b*v0", } rate_rules = { - 'v': '0.04*v^2 + 5*v + 140 - u + I0', - 'u': 'a*(b*v - u)', + "v": "0.04*v^2 + 5*v + 140 - u + I0", + "u": "a*(b*v - u)", } parameters = { - 'a': 0.02, - 'b': 0.3, - 'c': 65, - 'd': 0.9, - 'v0': -60, - 'I0': 10, + "a": 0.02, + "b": 0.3, + "c": 65, + "d": 0.9, + "v0": -60, + "I0": 10, } events = { - 'event_1': { - 'trigger': 'v > 30', - 'target': ['v', 'u'], - 'assignment': ['-c', 'd+u'] + "event_1": { + "trigger": "v > 30", + "target": ["v", "u"], + "assignment": ["-c", "d+u"], }, } observables = { - 'y1': { - 'name': 'v', - 'formula': 'v', + "y1": { + "name": "v", + "formula": "v", } } - event_observables = { - 'z1': { - 'name': 'z1', - 'event': 'event_1', - 'formula': 'time' - } - } + event_observables = {"z1": {"name": "z1", "event": "event_1", "formula": "time"}} return ( initial_assignments, parameters, @@ -74,7 +70,7 @@ def model_neuron_def(): species, events, observables, - event_observables + event_observables, ) @@ -102,58 +98,42 @@ def model_events_def(): observable: t """ # Model components - species = ['x1', 'x2', 'x3'] + species = ["x1", "x2", "x3"] initial_assignments = { - 'x1': 'k1', - 'x2': 'k2', - 'x3': 'k3', + "x1": "k1", + "x2": "k2", + "x3": "k3", } rate_rules = { - 'x1': '-p1*piecewise(1.0, time>p4, 0.0)*x1', - 'x2': 'p2*x1*exp(-0.1*time)-p3*x2', - 'x3': '-x3+piecewise(1.0, time>4, 0.0)' + "x1": "-p1*piecewise(1.0, time>p4, 0.0)*x1", + "x2": "p2*x1*exp(-0.1*time)-p3*x2", + "x3": "-x3+piecewise(1.0, time>4, 0.0)", } parameters = { - 'p1': 0.5, - 'p2': 2, - 'p3': 0.5, - 'p4': 0.5, - 'k1': 4, - 'k2': 8, - 'k3': 10, - 'k4': 4, + "p1": 0.5, + "p2": 2, + "p3": 0.5, + "p4": 0.5, + "k1": 4, + "k2": 8, + "k3": 10, + "k4": 4, } events = { - 'event_1': { - 'trigger': 'x2 > x3', - 'target': [], - 'assignment': [] - }, - 'event_2': { - 'trigger': 'x1 > x3', - 'target': [], - 'assignment': [] - }, + "event_1": {"trigger": "x2 > x3", "target": [], "assignment": []}, + "event_2": {"trigger": "x1 > x3", "target": [], "assignment": []}, } observables = { - 'y1': { - 'name': 'y1', - 'formula': 'p4*(x1+x2+x3)', + "y1": { + "name": "y1", + "formula": "p4*(x1+x2+x3)", } } event_observables = { - 'z1': { - 'name': 'z1', - 'event': 'event_1', - 'formula': 'time' - }, - 'z2': { - 'name': 'z2', - 'event': 'event_2', - 'formula': 'time' - } + "z1": {"name": "z1", "event": "event_1", "formula": "time"}, + "z2": {"name": "z2", "event": "event_2", "formula": "time"}, } return ( initial_assignments, @@ -162,18 +142,20 @@ def model_events_def(): species, events, observables, - event_observables + event_observables, ) models = [ - (model_neuron_def, 'model_neuron', ['v0', 'I0']), - (model_events_def, 'model_events', ['k1', 'k2', 'k3', 'k4']), + (model_neuron_def, "model_neuron", ["v0", "I0"]), + (model_events_def, "model_events", ["k1", "k2", "k3", "k4"]), ] -@pytest.mark.skipif(os.environ.get('AMICI_SKIP_CMAKE_TESTS', '') == 'TRUE', - reason='skipping cmake based test') +@pytest.mark.skipif( + os.environ.get("AMICI_SKIP_CMAKE_TESTS", "") == "TRUE", + reason="skipping cmake based test", +) @pytest.mark.parametrize("model_def,model_name,constants", models) def test_models(model_def, model_name, constants): ( @@ -183,7 +165,7 @@ def test_models(model_def, model_name, constants): species, events, observables, - event_observables + event_observables, ) = model_def() sbml_document, sbml_model = create_sbml_model( @@ -201,7 +183,7 @@ def test_models(model_def, model_name, constants): model_name=model_name, observables=observables, constant_parameters=constants, - event_observables=event_observables + event_observables=event_observables, ) run_test_cases(model) @@ -210,40 +192,36 @@ def test_models(model_def, model_name, constants): def run_test_cases(model): - solver = model.getSolver() model_name = model.getName() for case in list(expected_results[model_name].keys()): - - if case.startswith('sensi2'): + if case.startswith("sensi2"): continue amici.readModelDataFromHDF5( - options_file, model.get(), - f'/{model_name}/{case}/options' + options_file, model.get(), f"/{model_name}/{case}/options" ) amici.readSolverSettingsFromHDF5( - options_file, solver.get(), - f'/{model_name}/{case}/options' + options_file, solver.get(), f"/{model_name}/{case}/options" ) edata = None - if 'data' in expected_results[model.getName()][case].keys(): + if "data" in expected_results[model.getName()][case].keys(): edata = amici.readSimulationExpData( - str(expected_results_file), - f'/{model_name}/{case}/data', model.get() + str(expected_results_file), f"/{model_name}/{case}/data", model.get() ) rdata = amici.runAmiciSimulation(model, solver, edata) verify_simulation_opts = dict() - if model_name.startswith('model_neuron'): - verify_simulation_opts['atol'] = 1e-5 - verify_simulation_opts['rtol'] = 1e-2 + if model_name.startswith("model_neuron"): + verify_simulation_opts["atol"] = 1e-5 + verify_simulation_opts["rtol"] = 1e-2 verify_simulation_results( - rdata, expected_results[model.getName()][case]['results'], - **verify_simulation_opts + rdata, + expected_results[model.getName()][case]["results"], + **verify_simulation_opts, ) diff --git a/python/tests/test_ode_export.py b/python/tests/test_ode_export.py index 27e1a31ff9..b30d451a4a 100644 --- a/python/tests/test_ode_export.py +++ b/python/tests/test_ode_export.py @@ -4,23 +4,29 @@ from amici.cxxcodeprinter import AmiciCxxCodePrinter from amici.testing import skip_on_valgrind + @skip_on_valgrind def test_csc_matrix(): """Test sparse CSC matrix creation""" printer = AmiciCxxCodePrinter() matrix = sp.Matrix([[1, 0], [2, 3]]) - symbol_col_ptrs, symbol_row_vals, sparse_list, symbol_list, sparse_matrix \ - = printer.csc_matrix( + ( + symbol_col_ptrs, + symbol_row_vals, + sparse_list, + symbol_list, + sparse_matrix, + ) = printer.csc_matrix( matrix, - rownames=[sp.Symbol('a1'), sp.Symbol('a2')], - colnames=[sp.Symbol('b1'), sp.Symbol('b2')] + rownames=[sp.Symbol("a1"), sp.Symbol("a2")], + colnames=[sp.Symbol("b1"), sp.Symbol("b2")], ) assert symbol_col_ptrs == [0, 2, 3] assert symbol_row_vals == [0, 1, 1] assert sparse_list == sp.Matrix([[1], [2], [3]]) - assert symbol_list == ['da1_db1', 'da2_db1', 'da2_db2'] - assert str(sparse_matrix) == 'Matrix([[da1_db1, 0], [da2_db1, da2_db2]])' + assert symbol_list == ["da1_db1", "da2_db1", "da2_db2"] + assert str(sparse_matrix) == "Matrix([[da1_db1, 0], [da2_db1, da2_db2]])" @skip_on_valgrind @@ -28,14 +34,19 @@ def test_csc_matrix_empty(): """Test sparse CSC matrix creation for empty matrix""" printer = AmiciCxxCodePrinter() matrix = sp.Matrix() - symbol_col_ptrs, symbol_row_vals, sparse_list, symbol_list, sparse_matrix \ - = printer.csc_matrix(matrix, rownames=[], colnames=[]) + ( + symbol_col_ptrs, + symbol_row_vals, + sparse_list, + symbol_list, + sparse_matrix, + ) = printer.csc_matrix(matrix, rownames=[], colnames=[]) assert symbol_col_ptrs == [] assert symbol_row_vals == [] assert sparse_list == sp.Matrix(0, 0, []) assert symbol_list == [] - assert str(sparse_matrix) == 'Matrix(0, 0, [])' + assert str(sparse_matrix) == "Matrix(0, 0, [])" @skip_on_valgrind @@ -43,30 +54,43 @@ def test_csc_matrix_vector(): """Test sparse CSC matrix creation from matrix slice""" printer = AmiciCxxCodePrinter() matrix = sp.Matrix([[1, 0], [2, 3]]) - symbol_col_ptrs, symbol_row_vals, sparse_list, symbol_list, sparse_matrix \ - = printer.csc_matrix( - matrix[:, 0], colnames=[sp.Symbol('b')], - rownames=[sp.Symbol('a1'), sp.Symbol('a2')] - ) + ( + symbol_col_ptrs, + symbol_row_vals, + sparse_list, + symbol_list, + sparse_matrix, + ) = printer.csc_matrix( + matrix[:, 0], + colnames=[sp.Symbol("b")], + rownames=[sp.Symbol("a1"), sp.Symbol("a2")], + ) assert symbol_col_ptrs == [0, 2] assert symbol_row_vals == [0, 1] assert sparse_list == sp.Matrix([[1], [2]]) - assert symbol_list == ['da1_db', 'da2_db'] - assert str(sparse_matrix) == 'Matrix([[da1_db], [da2_db]])' + assert symbol_list == ["da1_db", "da2_db"] + assert str(sparse_matrix) == "Matrix([[da1_db], [da2_db]])" # Test continuation of numbering of symbols - symbol_col_ptrs, symbol_row_vals, sparse_list, symbol_list, sparse_matrix \ - = printer.csc_matrix( - matrix[:, 1], colnames=[sp.Symbol('b')], - rownames=[sp.Symbol('a1'), sp.Symbol('a2')], identifier=1 - ) + ( + symbol_col_ptrs, + symbol_row_vals, + sparse_list, + symbol_list, + sparse_matrix, + ) = printer.csc_matrix( + matrix[:, 1], + colnames=[sp.Symbol("b")], + rownames=[sp.Symbol("a1"), sp.Symbol("a2")], + identifier=1, + ) assert symbol_col_ptrs == [0, 1] assert symbol_row_vals == [1] assert sparse_list == sp.Matrix([[3]]) - assert symbol_list == ['da2_db_1'] - assert str(sparse_matrix) == 'Matrix([[0], [da2_db_1]])' + assert symbol_list == ["da2_db_1"] + assert str(sparse_matrix) == "Matrix([[0], [da2_db_1]])" def test_match_deriv(): diff --git a/python/tests/test_pandas.py b/python/tests/test_pandas.py index 80ee457354..b913b3e842 100644 --- a/python/tests/test_pandas.py +++ b/python/tests/test_pandas.py @@ -8,18 +8,18 @@ # test parameters for test_pandas_import_export -combos = itertools.product( - [(10, 5), (5, 10), ()], - repeat=3 -) -cases = [{ - 'fixedParameters': combo[0], - 'fixedParametersPreequilibration': combo[1], - 'fixedParametersPresimulation': combo[2], -} for combo in combos] - - -@pytest.mark.parametrize('case', cases) +combos = itertools.product([(10, 5), (5, 10), ()], repeat=3) +cases = [ + { + "fixedParameters": combo[0], + "fixedParametersPreequilibration": combo[1], + "fixedParametersPresimulation": combo[2], + } + for combo in combos +] + + +@pytest.mark.parametrize("case", cases) def test_pandas_import_export(sbml_example_presimulation_module, case): """TestCase class for testing csv import using pandas""" @@ -39,19 +39,19 @@ def test_pandas_import_export(sbml_example_presimulation_module, case): df_edata = amici.getDataObservablesAsDataFrame(model, edata) edata_reconstructed = amici.getEdataFromDataFrame(model, df_edata) - for fp in ['fixedParameters', 'fixedParametersPreequilibration', - 'fixedParametersPresimulation']: - - if fp != 'fixedParameters' or case[fp] != (): + for fp in [ + "fixedParameters", + "fixedParametersPreequilibration", + "fixedParametersPresimulation", + ]: + if fp != "fixedParameters" or case[fp] != (): assert getattr(edata[0], fp) == getattr(edata_reconstructed[0], fp) assert case[fp] == getattr(edata_reconstructed[0], fp) else: - assert model.getFixedParameters() \ - == getattr(edata_reconstructed[0], fp) + assert model.getFixedParameters() == getattr(edata_reconstructed[0], fp) - assert model.getFixedParameters() == \ - getattr(edata_reconstructed[0], fp) + assert model.getFixedParameters() == getattr(edata_reconstructed[0], fp) assert getattr(edata[0], fp) == case[fp] diff --git a/python/tests/test_parameter_mapping.py b/python/tests/test_parameter_mapping.py index e2663f4409..ef95b6233b 100644 --- a/python/tests/test_parameter_mapping.py +++ b/python/tests/test_parameter_mapping.py @@ -3,8 +3,7 @@ import pytest -from amici.parameter_mapping import (ParameterMapping, - ParameterMappingForCondition) +from amici.parameter_mapping import ParameterMapping, ParameterMappingForCondition from amici.testing import skip_on_valgrind @@ -14,27 +13,29 @@ def test_parameter_mapping_for_condition_default_args(): par_map_for_condition = ParameterMappingForCondition() for attr in [ - 'map_sim_var', 'scale_map_sim_var', 'map_preeq_fix', - 'scale_map_preeq_fix', 'map_sim_fix', 'scale_map_sim_fix']: + "map_sim_var", + "scale_map_sim_var", + "map_preeq_fix", + "scale_map_preeq_fix", + "map_sim_fix", + "scale_map_sim_fix", + ]: assert not getattr(par_map_for_condition, attr) - map_sim_var = {'sim_par0': 8, 'sim_par1': 'opt_par0'} - map_preeq_fix = {'sim_par2': 'opt_par1'} - map_sim_fix = {'sim_par2': 'opt_par2'} + map_sim_var = {"sim_par0": 8, "sim_par1": "opt_par0"} + map_preeq_fix = {"sim_par2": "opt_par1"} + map_sim_fix = {"sim_par2": "opt_par2"} par_map_for_condition = ParameterMappingForCondition( - map_sim_var=map_sim_var, map_preeq_fix=map_preeq_fix, - map_sim_fix=map_sim_fix) + map_sim_var=map_sim_var, map_preeq_fix=map_preeq_fix, map_sim_fix=map_sim_fix + ) - expected_scale_map_sim_var = {'sim_par0': 'lin', 'sim_par1': 'lin'} - expected_scale_map_preeq_fix = {'sim_par2': 'lin'} - expected_scale_map_sim_fix = {'sim_par2': 'lin'} + expected_scale_map_sim_var = {"sim_par0": "lin", "sim_par1": "lin"} + expected_scale_map_preeq_fix = {"sim_par2": "lin"} + expected_scale_map_sim_fix = {"sim_par2": "lin"} - assert par_map_for_condition.scale_map_sim_var == \ - expected_scale_map_sim_var - assert par_map_for_condition.scale_map_preeq_fix == \ - expected_scale_map_preeq_fix - assert par_map_for_condition.scale_map_sim_fix == \ - expected_scale_map_sim_fix + assert par_map_for_condition.scale_map_sim_var == expected_scale_map_sim_var + assert par_map_for_condition.scale_map_preeq_fix == expected_scale_map_preeq_fix + assert par_map_for_condition.scale_map_sim_fix == expected_scale_map_sim_fix @skip_on_valgrind @@ -44,12 +45,12 @@ def test_parameter_mapping(): parameter_mapping = ParameterMapping() assert len(parameter_mapping) == 0 - map_sim_var = {'sim_par0': 8, 'sim_par1': 'opt_par0'} - map_preeq_fix = {'sim_par2': 'opt_par1'} - map_sim_fix = {'sim_par2': 'opt_par2'} + map_sim_var = {"sim_par0": 8, "sim_par1": "opt_par0"} + map_preeq_fix = {"sim_par2": "opt_par1"} + map_sim_fix = {"sim_par2": "opt_par2"} par_map_for_condition = ParameterMappingForCondition( - map_sim_var=map_sim_var, map_preeq_fix=map_preeq_fix, - map_sim_fix=map_sim_fix) + map_sim_var=map_sim_var, map_preeq_fix=map_preeq_fix, map_sim_fix=map_sim_fix + ) parameter_mapping.append(par_map_for_condition) diff --git a/python/tests/test_petab_import.py b/python/tests/test_petab_import.py index eeaf21a3e9..ecd22678ee 100644 --- a/python/tests/test_petab_import.py +++ b/python/tests/test_petab_import.py @@ -19,7 +19,7 @@ def simple_sbml_model(): model.setId("simple_sbml_model") model.setTimeUnits("second") model.setExtentUnits("mole") - model.setSubstanceUnits('mole') + model.setSubstanceUnits("mole") for par_idx in range(1, 6): p = model.createParameter() @@ -30,7 +30,7 @@ def simple_sbml_model(): c.setId("c1") s = model.createSpecies() - s.setId('x1') + s.setId("x1") s.setConstant(True) s.setInitialConcentration(1.0) s.setCompartment(c.getId()) @@ -49,55 +49,63 @@ def test_get_fixed_parameters(simple_sbml_model): p5: fixed (implicitly, because not listed as estimated) """ from petab.models.sbml_model import SbmlModel + sbml_doc, sbml_model = simple_sbml_model condition_df = petab.get_condition_df( - pd.DataFrame({ - petab.CONDITION_ID: ["condition0"], - "p1": [1.0], - "p2": ["p1"], - }) + pd.DataFrame( + { + petab.CONDITION_ID: ["condition0"], + "p1": [1.0], + "p2": ["p1"], + } + ) ) parameter_df = petab.get_parameter_df( - pd.DataFrame({ - petab.PARAMETER_ID: ["p3", "p4"], - petab.ESTIMATE: [0, 1] - }) + pd.DataFrame({petab.PARAMETER_ID: ["p3", "p4"], petab.ESTIMATE: [0, 1]}) ) print(condition_df) print(parameter_df) - petab_problem = petab.Problem(model=SbmlModel(sbml_model), - parameter_df=parameter_df, - condition_df=condition_df) - assert set(amici_petab_import.get_fixed_parameters(petab_problem)) \ - == {"p1", "p3", "p5"} - - assert set(amici_petab_import.get_fixed_parameters( - petab_problem, - non_estimated_parameters_as_constants=False)) \ - == {"p1", "p5"} + petab_problem = petab.Problem( + model=SbmlModel(sbml_model), + parameter_df=parameter_df, + condition_df=condition_df, + ) + assert set(amici_petab_import.get_fixed_parameters(petab_problem)) == { + "p1", + "p3", + "p5", + } + + assert set( + amici_petab_import.get_fixed_parameters( + petab_problem, non_estimated_parameters_as_constants=False + ) + ) == {"p1", "p5"} @skip_on_valgrind def test_default_output_parameters(simple_sbml_model): from petab.models.sbml_model import SbmlModel + sbml_doc, sbml_model = simple_sbml_model condition_df = petab.get_condition_df( - pd.DataFrame({ - petab.CONDITION_ID: ["condition0"], - }) + pd.DataFrame( + { + petab.CONDITION_ID: ["condition0"], + } + ) ) parameter_df = petab.get_parameter_df( - pd.DataFrame({ - petab.PARAMETER_ID: [], - petab.ESTIMATE: [] - }) + pd.DataFrame({petab.PARAMETER_ID: [], petab.ESTIMATE: []}) ) observable_df = petab.get_observable_df( - pd.DataFrame({ - petab.OBSERVABLE_ID: ["obs1"], - petab.OBSERVABLE_FORMULA: ["observableParameter1_obs1"], - petab.NOISE_FORMULA: [1], - }) + pd.DataFrame( + { + petab.OBSERVABLE_ID: ["obs1"], + petab.OBSERVABLE_FORMULA: ["observableParameter1_obs1"], + petab.NOISE_FORMULA: [1], + } + ) ) petab_problem = petab.Problem( model=SbmlModel(sbml_model), @@ -109,17 +117,19 @@ def test_default_output_parameters(simple_sbml_model): with TemporaryDirectoryWinSafe() as outdir: sbml_importer = amici_petab_import.import_model( petab_problem=petab_problem, - output_parameter_defaults={'observableParameter1_obs1': 1.0}, + output_parameter_defaults={"observableParameter1_obs1": 1.0}, compile=False, model_output_dir=outdir, ) - assert 1.0 == sbml_importer.sbml\ - .getParameter("observableParameter1_obs1").getValue() + assert ( + 1.0 + == sbml_importer.sbml.getParameter("observableParameter1_obs1").getValue() + ) with pytest.raises(ValueError): amici_petab_import.import_model( petab_problem=petab_problem, - output_parameter_defaults={'nonExistentParameter': 1.0}, + output_parameter_defaults={"nonExistentParameter": 1.0}, compile=False, model_output_dir=outdir, ) diff --git a/python/tests/test_petab_objective.py b/python/tests/test_petab_objective.py index 5dfe7db890..43e78295ec 100755 --- a/python/tests/test_petab_objective.py +++ b/python/tests/test_petab_objective.py @@ -20,13 +20,15 @@ @pytest.fixture def lotka_volterra() -> petab.Problem: - return petab.Problem.from_yaml(str( - Path(__file__).parent - / 'petab_test_problems' - / 'lotka_volterra' - / 'petab' - / 'problem.yaml' - )) + return petab.Problem.from_yaml( + str( + Path(__file__).parent + / "petab_test_problems" + / "lotka_volterra" + / "petab" + / "problem.yaml" + ) + ) def test_simulate_petab_sensitivities(lotka_volterra): @@ -37,18 +39,19 @@ def test_simulate_petab_sensitivities(lotka_volterra): amici_solver.setSensitivityOrder(amici.SensitivityOrder_first) amici_solver.setMaxSteps(int(1e5)) - problem_parameters = dict(zip( - petab_problem.x_ids, - petab_problem.x_nominal, - )) + problem_parameters = dict( + zip( + petab_problem.x_ids, + petab_problem.x_nominal, + ) + ) results = {} for scaled_parameters in [True, False]: for scaled_gradients in [True, False]: _problem_parameters = problem_parameters.copy() if scaled_parameters: - _problem_parameters = \ - petab_problem.scale_parameters(problem_parameters) + _problem_parameters = petab_problem.scale_parameters(problem_parameters) results[(scaled_parameters, scaled_gradients)] = pd.Series( amici.petab_objective.simulate_petab( petab_problem=petab_problem, @@ -62,14 +65,18 @@ def test_simulate_petab_sensitivities(lotka_volterra): # Computed previously, is the same as a central difference gradient # check, to >4 s.f. - expected_results_scaled = pd.Series({ - "alpha": -2.112626, - "gamma": 21.388535, - }) - expected_results_unscaled = pd.Series({ - "alpha": -0.458800, - "gamma": 3.096308, - }) + expected_results_scaled = pd.Series( + { + "alpha": -2.112626, + "gamma": 21.388535, + } + ) + expected_results_unscaled = pd.Series( + { + "alpha": -0.458800, + "gamma": 3.096308, + } + ) assert_equal = partial(pd.testing.assert_series_equal, rtol=1e-3) diff --git a/python/tests/test_petab_simulate.py b/python/tests/test_petab_simulate.py index 1c8ebb59c3..f777f38951 100644 --- a/python/tests/test_petab_simulate.py +++ b/python/tests/test_petab_simulate.py @@ -12,7 +12,7 @@ @pytest.fixture def petab_problem() -> petab.Problem: """Create a PEtab problem for use in tests.""" - test_case = '0001' + test_case = "0001" test_case_dir = petabtests.get_case_dir( id_=test_case, format_="sbml", version="v1.0.0" ) @@ -48,18 +48,17 @@ def test_subset_call(petab_problem): `model_output_dir`, import is skipped if `amici_model` is specified), and :py:func:`amici.petab_objective.simulate_petab` (`amici_model`, `solver`). """ - model_name = 'model_name_dummy' + model_name = "model_name_dummy" model_output_dir = tempfile.mkdtemp() simulator0 = PetabSimulator(petab_problem) - assert not (Path(model_output_dir)/model_name).is_dir() - simulator0.simulate(model_name=model_name, - model_output_dir=model_output_dir) + assert not (Path(model_output_dir) / model_name).is_dir() + simulator0.simulate(model_name=model_name, model_output_dir=model_output_dir) # Model name is handled correctly assert simulator0.amici_model.getName() == model_name # Check model output directory is created, by # :py:func:`amici.petab_import.import_petab_problem` - assert (Path(model_output_dir)/model_name).is_dir() + assert (Path(model_output_dir) / model_name).is_dir() simulator = PetabSimulator(petab_problem) simulator.simulate(amici_model=simulator0.amici_model) diff --git a/python/tests/test_preequilibration.py b/python/tests/test_preequilibration.py index 1f16d1d240..5fe61b350f 100644 --- a/python/tests/test_preequilibration.py +++ b/python/tests/test_preequilibration.py @@ -12,7 +12,6 @@ @pytest.fixture def preeq_fixture(pysb_example_presimulation_module): - model = pysb_example_presimulation_module.getModel() model.setReinitializeFixedParameterInitialStates(True) @@ -30,56 +29,68 @@ def preeq_fixture(pysb_example_presimulation_module): edata_preeq = amici.ExpData(edata) edata_preeq.t_presim = 0 edata_preeq.setTimepoints([np.infty]) - edata_preeq.fixedParameters = \ - edata.fixedParametersPreequilibration + edata_preeq.fixedParameters = edata.fixedParametersPreequilibration edata_preeq.fixedParametersPresimulation = () edata_preeq.fixedParametersPreequilibration = () edata_presim = amici.ExpData(edata) edata_presim.t_presim = 0 edata_presim.setTimepoints([edata.t_presim]) - edata_presim.fixedParameters = \ - edata.fixedParametersPresimulation + edata_presim.fixedParameters = edata.fixedParametersPresimulation edata_presim.fixedParametersPresimulation = () edata_presim.fixedParametersPreequilibration = () edata_sim = amici.ExpData(edata) edata_sim.t_presim = 0 edata_sim.setTimepoints(edata.getTimepoints()) - edata_sim.fixedParameters = \ - edata.fixedParameters + edata_sim.fixedParameters = edata.fixedParameters edata_sim.fixedParametersPresimulation = () edata_sim.fixedParametersPreequilibration = () pscales = [ - amici.ParameterScaling.log10, amici.ParameterScaling.ln, + amici.ParameterScaling.log10, + amici.ParameterScaling.ln, amici.ParameterScaling.none, - amici.parameterScalingFromIntVector([ - amici.ParameterScaling.log10, amici.ParameterScaling.ln, - amici.ParameterScaling.none, amici.ParameterScaling.log10, - amici.ParameterScaling.ln, amici.ParameterScaling.none - ]) + amici.parameterScalingFromIntVector( + [ + amici.ParameterScaling.log10, + amici.ParameterScaling.ln, + amici.ParameterScaling.none, + amici.ParameterScaling.log10, + amici.ParameterScaling.ln, + amici.ParameterScaling.none, + ] + ), ] plists = [ - [3, 1, 2, 4], [0, 1, 2, 3, 4, 5], [5, 3, 2, 0, 4, 1], - [1, 2, 3, 4, 5], [1, 1, 1], + [3, 1, 2, 4], + [0, 1, 2, 3, 4, 5], + [5, 3, 2, 0, 4, 1], + [1, 2, 3, 4, 5], + [1, 1, 1], ] - return (model, solver, edata, edata_preeq, - edata_presim, edata_sim, pscales, plists) + return (model, solver, edata, edata_preeq, edata_presim, edata_sim, pscales, plists) def test_manual_preequilibration(preeq_fixture): """Manual preequilibration""" - model, solver, edata, edata_preeq, \ - edata_presim, edata_sim, pscales, plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture settings = itertools.product(pscales, plists) for pscale, plist in settings: - model.setInitialStates([]) model.setInitialStateSensitivities([]) model.setParameterList(plist) @@ -94,10 +105,10 @@ def test_manual_preequilibration(preeq_fixture): assert rdata_preeq.status == amici.AMICI_SUCCESS # manual reinitialization + presimulation - x0 = rdata_preeq['x'][0, :] + x0 = rdata_preeq["x"][0, :] x0[1] = edata_presim.fixedParameters[0] x0[2] = edata_presim.fixedParameters[1] - sx0 = rdata_preeq['sx'][0, :, :] + sx0 = rdata_preeq["sx"][0, :, :] sx0[:, 1] = 0 sx0[:, 2] = 0 model.setInitialStates(x0) @@ -106,10 +117,10 @@ def test_manual_preequilibration(preeq_fixture): assert rdata_presim.status == amici.AMICI_SUCCESS # manual reinitialization + simulation - x0 = rdata_presim['x'][0, :] + x0 = rdata_presim["x"][0, :] x0[1] = edata_sim.fixedParameters[0] x0[2] = edata_sim.fixedParameters[1] - sx0 = rdata_presim['sx'][0, :, :] + sx0 = rdata_presim["sx"][0, :, :] sx0[:, 1] = 0 sx0[:, 2] = 0 model.setInitialStates(x0) @@ -117,20 +128,29 @@ def test_manual_preequilibration(preeq_fixture): rdata_sim = amici.runAmiciSimulation(model, solver, edata_sim) assert rdata_sim.status == amici.AMICI_SUCCESS - for variable in ['x', 'sx']: + for variable in ["x", "sx"]: assert_allclose( rdata_auto[variable], rdata_sim[variable], - atol=1e-6, rtol=1e-6, - err_msg=str(dict(pscale=pscale, plist=plist, variable=variable)) + atol=1e-6, + rtol=1e-6, + err_msg=str(dict(pscale=pscale, plist=plist, variable=variable)), ) def test_parameter_reordering(preeq_fixture): """Test parameter reordering""" - model, solver, edata, edata_preeq, \ - edata_presim, edata_sim, pscales, plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture rdata_ordered = amici.runAmiciSimulation(model, solver, edata) @@ -140,17 +160,26 @@ def test_parameter_reordering(preeq_fixture): for ip, p_index in enumerate(plist): assert np.isclose( - rdata_ordered['sx'][:, p_index, :], - rdata_reordered['sx'][:, ip, :], - 1e-6, 1e-6 + rdata_ordered["sx"][:, p_index, :], + rdata_reordered["sx"][:, ip, :], + 1e-6, + 1e-6, ).all(), plist def test_data_replicates(preeq_fixture): """Test data replicates""" - model, solver, edata, edata_preeq, \ - edata_presim, edata_sim, pscales, plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture sensi_meth = amici.SensitivityMethod.forward solver.setSensitivityMethod(sensi_meth) @@ -175,20 +204,29 @@ def test_data_replicates(preeq_fixture): rdata_double = amici.runAmiciSimulation(model, solver, edata) - for variable in ['llh', 'sllh']: + for variable in ["llh", "sllh"]: assert_allclose( 2 * rdata_single[variable], rdata_double[variable], - atol=1e-6, rtol=1e-6, - err_msg=str(dict(variable=variable, sensi_meth=sensi_meth)) + atol=1e-6, + rtol=1e-6, + err_msg=str(dict(variable=variable, sensi_meth=sensi_meth)), ) def test_parameter_in_expdata(preeq_fixture): """Test parameter in ExpData""" - model, solver, edata, edata_preeq, edata_presim, \ - edata_sim, pscales, plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture rdata = amici.runAmiciSimulation(model, solver, edata) @@ -201,57 +239,61 @@ def test_parameter_in_expdata(preeq_fixture): edata.sx0 = model.getInitialStateSensitivities() # perturb model initial states - model.setInitialStates(rdata['x_ss'] * 4) - model.setInitialStateSensitivities(rdata['sx_ss'].flatten() / 2) + model.setInitialStates(rdata["x_ss"] * 4) + model.setInitialStateSensitivities(rdata["sx_ss"].flatten() / 2) # set ExpData plist edata.plist = model.getParameterList() # perturb model parameter list - model.setParameterList([ - i for i in reversed(model.getParameterList()) - ]) + model.setParameterList([i for i in reversed(model.getParameterList())]) # set ExpData parameters edata.parameters = model.getParameters() # perturb model parameters - model.setParameters(tuple( - p * 2 for p in model.getParameters() - )) + model.setParameters(tuple(p * 2 for p in model.getParameters())) # set ExpData pscale edata.pscale = model.getParameterScale() # perturb model pscale, needs to be done after getting parameters, # otherwise we will mess up parameter value - model.setParameterScale(amici.parameterScalingFromIntVector([ - amici.ParameterScaling.log10 - if scaling == amici.ParameterScaling.none - else amici.ParameterScaling.none - for scaling in model.getParameterScale() - ])) - - rdata_edata = amici.runAmiciSimulation( - model, solver, edata + model.setParameterScale( + amici.parameterScalingFromIntVector( + [ + amici.ParameterScaling.log10 + if scaling == amici.ParameterScaling.none + else amici.ParameterScaling.none + for scaling in model.getParameterScale() + ] + ) ) - for variable in ['x', 'sx']: + + rdata_edata = amici.runAmiciSimulation(model, solver, edata) + for variable in ["x", "sx"]: assert np.isclose( - rdata[variable][0, :], - rdata_edata[variable][0, :], - 1e-6, 1e-6 + rdata[variable][0, :], rdata_edata[variable][0, :], 1e-6, 1e-6 ).all(), variable def test_raise_presimulation_with_adjoints(preeq_fixture): """Test simulation failures with adjoin+presimulation""" - model, solver, edata, edata_preeq, \ - edata_presim, edata_sim, pscales, plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture # preequilibration and presimulation with adjoints: # this needs to fail unless we remove presimulation solver.setSensitivityMethod(amici.SensitivityMethod.adjoint) rdata = amici.runAmiciSimulation(model, solver, edata) - assert rdata['status'] == amici.AMICI_ERROR + assert rdata["status"] == amici.AMICI_ERROR # add postequilibration y = edata.getObservedData() @@ -267,15 +309,23 @@ def test_raise_presimulation_with_adjoints(preeq_fixture): # no presim any more, this should work rdata = amici.runAmiciSimulation(model, solver, edata) - assert rdata['status'] == amici.AMICI_SUCCESS + assert rdata["status"] == amici.AMICI_SUCCESS def test_equilibration_methods_with_adjoints(preeq_fixture): """Test different combinations of equilibration and simulation sensitivity methods""" - model, solver, edata, edata_preeq, \ - edata_presim, edata_sim, pscales, plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture # we don't want presim edata.t_presim = 0.0 @@ -290,11 +340,12 @@ def test_equilibration_methods_with_adjoints(preeq_fixture): edata.setObservedDataStdDev(np.hstack([stdy, stdy[0]])) rdatas = {} - equil_meths = [amici.SteadyStateSensitivityMode.newtonOnly, - amici.SteadyStateSensitivityMode.integrationOnly, - amici.SteadyStateSensitivityMode.integrateIfNewtonFails] - sensi_meths = [amici.SensitivityMethod.forward, - amici.SensitivityMethod.adjoint] + equil_meths = [ + amici.SteadyStateSensitivityMode.newtonOnly, + amici.SteadyStateSensitivityMode.integrationOnly, + amici.SteadyStateSensitivityMode.integrateIfNewtonFails, + ] + sensi_meths = [amici.SensitivityMethod.forward, amici.SensitivityMethod.adjoint] settings = itertools.product(equil_meths, sensi_meths) for setting in settings: @@ -308,23 +359,29 @@ def test_equilibration_methods_with_adjoints(preeq_fixture): rdatas[setting] = amici.runAmiciSimulation(model, solver, edata) # assert successful simulation - assert rdatas[setting]['status'] == amici.AMICI_SUCCESS + assert rdatas[setting]["status"] == amici.AMICI_SUCCESS for setting1, setting2 in itertools.product(settings, settings): # assert correctness of result - for variable in ['llh', 'sllh']: + for variable in ["llh", "sllh"]: assert np.isclose( - rdatas[setting1][variable], - rdatas[setting2][variable], - 1e-6, 1e-6 + rdatas[setting1][variable], rdatas[setting2][variable], 1e-6, 1e-6 ).all(), variable def test_newton_solver_equilibration(preeq_fixture): """Test data replicates""" - model, solver, edata, edata_preeq, \ - edata_presim, edata_sim, pscales, plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture # we don't want presim edata.t_presim = 0.0 @@ -339,8 +396,10 @@ def test_newton_solver_equilibration(preeq_fixture): edata.setObservedDataStdDev(np.hstack([stdy, stdy[0]])) rdatas = {} - settings = [amici.SteadyStateSensitivityMode.integrationOnly, - amici.SteadyStateSensitivityMode.newtonOnly] + settings = [ + amici.SteadyStateSensitivityMode.integrationOnly, + amici.SteadyStateSensitivityMode.newtonOnly, + ] solver.setNewtonStepSteadyStateCheck(True) solver.setRelativeToleranceSteadyState(1e-12) @@ -357,22 +416,28 @@ def test_newton_solver_equilibration(preeq_fixture): rdatas[equil_meth] = amici.runAmiciSimulation(model, solver, edata) # assert successful simulation - assert rdatas[equil_meth]['status'] == amici.AMICI_SUCCESS + assert rdatas[equil_meth]["status"] == amici.AMICI_SUCCESS # assert correct results - for variable in ['llh', 'sllh', 'sx0', 'sx_ss', 'x_ss']: + for variable in ["llh", "sllh", "sx0", "sx_ss", "x_ss"]: assert np.isclose( - rdatas[settings[0]][variable], - rdatas[settings[1]][variable], - 1e-5, 1e-5 + rdatas[settings[0]][variable], rdatas[settings[1]][variable], 1e-5, 1e-5 ).all(), variable def test_newton_steadystate_check(preeq_fixture): """Test data replicates""" - model, solver, edata, edata_preeq, edata_presim, edata_sim, pscales, \ - plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture # we don't want presim edata.t_presim = 0.0 @@ -399,38 +464,49 @@ def test_newton_steadystate_check(preeq_fixture): rdatas[newton_check] = amici.runAmiciSimulation(model, solver, edata) # assert successful simulation - assert rdatas[newton_check]['status'] == amici.AMICI_SUCCESS + assert rdatas[newton_check]["status"] == amici.AMICI_SUCCESS # assert correct results - for variable in ['llh', 'sllh', 'sx0', 'sx_ss', 'x_ss']: + for variable in ["llh", "sllh", "sx0", "sx_ss", "x_ss"]: assert np.isclose( - rdatas[True][variable], - rdatas[False][variable], - 1e-6, 1e-6 + rdatas[True][variable], rdatas[False][variable], 1e-6, 1e-6 ).all(), variable def test_simulation_errors(preeq_fixture): - model, solver, edata, edata_preeq, edata_presim, edata_sim, pscales, \ - plists = preeq_fixture + ( + model, + solver, + edata, + edata_preeq, + edata_presim, + edata_sim, + pscales, + plists, + ) = preeq_fixture solver.setSensitivityOrder(amici.SensitivityOrder.first) solver.setSensitivityMethodPreequilibration(amici.SensitivityMethod.forward) - model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.integrationOnly) + model.setSteadyStateSensitivityMode( + amici.SteadyStateSensitivityMode.integrationOnly + ) solver.setMaxSteps(1) # exceeded maxsteps # preeq & posteq for e in [edata, edata_preeq]: rdata = amici.runAmiciSimulation(model, solver, e) - assert rdata['status'] != amici.AMICI_SUCCESS + assert rdata["status"] != amici.AMICI_SUCCESS assert rdata._swigptr.messages[0].severity == amici.LogSeverity_debug - assert rdata._swigptr.messages[0].identifier == 'EQUILIBRATION_FAILURE' - assert 'exceeded maximum number of integration steps' in rdata._swigptr.messages[0].message + assert rdata._swigptr.messages[0].identifier == "EQUILIBRATION_FAILURE" + assert ( + "exceeded maximum number of integration steps" + in rdata._swigptr.messages[0].message + ) assert rdata._swigptr.messages[1].severity == amici.LogSeverity_error - assert rdata._swigptr.messages[1].identifier == 'OTHER' + assert rdata._swigptr.messages[1].identifier == "OTHER" assert rdata._swigptr.messages[2].severity == amici.LogSeverity_debug - assert rdata._swigptr.messages[2].identifier == 'BACKTRACE' + assert rdata._swigptr.messages[2].identifier == "BACKTRACE" # too long simulations solver.setMaxSteps(int(1e4)) @@ -439,18 +515,13 @@ def test_simulation_errors(preeq_fixture): # preeq & posteq for e in [edata_preeq, edata]: rdata = amici.runAmiciSimulation(model, solver, e) - assert rdata['status'] != amici.AMICI_SUCCESS + assert rdata["status"] != amici.AMICI_SUCCESS assert rdata._swigptr.messages[0].severity == amici.LogSeverity_debug - assert rdata._swigptr.messages[0].identifier == 'CVODES:CVode:RHSFUNC_FAIL' + assert rdata._swigptr.messages[0].identifier == "CVODES:CVode:RHSFUNC_FAIL" assert rdata._swigptr.messages[1].severity == amici.LogSeverity_debug - assert rdata._swigptr.messages[1].identifier == 'EQUILIBRATION_FAILURE' - assert 'exceedingly long simulation time' in rdata._swigptr.messages[1].message + assert rdata._swigptr.messages[1].identifier == "EQUILIBRATION_FAILURE" + assert "exceedingly long simulation time" in rdata._swigptr.messages[1].message assert rdata._swigptr.messages[2].severity == amici.LogSeverity_error - assert rdata._swigptr.messages[2].identifier == 'OTHER' + assert rdata._swigptr.messages[2].identifier == "OTHER" assert rdata._swigptr.messages[3].severity == amici.LogSeverity_debug - assert rdata._swigptr.messages[3].identifier == 'BACKTRACE' - - - - - + assert rdata._swigptr.messages[3].identifier == "BACKTRACE" diff --git a/python/tests/test_pregenerated_models.py b/python/tests/test_pregenerated_models.py index d63e4b924b..1b36198377 100755 --- a/python/tests/test_pregenerated_models.py +++ b/python/tests/test_pregenerated_models.py @@ -14,19 +14,23 @@ from amici.testing import skip_on_valgrind -cpp_test_dir = Path(__file__).parents[2] / 'tests' / 'cpp' -options_file = str(cpp_test_dir / 'testOptions.h5') -expected_results_file = str(cpp_test_dir / 'expectedResults.h5') -expected_results = h5py.File(expected_results_file, 'r') +cpp_test_dir = Path(__file__).parents[2] / "tests" / "cpp" +options_file = str(cpp_test_dir / "testOptions.h5") +expected_results_file = str(cpp_test_dir / "expectedResults.h5") +expected_results = h5py.File(expected_results_file, "r") -model_cases = [(sub_test, case) - for sub_test in expected_results.keys() - for case in list(expected_results[sub_test].keys())] +model_cases = [ + (sub_test, case) + for sub_test in expected_results.keys() + for case in list(expected_results[sub_test].keys()) +] @skip_on_valgrind -@pytest.mark.skipif(os.environ.get('AMICI_SKIP_CMAKE_TESTS', '') == 'TRUE', - reason='skipping cmake based test') +@pytest.mark.skipif( + os.environ.get("AMICI_SKIP_CMAKE_TESTS", "") == "TRUE", + reason="skipping cmake based test", +) @pytest.mark.parametrize("sub_test,case", model_cases) def test_pregenerated_model(sub_test, case): """Tests models that were pregenerated using the matlab code @@ -36,77 +40,81 @@ def test_pregenerated_model(sub_test, case): the python modules for the test models. """ - if case.startswith('sensi2'): - model_name = sub_test + '_o2' + if case.startswith("sensi2"): + model_name = sub_test + "_o2" else: model_name = sub_test - model_swig_folder = str(Path(__file__).parents[2] / 'build' / 'tests' - / 'cpp' / f'external_{model_name}-prefix' / 'src' - / f'external_{model_name}-build' / 'swig') + model_swig_folder = str( + Path(__file__).parents[2] + / "build" + / "tests" + / "cpp" + / f"external_{model_name}-prefix" + / "src" + / f"external_{model_name}-build" + / "swig" + ) test_model_module = amici.import_model_module( - module_name=model_name, module_path=model_swig_folder) + module_name=model_name, module_path=model_swig_folder + ) model = test_model_module.getModel() solver = model.getSolver() amici.readModelDataFromHDF5( - options_file, model.get(), - f'/{sub_test}/{case}/options' + options_file, model.get(), f"/{sub_test}/{case}/options" ) amici.readSolverSettingsFromHDF5( - options_file, solver.get(), - f'/{sub_test}/{case}/options' + options_file, solver.get(), f"/{sub_test}/{case}/options" ) edata = None - if 'data' in expected_results[sub_test][case].keys(): + if "data" in expected_results[sub_test][case].keys(): edata = amici.readSimulationExpData( - str(expected_results_file), - f'/{sub_test}/{case}/data', model.get() + str(expected_results_file), f"/{sub_test}/{case}/data", model.get() ) - rdata = amici.runAmiciSimulation(model, solver, - edata) + rdata = amici.runAmiciSimulation(model, solver, edata) check_derivative_opts = dict() - if model_name == 'model_nested_events': - check_derivative_opts['rtol'] = 1e-2 - elif model_name == 'model_events': - check_derivative_opts['atol'] = 1e-3 - - if edata \ - and solver.getSensitivityMethod() \ - and solver.getSensitivityOrder() \ - and len(model.getParameterList()) \ - and not model_name.startswith('model_neuron') \ - and not case.endswith('byhandpreeq'): + if model_name == "model_nested_events": + check_derivative_opts["rtol"] = 1e-2 + elif model_name == "model_events": + check_derivative_opts["atol"] = 1e-3 + + if ( + edata + and solver.getSensitivityMethod() + and solver.getSensitivityOrder() + and len(model.getParameterList()) + and not model_name.startswith("model_neuron") + and not case.endswith("byhandpreeq") + ): check_derivatives(model, solver, edata, **check_derivative_opts) verify_simulation_opts = dict() - if model_name.startswith('model_neuron'): - verify_simulation_opts['atol'] = 1e-5 - verify_simulation_opts['rtol'] = 1e-2 + if model_name.startswith("model_neuron"): + verify_simulation_opts["atol"] = 1e-5 + verify_simulation_opts["rtol"] = 1e-2 - if model_name.startswith('model_robertson') and \ - case == 'sensiforwardSPBCG': - verify_simulation_opts['atol'] = 1e-3 - verify_simulation_opts['rtol'] = 1e-3 + if model_name.startswith("model_robertson") and case == "sensiforwardSPBCG": + verify_simulation_opts["atol"] = 1e-3 + verify_simulation_opts["rtol"] = 1e-3 verify_simulation_results( - rdata, expected_results[sub_test][case]['results'], - **verify_simulation_opts + rdata, expected_results[sub_test][case]["results"], **verify_simulation_opts ) - if model_name == 'model_steadystate' and \ - case == 'sensiforwarderrorint': + if model_name == "model_steadystate" and case == "sensiforwarderrorint": edata = amici.amici.ExpData(model.get()) # Test runAmiciSimulations: ensure running twice # with same ExpData yields same results - if edata and model_name != 'model_neuron_o2' and not ( - model_name == 'model_robertson' and - case == 'sensiforwardSPBCG' + if ( + edata + and model_name != "model_neuron_o2" + and not (model_name == "model_robertson" and case == "sensiforwardSPBCG") ): if isinstance(edata, amici.amici.ExpData): edatas = [edata, edata] @@ -114,16 +122,17 @@ def test_pregenerated_model(sub_test, case): edatas = [edata.get(), edata.get()] rdatas = amici.runAmiciSimulations( - model, solver, edatas, num_threads=2, - failfast=False + model, solver, edatas, num_threads=2, failfast=False ) verify_simulation_results( - rdatas[0], expected_results[sub_test][case]['results'], - **verify_simulation_opts + rdatas[0], + expected_results[sub_test][case]["results"], + **verify_simulation_opts, ) verify_simulation_results( - rdatas[1], expected_results[sub_test][case]['results'], - **verify_simulation_opts + rdatas[1], + expected_results[sub_test][case]["results"], + **verify_simulation_opts, ) # test residuals mode @@ -134,9 +143,10 @@ def test_pregenerated_model(sub_test, case): solver.setReturnDataReportingMode(amici.RDataReporting.residuals) rdata = amici.runAmiciSimulation(model, solver, edata) verify_simulation_results( - rdata, expected_results[sub_test][case]['results'], - fields=['t', 'res', 'sres', 'y', 'sy', 'sigmay', 'ssigmay'], - **verify_simulation_opts + rdata, + expected_results[sub_test][case]["results"], + fields=["t", "res", "sres", "y", "sy", "sigmay", "ssigmay"], + **verify_simulation_opts, ) with pytest.raises(RuntimeError): solver.setSensitivityMethod(amici.SensitivityMethod.adjoint) @@ -147,22 +157,30 @@ def test_pregenerated_model(sub_test, case): solver.setReturnDataReportingMode(amici.RDataReporting.likelihood) rdata = amici.runAmiciSimulation(model, solver, edata) verify_simulation_results( - rdata, expected_results[sub_test][case]['results'], - fields=['t', 'llh', 'sllh', 's2llh', 'FIM'], **verify_simulation_opts + rdata, + expected_results[sub_test][case]["results"], + fields=["t", "llh", "sllh", "s2llh", "FIM"], + **verify_simulation_opts, ) # test sigma residuals - if model_name == 'model_jakstat_adjoint' and \ - solver.getSensitivityMethod() != amici.SensitivityMethod.adjoint: + if ( + model_name == "model_jakstat_adjoint" + and solver.getSensitivityMethod() != amici.SensitivityMethod.adjoint + ): model.setAddSigmaResiduals(True) solver.setReturnDataReportingMode(amici.RDataReporting.full) rdata = amici.runAmiciSimulation(model, solver, edata) # check whether activation changes chi2 assert chi2_ref != rdata.chi2 - if edata and solver.getSensitivityMethod() and \ - solver.getSensitivityOrder() and len(model.getParameterList()): + if ( + edata + and solver.getSensitivityMethod() + and solver.getSensitivityOrder() + and len(model.getParameterList()) + ): check_derivatives(model, solver, edata, **check_derivative_opts) chi2_ref = rdata.chi2 @@ -180,11 +198,12 @@ def test_pregenerated_model(sub_test, case): assert np.isnan(rdata.chi2) with pytest.raises(RuntimeError): - model.getParameterByName('thisParameterDoesNotExist') + model.getParameterByName("thisParameterDoesNotExist") -def verify_simulation_results(rdata, expected_results, fields=None, - atol=1e-8, rtol=1e-4): +def verify_simulation_results( + rdata, expected_results, fields=None, atol=1e-8, rtol=1e-4 +): """ compares all fields of the simulation results in rdata against the expectedResults using the provided tolerances @@ -200,42 +219,48 @@ def verify_simulation_results(rdata, expected_results, fields=None, if fields is None: attrs = expected_results.attrs.keys() fields = expected_results.keys() - if 'diagnosis' in expected_results.keys(): - subfields = expected_results['diagnosis'].keys() + if "diagnosis" in expected_results.keys(): + subfields = expected_results["diagnosis"].keys() else: - attrs = [field for field in fields - if field in expected_results.attrs.keys()] - if 'diagnosis' in expected_results.keys(): - subfields = [field for field in fields - if field in expected_results['diagnosis'].keys()] - fields = [field for field in fields - if field in expected_results.keys()] - - if expected_results.attrs['status'][0] != 0: - assert rdata['status'] == expected_results.attrs['status'][0] + attrs = [field for field in fields if field in expected_results.attrs.keys()] + if "diagnosis" in expected_results.keys(): + subfields = [ + field + for field in fields + if field in expected_results["diagnosis"].keys() + ] + fields = [field for field in fields if field in expected_results.keys()] + + if expected_results.attrs["status"][0] != 0: + assert rdata["status"] == expected_results.attrs["status"][0] return for field in expected_results.keys(): - if field == 'diagnosis': - for subfield in ['J', 'xdot']: + if field == "diagnosis": + for subfield in ["J", "xdot"]: if subfield not in subfields: assert rdata[subfield] is None, field continue - _check_results(rdata, subfield, - expected_results[field][subfield][()], - atol=1e-8, rtol=1e-8) + _check_results( + rdata, + subfield, + expected_results[field][subfield][()], + atol=1e-8, + rtol=1e-8, + ) else: if field not in fields: assert rdata[field] is None, field continue - if field == 's2llh': - _check_results(rdata, field, expected_results[field][()], - atol=1e-4, rtol=1e-3) + if field == "s2llh": + _check_results( + rdata, field, expected_results[field][()], atol=1e-4, rtol=1e-3 + ) else: - _check_results(rdata, field, expected_results[field][()], - atol=atol, rtol=rtol) + _check_results( + rdata, field, expected_results[field][()], atol=atol, rtol=rtol + ) for attr in attrs: - _check_results(rdata, attr, expected_results.attrs[attr], - atol=atol, rtol=rtol) + _check_results(rdata, attr, expected_results.attrs[attr], atol=atol, rtol=rtol) diff --git a/python/tests/test_pysb.py b/python/tests/test_pysb.py index fc0de542f1..2be65b2acd 100644 --- a/python/tests/test_pysb.py +++ b/python/tests/test_pysb.py @@ -21,9 +21,11 @@ from amici.testing import skip_on_valgrind, TemporaryDirectoryWinSafe from numpy.testing import assert_allclose + @skip_on_valgrind -def test_compare_to_sbml_import(pysb_example_presimulation_module, - sbml_example_presimulation_module): +def test_compare_to_sbml_import( + pysb_example_presimulation_module, sbml_example_presimulation_module +): # -------------- PYSB ----------------- model_pysb = pysb_example_presimulation_module.getModel() @@ -39,35 +41,51 @@ def test_compare_to_sbml_import(pysb_example_presimulation_module, rdata_sbml = get_results(model_sbml, edata) # check if preequilibration fixed parameters are correctly applied: - for rdata, model, importer in zip([rdata_sbml, rdata_pysb], - [model_sbml, model_pysb], - ['sbml', 'pysb']): + for rdata, model, importer in zip( + [rdata_sbml, rdata_pysb], [model_sbml, model_pysb], ["sbml", "pysb"] + ): # check equilibrium fixed parameters assert np.isclose( [sum(rdata["x_ss"][[1, 3]]), sum(rdata["x_ss"][[2, 4]])], edata.fixedParametersPreequilibration, - atol=1e-6, rtol=1e-6 - ).all(), f'{importer} preequilibration' + atol=1e-6, + rtol=1e-6, + ).all(), f"{importer} preequilibration" # check equilibrium initial parameters assert np.isclose( sum(rdata["x_ss"][[0, 3, 4, 5]]), - model.getParameterByName('PROT_0'), - atol=1e-6, rtol=1e-6 - ), f'{importer} preequilibration' + model.getParameterByName("PROT_0"), + atol=1e-6, + rtol=1e-6, + ), f"{importer} preequilibration" # check reinitialization with fixed parameter after # presimulation assert np.isclose( [rdata["x0"][1], rdata["x0"][2]], edata.fixedParameters, - atol=1e-6, rtol=1e-6 - ).all(), f'{importer} presimulation' - - skip_attrs = ['ptr', 'preeq_t', 'numsteps', 'preeq_numsteps', - 'numrhsevals', 'numerrtestfails', 'order', 'J', 'xdot', - 'preeq_wrms', 'preeq_cpu_time', 'cpu_time', - 'cpu_timeB', 'cpu_time_total', 'w'] + atol=1e-6, + rtol=1e-6, + ).all(), f"{importer} presimulation" + + skip_attrs = [ + "ptr", + "preeq_t", + "numsteps", + "preeq_numsteps", + "numrhsevals", + "numerrtestfails", + "order", + "J", + "xdot", + "preeq_wrms", + "preeq_cpu_time", + "cpu_time", + "cpu_timeB", + "cpu_time_total", + "w", + ] for field in rdata_pysb: if field in skip_attrs: @@ -83,61 +101,79 @@ def test_compare_to_sbml_import(pysb_example_presimulation_module, assert np.isnan(rdata_sbml[field]).all(), field else: assert_allclose( - rdata_sbml[field], rdata_pysb[field], - atol=1e-6, rtol=1e-6, - err_msg=field + rdata_sbml[field], + rdata_pysb[field], + atol=1e-6, + rtol=1e-6, + err_msg=field, ) pysb_models = [ - 'tyson_oscillator', 'robertson', 'expression_observables', - 'bax_pore_sequential', 'bax_pore', 'bngwiki_egfr_simple', - 'bngwiki_enzymatic_cycle_mm', 'bngwiki_simple', 'earm_1_0', - 'earm_1_3', 'move_connected', 'michment', 'kinase_cascade', - 'hello_pysb', 'fricker_2010_apoptosis', 'explicit', - 'fixed_initial', 'localfunc' + "tyson_oscillator", + "robertson", + "expression_observables", + "bax_pore_sequential", + "bax_pore", + "bngwiki_egfr_simple", + "bngwiki_enzymatic_cycle_mm", + "bngwiki_simple", + "earm_1_0", + "earm_1_3", + "move_connected", + "michment", + "kinase_cascade", + "hello_pysb", + "fricker_2010_apoptosis", + "explicit", + "fixed_initial", + "localfunc", ] custom_models = [ - 'bngwiki_egfr_simple_deletemolecules', + "bngwiki_egfr_simple_deletemolecules", ] @skip_on_valgrind -@pytest.mark.parametrize('example', pysb_models + custom_models) +@pytest.mark.parametrize("example", pysb_models + custom_models) def test_compare_to_pysb_simulation(example): - atol = 1e-8 rtol = 1e-8 with amici.add_path(os.path.dirname(pysb.examples.__file__)): - with amici.add_path(os.path.join(os.path.dirname(__file__), '..', - 'tests', 'pysb_test_models')): + with amici.add_path( + os.path.join(os.path.dirname(__file__), "..", "tests", "pysb_test_models") + ): # load example pysb.SelfExporter.cleanup() # reset pysb pysb.SelfExporter.do_export = True module = importlib.import_module(example) pysb_model = module.model - pysb_model.name = pysb_model.name.replace('pysb.examples.', '') + pysb_model.name = pysb_model.name.replace("pysb.examples.", "") # avoid naming clash for custom pysb models - pysb_model.name += '_amici' + pysb_model.name += "_amici" # pysb part tspan = np.linspace(0, 100, 101) sim = ScipyOdeSimulator( pysb_model, tspan=tspan, - integrator_options={'rtol': rtol, 'atol': atol}, - compiler='python' + integrator_options={"rtol": rtol, "atol": atol}, + compiler="python", ) pysb_simres = sim.run() # amici part with TemporaryDirectoryWinSafe(prefix=pysb_model.name) as outdir: - if pysb_model.name in ['move_connected_amici']: + if pysb_model.name in ["move_connected_amici"]: with pytest.raises(Exception): - pysb2amici(pysb_model, outdir, verbose=logging.INFO, - compute_conservation_laws=True) + pysb2amici( + pysb_model, + outdir, + verbose=logging.INFO, + compute_conservation_laws=True, + ) compute_conservation_laws = False else: compute_conservation_laws = True @@ -147,11 +183,10 @@ def test_compare_to_pysb_simulation(example): outdir, verbose=logging.INFO, compute_conservation_laws=compute_conservation_laws, - observables=list(pysb_model.observables.keys()) + observables=list(pysb_model.observables.keys()), ) - amici_model_module = amici.import_model_module(pysb_model.name, - outdir) + amici_model_module = amici.import_model_module(pysb_model.name, outdir) model_pysb = amici_model_module.getModel() model_pysb.setTimepoints(tspan) @@ -162,16 +197,22 @@ def test_compare_to_pysb_simulation(example): rdata = amici.runAmiciSimulation(model_pysb, solver) # check agreement of species simulations - assert np.isclose(rdata['x'], - pysb_simres.species, 1e-4, 1e-4).all() - - if example not in ['fricker_2010_apoptosis', 'fixed_initial', - 'bngwiki_egfr_simple_deletemolecules']: - if example in ['tyson_oscillator', 'bax_pore_sequential', - 'bax_pore', 'kinase_cascade', - 'bngwiki_egfr_simple', - 'bngwiki_enzymatic_cycle_mm', - 'bngwiki_simple']: + assert np.isclose(rdata["x"], pysb_simres.species, 1e-4, 1e-4).all() + + if example not in [ + "fricker_2010_apoptosis", + "fixed_initial", + "bngwiki_egfr_simple_deletemolecules", + ]: + if example in [ + "tyson_oscillator", + "bax_pore_sequential", + "bax_pore", + "kinase_cascade", + "bngwiki_egfr_simple", + "bngwiki_enzymatic_cycle_mm", + "bngwiki_simple", + ]: solver.setAbsoluteTolerance(1e-14) solver.setRelativeTolerance(1e-14) epsilon = 1e-4 @@ -182,17 +223,21 @@ def test_compare_to_pysb_simulation(example): model_pysb.setParameterScale( parameterScalingFromIntVector( [ - ParameterScaling.log10 if p > 0 + ParameterScaling.log10 + if p > 0 else ParameterScaling.none for p in model_pysb.getParameters() ] ) ) - check_derivatives(model_pysb, solver, - epsilon=epsilon, - rtol=1e-2, - atol=1e-2, - skip_zero_pars=True) + check_derivatives( + model_pysb, + solver, + epsilon=epsilon, + rtol=1e-2, + atol=1e-2, + skip_zero_pars=True, + ) def get_data(model): @@ -227,44 +272,45 @@ def get_results(model, edata): def test_names_and_ids(pysb_example_presimulation_module): model_pysb = pysb_example_presimulation_module.getModel() expected = { - 'ExpressionIds': ( - '__s2', - '__s1', - '__s5', - 'pPROT', - 'tPROT', - 'initProt', - 'initDrug', - 'initKin', - 'pPROT_obs'), - 'FixedParameterIds': ('DRUG_0', 'KIN_0'), - 'FixedParameterNames': ('DRUG_0', 'KIN_0'), - 'ObservableIds': ('pPROT_obs',), - 'ObservableNames': ('pPROT_obs',), - 'ParameterIds': ( - 'PROT_0', - 'kon_prot_drug', - 'koff_prot_drug', - 'kon_prot_kin', - 'kphospho_prot_kin', - 'kdephospho_prot' + "ExpressionIds": ( + "__s2", + "__s1", + "__s5", + "pPROT", + "tPROT", + "initProt", + "initDrug", + "initKin", + "pPROT_obs", ), - 'StateIds': ('__s0', '__s1', '__s2', '__s3', '__s4', '__s5'), - 'StateNames': ( + "FixedParameterIds": ("DRUG_0", "KIN_0"), + "FixedParameterNames": ("DRUG_0", "KIN_0"), + "ObservableIds": ("pPROT_obs",), + "ObservableNames": ("pPROT_obs",), + "ParameterIds": ( + "PROT_0", + "kon_prot_drug", + "koff_prot_drug", + "kon_prot_kin", + "kphospho_prot_kin", + "kdephospho_prot", + ), + "StateIds": ("__s0", "__s1", "__s2", "__s3", "__s4", "__s5"), + "StateNames": ( "PROT(kin=None, drug=None, phospho='u')", - 'DRUG(bound=None)', - 'KIN(bound=None)', + "DRUG(bound=None)", + "KIN(bound=None)", "DRUG(bound=1) % PROT(kin=None, drug=1, phospho='u')", "KIN(bound=1) % PROT(kin=1, drug=None, phospho='u')", - "PROT(kin=None, drug=None, phospho='p')" + "PROT(kin=None, drug=None, phospho='p')", ), } # Names and IDs are the same here - expected['ExpressionNames'] = expected['ExpressionIds'] - expected['ParameterNames'] = expected['ParameterIds'] + expected["ExpressionNames"] = expected["ExpressionIds"] + expected["ParameterNames"] = expected["ParameterIds"] for field_name, cur_expected in expected.items(): - actual = getattr(model_pysb, f'get{field_name}')() + actual = getattr(model_pysb, f"get{field_name}")() assert actual == cur_expected @@ -273,62 +319,59 @@ def test_heavyside_and_special_symbols(): pysb.SelfExporter.cleanup() # reset pysb pysb.SelfExporter.do_export = True - model = pysb.Model('piecewise_test') - a = pysb.Monomer('A') - pysb.Initial(a(), pysb.Parameter('a0')) + model = pysb.Model("piecewise_test") + a = pysb.Monomer("A") + pysb.Initial(a(), pysb.Parameter("a0")) pysb.Rule( - 'deg', + "deg", a() >> None, pysb.Expression( - 'rate', - sp.Piecewise((1, pysb.Observable('a', a()) < 1), - (0.0, True)) - ) + "rate", sp.Piecewise((1, pysb.Observable("a", a()) < 1), (0.0, True)) + ), ) with TemporaryDirectoryWinSafe(prefix=model.name) as outdir: - pysb2amici(model, outdir, verbose=True, observables=['a']) + pysb2amici(model, outdir, verbose=True, observables=["a"]) - model_module = amici.import_model_module(module_name=model.name, - module_path=outdir) + model_module = amici.import_model_module( + module_name=model.name, module_path=outdir + ) amici_model = model_module.getModel() assert amici_model.ne @skip_on_valgrind def test_energy(): - model_pysb = pysb.Model('energy') - pysb.Monomer('A', ['a', 'b']) - pysb.Monomer('B', ['a']) - pysb.Parameter('RT', 2) - pysb.Parameter('A_0', 10) - pysb.Parameter('AB_0', 10) - pysb.Parameter('phi', 0.5) - pysb.Expression('E_AAB_RT', -5 / RT) - pysb.Expression('E0_AA_RT', -1 / RT) + model_pysb = pysb.Model("energy") + pysb.Monomer("A", ["a", "b"]) + pysb.Monomer("B", ["a"]) + pysb.Parameter("RT", 2) + pysb.Parameter("A_0", 10) + pysb.Parameter("AB_0", 10) + pysb.Parameter("phi", 0.5) + pysb.Expression("E_AAB_RT", -5 / RT) + pysb.Expression("E0_AA_RT", -1 / RT) pysb.Rule( - 'A_dimerize', + "A_dimerize", A(a=None) + A(a=None) | A(a=1) % A(a=1), phi, E0_AA_RT, energy=True, ) - pysb.EnergyPattern('epAAB', A(a=1) % A(a=1, b=2) % B(a=2), E_AAB_RT) + pysb.EnergyPattern("epAAB", A(a=1) % A(a=1, b=2) % B(a=2), E_AAB_RT) pysb.Initial(A(a=None, b=None), A_0) pysb.Initial(A(a=None, b=1) % B(a=1), AB_0) with TemporaryDirectoryWinSafe(prefix=model_pysb.name) as outdir: pysb2amici(model_pysb, output_dir=outdir) - model_module = amici.import_model_module(module_name=model_pysb.name, - module_path=outdir) + model_module = amici.import_model_module( + module_name=model_pysb.name, module_path=outdir + ) amici_model = model_module.getModel() amici_model.setTimepoints(np.logspace(-4, 5, 10)) solver = amici_model.getSolver() solver.setRelativeTolerance(1e-14) solver.setAbsoluteTolerance(1e-14) - check_derivatives(amici_model, solver, - epsilon=1e-4, - rtol=1e-2, - atol=1e-2) + check_derivatives(amici_model, solver, epsilon=1e-4, rtol=1e-2, atol=1e-2) diff --git a/python/tests/test_rdata.py b/python/tests/test_rdata.py index 0e6847e689..0a6102d401 100644 --- a/python/tests/test_rdata.py +++ b/python/tests/test_rdata.py @@ -5,7 +5,8 @@ import amici from numpy.testing import assert_array_equal -@pytest.fixture(scope='session') + +@pytest.fixture(scope="session") def rdata_by_id_fixture(sbml_example_presimulation_module): model_module = sbml_example_presimulation_module model = model_module.getModel() @@ -21,35 +22,19 @@ def rdata_by_id_fixture(sbml_example_presimulation_module): def test_rdata_by_id(rdata_by_id_fixture): model, rdata = rdata_by_id_fixture - assert_array_equal( - rdata.by_id(model.getStateIds()[1]), - rdata.x[:, 1] - ) - assert_array_equal( - rdata.by_id(model.getStateIds()[1], 'x'), - rdata.x[:, 1] - ) - assert_array_equal( - rdata.by_id(model.getStateIds()[1], 'x', model), - rdata.x[:, 1] - ) - + assert_array_equal(rdata.by_id(model.getStateIds()[1]), rdata.x[:, 1]) + assert_array_equal(rdata.by_id(model.getStateIds()[1], "x"), rdata.x[:, 1]) + assert_array_equal(rdata.by_id(model.getStateIds()[1], "x", model), rdata.x[:, 1]) assert_array_equal( - rdata.by_id(model.getObservableIds()[0], 'y', model), - rdata.y[:, 0] + rdata.by_id(model.getObservableIds()[0], "y", model), rdata.y[:, 0] ) + assert_array_equal(rdata.by_id(model.getExpressionIds()[1]), rdata.w[:, 1]) assert_array_equal( - rdata.by_id(model.getExpressionIds()[1]), - rdata.w[:, 1] - ) - assert_array_equal( - rdata.by_id(model.getExpressionIds()[1], 'w', model), - rdata.w[:, 1] + rdata.by_id(model.getExpressionIds()[1], "w", model), rdata.w[:, 1] ) assert_array_equal( - rdata.by_id(model.getStateIds()[1], 'sx', model), - rdata.sx[:, :, 1] + rdata.by_id(model.getStateIds()[1], "sx", model), rdata.sx[:, :, 1] ) diff --git a/python/tests/test_sbml_import.py b/python/tests/test_sbml_import.py index 813690170d..b24bcee561 100644 --- a/python/tests/test_sbml_import.py +++ b/python/tests/test_sbml_import.py @@ -13,12 +13,15 @@ import amici from amici.gradient_check import check_derivatives from amici.sbml_import import SbmlImporter -from amici.testing import TemporaryDirectoryWinSafe as TemporaryDirectory, \ - skip_on_valgrind +from amici.testing import ( + TemporaryDirectoryWinSafe as TemporaryDirectory, + skip_on_valgrind, +) -EXAMPLES_DIR = Path(__file__).parent / '..' / 'examples' -STEADYSTATE_MODEL_FILE = (EXAMPLES_DIR / 'example_steadystate' - / 'model_steadystate_scaled.xml') +EXAMPLES_DIR = Path(__file__).parent / ".." / "examples" +STEADYSTATE_MODEL_FILE = ( + EXAMPLES_DIR / "example_steadystate" / "model_steadystate_scaled.xml" +) @pytest.fixture @@ -28,16 +31,16 @@ def simple_sbml_model(): model = document.createModel() model.setTimeUnits("second") model.setExtentUnits("mole") - model.setSubstanceUnits('mole') + model.setSubstanceUnits("mole") c1 = model.createCompartment() - c1.setId('C1') + c1.setId("C1") model.addCompartment(c1) s1 = model.createSpecies() - s1.setId('S1') - s1.setCompartment('C1') + s1.setId("S1") + s1.setCompartment("C1") model.addSpecies(s1) p1 = model.createParameter() - p1.setId('p1') + p1.setId("p1") p1.setValue(0.0) model.addParameter(p1) @@ -47,34 +50,33 @@ def simple_sbml_model(): def test_sbml2amici_no_observables(simple_sbml_model): """Test model generation works for model without observables""" sbml_doc, sbml_model = simple_sbml_model - sbml_importer = SbmlImporter(sbml_source=sbml_model, - from_file=False) + sbml_importer = SbmlImporter(sbml_source=sbml_model, from_file=False) model_name = "test_sbml2amici_no_observables" with TemporaryDirectory() as tmpdir: - sbml_importer.sbml2amici(model_name=model_name, - output_dir=tmpdir, - observables=None, - compute_conservation_laws=False) + sbml_importer.sbml2amici( + model_name=model_name, + output_dir=tmpdir, + observables=None, + compute_conservation_laws=False, + ) # Ensure import succeeds (no missing symbols) module_module = amici.import_model_module(model_name, tmpdir) - assert hasattr(module_module, 'getModel') + assert hasattr(module_module, "getModel") @skip_on_valgrind def test_sbml2amici_nested_observables_fail(simple_sbml_model): """Test model generation works for model without observables""" sbml_doc, sbml_model = simple_sbml_model - sbml_importer = SbmlImporter(sbml_source=sbml_model, - from_file=False) + sbml_importer = SbmlImporter(sbml_source=sbml_model, from_file=False) model_name = "test_sbml2amici_nested_observables_fail" with TemporaryDirectory() as tmpdir: with pytest.raises(ValueError, match="(?i)nested"): sbml_importer.sbml2amici( model_name=model_name, output_dir=tmpdir, - observables={'outer': {'formula': 'inner'}, - 'inner': {'formula': 'S1'}}, + observables={"outer": {"formula": "inner"}, "inner": {"formula": "S1"}}, compute_conservation_laws=False, generate_sensitivity_code=False, compile=False, @@ -83,18 +85,20 @@ def test_sbml2amici_nested_observables_fail(simple_sbml_model): def test_nosensi(simple_sbml_model): sbml_doc, sbml_model = simple_sbml_model - sbml_importer = SbmlImporter(sbml_source=sbml_model, - from_file=False) + sbml_importer = SbmlImporter(sbml_source=sbml_model, from_file=False) model_name = "test_nosensi" with TemporaryDirectory() as tmpdir: - sbml_importer.sbml2amici(model_name=model_name, - output_dir=tmpdir, - observables=None, - compute_conservation_laws=False, - generate_sensitivity_code=False) + sbml_importer.sbml2amici( + model_name=model_name, + output_dir=tmpdir, + observables=None, + compute_conservation_laws=False, + generate_sensitivity_code=False, + ) - model_module = amici.import_model_module(module_name=model_name, - module_path=tmpdir) + model_module = amici.import_model_module( + module_name=model_name, module_path=tmpdir + ) model = model_module.getModel() model.setTimepoints(np.linspace(0, 60, 61)) @@ -115,24 +119,26 @@ def observable_dependent_error_model(simple_sbml_model): rr.setVariable("S1") rr.setMath(libsbml.parseL3Formula("p1")) relative_sigma = sbml_model.createParameter() - relative_sigma.setId('relative_sigma') + relative_sigma.setId("relative_sigma") relative_sigma.setValue(0.05) - sbml_importer = SbmlImporter(sbml_source=sbml_model, - from_file=False) + sbml_importer = SbmlImporter(sbml_source=sbml_model, from_file=False) model_name = "observable_dependent_error_model" with TemporaryDirectory() as tmpdir: sbml_importer.sbml2amici( model_name=model_name, output_dir=tmpdir, - observables={'observable_s1': {'formula': 'S1'}, - 'observable_s1_scaled': {'formula': '0.5 * S1'}}, - sigmas={'observable_s1': '0.1 + relative_sigma * observable_s1', - 'observable_s1_scaled': '0.02 * observable_s1_scaled'}, + observables={ + "observable_s1": {"formula": "S1"}, + "observable_s1_scaled": {"formula": "0.5 * S1"}, + }, + sigmas={ + "observable_s1": "0.1 + relative_sigma * observable_s1", + "observable_s1_scaled": "0.02 * observable_s1_scaled", + }, ) - yield amici.import_model_module(module_name=model_name, - module_path=tmpdir) + yield amici.import_model_module(module_name=model_name, module_path=tmpdir) @skip_on_valgrind @@ -145,10 +151,10 @@ def test_sbml2amici_observable_dependent_error(observable_dependent_error_model) # generate artificial data rdata = amici.runAmiciSimulation(model, solver) - assert_allclose(rdata.sigmay[:, 0], 0.1 + 0.05 * rdata.y[:, 0], - rtol=1.e-5, atol=1.e-8) - assert_allclose(rdata.sigmay[:, 1], 0.02 * rdata.y[:, 1], - rtol=1.e-5, atol=1.e-8) + assert_allclose( + rdata.sigmay[:, 0], 0.1 + 0.05 * rdata.y[:, 0], rtol=1.0e-5, atol=1.0e-8 + ) + assert_allclose(rdata.sigmay[:, 1], 0.02 * rdata.y[:, 1], rtol=1.0e-5, atol=1.0e-8) edata = amici.ExpData(rdata, 1.0, 0.0) edata.setObservedDataStdDev(np.nan) @@ -179,50 +185,48 @@ def test_logging_works(observable_dependent_error_model, caplog): assert rdata.status != amici.AMICI_SUCCESS assert "mxstep steps taken" in caplog.text + @skip_on_valgrind def test_model_module_is_set(observable_dependent_error_model): model_module = observable_dependent_error_model assert isinstance(model_module.getModel().module, amici.ModelModule) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def model_steadystate_module(): sbml_file = STEADYSTATE_MODEL_FILE sbml_importer = amici.SbmlImporter(sbml_file) observables = amici.assignmentRules2observables( sbml_importer.sbml, - filter_function=lambda variable: - variable.getId().startswith('observable_') and - not variable.getId().endswith('_sigma') + filter_function=lambda variable: variable.getId().startswith("observable_") + and not variable.getId().endswith("_sigma"), ) - module_name = 'test_model_steadystate_scaled' + module_name = "test_model_steadystate_scaled" with TemporaryDirectory(prefix=module_name) as outdir: sbml_importer.sbml2amici( model_name=module_name, output_dir=outdir, observables=observables, - constant_parameters=['k0'], - sigmas={'observable_x1withsigma': 'observable_x1withsigma_sigma'}) + constant_parameters=["k0"], + sigmas={"observable_x1withsigma": "observable_x1withsigma_sigma"}, + ) - yield amici.import_model_module(module_name=module_name, - module_path=outdir) + yield amici.import_model_module(module_name=module_name, module_path=outdir) -@pytest.fixture(scope='session') +@pytest.fixture(scope="session") def model_units_module(): - sbml_file = EXAMPLES_DIR / 'example_units' / 'model_units.xml' - module_name = 'test_model_units' + sbml_file = EXAMPLES_DIR / "example_units" / "model_units.xml" + module_name = "test_model_units" sbml_importer = amici.SbmlImporter(sbml_file) with TemporaryDirectory() as outdir: - sbml_importer.sbml2amici(model_name=module_name, - output_dir=outdir) + sbml_importer.sbml2amici(model_name=module_name, output_dir=outdir) - yield amici.import_model_module(module_name=module_name, - module_path=outdir) + yield amici.import_model_module(module_name=module_name, module_path=outdir) def test_presimulation(sbml_example_presimulation_module): @@ -242,8 +246,8 @@ def test_presimulation(sbml_example_presimulation_module): edata.fixedParametersPresimulation = [10, 2] edata.fixedParametersPreequilibration = [3, 0] assert isinstance( - amici.runAmiciSimulation(model, solver, edata), - amici.ReturnDataView) + amici.runAmiciSimulation(model, solver, edata), amici.ReturnDataView + ) solver.setRelativeTolerance(1e-12) solver.setAbsoluteTolerance(1e-12) @@ -268,51 +272,61 @@ def test_steadystate_simulation(model_steadystate_module): edata_reconstructed = amici.getEdataFromDataFrame(model, df_edata) assert_allclose( - amici.ExpDataView(edata[0])['observedData'], - amici.ExpDataView(edata_reconstructed[0])['observedData'], - rtol=1.e-5, atol=1.e-8 + amici.ExpDataView(edata[0])["observedData"], + amici.ExpDataView(edata_reconstructed[0])["observedData"], + rtol=1.0e-5, + atol=1.0e-8, ) assert_allclose( - amici.ExpDataView(edata[0])['observedDataStdDev'], - amici.ExpDataView(edata_reconstructed[0])['observedDataStdDev'], - rtol=1.e-5, atol=1.e-8 + amici.ExpDataView(edata[0])["observedDataStdDev"], + amici.ExpDataView(edata_reconstructed[0])["observedDataStdDev"], + rtol=1.0e-5, + atol=1.0e-8, ) if len(edata[0].fixedParameters): - assert list(edata[0].fixedParameters) \ - == list(edata_reconstructed[0].fixedParameters) + assert list(edata[0].fixedParameters) == list( + edata_reconstructed[0].fixedParameters + ) else: - assert list(model.getFixedParameters()) \ - == list(edata_reconstructed[0].fixedParameters) + assert list(model.getFixedParameters()) == list( + edata_reconstructed[0].fixedParameters + ) - assert list(edata[0].fixedParametersPreequilibration) == \ - list(edata_reconstructed[0].fixedParametersPreequilibration) + assert list(edata[0].fixedParametersPreequilibration) == list( + edata_reconstructed[0].fixedParametersPreequilibration + ) df_state = amici.getSimulationStatesAsDataFrame(model, edata, rdata) assert_allclose( - rdata[0]['x'], df_state[list(model.getStateIds())].values, - rtol=1.e-5, atol=1.e-8 + rdata[0]["x"], + df_state[list(model.getStateIds())].values, + rtol=1.0e-5, + atol=1.0e-8, ) df_obs = amici.getSimulationObservablesAsDataFrame(model, edata, rdata) assert_allclose( - rdata[0]['y'], df_obs[list(model.getObservableIds())].values, - rtol=1.e-5, atol=1.e-8 + rdata[0]["y"], + df_obs[list(model.getObservableIds())].values, + rtol=1.0e-5, + atol=1.0e-8, ) amici.getResidualsAsDataFrame(model, edata, rdata) df_expr = amici.pandas.get_expressions_as_dataframe(model, edata, rdata) assert_allclose( - rdata[0]['w'], df_expr[list(model.getExpressionIds())].values, - rtol=1.e-5, atol=1.e-8 + rdata[0]["w"], + df_expr[list(model.getExpressionIds())].values, + rtol=1.0e-5, + atol=1.0e-8, ) solver.setRelativeTolerance(1e-12) solver.setAbsoluteTolerance(1e-12) - check_derivatives(model, solver, edata[0], atol=1e-3, - rtol=1e-3, epsilon=1e-4) + check_derivatives(model, solver, edata[0], atol=1e-3, rtol=1e-3, epsilon=1e-4) # Run some additional tests which need a working Model, # but don't need precomputed expectations. @@ -328,8 +342,8 @@ def test_solver_reuse(model_steadystate_module): edata = amici.ExpData(rdata, 1, 0) for sensi_method in ( - amici.SensitivityMethod.forward, - amici.SensitivityMethod.adjoint, + amici.SensitivityMethod.forward, + amici.SensitivityMethod.adjoint, ): solver.setSensitivityMethod(sensi_method) rdata1 = amici.runAmiciSimulation(model, solver, edata) @@ -338,13 +352,15 @@ def test_solver_reuse(model_steadystate_module): assert rdata1.status == amici.AMICI_SUCCESS for attr in rdata1: - if 'time' in attr: + if "time" in attr: continue val1 = getattr(rdata1, attr) val2 = getattr(rdata2, attr) - msg = f"Values for {attr} do not match for sensitivity "\ - f"method {sensi_method}" + msg = ( + f"Values for {attr} do not match for sensitivity " + f"method {sensi_method}" + ) if isinstance(val1, np.ndarray): assert_array_equal(val1, val2, err_msg=msg) elif isinstance(val1, Number) and np.isnan(val1): @@ -353,7 +369,6 @@ def test_solver_reuse(model_steadystate_module): assert val1 == val2, msg - @pytest.fixture def model_test_likelihoods(): """Test model for various likelihood functions.""" @@ -363,35 +378,38 @@ def model_test_likelihoods(): # define observables observables = { - 'o1': {'formula': 'x1'}, - 'o2': {'formula': '10^x1'}, - 'o3': {'formula': '10^x1'}, - 'o4': {'formula': 'x1'}, - 'o5': {'formula': '10^x1'}, - 'o6': {'formula': '10^x1'}, - 'o7': {'formula': 'x1'} + "o1": {"formula": "x1"}, + "o2": {"formula": "10^x1"}, + "o3": {"formula": "10^x1"}, + "o4": {"formula": "x1"}, + "o5": {"formula": "10^x1"}, + "o6": {"formula": "10^x1"}, + "o7": {"formula": "x1"}, } # define different noise models noise_distributions = { - 'o1': 'normal', 'o2': 'log-normal', 'o3': 'log10-normal', - 'o4': 'laplace', 'o5': 'log-laplace', 'o6': 'log10-laplace', - 'o7': lambda str_symbol: f'Abs({str_symbol} - m{str_symbol}) ' - f'/ sigma{str_symbol}', + "o1": "normal", + "o2": "log-normal", + "o3": "log10-normal", + "o4": "laplace", + "o5": "log-laplace", + "o6": "log10-laplace", + "o7": lambda str_symbol: f"Abs({str_symbol} - m{str_symbol}) " + f"/ sigma{str_symbol}", } - module_name = 'model_test_likelihoods' + module_name = "model_test_likelihoods" with TemporaryDirectory(prefix=module_name) as outdir: sbml_importer.sbml2amici( model_name=module_name, output_dir=outdir, observables=observables, - constant_parameters=['k0'], + constant_parameters=["k0"], noise_distributions=noise_distributions, ) - yield amici.import_model_module(module_name=module_name, - module_path=outdir) + yield amici.import_model_module(module_name=module_name, module_path=outdir) @skip_on_valgrind @@ -405,7 +423,7 @@ def test_likelihoods(model_test_likelihoods): # run model once to create an edata rdata = amici.runAmiciSimulation(model, solver) - sigmas = rdata['y'].max(axis=0) * 0.05 + sigmas = rdata["y"].max(axis=0) * 0.05 edata = amici.ExpData(rdata, sigmas, []) # just make all observables positive since some are logarithmic while min(edata.getObservedData()) < 0: @@ -415,38 +433,47 @@ def test_likelihoods(model_test_likelihoods): rdata = amici.runAmiciSimulations(model, solver, [edata])[0] # check if the values make overall sense - assert np.isfinite(rdata['llh']) - assert np.all(np.isfinite(rdata['sllh'])) - assert np.any(rdata['sllh']) + assert np.isfinite(rdata["llh"]) + assert np.all(np.isfinite(rdata["sllh"])) + assert np.any(rdata["sllh"]) rdata_df = amici.getSimulationObservablesAsDataFrame( - model, edata, rdata, by_id=True) - edata_df = amici.getDataObservablesAsDataFrame( - model, edata, by_id=True) + model, edata, rdata, by_id=True + ) + edata_df = amici.getDataObservablesAsDataFrame(model, edata, by_id=True) # check correct likelihood value - llh_exp = - sum([ - normal_nllh(edata_df['o1'], rdata_df['o1'], sigmas[0]), - log_normal_nllh(edata_df['o2'], rdata_df['o2'], sigmas[1]), - log10_normal_nllh(edata_df['o3'], rdata_df['o3'], sigmas[2]), - laplace_nllh(edata_df['o4'], rdata_df['o4'], sigmas[3]), - log_laplace_nllh(edata_df['o5'], rdata_df['o5'], sigmas[4]), - log10_laplace_nllh(edata_df['o6'], rdata_df['o6'], sigmas[5]), - custom_nllh(edata_df['o7'], rdata_df['o7'], sigmas[6]), - ]) - assert np.isclose(rdata['llh'], llh_exp) + llh_exp = -sum( + [ + normal_nllh(edata_df["o1"], rdata_df["o1"], sigmas[0]), + log_normal_nllh(edata_df["o2"], rdata_df["o2"], sigmas[1]), + log10_normal_nllh(edata_df["o3"], rdata_df["o3"], sigmas[2]), + laplace_nllh(edata_df["o4"], rdata_df["o4"], sigmas[3]), + log_laplace_nllh(edata_df["o5"], rdata_df["o5"], sigmas[4]), + log10_laplace_nllh(edata_df["o6"], rdata_df["o6"], sigmas[5]), + custom_nllh(edata_df["o7"], rdata_df["o7"], sigmas[6]), + ] + ) + assert np.isclose(rdata["llh"], llh_exp) # check gradient - for sensi_method in [amici.SensitivityMethod.forward, - amici.SensitivityMethod.adjoint]: + for sensi_method in [ + amici.SensitivityMethod.forward, + amici.SensitivityMethod.adjoint, + ]: solver = model.getSolver() solver.setSensitivityMethod(sensi_method) solver.setSensitivityOrder(amici.SensitivityOrder.first) solver.setRelativeTolerance(1e-12) solver.setAbsoluteTolerance(1e-12) check_derivatives( - model, solver, edata, atol=1e-2, rtol=1e-2, - epsilon=1e-5, check_least_squares=False + model, + solver, + edata, + atol=1e-2, + rtol=1e-2, + epsilon=1e-5, + check_least_squares=False, ) @@ -457,19 +484,19 @@ def test_likelihoods_error(): sbml_importer = amici.SbmlImporter(sbml_file) # define observables - observables = {'o1': {'formula': 'x1'}} + observables = {"o1": {"formula": "x1"}} # define different noise models - noise_distributions = {'o1': 'nörmal'} + noise_distributions = {"o1": "nörmal"} - module_name = 'test_likelihoods_error' - outdir = 'test_likelihoods_error' + module_name = "test_likelihoods_error" + outdir = "test_likelihoods_error" with pytest.raises(ValueError): sbml_importer.sbml2amici( model_name=module_name, output_dir=outdir, observables=observables, - constant_parameters=['k0'], + constant_parameters=["k0"], noise_distributions=noise_distributions, ) @@ -484,40 +511,46 @@ def test_units(model_units_module): solver = model.getSolver() rdata = amici.runAmiciSimulation(model, solver) - assert rdata['status'] == amici.AMICI_SUCCESS + assert rdata["status"] == amici.AMICI_SUCCESS @skip_on_valgrind -@pytest.mark.skipif(os.name == 'nt', - reason='Avoid `CERTIFICATE_VERIFY_FAILED` error') +@pytest.mark.skipif(os.name == "nt", reason="Avoid `CERTIFICATE_VERIFY_FAILED` error") def test_sympy_exp_monkeypatch(): """ This model contains a removeable discontinuity at t=0 that requires monkeypatching sympy.Pow._eval_derivative in order to be able to compute non-nan sensitivities """ - url = 'https://www.ebi.ac.uk/biomodels/model/download/BIOMD0000000529.2?' \ - 'filename=BIOMD0000000529_url.xml' + url = ( + "https://www.ebi.ac.uk/biomodels/model/download/BIOMD0000000529.2?" + "filename=BIOMD0000000529_url.xml" + ) importer = amici.SbmlImporter( - urlopen(url, timeout=20).read().decode('utf-8'), from_file=False + urlopen(url, timeout=20).read().decode("utf-8"), from_file=False ) - module_name = 'BIOMD0000000529' + module_name = "BIOMD0000000529" with TemporaryDirectory() as outdir: importer.sbml2amici(module_name, outdir) - model_module = amici.import_model_module(module_name=module_name, - module_path=outdir) + model_module = amici.import_model_module( + module_name=module_name, module_path=outdir + ) model = model_module.getModel() model.setTimepoints(np.linspace(0, 8, 250)) model.requireSensitivitiesForAllParameters() model.setAlwaysCheckFinite(True) - model.setParameterScale(amici.parameterScalingFromIntVector([ - amici.ParameterScaling.none - if re.match(r'n[0-9]+$', par_id) - else amici.ParameterScaling.log10 - for par_id in model.getParameterIds() - ])) + model.setParameterScale( + amici.parameterScalingFromIntVector( + [ + amici.ParameterScaling.none + if re.match(r"n[0-9]+$", par_id) + else amici.ParameterScaling.log10 + for par_id in model.getParameterIds() + ] + ) + ) solver = model.getSolver() solver.setSensitivityMethod(amici.SensitivityMethod.forward) @@ -526,40 +559,50 @@ def test_sympy_exp_monkeypatch(): rdata = amici.runAmiciSimulation(model, solver) # print sensitivity-related results - assert rdata['status'] == amici.AMICI_SUCCESS - check_derivatives(model, solver, None, atol=1e-2, rtol=1e-2, - epsilon=1e-3) + assert rdata["status"] == amici.AMICI_SUCCESS + check_derivatives(model, solver, None, atol=1e-2, rtol=1e-2, epsilon=1e-3) def normal_nllh(m, y, sigma): - return sum(.5*(np.log(2*np.pi*sigma**2) + ((y-m)/sigma)**2)) + return sum(0.5 * (np.log(2 * np.pi * sigma**2) + ((y - m) / sigma) ** 2)) def log_normal_nllh(m, y, sigma): - return sum(.5*(np.log(2*np.pi*sigma**2*m**2) - + ((np.log(y)-np.log(m))/sigma)**2)) + return sum( + 0.5 + * ( + np.log(2 * np.pi * sigma**2 * m**2) + + ((np.log(y) - np.log(m)) / sigma) ** 2 + ) + ) def log10_normal_nllh(m, y, sigma): - return sum(.5*(np.log(2*np.pi*sigma**2*m**2*np.log(10)**2) - + ((np.log10(y) - np.log10(m))/sigma)**2)) + return sum( + 0.5 + * ( + np.log(2 * np.pi * sigma**2 * m**2 * np.log(10) ** 2) + + ((np.log10(y) - np.log10(m)) / sigma) ** 2 + ) + ) def laplace_nllh(m, y, sigma): - return sum(np.log(2*sigma) + np.abs(y-m)/sigma) + return sum(np.log(2 * sigma) + np.abs(y - m) / sigma) def log_laplace_nllh(m, y, sigma): - return sum(np.log(2*sigma*m) + np.abs(np.log(y)-np.log(m))/sigma) + return sum(np.log(2 * sigma * m) + np.abs(np.log(y) - np.log(m)) / sigma) def log10_laplace_nllh(m, y, sigma): - return sum(np.log(2*sigma*m*np.log(10)) - + np.abs(np.log10(y)-np.log10(m))/sigma) + return sum( + np.log(2 * sigma * m * np.log(10)) + np.abs(np.log10(y) - np.log10(m)) / sigma + ) def custom_nllh(m, y, sigma): - return sum(np.abs(m-y)/sigma) + return sum(np.abs(m - y) / sigma) def _test_set_parameters_by_dict(model_module): @@ -600,9 +643,9 @@ def test_code_gen_uses_cse(extract_cse): model_name=model_name, compile=False, generate_sensitivity_code=False, - output_dir = tmpdir + output_dir=tmpdir, ) - xdot = Path(tmpdir, 'xdot.cpp').read_text() + xdot = Path(tmpdir, "xdot.cpp").read_text() assert ("__amici_cse_0 = " in xdot) == extract_cse finally: os.environ = old_environ @@ -618,7 +661,7 @@ def test_code_gen_uses_lhs_symbol_ids(): model_name=model_name, compile=False, generate_sensitivity_code=False, - output_dir=tmpdir + output_dir=tmpdir, ) - dwdx = Path(tmpdir, 'dwdx.cpp').read_text() + dwdx = Path(tmpdir, "dwdx.cpp").read_text() assert "dobservable_x1_dx1 = " in dwdx diff --git a/python/tests/test_sbml_import_special_functions.py b/python/tests/test_sbml_import_special_functions.py index 6615828992..224440182f 100644 --- a/python/tests/test_sbml_import_special_functions.py +++ b/python/tests/test_sbml_import_special_functions.py @@ -19,34 +19,38 @@ def model_special_likelihoods(): """Test model for special likelihood functions.""" # load sbml model - sbml_file = os.path.join(os.path.dirname(__file__), '..', - 'examples', 'example_steadystate', - 'model_steadystate_scaled.xml') + sbml_file = os.path.join( + os.path.dirname(__file__), + "..", + "examples", + "example_steadystate", + "model_steadystate_scaled.xml", + ) sbml_importer = amici.SbmlImporter(sbml_file) # define observables observables = { - 'o1': {'formula': '100*10^x1'}, - 'o2': {'formula': '100*10^x1'}, + "o1": {"formula": "100*10^x1"}, + "o2": {"formula": "100*10^x1"}, } # define different noise models noise_distributions = { - 'o1': 'binomial', 'o2': 'negative-binomial', + "o1": "binomial", + "o2": "negative-binomial", } - module_name = 'test_special_likelihoods' + module_name = "test_special_likelihoods" with TemporaryDirectoryWinSafe(prefix=module_name) as outdir: sbml_importer.sbml2amici( model_name=module_name, output_dir=outdir, observables=observables, - constant_parameters=['k0'], + constant_parameters=["k0"], noise_distributions=noise_distributions, ) - yield amici.import_model_module(module_name=module_name, - module_path=outdir) + yield amici.import_model_module(module_name=module_name, module_path=outdir) @skip_on_valgrind @@ -79,31 +83,35 @@ def test_special_likelihoods(model_special_likelihoods): rdata = amici.runAmiciSimulations(model, solver, [edata])[0] # check if the values make overall sense - assert np.isfinite(rdata['llh']) - assert np.all(np.isfinite(rdata['sllh'])) - assert np.any(rdata['sllh']) + assert np.isfinite(rdata["llh"]) + assert np.all(np.isfinite(rdata["sllh"])) + assert np.any(rdata["sllh"]) rdata_df = amici.getSimulationObservablesAsDataFrame( - model, edata, rdata, by_id=True) - edata_df = amici.getDataObservablesAsDataFrame( - model, edata, by_id=True) + model, edata, rdata, by_id=True + ) + edata_df = amici.getDataObservablesAsDataFrame(model, edata, by_id=True) # check correct likelihood value - llh_exp = - sum([ - binomial_nllh(edata_df['o1'], rdata_df['o1'], sigma), - negative_binomial_nllh(edata_df['o2'], rdata_df['o2'], sigma), - ]) - assert np.isclose(rdata['llh'], llh_exp) + llh_exp = -sum( + [ + binomial_nllh(edata_df["o1"], rdata_df["o1"], sigma), + negative_binomial_nllh(edata_df["o2"], rdata_df["o2"], sigma), + ] + ) + assert np.isclose(rdata["llh"], llh_exp) # check gradient - for sensi_method in [amici.SensitivityMethod.forward, - amici.SensitivityMethod.adjoint]: + for sensi_method in [ + amici.SensitivityMethod.forward, + amici.SensitivityMethod.adjoint, + ]: solver = model.getSolver() solver.setSensitivityMethod(sensi_method) solver.setSensitivityOrder(amici.SensitivityOrder.first) check_derivatives( - model, solver, edata, atol=1e-4, rtol=1e-3, - check_least_squares=False) + model, solver, edata, atol=1e-4, rtol=1e-3, check_least_squares=False + ) # Test for m > y, i.e. in region with 0 density @@ -120,19 +128,29 @@ def test_special_likelihoods(model_special_likelihoods): rdata = amici.runAmiciSimulations(model, solver, [edata])[0] # m > y -> outside binomial domain -> 0 density - assert rdata['llh'] == -np.inf + assert rdata["llh"] == -np.inf # check for non-informative gradient - assert all(np.isnan(rdata['sllh'])) + assert all(np.isnan(rdata["sllh"])) def binomial_nllh(m: np.ndarray, y: np.ndarray, p: float): if any(m > y): return np.inf - return sum(- loggamma(y+1) + loggamma(m+1) + loggamma(y-m+1) \ - - m * np.log(p) - (y-m) * np.log(1-p)) + return sum( + -loggamma(y + 1) + + loggamma(m + 1) + + loggamma(y - m + 1) + - m * np.log(p) + - (y - m) * np.log(1 - p) + ) def negative_binomial_nllh(m: np.ndarray, y: np.ndarray, p: float): - r = y * (1-p) / p - return sum(- loggamma(m+r) + loggamma(m+1) + loggamma(r) - - r * np.log(1-p) - m * np.log(p)) + r = y * (1 - p) / p + return sum( + -loggamma(m + r) + + loggamma(m + 1) + + loggamma(r) + - r * np.log(1 - p) + - m * np.log(p) + ) diff --git a/python/tests/test_splines.py b/python/tests/test_splines.py index 9e96db993d..a052c2a497 100644 --- a/python/tests/test_splines.py +++ b/python/tests/test_splines.py @@ -21,16 +21,16 @@ def test_multiple_splines(**kwargs): Test a SBML model containing multiple splines. """ spline0, params0, tols0 = example_spline_1( - 0, num_nodes=9, fixed_values=[0, 2], extrapolate='linear' + 0, num_nodes=9, fixed_values=[0, 2], extrapolate="linear" ) spline1, params1, tols1 = example_spline_1( - 1, num_nodes=14, scale=1.5, offset=5, extrapolate='linear' + 1, num_nodes=14, scale=1.5, offset=5, extrapolate="linear" ) spline2, params2, tols2 = example_spline_1( - 2, num_nodes=5, scale=0.5, offset=-5, extrapolate='linear' + 2, num_nodes=5, scale=0.5, offset=-5, extrapolate="linear" ) spline3, params3, tols3 = example_spline_1( - 3, fixed_values='all', extrapolate='linear' + 3, fixed_values="all", extrapolate="linear" ) spline4, params4, tols4 = example_spline_2(4) spline5, params5, tols5 = example_spline_3(5) @@ -58,8 +58,10 @@ def test_multiple_splines(**kwargs): tols5 = (tols5, tols5, tols5) tols = [] - for (t0, t1, t2, t3, t4, t5) in zip(tols0, tols1, tols2, tols3, tols4, tols5): - keys = set().union(t0.keys(), t1.keys(), t2.keys(), t3.keys(), t4.keys(), t5.keys()) + for t0, t1, t2, t3, t4, t5 in zip(tols0, tols1, tols2, tols3, tols4, tols5): + keys = set().union( + t0.keys(), t1.keys(), t2.keys(), t3.keys(), t4.keys(), t5.keys() + ) t = { key: max( t0.get(key, 0.0), @@ -68,30 +70,33 @@ def test_multiple_splines(**kwargs): t3.get(key, 0.0), t4.get(key, 0.0), t5.get(key, 0.0), - ) for key in keys + ) + for key in keys } tols.append(t) - tols[1]['x_rtol'] = max(1e-9, tols[1].get('x_rtol', -np.inf)) - tols[1]['x_atol'] = max(5e-9, tols[1].get('x_atol', -np.inf)) - tols[1]['sx_rtol'] = max(1e-5, tols[1].get('llh_rtol', -np.inf)) - tols[1]['sx_atol'] = max(5e-9, tols[1].get('sx_atol', -np.inf)) - tols[1]['llh_rtol'] = max(5e-14, tols[1].get('llh_rtol', -np.inf)) - tols[1]['sllh_atol'] = max(5e-5, tols[1].get('sllh_atol', -np.inf)) + tols[1]["x_rtol"] = max(1e-9, tols[1].get("x_rtol", -np.inf)) + tols[1]["x_atol"] = max(5e-9, tols[1].get("x_atol", -np.inf)) + tols[1]["sx_rtol"] = max(1e-5, tols[1].get("llh_rtol", -np.inf)) + tols[1]["sx_atol"] = max(5e-9, tols[1].get("sx_atol", -np.inf)) + tols[1]["llh_rtol"] = max(5e-14, tols[1].get("llh_rtol", -np.inf)) + tols[1]["sllh_atol"] = max(5e-5, tols[1].get("sllh_atol", -np.inf)) - tols[2]['x_rtol'] = max(5e-10, tols[2].get('x_rtol', -np.inf)) - tols[2]['x_atol'] = max(1e-8, tols[2].get('x_atol', -np.inf)) - tols[2]['llh_rtol'] = max(5e-14, tols[2].get('llh_rtol', -np.inf)) - tols[2]['sllh_atol'] = max(5e-5, tols[2].get('sllh_atol', -np.inf)) + tols[2]["x_rtol"] = max(5e-10, tols[2].get("x_rtol", -np.inf)) + tols[2]["x_atol"] = max(1e-8, tols[2].get("x_atol", -np.inf)) + tols[2]["llh_rtol"] = max(5e-14, tols[2].get("llh_rtol", -np.inf)) + tols[2]["sllh_atol"] = max(5e-5, tols[2].get("sllh_atol", -np.inf)) + + if os.name == "nt": + tols[2]["sllh_atol"] = max(5e-4, tols[2]["sllh_atol"]) - if os.name == 'nt': - tols[2]['sllh_atol'] = max(5e-4, tols[2]['sllh_atol']) - # Load precomputed results # They be computed again by # groundtruth = test_multiple_splines(return_groundtruth=True) # They should be recomputed only if the splines used in the test change - precomputed_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_splines_precomputed.npz") - kwargs['groundtruth'] = dict(np.load(precomputed_path)) + precomputed_path = os.path.join( + os.path.dirname(os.path.abspath(__file__)), "test_splines_precomputed.npz" + ) + kwargs["groundtruth"] = dict(np.load(precomputed_path)) return check_splines_full(splines, params, tols, **kwargs) diff --git a/python/tests/test_splines_python.py b/python/tests/test_splines_python.py index a525a4be80..ad64664f72 100644 --- a/python/tests/test_splines_python.py +++ b/python/tests/test_splines_python.py @@ -12,23 +12,23 @@ def test_SplineUniform(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), values_at_nodes=[0.0, 2.0, 0.5, 1.0], ) assert math.isclose(float(spline.evaluate(0.0)), 0.0) assert math.isclose(float(spline.evaluate(0.25)), 1.74609375) - assert math.isclose(float(spline.evaluate(1.0/3)), 2.0) + assert math.isclose(float(spline.evaluate(1.0 / 3)), 2.0) assert math.isclose(float(spline.evaluate(0.50)), 1.3437499999999996) - assert math.isclose(float(spline.evaluate(2.0/3)), 0.5) + assert math.isclose(float(spline.evaluate(2.0 / 3)), 0.5) assert math.isclose(float(spline.evaluate(0.75)), 0.484375) assert math.isclose(float(spline.evaluate(1.00)), 1.0) def test_SplineNonUniform(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=[0.0, 0.1, 0.5, 1.0], values_at_nodes=[0.0, 2.0, 0.5, 1.0], @@ -44,10 +44,10 @@ def test_SplineNonUniform(): def test_SplineExplicit(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=5), - values_at_nodes=[0.0, 2.0, 0.5, 1.0, 0.75], + values_at_nodes=[0.0, 2.0, 0.5, 1.0, 0.75], derivatives_at_nodes=[1.0, 0.0, 0.1, -0.1, 0.0], ) assert math.isclose(float(spline.evaluate(0.00)), 0.0) @@ -63,11 +63,11 @@ def test_SplineExplicit(): def test_SplineZeroBC(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), values_at_nodes=[0.0, 2.0, 0.5, 1.0], - bc='zeroderivative', + bc="zeroderivative", ) assert math.isclose(float(spline.evaluate(0.00)), 0.0) assert math.isclose(float(spline.evaluate(0.25)), 1.65234375) @@ -78,7 +78,7 @@ def test_SplineZeroBC(): def test_SplineLogarithmic(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=5), values_at_nodes=[0.2, 2.0, 0.5, 1.0, 0.75], @@ -97,19 +97,19 @@ def test_SplineLogarithmic(): def test_SplineUniformConstantExtrapolation(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), values_at_nodes=[0.0, 2.0, 0.5, 1.0], - extrapolate='constant', + extrapolate="constant", ) assert math.isclose(float(spline.evaluate(-2.00)), 0.0) assert math.isclose(float(spline.evaluate(-1.00)), 0.0) assert math.isclose(float(spline.evaluate(0.00)), 0.0) assert math.isclose(float(spline.evaluate(0.25)), 1.65234375) - assert math.isclose(float(spline.evaluate(1.0/3)), 2.0) + assert math.isclose(float(spline.evaluate(1.0 / 3)), 2.0) assert math.isclose(float(spline.evaluate(0.50)), 1.3437499999999996) - assert math.isclose(float(spline.evaluate(2.0/3)), 0.5) + assert math.isclose(float(spline.evaluate(2.0 / 3)), 0.5) assert math.isclose(float(spline.evaluate(0.75)), 0.5078125) assert math.isclose(float(spline.evaluate(1.00)), 1.0) assert math.isclose(float(spline.evaluate(2.00)), 1.0) @@ -118,19 +118,19 @@ def test_SplineUniformConstantExtrapolation(): def test_SplineUniformLinearExtrapolation(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), values_at_nodes=[0.0, 2.0, 0.5, 1.0], - extrapolate='linear', + extrapolate="linear", ) assert math.isclose(float(spline.evaluate(-2.00)), -12.0) assert math.isclose(float(spline.evaluate(-1.00)), -6.0) assert math.isclose(float(spline.evaluate(0.00)), 0.0) assert math.isclose(float(spline.evaluate(0.25)), 1.74609375) - assert math.isclose(float(spline.evaluate(1.0/3)), 2.0) + assert math.isclose(float(spline.evaluate(1.0 / 3)), 2.0) assert math.isclose(float(spline.evaluate(0.50)), 1.3437499999999996) - assert math.isclose(float(spline.evaluate(2.0/3)), 0.5) + assert math.isclose(float(spline.evaluate(2.0 / 3)), 0.5) assert math.isclose(float(spline.evaluate(0.75)), 0.484375) assert math.isclose(float(spline.evaluate(1.00)), 1.0) assert math.isclose(float(spline.evaluate(2.00)), 2.5) @@ -139,19 +139,19 @@ def test_SplineUniformLinearExtrapolation(): def test_SplineUniformPolynomialExtrapolation(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), values_at_nodes=[0.0, 2.0, 0.5, 1.0], - extrapolate='polynomial', + extrapolate="polynomial", ) assert math.isclose(float(spline.evaluate(-2.00)), 429.0) assert math.isclose(float(spline.evaluate(-1.00)), 57.0) assert math.isclose(float(spline.evaluate(0.00)), 0.0) assert math.isclose(float(spline.evaluate(0.25)), 1.74609375) - assert math.isclose(float(spline.evaluate(1.0/3)), 2.0) + assert math.isclose(float(spline.evaluate(1.0 / 3)), 2.0) assert math.isclose(float(spline.evaluate(0.50)), 1.3437499999999996) - assert math.isclose(float(spline.evaluate(2.0/3)), 0.5) + assert math.isclose(float(spline.evaluate(2.0 / 3)), 0.5) assert math.isclose(float(spline.evaluate(0.75)), 0.484375) assert math.isclose(float(spline.evaluate(1.00)), 1.0) assert math.isclose(float(spline.evaluate(2.00)), -33.5) @@ -160,19 +160,19 @@ def test_SplineUniformPolynomialExtrapolation(): def test_SplineUniformPeriodicExtrapolation(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), values_at_nodes=[1.0, 2.0, 0.5, 1.0], - extrapolate='periodic', + extrapolate="periodic", ) - assert math.isclose(float(spline.evaluate(-4/3)), 0.5) + assert math.isclose(float(spline.evaluate(-4 / 3)), 0.5) assert math.isclose(float(spline.evaluate(-0.5)), 1.2812499999999996) assert math.isclose(float(spline.evaluate(0.00)), 1.0) assert math.isclose(float(spline.evaluate(0.25)), 1.9140625) - assert math.isclose(float(spline.evaluate(1/3)), 2.0) + assert math.isclose(float(spline.evaluate(1 / 3)), 2.0) assert math.isclose(float(spline.evaluate(0.50)), 1.2812499999999996) - assert math.isclose(float(spline.evaluate(2/3)), 0.5) + assert math.isclose(float(spline.evaluate(2 / 3)), 0.5) assert math.isclose(float(spline.evaluate(0.75)), 0.47265625) assert math.isclose(float(spline.evaluate(1.00)), 1.0) assert math.isclose(float(spline.evaluate(1.25)), 1.9140625) @@ -181,101 +181,134 @@ def test_SplineUniformPeriodicExtrapolation(): def test_SplineNonUniformPeriodicExtrapolation(): spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=[0.0, 0.1, 0.5, 1.0], values_at_nodes=[1.0, 2.0, 0.5, 1.0], - extrapolate='periodic', + extrapolate="periodic", ) assert math.isclose(float(spline.evaluate(-1.90)), 2.0) assert math.isclose(float(spline.evaluate(-0.25)), 0.3203125) - assert math.isclose(float(spline.evaluate( 0.00)), 1.0) - assert math.isclose(float(spline.evaluate( 0.05)), 1.5296875) - assert math.isclose(float(spline.evaluate( 0.10)), 2.0) - assert math.isclose(float(spline.evaluate( 0.25)), 1.7568359375) - assert math.isclose(float(spline.evaluate( 0.50)), 0.5) - assert math.isclose(float(spline.evaluate( 0.75)), 0.3203125) - assert math.isclose(float(spline.evaluate( 1.00)), 1.0) - assert math.isclose(float(spline.evaluate( 1.50)), 0.5) - assert math.isclose(float(spline.evaluate( 2.05)), 1.5296875) + assert math.isclose(float(spline.evaluate(0.00)), 1.0) + assert math.isclose(float(spline.evaluate(0.05)), 1.5296875) + assert math.isclose(float(spline.evaluate(0.10)), 2.0) + assert math.isclose(float(spline.evaluate(0.25)), 1.7568359375) + assert math.isclose(float(spline.evaluate(0.50)), 0.5) + assert math.isclose(float(spline.evaluate(0.75)), 0.3203125) + assert math.isclose(float(spline.evaluate(1.00)), 1.0) + assert math.isclose(float(spline.evaluate(1.50)), 0.5) + assert math.isclose(float(spline.evaluate(2.05)), 1.5296875) def check_gradient(spline, t, params, params_values, expected, rel_tol=1e-9): value = spline.evaluate(t) subs = {pname: pvalue for (pname, pvalue) in zip(params, params_values)} - for (p, exp) in zip(params, expected): + for p, exp in zip(params, expected): assert math.isclose(float(value.diff(p).subs(subs)), exp, rel_tol=rel_tol) def test_SplineUniformSensitivity(): - params = (a, b, c) = sp.symbols('a b c') + params = (a, b, c) = sp.symbols("a b c") params_values = [0.5, 1.0, 2.5] spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), - values_at_nodes=[3*a + b, c**2 - 3, 1, sp.log(b) + 3*c - 6*a], + values_at_nodes=[3 * a + b, c**2 - 3, 1, sp.log(b) + 3 * c - 6 * a], ) check_gradient(spline, 0.00, params, params_values, [3.0, 1.0, 0.0]) - check_gradient(spline, 0.25, params, params_values, [0.539062, 0.179688, 4.45312], rel_tol=1e-5) - check_gradient(spline, 1.0/3, params, params_values, [0.0, 0.0, 5.0]) + check_gradient( + spline, 0.25, params, params_values, [0.539062, 0.179688, 4.45312], rel_tol=1e-5 + ) + check_gradient(spline, 1.0 / 3, params, params_values, [0.0, 0.0, 5.0]) check_gradient(spline, 0.50, params, params_values, [0.1875, -0.125, 2.625]) - check_gradient(spline, 2.0/3, params, params_values, [0.0, 0.0, 0.0]) - check_gradient(spline, 0.75, params, params_values, [-1.07812, 0.179688, 0.1875], rel_tol=1e-5) + check_gradient(spline, 2.0 / 3, params, params_values, [0.0, 0.0, 0.0]) + check_gradient( + spline, 0.75, params, params_values, [-1.07812, 0.179688, 0.1875], rel_tol=1e-5 + ) check_gradient(spline, 1.00, params, params_values, [-6.0, 1.0, 3.0]) def test_SplineNonUniformSensitivity(): - params = (a, b, c) = sp.symbols('a b c') + params = (a, b, c) = sp.symbols("a b c") params_values = [0.5, 1.0, 2.5] spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=[0.0, 0.1, 0.5, 1.0], - values_at_nodes=[3*a + b, c**2 - 3, 1, sp.log(b) + 3*c - 6*a], + values_at_nodes=[3 * a + b, c**2 - 3, 1, sp.log(b) + 3 * c - 6 * a], + ) + check_gradient(spline, 0.00, params, params_values, [3.0, 1.0, 0.0]) + check_gradient( + spline, 0.05, params, params_values, [1.3125, 0.4375, 2.89062], rel_tol=1e-5 ) - check_gradient(spline, 0.00, params, params_values, [3.0, 1.0, 0.0]) - check_gradient(spline, 0.05, params, params_values, [1.3125, 0.4375, 2.89062], rel_tol=1e-5) - check_gradient(spline, 0.10, params, params_values, [0.0, 0.0, 5.0]) - check_gradient(spline, 0.30, params, params_values, [-0.45, -0.3, 3.6]) - check_gradient(spline, 0.50, params, params_values, [ 0.0, 0.0, 0.0]) - check_gradient(spline, 0.75, params, params_values, [-2.625, 0.4375, 0.921875]) - check_gradient(spline, 1.00, params, params_values, [-6.0, 1.0, 3.0]) + check_gradient(spline, 0.10, params, params_values, [0.0, 0.0, 5.0]) + check_gradient(spline, 0.30, params, params_values, [-0.45, -0.3, 3.6]) + check_gradient(spline, 0.50, params, params_values, [0.0, 0.0, 0.0]) + check_gradient(spline, 0.75, params, params_values, [-2.625, 0.4375, 0.921875]) + check_gradient(spline, 1.00, params, params_values, [-6.0, 1.0, 3.0]) def test_SplineExplicitSensitivity(): - params = (a, b, c) = sp.symbols('a b c') + params = (a, b, c) = sp.symbols("a b c") params_values = [0.5, 1.0, 2.5] spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), - values_at_nodes=[3*a + b, c**2 - 3, 1, sp.log(b) + 3*c - 6*a], - derivatives_at_nodes=[c**3 - 2, sp.sqrt(b) * sp.log(b) + 3*c, 4*a - sp.sin(b), 1], + values_at_nodes=[3 * a + b, c**2 - 3, 1, sp.log(b) + 3 * c - 6 * a], + derivatives_at_nodes=[ + c**3 - 2, + sp.sqrt(b) * sp.log(b) + 3 * c, + 4 * a - sp.sin(b), + 1, + ], ) - check_gradient(spline, 0.00, params, params_values, [3.0, 1.0, 0.0]) - check_gradient(spline, 0.25, params, params_values, [0.46875, 0.109375, 4.37109], rel_tol=1e-6) - check_gradient(spline, 1.0/3, params, params_values, [0.0, 0.0, 5.0]) - check_gradient(spline, 0.50, params, params_values, [-0.166667, 0.0641793, 2.625], rel_tol=1e-5) - check_gradient(spline, 2.0/3, params, params_values, [ 0.0, 0.0, 0.0]) - check_gradient(spline, 0.75, params, params_values, [-0.75, 0.130923, 0.46875], rel_tol=1e-5) - check_gradient(spline, 1.00, params, params_values, [-6.0, 1.0, 3.0]) + check_gradient(spline, 0.00, params, params_values, [3.0, 1.0, 0.0]) + check_gradient( + spline, 0.25, params, params_values, [0.46875, 0.109375, 4.37109], rel_tol=1e-6 + ) + check_gradient(spline, 1.0 / 3, params, params_values, [0.0, 0.0, 5.0]) + check_gradient( + spline, 0.50, params, params_values, [-0.166667, 0.0641793, 2.625], rel_tol=1e-5 + ) + check_gradient(spline, 2.0 / 3, params, params_values, [0.0, 0.0, 0.0]) + check_gradient( + spline, 0.75, params, params_values, [-0.75, 0.130923, 0.46875], rel_tol=1e-5 + ) + check_gradient(spline, 1.00, params, params_values, [-6.0, 1.0, 3.0]) def test_SplineLogarithmicSensitivity(): - params = (a, b, c) = sp.symbols('a b c') + params = (a, b, c) = sp.symbols("a b c") params_values = [0.5, 1.0, 2.5] spline = amici.splines.CubicHermiteSpline( - sbml_id='f', + sbml_id="f", evaluate_at=amici.sbml_utils.amici_time_symbol, nodes=amici.splines.UniformGrid(0, 1, number_of_nodes=4), - values_at_nodes=[3*a + b, c**2 - 3, 1, sp.log(b) + 3*c - 6*a], + values_at_nodes=[3 * a + b, c**2 - 3, 1, sp.log(b) + 3 * c - 6 * a], logarithmic_parametrization=True, ) - check_gradient(spline, 0.00, params, params_values, [3.0, 1.0, 0.0]) - check_gradient(spline, 0.25, params, params_values, [0.585881, 0.195294, 4.38532], rel_tol=1e-5) - check_gradient(spline, 1.0/3, params, params_values, [0.0, 0.0, 5.0]) - check_gradient(spline, 0.50, params, params_values, [0.514003, -0.132395, 1.52044], rel_tol=1e-5) - check_gradient(spline, 2.0/3, params, params_values, [ 0.0, 0.0, 0.0]) - check_gradient(spline, 0.75, params, params_values, [-0.820743, 0.13679, -0.0577988], rel_tol=1e-5) - check_gradient(spline, 1.00, params, params_values, [-6.0, 1.0, 3.0]) + check_gradient(spline, 0.00, params, params_values, [3.0, 1.0, 0.0]) + check_gradient( + spline, 0.25, params, params_values, [0.585881, 0.195294, 4.38532], rel_tol=1e-5 + ) + check_gradient(spline, 1.0 / 3, params, params_values, [0.0, 0.0, 5.0]) + check_gradient( + spline, + 0.50, + params, + params_values, + [0.514003, -0.132395, 1.52044], + rel_tol=1e-5, + ) + check_gradient(spline, 2.0 / 3, params, params_values, [0.0, 0.0, 0.0]) + check_gradient( + spline, + 0.75, + params, + params_values, + [-0.820743, 0.13679, -0.0577988], + rel_tol=1e-5, + ) + check_gradient(spline, 1.00, params, params_values, [-6.0, 1.0, 3.0]) diff --git a/python/tests/test_splines_short.py b/python/tests/test_splines_short.py index ed1841d240..b9cd4f2a93 100644 --- a/python/tests/test_splines_short.py +++ b/python/tests/test_splines_short.py @@ -29,10 +29,10 @@ def test_two_splines(**kwargs): Test a SBML model containing two splines. """ spline0, params0, tols0 = example_spline_1( - 0, num_nodes=4, fixed_values=[0, 2], extrapolate='linear' + 0, num_nodes=4, fixed_values=[0, 2], extrapolate="linear" ) spline1, params1, tols1 = example_spline_1( - 1, num_nodes=5, scale=1.5, offset=5, extrapolate='linear' + 1, num_nodes=5, scale=1.5, offset=5, extrapolate="linear" ) splines = [spline0, spline1] @@ -46,27 +46,28 @@ def test_two_splines(**kwargs): tols1 = (tols1, tols1, tols1) tols = [] - for (t0, t1) in zip(tols0, tols1): + for t0, t1 in zip(tols0, tols1): keys = set().union(t0.keys(), t1.keys()) t = { key: max( t0.get(key, 0.0), t1.get(key, 0.0), - ) for key in keys + ) + for key in keys } tols.append(t) - tols[1]['x_rtol'] = max(1e-9, tols[1].get('x_rtol', -np.inf)) - tols[1]['x_atol'] = max(5e-9, tols[1].get('x_atol', -np.inf)) - tols[1]['sx_rtol'] = max(1e-5, tols[1].get('llh_rtol', -np.inf)) - tols[1]['sx_atol'] = max(5e-9, tols[1].get('sx_atol', -np.inf)) - tols[1]['llh_rtol'] = max(5e-14, tols[1].get('llh_rtol', -np.inf)) - tols[1]['sllh_atol'] = max(5e-5, tols[1].get('sllh_atol', -np.inf)) + tols[1]["x_rtol"] = max(1e-9, tols[1].get("x_rtol", -np.inf)) + tols[1]["x_atol"] = max(5e-9, tols[1].get("x_atol", -np.inf)) + tols[1]["sx_rtol"] = max(1e-5, tols[1].get("llh_rtol", -np.inf)) + tols[1]["sx_atol"] = max(5e-9, tols[1].get("sx_atol", -np.inf)) + tols[1]["llh_rtol"] = max(5e-14, tols[1].get("llh_rtol", -np.inf)) + tols[1]["sllh_atol"] = max(5e-5, tols[1].get("sllh_atol", -np.inf)) - tols[2]['x_rtol'] = max(5e-10, tols[2].get('x_rtol', -np.inf)) - tols[2]['x_atol'] = max(1e-8, tols[2].get('x_atol', -np.inf)) - tols[2]['llh_rtol'] = max(5e-14, tols[2].get('llh_rtol', -np.inf)) - tols[2]['sllh_atol'] = max(5e-5, tols[2].get('sllh_atol', -np.inf)) + tols[2]["x_rtol"] = max(5e-10, tols[2].get("x_rtol", -np.inf)) + tols[2]["x_atol"] = max(1e-8, tols[2].get("x_atol", -np.inf)) + tols[2]["llh_rtol"] = max(5e-14, tols[2].get("llh_rtol", -np.inf)) + tols[2]["sllh_atol"] = max(5e-5, tols[2].get("sllh_atol", -np.inf)) check_splines_full(splines, params, tols, check_piecewise=False, **kwargs) @@ -80,44 +81,46 @@ def test_splines_plist(): xx = UniformGrid(0, 5, number_of_nodes=3) yy = np.asarray([0.0, 1.0, 0.5]) spline1 = CubicHermiteSpline( - 'y1', + "y1", nodes=xx, values_at_nodes=yy, - bc='auto', extrapolate=(None, 'constant'), + bc="auto", + extrapolate=(None, "constant"), ) # Dummy spline #2 xx = UniformGrid(0, 5, number_of_nodes=4) yy = np.asarray([0.0, 0.5, -0.5, 0.5]) spline2 = CubicHermiteSpline( - 'y2', + "y2", nodes=xx, values_at_nodes=yy, - bc='auto', extrapolate=(None, 'constant'), + bc="auto", + extrapolate=(None, "constant"), ) # Real spline #3 xx = UniformGrid(0, 5, number_of_nodes=6) - p1, p2, p3, p4, p5 = sp.symbols('p1 p2 p3 p4 p5') + p1, p2, p3, p4, p5 = sp.symbols("p1 p2 p3 p4 p5") yy = np.asarray([p1 + p2, p2 * p3, p4, sp.cos(p1 + p3), p4 * sp.log(p1), p3]) dd = np.asarray([-0.75, -0.875, p5, 0.125, 1.15057181, 0.0]) - params = { - p1: 1.0, p2: 0.5, p3: 1.5, p4: -0.25, p5: -0.5 - } + params = {p1: 1.0, p2: 0.5, p3: 1.5, p4: -0.25, p5: -0.5} # print([y.subs(params).evalf() for y in yy]) spline3 = CubicHermiteSpline( - 'y3', + "y3", nodes=xx, values_at_nodes=yy, derivatives_at_nodes=dd, - bc='auto', extrapolate=(None, 'constant'), + bc="auto", + extrapolate=(None, "constant"), ) # Dummy spline 4 xx = UniformGrid(0, 5, number_of_nodes=3) yy = np.asarray([0.0, -0.5, 0.5]) spline4 = CubicHermiteSpline( - 'y4', + "y4", nodes=xx, values_at_nodes=yy, - bc='auto', extrapolate=(None, 'constant'), + bc="auto", + extrapolate=(None, "constant"), ) tols = dict( x_rtol=1e-6, @@ -128,7 +131,9 @@ def test_splines_plist(): sllh_atol=5e-9, ) check_splines_full( - [spline1, spline2, spline3, spline4], params, tols, + [spline1, spline2, spline3, spline4], + params, + tols, check_piecewise=False, check_forward=False, check_adjoint=True, # plist cannot be checked, but complex parameter dependence can diff --git a/python/tests/test_swig_interface.py b/python/tests/test_swig_interface.py index eeaebceede..c2ae631030 100644 --- a/python/tests/test_swig_interface.py +++ b/python/tests/test_swig_interface.py @@ -21,10 +21,12 @@ def test_copy_constructors(pysb_example_presimulation_module): for obj in [model, solver]: for attr in dir(obj): - if attr.startswith('__') \ - or attr == 'this' \ - or attr == 'thisown' \ - or is_callable_but_not_getter(obj, attr): + if ( + attr.startswith("__") + or attr == "this" + or attr == "thisown" + or is_callable_but_not_getter(obj, attr) + ): continue # objects will be initialized with default values so we @@ -47,8 +49,7 @@ def test_copy_constructors(pysb_example_presimulation_module): obj_clone = obj.clone() - assert get_val(obj, attr) == get_val(obj_clone, attr), \ - f"{obj} - {attr}" + assert get_val(obj, attr) == get_val(obj_clone, attr), f"{obj} - {attr}" # `None` values are skipped in `test_model_instance_settings`. @@ -61,57 +62,51 @@ def test_copy_constructors(pysb_example_presimulation_module): # Default values are based on `pysb_example_presimulation_module`. model_instance_settings0 = { # setting name: [default value, custom value] - 'AddSigmaResiduals': [ - False, - True - ], - 'AlwaysCheckFinite': [ + "AddSigmaResiduals": [False, True], + "AlwaysCheckFinite": [ False, True, ], # Skipped due to model dependency in `'InitialStates'`. - 'FixedParameters': None, - 'InitialStates': [ + "FixedParameters": None, + "InitialStates": [ (10.0, 9.0, 1.0, 0.0, 0.0, 0.0), - tuple([.1]*6), + tuple([0.1] * 6), ], - ('getInitialStateSensitivities', 'setUnscaledInitialStateSensitivities'): [ - tuple([1.0] + [0.0]*35), - tuple([.1]*36), + ("getInitialStateSensitivities", "setUnscaledInitialStateSensitivities"): [ + tuple([1.0] + [0.0] * 35), + tuple([0.1] * 36), ], - 'MinimumSigmaResiduals': [ + "MinimumSigmaResiduals": [ 50.0, 60.0, ], - ('nMaxEvent', 'setNMaxEvent'): [ + ("nMaxEvent", "setNMaxEvent"): [ 10, 20, ], - 'Parameters': [ - (10.0, 0.1, 0.1, 0.1, 0.1, 0.1), - tuple([1.0] * 6) - ], + "Parameters": [(10.0, 0.1, 0.1, 0.1, 0.1, 0.1), tuple([1.0] * 6)], # Skipped due to interdependency with `'InitialStateSensitivities'`. - 'ParameterList': None, + "ParameterList": None, # Skipped due to interdependency with `'InitialStateSensitivities'`. - 'ParameterScale': None, + "ParameterScale": None, # Skipped due to interdependencies with # `'ReinitializeFixedParameterInitialStates'`. - 'ReinitializationStateIdxs': None, + "ReinitializationStateIdxs": None, # Skipped due to interdependencies with `'ReinitializationStateIdxs'`. - 'ReinitializeFixedParameterInitialStates': None, + "ReinitializeFixedParameterInitialStates": None, # Skipped due to conservation laws in the test model # `pysb_example_presimulation_module.getModel()`. - 'StateIsNonNegative': None, - 'SteadyStateSensitivityMode': [ + "StateIsNonNegative": None, + "SteadyStateSensitivityMode": [ 0, 1, ], - ('t0', 'setT0'): [ + ("t0", "setT0"): [ 0.0, 1.0, ], - 'Timepoints': [ + "Timepoints": [ tuple(), (1.0, 2.0, 3.0), ], @@ -129,24 +124,26 @@ def test_model_instance_settings(pysb_example_presimulation_module): i_setter = 1 # All settings are tested. - assert set(model_instance_settings0) \ - == set(amici.swig_wrappers.model_instance_settings) + assert set(model_instance_settings0) == set( + amici.swig_wrappers.model_instance_settings + ) # Skip settings with interdependencies. - model_instance_settings = \ - {k: v for k, v in model_instance_settings0.items() if v is not None} + model_instance_settings = { + k: v for k, v in model_instance_settings0.items() if v is not None + } # All custom values are different to default values. assert all( default != custom for name, (default, custom) in model_instance_settings.items() - if name != 'ReinitializeFixedParameterInitialStates' + if name != "ReinitializeFixedParameterInitialStates" ) # All default values are as expected. for name, (default, custom) in model_instance_settings.items(): - getter = name[i_getter] if isinstance(name, tuple) else f'get{name}' - setter = name[i_setter] if isinstance(name, tuple) else f'set{name}' + getter = name[i_getter] if isinstance(name, tuple) else f"get{name}" + setter = name[i_setter] if isinstance(name, tuple) else f"set{name}" # Default values are as expected. assert getattr(model0, getter)() == default # Custom value is set correctly. @@ -164,15 +161,17 @@ def test_model_instance_settings(pysb_example_presimulation_module): # The new model has the default settings. model_default_settings = amici.get_model_settings(model) for name in model_instance_settings: - if (name == "InitialStates" and not model.hasCustomInitialStates())\ - or (name == ('getInitialStateSensitivities', - 'setUnscaledInitialStateSensitivities') - and not model.hasCustomInitialStateSensitivities()): + if (name == "InitialStates" and not model.hasCustomInitialStates()) or ( + name + == ("getInitialStateSensitivities", "setUnscaledInitialStateSensitivities") + and not model.hasCustomInitialStateSensitivities() + ): # Here the expected value differs from what the getter would return assert model_default_settings[name] == [] else: - assert model_default_settings[name] == \ - model_instance_settings[name][i_default], name + assert ( + model_default_settings[name] == model_instance_settings[name][i_default] + ), name # The grouped setter method works. custom_settings_not_none = { @@ -197,20 +196,20 @@ def test_interdependent_settings(pysb_example_presimulation_module): model = pysb_example_presimulation_module.getModel() original_settings = { - 'FixedParameters': (9.0, 1.0), - 'ParameterList': (0, 1, 2, 3, 4, 5), - 'ParameterScale': [0, 0, 0, 0, 0, 0], - 'ReinitializationStateIdxs': tuple(), - 'ReinitializeFixedParameterInitialStates': False, - 'StateIsNonNegative': (False, False, False), + "FixedParameters": (9.0, 1.0), + "ParameterList": (0, 1, 2, 3, 4, 5), + "ParameterScale": [0, 0, 0, 0, 0, 0], + "ReinitializationStateIdxs": tuple(), + "ReinitializeFixedParameterInitialStates": False, + "StateIsNonNegative": (False, False, False), } expected_settings = { - 'FixedParameters': (8.0, 2.0), - 'ParameterList': (0, 1, 2, 3, 4), - 'ParameterScale': [1, 0, 0, 0, 0, 0], - 'ReinitializationStateIdxs': (0,), - 'ReinitializeFixedParameterInitialStates': True, + "FixedParameters": (8.0, 2.0), + "ParameterList": (0, 1, 2, 3, 4), + "ParameterScale": [1, 0, 0, 0, 0, 0], + "ReinitializationStateIdxs": (0,), + "ReinitializeFixedParameterInitialStates": True, # Skipped due to conservation laws in the test model. # 'StateIsNonNegative': None, } @@ -218,14 +217,13 @@ def test_interdependent_settings(pysb_example_presimulation_module): # Some values need to be transformed to be tested in Python # (e.g. SWIG objects). Default transformer is no transformation # (the identity function). - getter_transformers = { - setting: (lambda x: x) - for setting in original_settings - } - getter_transformers.update({ - # Convert from SWIG object. - 'ParameterScale': lambda x: list(x) - }) + getter_transformers = {setting: (lambda x: x) for setting in original_settings} + getter_transformers.update( + { + # Convert from SWIG object. + "ParameterScale": lambda x: list(x) + } + ) default_settings = amici.get_model_settings(model) for original_setting, original_setting_value in original_settings.items(): @@ -241,18 +239,14 @@ def test_interdependent_settings(pysb_example_presimulation_module): amici.set_model_settings(model, input_settings) output_settings = amici.get_model_settings(model) - test_value = getter_transformers[setting]( - output_settings[setting] - ) + test_value = getter_transformers[setting](output_settings[setting]) # The setter works. assert test_value == expected_value input_settings = {setting: output_settings[setting]} amici.set_model_settings(model, input_settings) output_settings = amici.get_model_settings(model) - test_value = getter_transformers[setting]( - output_settings[setting] - ) + test_value = getter_transformers[setting](output_settings[setting]) # (round-trip) The output of the getter can be used as input to the # setter, and does not change the value. assert test_value == expected_value @@ -272,54 +266,53 @@ def test_unhandled_settings(pysb_example_presimulation_module): model = pysb_example_presimulation_module.getModel() not_handled = [ - 'get', - 'getAmiciCommit', - 'getAmiciVersion', - 'getExpressionIds', - 'getExpressionNames', - 'getFixedParameterById', - 'getFixedParameterByName', - 'getFixedParameterIds', - 'getFixedParameterNames', - 'getName', - 'getObservableIds', - 'getObservableNames', - 'getObservableScaling', - 'getParameterById', - 'getParameterByName', - 'getParameterIds', - 'getParameterNames', - 'getSolver', - 'getStateIds', - 'getStateNames', - 'getStateIdsSolver', - 'getStateNamesSolver', - 'getTimepoint', - 'getUnscaledParameters', - 'setAllStatesNonNegative', - 'setFixedParameterById', - 'setFixedParameterByName', - 'setFixedParametersByIdRegex', - 'setFixedParametersByNameRegex', - 'setParameterById', - 'setParameterByName', - 'setParametersByIdRegex', - 'setParametersByNameRegex', - 'setInitialStateSensitivities', + "get", + "getAmiciCommit", + "getAmiciVersion", + "getExpressionIds", + "getExpressionNames", + "getFixedParameterById", + "getFixedParameterByName", + "getFixedParameterIds", + "getFixedParameterNames", + "getName", + "getObservableIds", + "getObservableNames", + "getObservableScaling", + "getParameterById", + "getParameterByName", + "getParameterIds", + "getParameterNames", + "getSolver", + "getStateIds", + "getStateNames", + "getStateIdsSolver", + "getStateNamesSolver", + "getTimepoint", + "getUnscaledParameters", + "setAllStatesNonNegative", + "setFixedParameterById", + "setFixedParameterByName", + "setFixedParametersByIdRegex", + "setFixedParametersByNameRegex", + "setParameterById", + "setParameterByName", + "setParametersByIdRegex", + "setParametersByNameRegex", + "setInitialStateSensitivities", ] from amici.swig_wrappers import model_instance_settings + handled = [ name for names in model_instance_settings for name in ( - names - if isinstance(names, tuple) else - (f'get{names}', f'set{names}') + names if isinstance(names, tuple) else (f"get{names}", f"set{names}") ) ] for attribute in dir(model): - if attribute[:3] in ['get', 'set'] and attribute not in not_handled: + if attribute[:3] in ["get", "set"] and attribute not in not_handled: assert attribute in handled, attribute @@ -327,11 +320,12 @@ def is_callable_but_not_getter(obj, attr): if not callable(getattr(obj, attr)): return False - if attr.startswith('get'): - return \ - 'set' + attr[3:] not in dir(obj) \ - or attr.endswith('ById') \ - or attr.endswith('ByName') + if attr.startswith("get"): + return ( + "set" + attr[3:] not in dir(obj) + or attr.endswith("ById") + or attr.endswith("ByName") + ) else: return True @@ -344,12 +338,12 @@ def get_val(obj, attr): def get_mod_val(val, attr): - if attr == 'getReturnDataReportingMode': + if attr == "getReturnDataReportingMode": return amici.RDataReporting.likelihood - elif attr == 'getParameterList': - return tuple(get_mod_val(val[0], '') for _ in val) - elif attr == 'getStateIsNonNegative': - raise ValueError('Cannot modify value') + elif attr == "getParameterList": + return tuple(get_mod_val(val[0], "") for _ in val) + elif attr == "getStateIsNonNegative": + raise ValueError("Cannot modify value") elif isinstance(val, bool): return not val elif isinstance(val, numbers.Number): @@ -357,12 +351,12 @@ def get_mod_val(val, attr): elif isinstance(val, tuple): return tuple(get_mod_val(v, attr) for v in val) - raise ValueError('Cannot modify value') + raise ValueError("Cannot modify value") def set_val(obj, attr, val): if callable(getattr(obj, attr)): - getattr(obj, 'set' + attr[3:])(val) + getattr(obj, "set" + attr[3:])(val) else: setattr(obj, attr, val) @@ -377,8 +371,7 @@ def test_model_instance_settings_custom_x0(pysb_example_presimulation_module): assert not model.hasCustomInitialStateSensitivities() settings = amici.get_model_settings(model) model.setInitialStates(model.getInitialStates()) - model.setUnscaledInitialStateSensitivities( - model.getInitialStateSensitivities()) + model.setUnscaledInitialStateSensitivities(model.getInitialStateSensitivities()) amici.set_model_settings(model, settings) assert not model.hasCustomInitialStates() assert not model.hasCustomInitialStateSensitivities() @@ -420,10 +413,10 @@ def test_edata_repr(): edata = amici.ExpData(ny, nz, ne, range(nt)) edata_ptr = amici.ExpDataPtr(edata.this) expected_strs = ( - f'{nt}x{ny} time-resolved datapoints', - f'{ne}x{nz} event-resolved datapoints', - f'(0/{ny * nt} measurements', - f'(0/{nz * ne} measurements' + f"{nt}x{ny} time-resolved datapoints", + f"{ne}x{nz} event-resolved datapoints", + f"(0/{ny * nt} measurements", + f"(0/{nz * ne} measurements", ) for e in [edata, edata_ptr]: for expected_str in expected_strs: @@ -431,4 +424,3 @@ def test_edata_repr(): assert expected_str in repr(e) # avoid double delete!! edata_ptr.release() - diff --git a/python/tests/util.py b/python/tests/util.py index c51070a8b0..aba8bb3b0b 100644 --- a/python/tests/util.py +++ b/python/tests/util.py @@ -9,7 +9,7 @@ runAmiciSimulation, SbmlImporter, SensitivityMethod, - SensitivityOrder + SensitivityOrder, ) from amici.gradient_check import _check_close @@ -18,16 +18,14 @@ def create_amici_model(sbml_model, model_name, **kwargs) -> AmiciModel: """ Import an sbml file and create an AMICI model from it """ - sbml_test_models = Path('sbml_test_models') - sbml_test_models_output_dir = sbml_test_models / 'amici_models' + sbml_test_models = Path("sbml_test_models") + sbml_test_models_output_dir = sbml_test_models / "amici_models" sbml_test_models_output_dir.mkdir(parents=True, exist_ok=True) sbml_importer = SbmlImporter(sbml_model) output_dir = sbml_test_models_output_dir / model_name sbml_importer.sbml2amici( - model_name=model_name, - output_dir=str(output_dir), - **kwargs + model_name=model_name, output_dir=str(output_dir), **kwargs ) model_module = import_model_module(model_name, str(output_dir.resolve())) @@ -36,12 +34,12 @@ def create_amici_model(sbml_model, model_name, **kwargs) -> AmiciModel: def create_sbml_model( - initial_assignments, - parameters, - rate_rules, - species, - events, - to_file: str = None, + initial_assignments, + parameters, + rate_rules, + species, + events, + to_file: str = None, ): """Create an SBML model from simple definitions. @@ -54,18 +52,18 @@ def create_sbml_model( model = document.createModel() compartment = model.createCompartment() - compartment.setId('compartment') + compartment.setId("compartment") compartment.setConstant(True) compartment.setSize(1) compartment.setSpatialDimensions(3) - compartment.setUnits('dimensionless') + compartment.setUnits("dimensionless") for species_id in species: species = model.createSpecies() species.setId(species_id) - species.setCompartment('compartment') + species.setCompartment("compartment") species.setConstant(False) - species.setSubstanceUnits('dimensionless') + species.setSubstanceUnits("dimensionless") species.setBoundaryCondition(False) species.setHasOnlySubstanceUnits(False) species.setInitialConcentration(1.0) @@ -85,7 +83,7 @@ def create_sbml_model( parameter.setId(parameter_id) parameter.setConstant(True) parameter.setValue(parameter_value) - parameter.setUnits('dimensionless') + parameter.setUnits("dimensionless") for event_id, event_def in events.items(): event = model.createEvent() @@ -93,7 +91,7 @@ def create_sbml_model( event.setName(event_id) event.setUseValuesFromTriggerTime(True) trigger = event.createTrigger() - trigger.setMath(libsbml.parseL3Formula(event_def['trigger'])) + trigger.setMath(libsbml.parseL3Formula(event_def["trigger"])) trigger.setPersistent(True) trigger.setInitialValue(True) @@ -102,15 +100,14 @@ def creat_event_assignment(target, assignment): ea.setVariable(target) ea.setMath(libsbml.parseL3Formula(assignment)) - if isinstance(event_def['target'], list): + if isinstance(event_def["target"], list): for event_target, event_assignment in zip( - event_def['target'], event_def['assignment'] + event_def["target"], event_def["assignment"] ): creat_event_assignment(event_target, event_assignment) else: - creat_event_assignment(event_def['target'], - event_def['assignment']) + creat_event_assignment(event_def["target"], event_def["assignment"]) if to_file: libsbml.writeSBMLToFile( @@ -124,8 +121,8 @@ def creat_event_assignment(target, assignment): def check_trajectories_without_sensitivities( - amici_model: AmiciModel, - result_expected_x: np.ndarray, + amici_model: AmiciModel, + result_expected_x: np.ndarray, ): """ Check whether the AMICI simulation matches a known solution @@ -136,22 +133,20 @@ def check_trajectories_without_sensitivities( solver = amici_model.getSolver() solver.setAbsoluteTolerance(1e-15) rdata = runAmiciSimulation(amici_model, solver=solver) - _check_close(rdata['x'], result_expected_x, field="x", - rtol=5e-5, atol=1e-13) + _check_close(rdata["x"], result_expected_x, field="x", rtol=5e-5, atol=1e-13) # Show that we can do arbitrary precision here (test 8 digits) solver = amici_model.getSolver() solver.setAbsoluteTolerance(1e-15) solver.setRelativeTolerance(1e-12) rdata = runAmiciSimulation(amici_model, solver=solver) - _check_close(rdata['x'], result_expected_x, field="x", - rtol=5e-9, atol=1e-13) + _check_close(rdata["x"], result_expected_x, field="x", rtol=5e-9, atol=1e-13) def check_trajectories_with_forward_sensitivities( - amici_model: AmiciModel, - result_expected_x: np.ndarray, - result_expected_sx: np.ndarray, + amici_model: AmiciModel, + result_expected_x: np.ndarray, + result_expected_sx: np.ndarray, ): """ Check whether the forward sensitivities of the AMICI simulation match @@ -164,10 +159,8 @@ def check_trajectories_with_forward_sensitivities( solver.setSensitivityOrder(SensitivityOrder.first) solver.setSensitivityMethod(SensitivityMethod.forward) rdata = runAmiciSimulation(amici_model, solver=solver) - _check_close(rdata['x'], result_expected_x, field="x", - rtol=1e-5, atol=1e-13) - _check_close(rdata['sx'], result_expected_sx, field="sx", - rtol=1e-5, atol=1e-7) + _check_close(rdata["x"], result_expected_x, field="x", rtol=1e-5, atol=1e-13) + _check_close(rdata["sx"], result_expected_sx, field="sx", rtol=1e-5, atol=1e-7) # Show that we can do arbitrary precision here (test 8 digits) solver = amici_model.getSolver() @@ -178,7 +171,5 @@ def check_trajectories_with_forward_sensitivities( solver.setAbsoluteToleranceFSA(1e-15) solver.setRelativeToleranceFSA(1e-13) rdata = runAmiciSimulation(amici_model, solver=solver) - _check_close(rdata['x'], result_expected_x, field="x", - rtol=1e-10, atol=1e-12) - _check_close(rdata['sx'], result_expected_sx, field="sx", - rtol=1e-10, atol=1e-9) + _check_close(rdata["x"], result_expected_x, field="x", rtol=1e-10, atol=1e-12) + _check_close(rdata["sx"], result_expected_sx, field="sx", rtol=1e-10, atol=1e-9) diff --git a/scripts/README.md b/scripts/README.md index d656d9d499..f7e75f34b4 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -1,9 +1,9 @@ # Contents of `scripts/` -This directory contains a number of build, installation, and CI scripts. +This directory contains a number of build, installation, and CI scripts. * `buildAll.sh` - + Build AMICI along with dependencies and test suite * `buildAmici.sh` @@ -12,14 +12,14 @@ This directory contains a number of build, installation, and CI scripts. * `buildBNGL.sh` - Download and build + Download and build [BioNetGen](https://github.com/RuleWorld/bionetgen) (required for some tests) - + * `buildSuiteSparse.sh` Build [SuiteSparse](http://faculty.cse.tamu.edu/davis/suitesparse.html) included in this repository - + * `buildSundials.sh` Build [Sundials](https://computation.llnl.gov/projects/sundials/) @@ -41,14 +41,14 @@ This directory contains a number of build, installation, and CI scripts. * `downloadAndBuildSwig.sh` - Download and build [SWIG](http://www.swig.org/) + Download and build [SWIG](http://www.swig.org/) * `installAmiciArchive.sh` Create a Python virtual environment and do an AMICI development installation * `installAmiciSource.sh` - + Create a Python virtual environment and do a regular AMICI installation * `run-codecov.sh` @@ -78,10 +78,10 @@ This directory contains a number of build, installation, and CI scripts. * `run-SBMLTestsuite.sh` - Download and run the semantic + Download and run the semantic [SBML Test Suite](https://github.com/sbmlteam/sbml-test-suite/) * `run-valgrind.sh` Run memory leak check using valgrind for all unit and integration tests. - Assumes they have been built before in the default location. + Assumes they have been built before in the default location. diff --git a/scripts/run-cppcheck.sh b/scripts/run-cppcheck.sh index 57c669439a..5726bf8e3b 100755 --- a/scripts/run-cppcheck.sh +++ b/scripts/run-cppcheck.sh @@ -15,4 +15,3 @@ cppcheck \ "-I${AMICI_PATH}/include/" \ --enable=style \ "--exitcode-suppressions=${AMICI_PATH}/.cppcheck-exitcode-suppressions" - diff --git a/scripts/run-python-tests.sh b/scripts/run-python-tests.sh index 76b6bc49e9..982aa02f0f 100755 --- a/scripts/run-python-tests.sh +++ b/scripts/run-python-tests.sh @@ -16,4 +16,3 @@ pip install scipy h5py pytest pytest-cov # PEtab tests are run separately pytest --ignore-glob=*petab* --ignore-glob=*test_splines.py - diff --git a/swig/model_ode.i b/swig/model_ode.i index de342fef93..a372c1efad 100644 --- a/swig/model_ode.i +++ b/swig/model_ode.i @@ -15,5 +15,3 @@ using namespace amici; // Process symbols in header %include "amici/model_ode.h" - - diff --git a/tests/benchmark-models/benchmark_models.yaml b/tests/benchmark-models/benchmark_models.yaml index 6e291cb010..4e196e5261 100644 --- a/tests/benchmark-models/benchmark_models.yaml +++ b/tests/benchmark-models/benchmark_models.yaml @@ -116,4 +116,3 @@ Zheng_PNAS2012: t_fwd: 0.05 t_adj: 0.05 note: benchmark collection reference ignores factor 1/2 - diff --git a/tests/benchmark-models/evaluate_benchmark.py b/tests/benchmark-models/evaluate_benchmark.py index f1d88197db..358290a886 100644 --- a/tests/benchmark-models/evaluate_benchmark.py +++ b/tests/benchmark-models/evaluate_benchmark.py @@ -10,40 +10,45 @@ # read benchmark results for different models -outfile = 'computation_times.csv' -df = pd.concat([ - pd.read_csv(f, header=[0], index_col=[0]).rename(columns={'0': '_'.join(f.split('_')[:2])}).T - for f in os.listdir() if f.endswith('.csv') if f != outfile -]) -df.sort_values('np', inplace=True) +outfile = "computation_times.csv" +df = pd.concat( + [ + pd.read_csv(f, header=[0], index_col=[0]) + .rename(columns={"0": "_".join(f.split("_")[:2])}) + .T + for f in os.listdir() + if f.endswith(".csv") + if f != outfile + ] +) +df.sort_values("np", inplace=True) df.to_csv(outfile) -ratios = pd.concat( - [df[sensi]/df['t_sim'].values for sensi in ['t_fwd', 't_adj']] + [df.np], axis=1, -).reset_index().melt(id_vars=['index', 'np']).rename( - columns={'index': 'model', 'variable': 'sensitivity', 'value': 'ratio'} +ratios = ( + pd.concat( + [df[sensi] / df["t_sim"].values for sensi in ["t_fwd", "t_adj"]] + [df.np], + axis=1, + ) + .reset_index() + .melt(id_vars=["index", "np"]) + .rename(columns={"index": "model", "variable": "sensitivity", "value": "ratio"}) ) -ratios['sensitivity'] = ratios['sensitivity'].replace( - {'t_fwd': 'forward', 't_adj': 'adjoint'} +ratios["sensitivity"] = ratios["sensitivity"].replace( + {"t_fwd": "forward", "t_adj": "adjoint"} ) plt.figure(figsize=(10, 5)) g = sns.barplot( - data=ratios, - order=list(df.index), - x='model', - y='ratio', - hue='sensitivity' + data=ratios, order=list(df.index), x="model", y="ratio", hue="sensitivity" ) for ir, row in ratios.iterrows(): - if row.sensitivity == 'adjoint': + if row.sensitivity == "adjoint": continue - g.text(ir, row['np'], int(row['np']), color='black', ha="center", weight='bold') + g.text(ir, row["np"], int(row["np"]), color="black", ha="center", weight="bold") -plt.xticks(rotation=30, horizontalalignment='right') +plt.xticks(rotation=30, horizontalalignment="right") plt.tight_layout() -plt.savefig('computation_times.png') - +plt.savefig("computation_times.png") diff --git a/tests/benchmark-models/test_petab_benchmark.py b/tests/benchmark-models/test_petab_benchmark.py index 6ed237190d..1a5cd19682 100755 --- a/tests/benchmark-models/test_petab_benchmark.py +++ b/tests/benchmark-models/test_petab_benchmark.py @@ -22,25 +22,29 @@ ATOL: float = 1e-3 RTOL: float = 1e-2 -benchmark_path = Path(__file__).parent.parent.parent / "Benchmark-Models-PEtab" / "Benchmark-Models" +benchmark_path = ( + Path(__file__).parent.parent.parent / "Benchmark-Models-PEtab" / "Benchmark-Models" +) # reuse compiled models from test_benchmark_collection.sh benchmark_outdir = Path(__file__).parent.parent.parent / "test_bmc" models = [ str(petab_path.stem) - for petab_path in benchmark_path.glob("*") if petab_path.is_dir() - if str(petab_path.stem) not in ( + for petab_path in benchmark_path.glob("*") + if petab_path.is_dir() + if str(petab_path.stem) + not in ( # excluded due to excessive runtime - 'Bachmann_MSB2011', - 'Chen_MSB2009', - 'Froehlich_CellSystems2018', - 'Raimundez_PCB2020', - 'Lucarelli_CellSystems2018', - 'Isensee_JCB2018', - 'Beer_MolBioSystems2014', - 'Alkan_SciSignal2018', + "Bachmann_MSB2011", + "Chen_MSB2009", + "Froehlich_CellSystems2018", + "Raimundez_PCB2020", + "Lucarelli_CellSystems2018", + "Isensee_JCB2018", + "Beer_MolBioSystems2014", + "Alkan_SciSignal2018", # excluded due to excessive numerical failures - 'Crauste_CellSystems2017', - 'Fujita_SciSignal2010', + "Crauste_CellSystems2017", + "Fujita_SciSignal2010", ) ] @@ -54,13 +58,13 @@ @pytest.mark.parametrize("model", models) def test_benchmark_gradient(model, scale): if not scale and model in ( - 'Smith_BMCSystBiol2013', - 'Brannmark_JBC2010', - 'Elowitz_Nature2000', - 'Borghans_BiophysChem1997', - 'Sneyd_PNAS2002', - 'Bertozzi_PNAS2020', - 'Okuonghae_ChaosSolitonsFractals2020', + "Smith_BMCSystBiol2013", + "Brannmark_JBC2010", + "Elowitz_Nature2000", + "Borghans_BiophysChem1997", + "Sneyd_PNAS2002", + "Bertozzi_PNAS2020", + "Okuonghae_ChaosSolitonsFractals2020", ): # not really worth the effort trying to fix these cases if they # only fail on linear scale @@ -82,22 +86,20 @@ def test_benchmark_gradient(model, scale): amici_solver.setAbsoluteTolerance(1e-12) amici_solver.setRelativeTolerance(1e-12) if model in ( - 'Smith_BMCSystBiol2013', - 'Oliveira_NatCommun2021', + "Smith_BMCSystBiol2013", + "Oliveira_NatCommun2021", ): amici_solver.setAbsoluteTolerance(1e-10) amici_solver.setRelativeTolerance(1e-10) - elif model in ( - 'Okuonghae_ChaosSolitonsFractals2020', - ): + elif model in ("Okuonghae_ChaosSolitonsFractals2020",): amici_solver.setAbsoluteTolerance(1e-14) amici_solver.setRelativeTolerance(1e-14) amici_solver.setMaxSteps(int(1e5)) - if model in ( - 'Brannmark_JBC2010', - ): - amici_model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.integrationOnly) + if model in ("Brannmark_JBC2010",): + amici_model.setSteadyStateSensitivityMode( + amici.SteadyStateSensitivityMode.integrationOnly + ) amici_function, amici_derivative = simulate_petab_to_cached_functions( petab_problem=petab_problem, @@ -113,9 +115,13 @@ def test_benchmark_gradient(model, scale): np.random.seed(0) if scale: - point = np.asarray(list( - petab_problem.scale_parameters(dict(parameter_df_free.nominalValue)).values() - )) + point = np.asarray( + list( + petab_problem.scale_parameters( + dict(parameter_df_free.nominalValue) + ).values() + ) + ) point_noise = np.random.randn(len(point)) * noise_level else: point = parameter_df_free.nominalValue.values @@ -131,9 +137,7 @@ def test_benchmark_gradient(model, scale): 1e-4, 1e-5, ] - if model in ( - 'Okuonghae_ChaosSolitonsFractals2020', - ): + if model in ("Okuonghae_ChaosSolitonsFractals2020",): sizes.insert(0, 0.2) derivative = get_derivative( @@ -156,18 +160,22 @@ def test_benchmark_gradient(model, scale): success = check(rtol=RTOL, atol=ATOL) if debug: - df = pd.DataFrame([ - { - ('fd', r.metadata['size_absolute'], str(r.method_id)): r.value - for c in d.computers - for r in c.results - } for d in derivative.directional_derivatives - ], index=parameter_ids) - df[('fd', 'full', '')] = derivative.series.values - df[('amici', '', '')] = expected_derivative + df = pd.DataFrame( + [ + { + ("fd", r.metadata["size_absolute"], str(r.method_id)): r.value + for c in d.computers + for r in c.results + } + for d in derivative.directional_derivatives + ], + index=parameter_ids, + ) + df[("fd", "full", "")] = derivative.series.values + df[("amici", "", "")] = expected_derivative file_name = f"{model}_scale={scale}.tsv" - df.to_csv(debug_path / file_name, sep='\t') + df.to_csv(debug_path / file_name, sep="\t") # The gradients for all parameters are correct. assert success, derivative.df diff --git a/tests/benchmark-models/test_petab_model.py b/tests/benchmark-models/test_petab_model.py index 42b29bb983..0570242189 100755 --- a/tests/benchmark-models/test_petab_model.py +++ b/tests/benchmark-models/test_petab_model.py @@ -17,8 +17,7 @@ import amici from amici.logging import get_logger -from amici.petab_objective import (simulate_petab, rdatas_to_measurement_df, - LLH, RDATAS) +from amici.petab_objective import simulate_petab, rdatas_to_measurement_df, LLH, RDATAS from petab.visualize import plot_problem logger = get_logger(f"amici.{__name__}", logging.WARNING) @@ -32,33 +31,64 @@ def parse_cli_args(): """ parser = argparse.ArgumentParser( - description='Simulate PEtab-format model using AMICI.') + description="Simulate PEtab-format model using AMICI." + ) # General options: - parser.add_argument('-v', '--verbose', dest='verbose', action='store_true', - help='More verbose output') - parser.add_argument('-c', '--check', dest='check', action='store_true', - help='Compare to reference value') - parser.add_argument('-p', '--plot', dest='plot', action='store_true', - help='Plot measurement and simulation results') + parser.add_argument( + "-v", + "--verbose", + dest="verbose", + action="store_true", + help="More verbose output", + ) + parser.add_argument( + "-c", + "--check", + dest="check", + action="store_true", + help="Compare to reference value", + ) + parser.add_argument( + "-p", + "--plot", + dest="plot", + action="store_true", + help="Plot measurement and simulation results", + ) # PEtab problem - parser.add_argument('-y', '--yaml', dest='yaml_file_name', - required=True, - help='PEtab YAML problem filename') + parser.add_argument( + "-y", + "--yaml", + dest="yaml_file_name", + required=True, + help="PEtab YAML problem filename", + ) # Corresponding AMICI model - parser.add_argument('-m', '--model-name', dest='model_name', - help='Name of the AMICI module of the model to ' - 'simulate.', required=True) - parser.add_argument('-d', '--model-dir', dest='model_directory', - help='Directory containing the AMICI module of the ' - 'model to simulate. Required if model is not ' - 'in python path.') - - parser.add_argument('-o', '--simulation-file', dest='simulation_file', - help='File to write simulation result to, in PEtab' - 'measurement table format.') + parser.add_argument( + "-m", + "--model-name", + dest="model_name", + help="Name of the AMICI module of the model to " "simulate.", + required=True, + ) + parser.add_argument( + "-d", + "--model-dir", + dest="model_directory", + help="Directory containing the AMICI module of the " + "model to simulate. Required if model is not " + "in python path.", + ) + + parser.add_argument( + "-o", + "--simulation-file", + dest="simulation_file", + help="File to write simulation result to, in PEtab" "measurement table format.", + ) return parser.parse_args() @@ -70,9 +100,11 @@ def main(): loglevel = logging.DEBUG if args.verbose else logging.INFO logger.setLevel(loglevel) - logger.info(f"Simulating '{args.model_name}' " - f"({args.model_directory}) using PEtab data from " - f"{args.yaml_file_name}") + logger.info( + f"Simulating '{args.model_name}' " + f"({args.model_directory}) using PEtab data from " + f"{args.yaml_file_name}" + ) # load PEtab files problem = petab.Problem.from_yaml(args.yaml_file_name) @@ -88,7 +120,7 @@ def main(): amici_solver.setAbsoluteTolerance(1e-8) amici_solver.setRelativeTolerance(1e-8) amici_solver.setMaxSteps(int(1e4)) - if args.model_name in ('Brannmark_JBC2010', 'Isensee_JCB2018'): + if args.model_name in ("Brannmark_JBC2010", "Isensee_JCB2018"): amici_model.setSteadyStateSensitivityMode( amici.SteadyStateSensitivityMode.integrationOnly ) @@ -96,9 +128,9 @@ def main(): times = dict() for label, sensi_mode in { - 't_sim': amici.SensitivityMethod.none, - 't_fwd': amici.SensitivityMethod.forward, - 't_adj': amici.SensitivityMethod.adjoint + "t_sim": amici.SensitivityMethod.none, + "t_fwd": amici.SensitivityMethod.forward, + "t_adj": amici.SensitivityMethod.adjoint, }.items(): amici_solver.setSensitivityMethod(sensi_mode) if sensi_mode == amici.SensitivityMethod.none: @@ -107,35 +139,39 @@ def main(): amici_solver.setSensitivityOrder(amici.SensitivityOrder.first) res_repeats = [ - simulate_petab(petab_problem=problem, amici_model=amici_model, - solver=amici_solver, log_level=loglevel) + simulate_petab( + petab_problem=problem, + amici_model=amici_model, + solver=amici_solver, + log_level=loglevel, + ) for _ in range(3) # repeat to get more stable timings ] res = res_repeats[0] - times[label] = np.mean([ - sum(r.cpu_time + r.cpu_timeB for r in res[RDATAS]) / 1000 - # only forwards/backwards simulation - for res in res_repeats - ]) + times[label] = np.mean( + [ + sum(r.cpu_time + r.cpu_timeB for r in res[RDATAS]) / 1000 + # only forwards/backwards simulation + for res in res_repeats + ] + ) if sensi_mode == amici.SensitivityMethod.none: rdatas = res[RDATAS] llh = res[LLH] - times['np'] = sum(problem.parameter_df[petab.ESTIMATE]) + times["np"] = sum(problem.parameter_df[petab.ESTIMATE]) - pd.Series(times).to_csv( - f'./tests/benchmark-models/{args.model_name}_benchmark.csv' - ) + pd.Series(times).to_csv(f"./tests/benchmark-models/{args.model_name}_benchmark.csv") for rdata in rdatas: - assert rdata.status == amici.AMICI_SUCCESS, \ - f"Simulation failed for {rdata.id}" + assert rdata.status == amici.AMICI_SUCCESS, f"Simulation failed for {rdata.id}" # create simulation PEtab table - sim_df = rdatas_to_measurement_df(rdatas=rdatas, model=amici_model, - measurement_df=problem.measurement_df) + sim_df = rdatas_to_measurement_df( + rdatas=rdatas, model=amici_model, measurement_df=problem.measurement_df + ) sim_df.rename(columns={petab.MEASUREMENT: petab.SIMULATION}, inplace=True) if args.simulation_file: @@ -148,14 +184,16 @@ def main(): # save figure for plot_id, ax in axs.items(): - fig_path = os.path.join(args.model_directory, - f"{args.model_name}_{plot_id}_vis.png") + fig_path = os.path.join( + args.model_directory, f"{args.model_name}_{plot_id}_vis.png" + ) logger.info(f"Saving figure to {fig_path}") ax.get_figure().savefig(fig_path, dpi=150) if args.check: - references_yaml = os.path.join(os.path.dirname(__file__), - "benchmark_models.yaml") + references_yaml = os.path.join( + os.path.dirname(__file__), "benchmark_models.yaml" + ) with open(references_yaml) as f: refs = yaml.full_load(f) @@ -166,14 +204,15 @@ def main(): rtol = 1e-3 adiff = np.abs(llh - ref_llh) atol = 1e-3 - tolstr = f' Absolute difference is {adiff:.2e} ' \ - f'(tol {atol:.2e}) and relative difference is ' \ - f'{rdiff:.2e} (tol {rtol:.2e}).' + tolstr = ( + f" Absolute difference is {adiff:.2e} " + f"(tol {atol:.2e}) and relative difference is " + f"{rdiff:.2e} (tol {rtol:.2e})." + ) if np.isclose(llh, ref_llh, rtol=rtol, atol=atol): logger.info( - f"Computed llh {llh:.4e} matches reference {ref_llh:.4e}." - + tolstr + f"Computed llh {llh:.4e} matches reference {ref_llh:.4e}." + tolstr ) else: logger.error( @@ -182,13 +221,15 @@ def main(): ) sys.exit(1) except KeyError: - logger.error("No reference likelihood found for " - f"{args.model_name} in {references_yaml}") + logger.error( + "No reference likelihood found for " + f"{args.model_name} in {references_yaml}" + ) for label, key in { - 'simulation': 't_sim', - 'adjoint sensitivity': 't_adj', - 'forward sensitivity': 't_fwd', + "simulation": "t_sim", + "adjoint sensitivity": "t_adj", + "forward sensitivity": "t_fwd", }.items(): try: ref = refs[args.model_name][key] @@ -204,8 +245,10 @@ def main(): f"within reference ({ref:.2e})." ) except KeyError: - logger.error(f"No reference time for {label} found for " - f"{args.model_name} in {references_yaml}") + logger.error( + f"No reference time for {label} found for " + f"{args.model_name} in {references_yaml}" + ) if __name__ == "__main__": diff --git a/tests/conftest.py b/tests/conftest.py index a213d1901c..967b25ed0e 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,8 +11,9 @@ # stores passed SBML semantic test suite IDs passed_ids = [] -SBML_SEMANTIC_CASES_DIR = \ - Path(__file__).parent / 'sbml-test-suite' / 'cases' / 'semantic' +SBML_SEMANTIC_CASES_DIR = ( + Path(__file__).parent / "sbml-test-suite" / "cases" / "semantic" +) @pytest.fixture @@ -29,11 +30,11 @@ def parse_selection(selection_str: str, last: int) -> List[int]: Valid input e.g.: "1", "1,3", "-3,4,6-7" """ indices = [] - for group in selection_str.split(','): - if not re.match(r'^(?:-?\d+|\d+-\d*)$', group): + for group in selection_str.split(","): + if not re.match(r"^(?:-?\d+|\d+-\d*)$", group): print("Invalid selection", group) sys.exit() - spl = group.split('-') + spl = group.split("-") if len(spl) == 1: indices.append(int(spl[0])) elif len(spl) == 2: @@ -46,9 +47,10 @@ def parse_selection(selection_str: str, last: int) -> List[int]: def get_all_semantic_case_ids(): """Get iterator over test sorted IDs of all cases in the SBML semantic suite""" - pattern = re.compile(r'\d{5}') - return sorted(str(x.name) for x in SBML_SEMANTIC_CASES_DIR.iterdir() - if pattern.match(x.name)) + pattern = re.compile(r"\d{5}") + return sorted( + str(x.name) for x in SBML_SEMANTIC_CASES_DIR.iterdir() if pattern.match(x.name) + ) def pytest_addoption(parser): @@ -77,12 +79,12 @@ def pytest_generate_tests(metafunc): def pytest_sessionfinish(session, exitstatus): """Process test results""" global passed_ids - terminalreporter = session.config.pluginmanager.get_plugin( - 'terminalreporter') + terminalreporter = session.config.pluginmanager.get_plugin("terminalreporter") terminalreporter.ensure_newline() # parse test names to get passed case IDs (don't know any better way to # access fixture values) from testSBMLSuite import format_test_id + passed_ids = [format_test_id(_) for _ in passed_ids] if passed_ids: write_passed_tags(passed_ids, terminalreporter) @@ -99,21 +101,22 @@ def write_passed_tags(passed_ids, out=sys.stdout): passed_component_tags |= cur_component_tags passed_test_tags |= cur_test_tags - out.write("\nAt least one test with the following component tags has " - "passed:\n") - out.write(' ' + '\n '.join(sorted(passed_component_tags))) - out.write("\n\nAt least one test with the following test tags has " - "passed:\n") - out.write(' ' + '\n '.join(sorted(passed_test_tags))) + out.write("\nAt least one test with the following component tags has " "passed:\n") + out.write(" " + "\n ".join(sorted(passed_component_tags))) + out.write("\n\nAt least one test with the following test tags has " "passed:\n") + out.write(" " + "\n ".join(sorted(passed_test_tags))) def pytest_runtest_logreport(report: "TestReport") -> None: """Collect test case IDs of passed SBML semantic test suite cases""" - if report.when == 'call'\ - and report.outcome == 'passed'\ - and '::test_sbml_testsuite_case[' in report.nodeid: - test_case_id = re.sub(r'^.*::test_sbml_testsuite_case\[(\d+)].*$', - r'\1', report.nodeid) + if ( + report.when == "call" + and report.outcome == "passed" + and "::test_sbml_testsuite_case[" in report.nodeid + ): + test_case_id = re.sub( + r"^.*::test_sbml_testsuite_case\[(\d+)].*$", r"\1", report.nodeid + ) passed_ids.append(test_case_id) @@ -124,19 +127,19 @@ def get_tags_for_test(test_id: str) -> Tuple[Set[str], Set[str]]: Tuple of set of strings for componentTags and testTags """ current_test_path = SBML_SEMANTIC_CASES_DIR / test_id - info_file = current_test_path / f'{test_id}-model.m' + info_file = current_test_path / f"{test_id}-model.m" with open(info_file) as f: component_tags = set() test_tags = set() for line in f: - if line.startswith('testTags:'): - test_tags = set( - re.split(r'[ ,:]', line[len('testTags:'):].strip())) - test_tags.discard('') - if line.startswith('componentTags:'): + if line.startswith("testTags:"): + test_tags = set(re.split(r"[ ,:]", line[len("testTags:") :].strip())) + test_tags.discard("") + if line.startswith("componentTags:"): component_tags = set( - re.split(r'[ ,:]', line[len('componentTags:'):].strip())) - component_tags.discard('') + re.split(r"[ ,:]", line[len("componentTags:") :].strip()) + ) + component_tags.discard("") if test_tags and component_tags: return component_tags, test_tags print(f"No componentTags or testTags found for test case {test_id}.") diff --git a/tests/cpp/wrapTestModels.m b/tests/cpp/wrapTestModels.m index 28963e2e3a..80d8d05936 100644 --- a/tests/cpp/wrapTestModels.m +++ b/tests/cpp/wrapTestModels.m @@ -4,14 +4,14 @@ function wrapTestModels() % % Return values: % void - + amiciPath = fileparts(mfilename('fullpath')); amiciPath = [amiciPath '/../../matlab']; - + %% EXAMPLE STEADYSTATE - + cd([amiciPath '/examples/example_steadystate/']); - + try [exdir,~,~]=fileparts(which('example_steadystate.m')); amiwrap('model_steadystate','model_steadystate_syms',exdir); @@ -19,10 +19,10 @@ function wrapTestModels() disp(err.message) cd(fileparts(mfilename('fullpath'))); end - + %% EXAMPLE DIRAC cd([amiciPath '/examples/example_dirac/']); - + try [exdir,~,~]=fileparts(which('example_dirac.m')); amiwrap('model_dirac','model_dirac_syms',exdir); @@ -30,10 +30,10 @@ function wrapTestModels() disp(err.message) cd(fileparts(mfilename('fullpath'))); end - + %% EXAMPLE JAKSTAT cd([amiciPath '/examples/example_jakstat_adjoint/']); - + try [exdir,~,~]=fileparts(which('example_jakstat_adjoint.m')); amiwrap('model_jakstat_adjoint', 'model_jakstat_adjoint_syms', exdir, 1); @@ -44,7 +44,7 @@ function wrapTestModels() %% EXAMPLE NEURON cd([amiciPath '/examples/example_neuron/']); - + try [exdir,~,~]=fileparts(which('example_neuron.m')); amiwrap('model_neuron', 'model_neuron_syms', exdir, 1); @@ -54,10 +54,10 @@ function wrapTestModels() end cd(fileparts(mfilename('fullpath'))); - + %% EXAMPLE EVENTS cd([amiciPath '/examples/example_events/']); - + try [exdir,~,~]=fileparts(which('example_events.m')); amiwrap('model_events', 'model_events_syms', exdir); @@ -65,10 +65,10 @@ function wrapTestModels() disp(err.message) cd(fileparts(mfilename('fullpath'))); end - + %% EXAMPLE NESTED EVENTS cd([amiciPath '/examples/example_nested_events/']); - + try [exdir,~,~]=fileparts(which('example_nested_events.m')); amiwrap('model_nested_events', 'model_nested_events_syms', exdir); @@ -78,10 +78,10 @@ function wrapTestModels() end cd(fileparts(mfilename('fullpath'))); - + %% EXAMPLE ROBERTSON cd([amiciPath '/examples/example_robertson/']); - + try [exdir,~,~]=fileparts(which('example_robertson.m')); amiwrap('model_robertson', 'model_robertson_syms', exdir); @@ -91,10 +91,10 @@ function wrapTestModels() end cd(fileparts(mfilename('fullpath'))); - + %% EXAMPLE CALVETTI cd([amiciPath '/examples/example_calvetti/']); - + try [exdir,~,~]=fileparts(which('example_calvetti.m')); amiwrap('model_calvetti', 'model_calvetti_syms', exdir); @@ -104,6 +104,5 @@ function wrapTestModels() end cd(fileparts(mfilename('fullpath'))); - -end +end diff --git a/tests/generateTestConfig/example.py b/tests/generateTestConfig/example.py index 6fad77dfa9..a0b2891344 100644 --- a/tests/generateTestConfig/example.py +++ b/tests/generateTestConfig/example.py @@ -2,81 +2,77 @@ import numpy as np import pandas as pd + def dict2hdf5(object, dictionary): for key, value in dictionary.items(): if isArray(value): a = np.array(value) if not len(value): - dtype = 'f8' + dtype = "f8" elif isArray(value[0]): if isinstance(value[0][0], (np.float64, float)): - dtype = 'f8' + dtype = "f8" else: - dtype = ' List[int]: Valid input e.g.: "1", "1,3", "-3,4,6-7" """ indices = [] - for group in selection_str.split(','): - if not re.match(r'^(?:-?\d+)|(?:\d+(?:-\d+))$', group): + for group in selection_str.split(","): + if not re.match(r"^(?:-?\d+)|(?:\d+(?:-\d+))$", group): print("Invalid selection", group) sys.exit() - spl = group.split('-') + spl = group.split("-") if len(spl) == 1: indices.append(int(spl[0])) elif len(spl) == 2: @@ -34,17 +34,18 @@ def pytest_addoption(parser): # TODO: re-enable in #1800 # parser.addoption("--only-pysb", help="Run only PySB tests", # action="store_true") - parser.addoption("--only-sbml", help="Run only SBML tests", - action="store_true", ) + parser.addoption( + "--only-sbml", + help="Run only SBML tests", + action="store_true", + ) def pytest_generate_tests(metafunc): """Parameterize tests""" # Run for all PEtab test suite cases - if "case" in metafunc.fixturenames \ - and "model_type" in metafunc.fixturenames: - + if "case" in metafunc.fixturenames and "model_type" in metafunc.fixturenames: # Get CLI option cases = metafunc.config.getoption("--petab-cases") if cases: @@ -56,10 +57,11 @@ def pytest_generate_tests(metafunc): if metafunc.config.getoption("--only-sbml"): argvalues = [ - (case, 'sbml', version) - for version in ('v1.0.0', ) - for case in (test_numbers if test_numbers - else get_cases("sbml", version=version)) + (case, "sbml", version) + for version in ("v1.0.0",) + for case in ( + test_numbers if test_numbers else get_cases("sbml", version=version) + ) ] # TODO: re-enable in #1800 # elif metafunc.config.getoption("--only-pysb"): @@ -70,8 +72,8 @@ def pytest_generate_tests(metafunc): # ] else: argvalues = [] - for version in ('v1.0.0',): - for format in ('sbml',): + for version in ("v1.0.0",): + for format in ("sbml",): argvalues.extend( (case, format, version) for case in test_numbers or get_cases(format, version) diff --git a/tests/petab_test_suite/test_petab_suite.py b/tests/petab_test_suite/test_petab_suite.py index 2e1761ef61..f66478b893 100755 --- a/tests/petab_test_suite/test_petab_suite.py +++ b/tests/petab_test_suite/test_petab_suite.py @@ -15,8 +15,11 @@ from amici.gradient_check import check_derivatives as amici_check_derivatives from amici.logging import get_logger, set_log_level from amici.petab_import import PysbPetabProblem, import_petab_problem -from amici.petab_objective import (create_parameterized_edatas, - rdatas_to_measurement_df, simulate_petab) +from amici.petab_objective import ( + create_parameterized_edatas, + rdatas_to_measurement_df, + simulate_petab, +) logger = get_logger(__name__, logging.DEBUG) set_log_level(get_logger("amici.petab_import"), logging.DEBUG) @@ -29,11 +32,14 @@ def test_case(case, model_type, version): try: _test_case(case, model_type, version) except Exception as e: - if isinstance(e, NotImplementedError) \ - or "Timepoint-specific parameter overrides" in str(e): - logger.info(f"Case {case} expectedly failed. " - "Required functionality is not yet " - f"implemented: {e}") + if isinstance( + e, NotImplementedError + ) or "Timepoint-specific parameter overrides" in str(e): + logger.info( + f"Case {case} expectedly failed. " + "Required functionality is not yet " + f"implemented: {e}" + ) pytest.skip(str(e)) else: raise e @@ -50,16 +56,16 @@ def _test_case(case, model_type, version): problem = petab.Problem.from_yaml(yaml_file) # compile amici model - if case.startswith('0006') and model_type != "pysb": + if case.startswith("0006") and model_type != "pysb": petab.flatten_timepoint_specific_output_overrides(problem) - model_name = f"petab_{model_type}_test_case_{case}"\ - f"_{version.replace('.', '_')}" - model_output_dir = f'amici_models/{model_name}' + model_name = f"petab_{model_type}_test_case_{case}" f"_{version.replace('.', '_')}" + model_output_dir = f"amici_models/{model_name}" model = import_petab_problem( petab_problem=problem, model_output_dir=model_output_dir, model_name=model_name, - force_compile=True) + force_compile=True, + ) solver = model.getSolver() solver.setSteadyStateToleranceFactor(1.0) @@ -71,23 +77,23 @@ def _test_case(case, model_type, version): log_level=logging.DEBUG, ) - rdatas = ret['rdatas'] - chi2 = sum(rdata['chi2'] for rdata in rdatas) - llh = ret['llh'] - simulation_df = rdatas_to_measurement_df(rdatas, model, - problem.measurement_df) + rdatas = ret["rdatas"] + chi2 = sum(rdata["chi2"] for rdata in rdatas) + llh = ret["llh"] + simulation_df = rdatas_to_measurement_df(rdatas, model, problem.measurement_df) petab.check_measurement_df(simulation_df, problem.observable_df) - simulation_df = simulation_df.rename( - columns={petab.MEASUREMENT: petab.SIMULATION}) + simulation_df = simulation_df.rename(columns={petab.MEASUREMENT: petab.SIMULATION}) simulation_df[petab.TIME] = simulation_df[petab.TIME].astype(int) solution = petabtests.load_solution(case, model_type, version=version) gt_chi2 = solution[petabtests.CHI2] gt_llh = solution[petabtests.LLH] gt_simulation_dfs = solution[petabtests.SIMULATION_DFS] - if case.startswith('0006'): + if case.startswith("0006"): # account for flattening - gt_simulation_dfs[0].loc[:, petab.OBSERVABLE_ID] = ('obs_a__10__c0', - 'obs_a__15__c0') + gt_simulation_dfs[0].loc[:, petab.OBSERVABLE_ID] = ( + "obs_a__10__c0", + "obs_a__15__c0", + ) tol_chi2 = solution[petabtests.TOL_CHI2] tol_llh = solution[petabtests.TOL_LLH] tol_simulations = solution[petabtests.TOL_SIMULATIONS] @@ -95,41 +101,43 @@ def _test_case(case, model_type, version): chi2s_match = petabtests.evaluate_chi2(chi2, gt_chi2, tol_chi2) llhs_match = petabtests.evaluate_llh(llh, gt_llh, tol_llh) simulations_match = petabtests.evaluate_simulations( - [simulation_df], gt_simulation_dfs, tol_simulations) + [simulation_df], gt_simulation_dfs, tol_simulations + ) - logger.log(logging.DEBUG if simulations_match else logging.ERROR, - f"Simulations: match = {simulations_match}") + logger.log( + logging.DEBUG if simulations_match else logging.ERROR, + f"Simulations: match = {simulations_match}", + ) if not simulations_match: - with pd.option_context('display.max_rows', None, - 'display.max_columns', None, - 'display.width', 200): - logger.log(logging.DEBUG, f"x_ss: {model.getStateIds()} " - f"{[rdata.x_ss for rdata in rdatas]}") - logger.log(logging.ERROR, - f"Expected simulations:\n{gt_simulation_dfs}") - logger.log(logging.ERROR, - f"Actual simulations:\n{simulation_df}") - logger.log(logging.DEBUG if chi2s_match else logging.ERROR, - f"CHI2: simulated: {chi2}, expected: {gt_chi2}," - f" match = {chi2s_match}") - logger.log(logging.DEBUG if simulations_match else logging.ERROR, - f"LLH: simulated: {llh}, expected: {gt_llh}, " - f"match = {llhs_match}") + with pd.option_context( + "display.max_rows", None, "display.max_columns", None, "display.width", 200 + ): + logger.log( + logging.DEBUG, + f"x_ss: {model.getStateIds()} " f"{[rdata.x_ss for rdata in rdatas]}", + ) + logger.log(logging.ERROR, f"Expected simulations:\n{gt_simulation_dfs}") + logger.log(logging.ERROR, f"Actual simulations:\n{simulation_df}") + logger.log( + logging.DEBUG if chi2s_match else logging.ERROR, + f"CHI2: simulated: {chi2}, expected: {gt_chi2}," f" match = {chi2s_match}", + ) + logger.log( + logging.DEBUG if simulations_match else logging.ERROR, + f"LLH: simulated: {llh}, expected: {gt_llh}, " f"match = {llhs_match}", + ) check_derivatives(problem, model, solver) if not all([llhs_match, simulations_match]) or not chi2s_match: logger.error(f"Case {case} failed.") - raise AssertionError(f"Case {case}: Test results do not match " - "expectations") + raise AssertionError(f"Case {case}: Test results do not match " "expectations") logger.info(f"Case {case} passed.") def check_derivatives( - problem: petab.Problem, - model: amici.Model, - solver: amici.Solver + problem: petab.Problem, model: amici.Model, solver: amici.Solver ) -> None: """Check derivatives using finite differences for all experimental conditions @@ -139,18 +147,21 @@ def check_derivatives( model: AMICI model matching ``problem`` solver: AMICI solver """ - problem_parameters = {t.Index: getattr(t, petab.NOMINAL_VALUE) for t in - problem.parameter_df.itertuples()} + problem_parameters = { + t.Index: getattr(t, petab.NOMINAL_VALUE) + for t in problem.parameter_df.itertuples() + } solver.setSensitivityMethod(amici.SensitivityMethod.forward) solver.setSensitivityOrder(amici.SensitivityOrder.first) # Required for case 9 to not fail in # amici::NewtonSolver::computeNewtonSensis model.setSteadyStateSensitivityMode( - SteadyStateSensitivityMode.integrateIfNewtonFails) + SteadyStateSensitivityMode.integrateIfNewtonFails + ) for edata in create_parameterized_edatas( - amici_model=model, petab_problem=problem, - problem_parameters=problem_parameters): + amici_model=model, petab_problem=problem, problem_parameters=problem_parameters + ): # check_derivatives does currently not support parameters in ExpData model.setParameters(edata.parameters) model.setParameterScale(edata.pscale) @@ -165,11 +176,11 @@ def run(): n_skipped = 0 n_total = 0 for version in ("v1.0.0",): - cases = petabtests.get_cases('sbml', version=version) + cases = petabtests.get_cases("sbml", version=version) n_total += len(cases) for case in cases: try: - test_case(case, 'sbml', version=version) + test_case(case, "sbml", version=version) n_success += 1 except Skipped: n_skipped += 1 @@ -178,11 +189,10 @@ def run(): logger.error(f"Case {case} failed.") logger.error(e) - logger.info(f"{n_success} / {n_total} successful, " - f"{n_skipped} skipped") + logger.info(f"{n_success} / {n_total} successful, " f"{n_skipped} skipped") if n_success != len(cases): sys.exit(1) -if __name__ == '__main__': +if __name__ == "__main__": run() diff --git a/tests/testSBMLSuite.py b/tests/testSBMLSuite.py index 51c5046535..e2177a6d5b 100755 --- a/tests/testSBMLSuite.py +++ b/tests/testSBMLSuite.py @@ -29,7 +29,7 @@ @pytest.fixture(scope="session") def result_path() -> Path: - return Path(__file__).parent / 'amici-semantic-results' + return Path(__file__).parent / "amici-semantic-results" @pytest.fixture(scope="function", autouse=True) @@ -45,11 +45,7 @@ def sbml_test_dir(): sys.path = old_path -def test_sbml_testsuite_case( - test_number, - result_path, - sbml_semantic_cases_dir -): +def test_sbml_testsuite_case(test_number, result_path, sbml_semantic_cases_dir): test_id = format_test_id(test_number) model_dir = None @@ -60,43 +56,43 @@ def test_sbml_testsuite_case( # key: case ID; value: epsilon for finite differences sensitivity_check_cases = { # parameter-dependent conservation laws - '00783': 1.5e-2, + "00783": 1.5e-2, # initial events - '00995': 1e-3, + "00995": 1e-3, } try: current_test_path = sbml_semantic_cases_dir / test_id # parse expected results - results_file = current_test_path / f'{test_id}-results.csv' - results = pd.read_csv(results_file, delimiter=',') - results.rename(columns={c: c.replace(' ', '') - for c in results.columns}, - inplace=True) + results_file = current_test_path / f"{test_id}-results.csv" + results = pd.read_csv(results_file, delimiter=",") + results.rename( + columns={c: c.replace(" ", "") for c in results.columns}, inplace=True + ) # setup model - model_dir = Path(__file__).parent / 'SBMLTestModels' / test_id + model_dir = Path(__file__).parent / "SBMLTestModels" / test_id model, solver, wrapper = compile_model( - current_test_path, test_id, model_dir, - generate_sensitivity_code=test_id in sensitivity_check_cases) + current_test_path, + test_id, + model_dir, + generate_sensitivity_code=test_id in sensitivity_check_cases, + ) settings = read_settings_file(current_test_path, test_id) atol, rtol = apply_settings(settings, solver, model, test_id) # simulate model rdata = amici.runAmiciSimulation(model, solver) - if rdata['status'] != amici.AMICI_SUCCESS: - if test_id in ( - '00748', '00374', '00369' - ): - pytest.skip('Simulation Failed expectedly') + if rdata["status"] != amici.AMICI_SUCCESS: + if test_id in ("00748", "00374", "00369"): + pytest.skip("Simulation Failed expectedly") else: - raise RuntimeError('Simulation failed unexpectedly') + raise RuntimeError("Simulation failed unexpectedly") # verify - simulated = verify_results(settings, rdata, results, wrapper, - model, atol, rtol) + simulated = verify_results(settings, rdata, results, wrapper, model, atol, rtol) # record results write_result_file(simulated, test_id, result_path) @@ -114,36 +110,33 @@ def test_sbml_testsuite_case( shutil.rmtree(model_dir, ignore_errors=True) -def verify_results( - settings, rdata, expected, wrapper, - model, atol, rtol -): +def verify_results(settings, rdata, expected, wrapper, model, atol, rtol): """Verify test results""" amount_species, variables = get_amount_and_variables(settings) # collect states simulated = pd.DataFrame( - rdata['y'], - columns=[obs['name'] - for obs in wrapper.symbols[SymbolId.OBSERVABLE].values()] + rdata["y"], + columns=[obs["name"] for obs in wrapper.symbols[SymbolId.OBSERVABLE].values()], ) - simulated['time'] = rdata['ts'] + simulated["time"] = rdata["ts"] # collect parameters for par in model.getParameterIds(): - simulated[par] = rdata['ts'] * 0 + model.getParameterById(par) + simulated[par] = rdata["ts"] * 0 + model.getParameterById(par) # collect fluxes for expr_idx, expr_id in enumerate(model.getExpressionIds()): if expr_id.startswith("flux_"): simulated[expr_id.removeprefix("flux_")] = rdata.w[:, expr_idx] # handle renamed reserved symbols - simulated.rename(columns={c: c.replace('amici_', '') - for c in simulated.columns}, inplace=True) + simulated.rename( + columns={c: c.replace("amici_", "") for c in simulated.columns}, inplace=True + ) # SBML test suite case 01308 defines species with initialAmount and # hasOnlySubstanceUnits="true", but then request results as concentrations. requested_concentrations = [ - s for s in - settings['concentration'].replace(' ', '').replace('\n', '').split(',') + s + for s in settings["concentration"].replace(" ", "").replace("\n", "").split(",") if s ] # We only need to convert species that have only substance units @@ -153,13 +146,15 @@ def verify_results( **wrapper.symbols[SymbolId.SPECIES], **wrapper.symbols[SymbolId.ALGEBRAIC_STATE], }.items() - if str(state_id) in requested_concentrations and state.get('amount', False) + if str(state_id) in requested_concentrations and state.get("amount", False) ] - amounts_to_concentrations(concentration_species, wrapper, - simulated, requested_concentrations) + amounts_to_concentrations( + concentration_species, wrapper, simulated, requested_concentrations + ) - concentrations_to_amounts(amount_species, wrapper, simulated, - requested_concentrations) + concentrations_to_amounts( + amount_species, wrapper, simulated, requested_concentrations + ) # simulated may contain `object` dtype columns and `expected` may # contain `np.int64` columns, so we cast everything to `np.float64`. @@ -170,18 +165,19 @@ def verify_results( except KeyError as e: raise KeyError(f"Missing simulated value for `{variable}`") from e assert_allclose( - actual, expectation, atol, rtol, equal_nan=True, - err_msg=f"Mismatch for {variable}" + actual, + expectation, + atol, + rtol, + equal_nan=True, + err_msg=f"Mismatch for {variable}", ) - return simulated[variables + ['time']] + return simulated[variables + ["time"]] def amounts_to_concentrations( - amount_species, - wrapper, - simulated, - requested_concentrations + amount_species, wrapper, simulated, requested_concentrations ): """ Convert AMICI simulated amounts to concentrations @@ -197,18 +193,16 @@ def amounts_to_concentrations( This allows for the reuse of the concentrations_to_amounts method... """ for species in amount_species: - if species != '': + if species != "": simulated.loc[:, species] = 1 / simulated.loc[:, species] - concentrations_to_amounts([species], wrapper, simulated, - requested_concentrations) + concentrations_to_amounts( + [species], wrapper, simulated, requested_concentrations + ) simulated.loc[:, species] = 1 / simulated.loc[:, species] def concentrations_to_amounts( - amount_species, - wrapper, - simulated, - requested_concentrations + amount_species, wrapper, simulated, requested_concentrations ): """Convert AMICI simulated concentrations to amounts""" for species in amount_species: @@ -225,20 +219,15 @@ def concentrations_to_amounts( # Species with OnlySubstanceUnits don't have to be converted as long # as we don't request concentrations for them. Only applies when # called from amounts_to_concentrations. - if (is_amt and species not in requested_concentrations) \ - or comp is None: + if (is_amt and species not in requested_concentrations) or comp is None: continue simulated.loc[:, species] *= simulated.loc[ - :, comp if comp in simulated.columns else f'amici_{comp}' + :, comp if comp in simulated.columns else f"amici_{comp}" ] -def write_result_file( - simulated: pd.DataFrame, - test_id: str, - result_path: Path -): +def write_result_file(simulated: pd.DataFrame, test_id: str, result_path: Path): """ Create test result file for upload to http://raterule.caltech.edu/Facilities/Database @@ -247,7 +236,7 @@ def write_result_file( """ # TODO: only states are reported here, not compartments or parameters - filename = result_path / f'{test_id}.csv' + filename = result_path / f"{test_id}.csv" simulated.to_csv(filename, index=False) @@ -255,16 +244,10 @@ def get_amount_and_variables(settings): """Read amount and species from settings file""" # species for which results are expected as amounts - amount_species = settings['amount'] \ - .replace(' ', '') \ - .replace('\n', '') \ - .split(',') + amount_species = settings["amount"].replace(" ", "").replace("\n", "").split(",") # IDs of all variables for which results are expected/provided - variables = settings['variables'] \ - .replace(' ', '') \ - .replace('\n', '') \ - .split(',') + variables = settings["variables"].replace(" ", "").replace("\n", "").split(",") return amount_species, variables @@ -272,12 +255,13 @@ def get_amount_and_variables(settings): def apply_settings(settings, solver, model, test_id: str): """Apply model and solver settings as specified in the test case""" # start/duration/steps may be empty - ts = np.linspace(float(settings['start'] or 0), - float(settings['start'] or 0) - + float(settings['duration'] or 0), - int(settings['steps'] or 0) + 1) - atol = float(settings['absolute']) - rtol = float(settings['relative']) + ts = np.linspace( + float(settings["start"] or 0), + float(settings["start"] or 0) + float(settings["duration"] or 0), + int(settings["steps"] or 0) + 1, + ) + atol = float(settings["absolute"]) + rtol = float(settings["relative"]) model.setTimepoints(ts) solver.setMaxSteps(int(1e6)) @@ -291,18 +275,23 @@ def apply_settings(settings, solver, model, test_id: str): return atol, rtol -def compile_model(sbml_dir: Path, test_id: str, model_dir: Path, - generate_sensitivity_code: bool = False): +def compile_model( + sbml_dir: Path, + test_id: str, + model_dir: Path, + generate_sensitivity_code: bool = False, +): """Import the given test model to AMICI""" model_dir.mkdir(parents=True, exist_ok=True) sbml_file = find_model_file(sbml_dir, test_id) sbml_importer = amici.SbmlImporter(sbml_file) - model_name = f'SBMLTest{test_id}' + model_name = f"SBMLTest{test_id}" sbml_importer.sbml2amici( - model_name, output_dir=model_dir, - generate_sensitivity_code=generate_sensitivity_code + model_name, + output_dir=model_dir, + generate_sensitivity_code=generate_sensitivity_code, ) # settings @@ -317,27 +306,27 @@ def compile_model(sbml_dir: Path, test_id: str, model_dir: Path, def find_model_file(current_test_path: Path, test_id: str) -> Path: """Find model file for the given test (guess filename extension)""" - sbml_file = current_test_path / f'{test_id}-sbml-l3v2.xml' + sbml_file = current_test_path / f"{test_id}-sbml-l3v2.xml" if not sbml_file.is_file(): # fallback l3v1 - sbml_file = current_test_path / f'{test_id}-sbml-l3v1.xml' + sbml_file = current_test_path / f"{test_id}-sbml-l3v1.xml" if not sbml_file.is_file(): # fallback l2v5 - sbml_file = current_test_path / f'{test_id}-sbml-l2v5.xml' + sbml_file = current_test_path / f"{test_id}-sbml-l2v5.xml" return sbml_file def read_settings_file(current_test_path: Path, test_id: str): """Read settings for the given test""" - settings_file = current_test_path / f'{test_id}-settings.txt' + settings_file = current_test_path / f"{test_id}-settings.txt" settings = {} with open(settings_file) as f: for line in f: - if line != '\n': - (key, val) = line.split(':') + if line != "\n": + (key, val) = line.split(":") settings[key] = val.strip() return settings