diff --git a/.cppcheck-exitcode-suppressions b/.cppcheck-exitcode-suppressions new file mode 100644 index 0000000000..fcc48d95ea --- /dev/null +++ b/.cppcheck-exitcode-suppressions @@ -0,0 +1 @@ +alloca:*:* diff --git a/CMakeLists.txt b/CMakeLists.txt index 0608664109..cf40b33264 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -194,6 +194,8 @@ add_custom_target( swig/CMakeLists_model.cmake swig/modelname.template.i ) +set_target_properties(fileTemplates + PROPERTIES INCLUDE_DIRECTORIES "${CMAKE_SOURCE_DIR}/include/") if($ENV{ENABLE_GCOV_COVERAGE}) diff --git a/README.md b/README.md index 750fc1b2c6..efac331da8 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,8 @@ AMICI provides a multi-language (Python, C++, Matlab) interface for the [IDAS](https://computing.llnl.gov/projects/sundials/idas) (for algebraic differential equations). AMICI allows the user to read differential equation models specified as [SBML](http://sbml.org/) -and automatically compiles such models as `.mex` simulation files +or [PySB](http://pysb.org/) +and automatically compiles such models into `.mex` simulation files (Matlab), C++ executables or Python modules. In contrast to the (no longer maintained) @@ -17,7 +18,7 @@ C++ code, which allows for a significantly faster simulation. Beyond forward integration, the compiled simulation file also allows for forward sensitivity analysis, steady state sensitivity analysis and -adjoint sensitivity analysis for likelihood based output functions. +adjoint sensitivity analysis for likelihood-based output functions. The interface was designed to provide routines for efficient gradient computation in parameter estimation of biochemical reaction models but @@ -28,6 +29,7 @@ constrained optimization problems. ## Features * SBML import (see details below) +* PySB import * Generation of C++ code for model simulation and sensitivity computation * Access to and high customizability of CVODES and IDAS solver @@ -40,7 +42,7 @@ constrained optimization problems. * Pre-equilibration and pre-simulation conditions * Support for [discrete events and logical operations](https://academic.oup.com/bioinformatics/article/33/7/1049/2769435) - + (Matlab-only) ## Interfaces & workflow @@ -55,21 +57,21 @@ is then used for model simulation. ## Getting started -AMICI installation instructions are provided -[here](http://icb-dcm.github.io/AMICI/md__i_n_s_t_a_l_l.html). +The AMICI source code is available at https://github.com/ICB-DCM/AMICI/. +To install AMICI, first read the +[installation instructions](http://icb-dcm.github.io/AMICI/md__i_n_s_t_a_l_l.html). -To get you started with Python-AMICI the best way might be this +To get you started with Python-AMICI, the best way might be checking out this [Jupyter notebook](https://github.com/ICB-DCM/AMICI/blob/master/python/examples/example_steadystate/ExampleSteadystate.ipynb). -For Matlab, various examples are available -[here](https://github.com/ICB-DCM/AMICI/tree/master/matlab/examples). - +To get started with Matlab-AMICI, various examples are available +in [matlab/examples/](https://github.com/ICB-DCM/AMICI/tree/master/matlab/examples). Comprehensive documentation on installation and usage of AMICI is available online at [http://icb-dcm.github.io/AMICI/](http://icb-dcm.github.io/AMICI/). -Any contributions to AMICI are welcome, read more contributing -[here](http://icb-dcm.github.io/AMICI/md__c_o_n_t_r_i_b_u_t_i_n_g.html). +Any [contributions](http://icb-dcm.github.io/AMICI/md__c_o_n_t_r_i_b_u_t_i_n_g.html) +to AMICI are welcome (code, bug reports, suggestions for improvements, ...). ### Getting help @@ -88,18 +90,25 @@ If you used AMICI in your work, we are happy to include your project, please let us know via a Github issue. When using AMICI in your project, please cite -* [Fröhlich, F., Kaltenbacher, B., Theis, F. J., & Hasenauer, J. (2017). Scalable Parameter Estimation for Genome-Scale Biochemical Reaction Networks. Plos Computational Biology, 13(1), e1005331. doi: 10.1371/journal.pcbi.1005331](https://doi.org/10.1371/journal.pcbi.1005331) +* Fröhlich, F., Kaltenbacher, B., Theis, F. J., & Hasenauer, J. (2017). + Scalable Parameter Estimation for Genome-Scale Biochemical Reaction Networks. + Plos Computational Biology, 13(1), e1005331. + doi:[10.1371/journal.pcbi.1005331](https://doi.org/10.1371/journal.pcbi.1005331) and/or -* [Fröhlich, F., Theis, F. J., Rädler, J. O., & Hasenauer, J. (2017). Parameter estimation for dynamical systems with discrete events and logical operations. Bioinformatics, 33(7), 1049-1056. doi: 10.1093/bioinformatics/btw764](https://doi.org/10.1093/bioinformatics/btw764) +* Fröhlich, F., Theis, F. J., Rädler, J. O., & Hasenauer, J. (2017). + Parameter estimation for dynamical systems with discrete events and logical + operations. Bioinformatics, 33(7), 1049-1056. + doi:[10.1093/bioinformatics/btw764](https://doi.org/10.1093/bioinformatics/btw764) ## Status of SBML support in Python-AMICI -Python-AMICI currently passes 494 out of the 1780 (~28%) test cases from +Python-AMICI currently passes 500 out of the 1780 (~28%) test cases from the semantic -[SBML Test Suite](https://github.com/sbmlteam/sbml-test-suite/). +[SBML Test Suite](https://github.com/sbmlteam/sbml-test-suite/) +([current status](https://github.com/ICB-DCM/AMICI/actions)). -In additional, we currently plan to add support for the following features +In addition, we currently plan to add support for the following features (see corresponding issues for details and progress): - Events (currently Matlab-only) @@ -108,7 +117,6 @@ In additional, we currently plan to add support for the following features - Species assignment rules - Compartment assignment rules - Models without species -- Logical operators contributions are welcome. diff --git a/documentation/README.md b/documentation/README.md index 8108891f5e..bc491d0d4d 100644 --- a/documentation/README.md +++ b/documentation/README.md @@ -33,11 +33,8 @@ This is configured in `matlab/mtoc/config`. ## Python documentation -Python documentation is processed by doxygen using the script and filters -in `scripts/`. - -**NOTE:** Unfortunately, the current doxygen Python filter is unable to deal -with Python type hints (https://github.com/ICB-DCM/AMICI/issues/613). +Python documentation is processed by doxygen and doxypypy using the script and +filters in `scripts/`. ## Out-of-source documentation @@ -60,7 +57,7 @@ Some guidelines: * Please stick to the limit of 80 characters per line for readability of raw 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. diff --git a/documentation/development.md b/documentation/development.md index eedfeb5d88..f8b951bc91 100644 --- a/documentation/development.md +++ b/documentation/development.md @@ -19,7 +19,7 @@ and deploy a new release on [PyPI](https://pypi.org/project/amici/). We try to keep a clean git history. Therefore, feature pull requests are squash-merged to `develop`. Merging of release branches to master is done via -merge commits. +merge commits. ## When starting to work on some issue @@ -127,6 +127,9 @@ described below: * We want to maintain compatibility with g++, clang and the Intel C++ compiler +* For code formatting, we use the settings from `.clang-format` in the root + directory + * *Details to be defined* diff --git a/include/amici/misc.h b/include/amici/misc.h index 8b6a20cead..72f536acbd 100644 --- a/include/amici/misc.h +++ b/include/amici/misc.h @@ -11,7 +11,7 @@ #include namespace amici { - + /** * @brief creates a slice from existing data * @@ -20,9 +20,9 @@ namespace amici { * @param size slice size * @return span of the slice */ - - gsl::span slice(std::vector &data, const int index, - const unsigned size); + + gsl::span slice(std::vector &data, int index, + unsigned size); /** * @brief Checks the values in an array for NaNs and Infs diff --git a/include/amici/model.h b/include/amici/model.h index 542abb971c..1ab5e5f847 100644 --- a/include/amici/model.h +++ b/include/amici/model.h @@ -183,7 +183,7 @@ class Model : public AbstractModel { * @param sx pointer to state variable sensititivies * @param x pointer to state variables */ - void initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x); + void initializeStateSensitivities(AmiVectorArray &sx, const AmiVector &x); /** * Initialises the heaviside variables h at the intial time t0 @@ -191,7 +191,7 @@ class Model : public AbstractModel { * @param x pointer to state variables * @param dx pointer to time derivative of states (DAE only) */ - void initHeaviside(AmiVector &x, AmiVector &dx); + void initHeaviside(const AmiVector &x, const AmiVector &dx); /** * @brief Number of parameters wrt to which sensitivities are computed @@ -524,7 +524,7 @@ class Model : public AbstractModel { * @param it time index * @return t timepoint */ - realtype getTimepoint(const int it) const; + realtype getTimepoint(int it) const; /** * @brief Set the timepoint vector diff --git a/python/amici/__init__.py b/python/amici/__init__.py index 0fdf6654ad..c26c9ff281 100644 --- a/python/amici/__init__.py +++ b/python/amici/__init__.py @@ -32,6 +32,38 @@ import sys from contextlib import suppress + +def _get_amici_path(): + """ + Determine package installation path, or, if used directly from git + repository, get repository root + """ + basedir = os.path.dirname(os.path.dirname(os.path.dirname(__file__))) + if os.path.exists(os.path.join(basedir, '.git')): + return os.path.abspath(basedir) + return os.path.dirname(__file__) + + +def _get_commit_hash(): + """Get commit hash from file""" + 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'), + ] + if os.path.isfile(file) + ), + None + ) + + if commitfile: + with open(commitfile) as f: + return str(re.search(r'^([\w]*)', f.read().strip()).group()) + return 'unknown' + + # redirect C/C++ stdout to python stdout if python stdout is redirected, # e.g. in ipython notebook capture_cstdout = suppress @@ -57,14 +89,8 @@ except (ImportError, ModuleNotFoundError, AttributeError): pass -# determine package installation path, or, if used directly from git -# repository, get repository root -if os.path.exists(os.path.join(os.path.dirname(__file__), '..', '..', '.git')): - amici_path = os.path.abspath(os.path.join( - os.path.dirname(__file__), '..', '..')) -else: - amici_path = os.path.dirname(__file__) - +# Initialize AMICI paths +amici_path = _get_amici_path() amiciSwigPath = os.path.join(amici_path, 'swig') amiciSrcPath = os.path.join(amici_path, 'src') amiciModulePath = os.path.dirname(__file__) @@ -73,24 +99,7 @@ with open(os.path.join(amici_path, 'version.txt')) as f: __version__ = f.read().strip() -# get commit hash from file -_commitfile = next( - ( - file for file in [ - os.path.join(amici_path, '..', '..', '..', '.git', 'FETCH_HEAD'), - os.path.join(amici_path, '..', '..', '..', '.git', 'ORIG_HEAD'), - ] - if os.path.isfile(file) - ), - None -) - -if _commitfile: - with open(_commitfile) as f: - __commit__ = str(re.search(r'^([\w]*)', f.read().strip()).group()) -else: - __commit__ = 'unknown' - +__commit__ = _get_commit_hash() try: # These module require the swig interface and other dependencies which will diff --git a/python/bin/amici_import_petab.py b/python/bin/amici_import_petab.py index 2329cded19..7822521a00 100755 --- a/python/bin/amici_import_petab.py +++ b/python/bin/amici_import_petab.py @@ -12,6 +12,7 @@ import argparse import math import logging +from typing import List from colorama import init as init_colorama from colorama import Fore @@ -118,10 +119,16 @@ def get_fixed_parameters(condition_file_name, sbml_model, # remove overridden parameters fixed_parameters = [p for p in fixed_parameters if condition_df[p].dtype != 'O'] - # must be unique assert(len(fixed_parameters) == len(set(fixed_parameters))) + # States occurring as column names of the condition table need to be + # converted to parameters + species_to_convert = [x for x in fixed_parameters + if sbml_model.getSpecies(x)] + species_to_parameters(species_to_convert, sbml_model) + + # Others are optional if const_species_to_parameters: # Turn species which are marked constant in the SBML model into # parameters @@ -132,10 +139,10 @@ def get_fixed_parameters(condition_file_name, sbml_model, logger.log(logging.INFO, "Non-constant species " + str(len(sbml_model.getListOfSpecies()))) - # ... and append them to the list of fixed_parameters - for species in constant_species: - if species not in fixed_parameters: - fixed_parameters.append(species) + # ... and append them to the list of fixed_parameters + for species in constant_species: + if species not in fixed_parameters: + fixed_parameters.append(species) # Ensure mentioned parameters exist in the model. Remove additional ones # from list @@ -152,44 +159,41 @@ def get_fixed_parameters(condition_file_name, sbml_model, return fixed_parameters -def constant_species_to_parameters(sbml_model): - """Convert constant species in the SBML model to constant parameters. - - This can be used e.g. for setting up models with condition-specific - constant species for PEtab, since there it is not possible to specify - constant species in the condition table. +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. Arguments: - sbml_model: libsbml model instance + species_ids: List of SBML species ID to convert to parameters with the + same ID as the replaced species. + sbml_model: SBML model to modify Returns: - List of IDs of SBML species that have been turned into constants - - Raises: - + List of IDs of species which have been converted to parameters """ transformables = [] - for species in sbml_model.getListOfSpecies(): - if not species.getConstant() and not species.getBoundaryCondition(): - continue + + for species_id in species_ids: + species = sbml_model.getSpecies(species_id) if species.getHasOnlySubstanceUnits(): logger.warning( f"Ignoring {species.getId()} which has only substance units." " Conversion not yet implemented.") - continue + return if math.isnan(species.getInitialConcentration()): logger.warning( f"Ignoring {species.getId()} which has no initial " "concentration. Amount conversion not yet implemented.") - continue + return - transformables.append(species.getId()) + transformables.append(species_id) # Must not remove species while iterating over getListOfSpecies() - for speciesId in transformables: - species = sbml_model.removeSpecies(speciesId) + for species_id in transformables: + species = sbml_model.removeSpecies(species_id) par = sbml_model.createParameter() par.setId(species.getId()) par.setName(species.getName()) @@ -199,18 +203,45 @@ def constant_species_to_parameters(sbml_model): # Remove from reactants and products for reaction in sbml_model.getListOfReactions(): - for speciesId in transformables: + for species_id in transformables: # loop, since removeX only removes one instance - while reaction.removeReactant(speciesId): + while reaction.removeReactant(species_id): pass - while reaction.removeProduct(speciesId): + while reaction.removeProduct(species_id): pass - while reaction.removeModifier(speciesId): + while reaction.removeModifier(species_id): pass return transformables +def constant_species_to_parameters(sbml_model: 'libsbml.Model' + ) -> List[str]: + """Convert constant species in the SBML model to constant parameters. + + This can be used e.g. for setting up models with condition-specific + constant species for PEtab, since there it is not possible to specify + constant species in the condition table. + + Arguments: + sbml_model: libsbml model instance + + Returns: + List of IDs of SBML species that have been turned into constants + + Raises: + + """ + transformables = [] + for species in sbml_model.getListOfSpecies(): + if not species.getConstant() and not species.getBoundaryCondition(): + continue + + transformables.append(species.getId()) + + return species_to_parameters(transformables, sbml_model) + + def import_model(sbml_file: str, condition_file: str, measurement_file: str = None, @@ -252,7 +283,8 @@ def import_model(sbml_file: str, # Replace observables in assignment import sympy as sp for observable_id, formula in sigmas.items(): - repl = sp.sympify(formula).subs(observable_id, observables[observable_id]['formula']) + repl = sp.sympify(formula).subs(observable_id, + observables[observable_id]['formula']) sigmas[observable_id] = str(repl) if verbose: diff --git a/python/sdist/setup.py b/python/sdist/setup.py index ba239f3534..fe6356d6e9 100755 --- a/python/sdist/setup.py +++ b/python/sdist/setup.py @@ -163,7 +163,7 @@ def main(): 'develop': my_develop, }, version=__version__, - description='Advanced multi-language Interface to CVODES and IDAS (%s)', + description='Advanced multi-language Interface to CVODES and IDAS', long_description=long_description, long_description_content_type="text/markdown", url='https://github.com/ICB-DCM/AMICI', diff --git a/scripts/run-cppcheck.sh b/scripts/run-cppcheck.sh index 721d348620..82018977ea 100755 --- a/scripts/run-cppcheck.sh +++ b/scripts/run-cppcheck.sh @@ -3,46 +3,13 @@ # Note: CppuTest memcheck should be disabled # Note: Consider using ctest -T memcheck instead -SCRIPT_PATH=$(dirname $BASH_SOURCE) -AMICI_PATH=$(cd $SCRIPT_PATH/.. && pwd) +SCRIPT_PATH=$(dirname "$BASH_SOURCE") +AMICI_PATH=$(cd "$SCRIPT_PATH"/.. && pwd) cd ${AMICI_PATH} -cppcheck -i${AMICI_PATH}/src/doc ${AMICI_PATH}/src -I${AMICI_PATH}/include/ --enable=style 2> cppcheck.txt +cppcheck -i"${AMICI_PATH}"/src/doc "${AMICI_PATH}"/src \ + -I$"{AMICI_PATH}"/include/ \ + --enable=style \ + --exitcode-suppressions="${AMICI_PATH}"/.cppcheck-exitcode-suppressions -# suppress alloca warnings -grep -v "(warning) Obsolete function 'alloca' called." cppcheck.txt > cppcheck_tmp.txt -mv cppcheck_tmp.txt cppcheck.txt - - -# suppress header warnings for standard libraries -grep -v "Cppcheck cannot find all the include files" cppcheck.txt > cppcheck_tmp.txt -mv cppcheck_tmp.txt cppcheck.txt - -grep -v "'AmiVectorArray' does not have a operator=" cppcheck.txt > cppcheck_tmp.txt -mv cppcheck_tmp.txt cppcheck.txt - -grep -v "Member variable 'ExpData::nytrue_' is not initialized in the constructor" cppcheck.txt > cppcheck_tmp.txt -mv cppcheck_tmp.txt cppcheck.txt - -grep -v "Member variable 'ExpData::nztrue_' is not initialized in the constructor" cppcheck.txt > cppcheck_tmp.txt -mv cppcheck_tmp.txt cppcheck.txt - -grep -v "Member variable 'ExpData::nmaxevent_' is not initialized in the constructor" cppcheck.txt > cppcheck_tmp.txt -mv cppcheck_tmp.txt cppcheck.txt - -# check if error log was created -if [ -f cppcheck.txt ]; then - # check if error log is empty - if [ -s cppcheck.txt ]; then - echo "CPPCHECK failed:" - cat cppcheck.txt - rm cppcheck.txt - exit 1 - else - rm cppcheck.txt - exit 0 - fi -else - exit 1 -fi diff --git a/src/interface_matlab.cpp b/src/interface_matlab.cpp index bd39951644..34a056c96b 100644 --- a/src/interface_matlab.cpp +++ b/src/interface_matlab.cpp @@ -2,8 +2,8 @@ * @file interface_matlab.cpp * @brief core routines for mex interface * - * This file defines the fuction mexFunction which is executed upon calling the - * mex file from matlab + * This file defines the function `mexFunction` which is executed upon calling + * the `.mex` file from Matlab. */ #include "amici/interface_matlab.h" @@ -23,7 +23,7 @@ namespace amici { int dbl2int(const double x); /** - * @brief The mexFunctionArguments enum takes care of the ordering of mex file + * @brief The mexRhsArguments enum takes care of the ordering of mex file * arguments (indexing in prhs) */ enum mexRhsArguments { @@ -41,8 +41,7 @@ enum mexRhsArguments { /*! - * amici_blasCBlasTransToBlasTrans translates AMICI_BLAS_TRANSPOSE values to - * CBlas readable strings + * Translates AMICI_BLAS_TRANSPOSE values to CBLAS readable strings * * @param trans flag indicating transposition and complex conjugation * @@ -480,7 +479,7 @@ void mexFunction(int nlhs, mxArray *plhs[], int nrhs, const mxArray *prhs[]) { std::unique_ptr edata; if (nrhs > amici::RHS_DATA && mxGetPr(prhs[amici::RHS_DATA])) { try { - edata = std::move(amici::expDataFromMatlabCall(prhs, *model)); + edata = amici::expDataFromMatlabCall(prhs, *model); } catch (amici::AmiException const& ex) { amici::errMsgIdAndTxt("AMICI:mex:setup","Failed to read experimental data:\n%s",ex.what()); } diff --git a/src/main.template.cpp b/src/main.template.cpp index 6d6caf54a8..07ff62a322 100644 --- a/src/main.template.cpp +++ b/src/main.template.cpp @@ -10,8 +10,7 @@ #include "wrapfunctions.h" /* model-provided functions */ /* This is a scaffold for a stand-alone AMICI simulation executable - * demonstrating - * use of the AMICI C++ API. + * demonstrating the use of the AMICI C++ API. * * This program reads AMICI options from an HDF5 file, prints some results * and writes additional results to an HDF5 file. The name of the HDF5 file diff --git a/src/model.cpp b/src/model.cpp index 9e21e5a6d6..1bc8cc2fe8 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -216,7 +216,8 @@ void Model::initializeStates(AmiVector &x) { } } -void Model::initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x) { +void Model::initializeStateSensitivities(AmiVectorArray &sx, + AmiVector const &x) { if (sx0data.empty()) { fsx0(sx, x); } else { @@ -234,7 +235,7 @@ void Model::initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x) { } } -void Model::initHeaviside(AmiVector &x, AmiVector &dx) { +void Model::initHeaviside(AmiVector const &x, AmiVector const &dx) { std::vector rootvals(ne, 0.0); froot(tstart, x, dx, rootvals); for (int ie = 0; ie < ne; ie++) { diff --git a/src/solver_cvodes.cpp b/src/solver_cvodes.cpp index 4267cee5d3..56441f7828 100644 --- a/src/solver_cvodes.cpp +++ b/src/solver_cvodes.cpp @@ -64,9 +64,6 @@ static int fJBandB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, N_Vector tmp2B, N_Vector tmp3B); -static int fJDiag(realtype t, N_Vector JDiag, N_Vector x, void *user_data) -__attribute__((unused)); - static int fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, N_Vector xdot, void *user_data, N_Vector tmp); @@ -839,19 +836,6 @@ int fJBandB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, return fJB(t, x, xB, xBdot, JB, user_data, tmp1B, tmp2B, tmp3B); } -/** - * @brief Diagonalized Jacobian (for preconditioning) - * @param t timepoint - * @param JDiag Vector to which the Jacobian diagonal will be written - * @param x Vector with the states - * @param user_data object with user input @type Model_ODE - **/ -int fJDiag(realtype t, N_Vector JDiag, N_Vector x, void *user_data) { - auto model = static_cast(user_data); - model->fJDiag(t, JDiag, x); - return model->checkFinite(gsl::make_span(JDiag), "Jacobian"); -} - /** * @brief Matrix vector product of J with a vector v (for iterative solvers) * @param t timepoint diff --git a/src/vector.cpp b/src/vector.cpp index 5d57dd9610..cefa789250 100644 --- a/src/vector.cpp +++ b/src/vector.cpp @@ -1,5 +1,7 @@ #include "amici/vector.h" +#include + namespace amici { AmiVector &AmiVector::operator=(AmiVector const &other) { diff --git a/tests/testSBMLSuite.py b/tests/testSBMLSuite.py index 9dcc1284f8..1a0b54dff8 100755 --- a/tests/testSBMLSuite.py +++ b/tests/testSBMLSuite.py @@ -13,17 +13,22 @@ import os import sys import importlib -import numpy as np -import sympy as sp -import amici import unittest import copy +import shutil from typing import List +import amici +import numpy as np +import sympy as sp +import pandas as pd + + # directory with sbml semantic test cases test_path = os.path.join(os.path.dirname(__file__), 'sbml-test-suite', 'cases', 'semantic') - +upload_result_path = os.path.join(os.path.dirname(__file__), + 'amici-semantic-results') ALL_TESTS = set(range(1, 1781)) @@ -112,9 +117,27 @@ def run_sbml_testsuite_case(self, test_id): ) print(f'TestCase {test_id} passed.') + write_result_file(simulated_x, model, test_id) + except amici.sbml_import.SBMLException as err: print(f'TestCase {test_id} was skipped: {err}') +def write_result_file(simulated_x: np.array, + model: amici.Model, + test_id: str): + """ + Create test result file for upload to + http://sbml.org/Facilities/Database/Submission/Create + + Requires csv file with test ID in name and content of [time, Species, ...] + """ + filename = os.path.join(upload_result_path, f'{test_id}.csv') + + df = pd.DataFrame(simulated_x) + df.columns = model.getStateIds() + df.insert(0, 'time', model.getTimepoints()) + df.to_csv(filename, index=False) + def get_amount_and_variables(current_test_path, test_id): settings = read_settings_file(current_test_path, test_id) @@ -228,6 +251,11 @@ def parse_selection(selection_str: str) -> List[int]: if __name__ == '__main__': + # ensure directory for test results is empty + if os.path.exists(upload_result_path): + shutil.rmtree(upload_result_path) + os.mkdir(upload_result_path) + TestAmiciSBMLTestSuite.SBML_TEST_IDS = set(parse_selection(sys.argv.pop())) suite = unittest.TestSuite() diff --git a/version.txt b/version.txt index 223df19846..70016a7c6a 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.10.11 +0.10.12