From d6a076839dbeb55951c80d6f0d5ca7ade220ea22 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 13 Jul 2021 13:22:44 +0200 Subject: [PATCH 01/17] C++: Change test suite from CppUTest to gtest (#1532) --- .github/workflows/test_install.yml | 4 - .github/workflows/test_python_cplusplus.yml | 8 +- .gitignore | 11 +- CMakeLists.txt | 2 +- documentation/CI.md | 12 +- documentation/development.rst | 6 +- matlab/tests/testModels.m | 14 +- python/tests/test_pregenerated_models.py | 26 +- scripts/README.md | 9 +- scripts/buildAmici.sh | 23 +- scripts/buildCpputest.sh | 32 - scripts/buildDependencies.sh | 1 - scripts/buildXcode.sh | 10 +- scripts/run-cpp-tests.sh | 20 + scripts/run-cppcheck.sh | 15 +- scripts/run-cpputest.sh | 13 - scripts/run-valgrind-cpp.sh | 14 +- tests/README.md | 2 +- tests/{cpputest => cpp}/CMakeLists.txt | 45 +- tests/cpp/CMakeLists.txt.in | 15 + .../robertson => cpp/calvetti}/CMakeLists.txt | 10 +- tests/{cpputest => cpp}/calvetti/tests1.cpp | 7 +- .../calvetti => cpp/dirac}/CMakeLists.txt | 10 +- tests/{cpputest => cpp}/dirac/tests1.cpp | 10 +- .../dirac => cpp/events}/CMakeLists.txt | 11 +- tests/{cpputest => cpp}/events/tests1.cpp | 11 +- tests/{cpputest => cpp}/expectedResults.h5 | Bin .../jakstat_adjoint/CMakeLists.txt | 11 +- .../jakstat_adjoint/tests1.cpp | 27 +- tests/cpp/jakstat_adjoint_o2/CMakeLists.txt | 21 + .../jakstat_adjoint_o2/tests1.cpp | 12 +- tests/cpp/nested_events/CMakeLists.txt | 21 + .../nested_events/tests1.cpp | 9 +- tests/cpp/neuron/CMakeLists.txt | 21 + tests/{cpputest => cpp}/neuron/tests1.cpp | 9 +- tests/cpp/neuron_o2/CMakeLists.txt | 21 + tests/{cpputest => cpp}/neuron_o2/tests1.cpp | 7 +- tests/cpp/robertson/CMakeLists.txt | 21 + tests/{cpputest => cpp}/robertson/tests1.cpp | 13 +- tests/cpp/steadystate/CMakeLists.txt | 21 + .../{cpputest => cpp}/steadystate/tests1.cpp | 93 +- tests/{cpputest => cpp}/testOptions.h5 | Bin tests/{cpputest => cpp}/testfunctions.cpp | 57 +- tests/{cpputest => cpp}/testfunctions.h | 5 +- tests/cpp/unittests/CMakeLists.txt | 27 + tests/cpp/unittests/testExpData.cpp | 328 +++++++ tests/cpp/unittests/testMisc.cpp | 597 ++++++++++++ tests/cpp/unittests/testSerialization.cpp | 257 +++++ tests/{cpputest => cpp}/wrapTestModels.m | 0 tests/cpputest/events/CMakeLists.txt | 18 - .../jakstat_adjoint_o2/CMakeLists.txt | 18 - tests/cpputest/main.cpp | 10 - tests/cpputest/nested_events/CMakeLists.txt | 18 - tests/cpputest/neuron/CMakeLists.txt | 18 - tests/cpputest/neuron_o2/CMakeLists.txt | 18 - tests/cpputest/steadystate/CMakeLists.txt | 18 - tests/cpputest/unittests/CMakeLists.txt | 27 - tests/cpputest/unittests/tests1.cpp | 911 ------------------ .../cpputest/unittests/testsSerialization.cpp | 258 ----- tests/generateTestConfigurationForExamples.sh | 28 +- 60 files changed, 1633 insertions(+), 1628 deletions(-) delete mode 100755 scripts/buildCpputest.sh create mode 100755 scripts/run-cpp-tests.sh delete mode 100755 scripts/run-cpputest.sh rename tests/{cpputest => cpp}/CMakeLists.txt (60%) create mode 100644 tests/cpp/CMakeLists.txt.in rename tests/{cpputest/robertson => cpp/calvetti}/CMakeLists.txt (67%) rename tests/{cpputest => cpp}/calvetti/tests1.cpp (50%) rename tests/{cpputest/calvetti => cpp/dirac}/CMakeLists.txt (67%) rename tests/{cpputest => cpp}/dirac/tests1.cpp (54%) rename tests/{cpputest/dirac => cpp/events}/CMakeLists.txt (54%) rename tests/{cpputest => cpp}/events/tests1.cpp (55%) rename tests/{cpputest => cpp}/expectedResults.h5 (100%) rename tests/{cpputest => cpp}/jakstat_adjoint/CMakeLists.txt (54%) rename tests/{cpputest => cpp}/jakstat_adjoint/tests1.cpp (84%) create mode 100644 tests/cpp/jakstat_adjoint_o2/CMakeLists.txt rename tests/{cpputest => cpp}/jakstat_adjoint_o2/tests1.cpp (53%) create mode 100644 tests/cpp/nested_events/CMakeLists.txt rename tests/{cpputest => cpp}/nested_events/tests1.cpp (55%) create mode 100644 tests/cpp/neuron/CMakeLists.txt rename tests/{cpputest => cpp}/neuron/tests1.cpp (62%) create mode 100644 tests/cpp/neuron_o2/CMakeLists.txt rename tests/{cpputest => cpp}/neuron_o2/tests1.cpp (56%) create mode 100644 tests/cpp/robertson/CMakeLists.txt rename tests/{cpputest => cpp}/robertson/tests1.cpp (63%) create mode 100644 tests/cpp/steadystate/CMakeLists.txt rename tests/{cpputest => cpp}/steadystate/tests1.cpp (64%) rename tests/{cpputest => cpp}/testOptions.h5 (100%) rename tests/{cpputest => cpp}/testfunctions.cpp (91%) rename tests/{cpputest => cpp}/testfunctions.h (98%) create mode 100644 tests/cpp/unittests/CMakeLists.txt create mode 100644 tests/cpp/unittests/testExpData.cpp create mode 100644 tests/cpp/unittests/testMisc.cpp create mode 100644 tests/cpp/unittests/testSerialization.cpp rename tests/{cpputest => cpp}/wrapTestModels.m (100%) delete mode 100644 tests/cpputest/events/CMakeLists.txt delete mode 100644 tests/cpputest/jakstat_adjoint_o2/CMakeLists.txt delete mode 100644 tests/cpputest/main.cpp delete mode 100644 tests/cpputest/nested_events/CMakeLists.txt delete mode 100644 tests/cpputest/neuron/CMakeLists.txt delete mode 100644 tests/cpputest/neuron_o2/CMakeLists.txt delete mode 100644 tests/cpputest/steadystate/CMakeLists.txt delete mode 100644 tests/cpputest/unittests/CMakeLists.txt delete mode 100644 tests/cpputest/unittests/tests1.cpp delete mode 100644 tests/cpputest/unittests/testsSerialization.cpp diff --git a/.github/workflows/test_install.yml b/.github/workflows/test_install.yml index b075d8d6f2..b47ae6b090 100644 --- a/.github/workflows/test_install.yml +++ b/.github/workflows/test_install.yml @@ -33,10 +33,6 @@ jobs: run: | scripts/buildSundials.sh - - name: Build cpputest - run: | - scripts/buildCpputest.sh - - name: Build AMICI run: | scripts/buildAmici.sh diff --git a/.github/workflows/test_python_cplusplus.yml b/.github/workflows/test_python_cplusplus.yml index 2d26cd2884..81d32223cd 100644 --- a/.github/workflows/test_python_cplusplus.yml +++ b/.github/workflows/test_python_cplusplus.yml @@ -69,7 +69,7 @@ jobs: - name: C++ tests run: | - scripts/run-cpputest.sh + scripts/run-cpp-tests.sh - name: Install python package run: | @@ -85,7 +85,7 @@ jobs: --cov-report=xml:"${AMICI_DIR}/build/coverage_py.xml" \ --cov-append \ ${AMICI_DIR}/python/tests - + - name: example notebooks run: | scripts/runNotebook.sh python/examples/example_*/ @@ -96,7 +96,7 @@ jobs: - name: Codecov Python uses: codecov/codecov-action@v1 - with: + with: token: ${{ secrets.CODECOV_TOKEN }} file: ./build/coverage_py.xml flags: python @@ -163,4 +163,4 @@ jobs: - name: C++ tests run: | - scripts/run-cpputest.sh + scripts/run-cpp-tests.sh diff --git a/.gitignore b/.gitignore index 44053581ec..4b17a59451 100644 --- a/.gitignore +++ b/.gitignore @@ -4,9 +4,9 @@ build/* build-debug/* build_xcode/* swig/python/build/* -tests/cpputest/build/* -tests/cpputest/build_xcode/* -tests/cpputest/Testing/* +tests/cpp/build/* +tests/cpp/build_xcode/* +tests/cpp/Testing/* doc-venv/* doc/* @@ -136,8 +136,8 @@ tests/test/* */tests/fricker_2010_apoptosis_amici/* */tests/explicit_amici/* */tests/fixed_initial_amici/* -tests/cpputest/writeResults.h5 -tests/cpputest/writeResults.h5.bak +tests/cpp/writeResults.h5 +tests/cpp/writeResults.h5.bak tests/sbml-test-suite/* tests/sbml-test-suite/ tests/sedml-test-suite/ @@ -171,7 +171,6 @@ AMICI_guide.pdf ThirdParty/bionetgen.tar.gz ThirdParty/BioNetGen-* -ThirdParty/cpputest-master* ThirdParty/doxygen/* ThirdParty/mtocpp-master* ThirdParty/sundials/build/* diff --git a/CMakeLists.txt b/CMakeLists.txt index 3e2a7f1f95..09d4643528 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -268,7 +268,7 @@ if(BUILD_TESTS) if(ENABLE_HDF5) enable_testing() - add_subdirectory(tests/cpputest) + add_subdirectory(tests/cpp) else() message(WARNING "Cannot build tests with ENABLE_HDF5=OFF.") endif() diff --git a/documentation/CI.md b/documentation/CI.md index 93fd106dc2..3fd72d99f9 100644 --- a/documentation/CI.md +++ b/documentation/CI.md @@ -18,7 +18,7 @@ tests are integrated with CMake, see `make help` in the build directory. ## C++ unit and integration tests To run C++ tests, build AMICI with `make` or `scripts/buildAll.sh`, -then run `scripts/run-cpputest.sh`. +then run `scripts/run-cpp-tests.sh`. ## Python unit and integration tests @@ -58,7 +58,7 @@ To execute the Matlab test suite, run `tests/testModels.m`. Many of our integration tests are model simulations. The simulation results obtained from the Python and C++ are compared to results saved in an HDF5 file -(`tests/cpputest/expectedResults.h5`). +(`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 @@ -73,7 +73,7 @@ the Matlab model import routines change. This is done with - tests/cpputest/wrapTestModels.m + 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, @@ -84,13 +84,13 @@ for Python. ### Regenerating expected results To update test results, run `make test` in the build directory, -replace `tests/cpputest/expectedResults.h5` by -`tests/cpputest/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 - h5diff -v --relative 1e-8 tests/cpputest/expectedResults.h5 tests/cpputest/writeResults.h5.bak | less + h5diff -v --relative 1e-8 tests/cpp/expectedResults.h5 tests/cpp/writeResults.h5.bak | less ## Adding/Updating tests diff --git a/documentation/development.rst b/documentation/development.rst index 0e4899bd27..365da13c67 100644 --- a/documentation/development.rst +++ b/documentation/development.rst @@ -60,7 +60,7 @@ process described below: (our CI pipeline will do this for you) - When adding new functionality, please also provide test cases (see - ``tests/cpputest/`` and + ``tests/cpp/`` and `documentation/CI.md `__) - Write meaningful commit messages @@ -68,10 +68,10 @@ process described below: - Run all tests to ensure nothing was broken (`more details `__) - - Run ``scripts/buildAll.sh && scripts/run-cpputest.sh``. + - Run ``scripts/buildAll.sh && scripts/run-cpp-tests.sh``. - If you made changes to the Matlab or C++ code and have a Matlab - license, please also run ``tests/cpputest/wrapTestModels.m`` and + license, please also run ``tests/cpp/wrapTestModels.m`` and ``tests/testModels.m`` - If you made changes to the Python or C++ code, run diff --git a/matlab/tests/testModels.m b/matlab/tests/testModels.m index baf1c57bda..047f86daf2 100644 --- a/matlab/tests/testModels.m +++ b/matlab/tests/testModels.m @@ -18,18 +18,18 @@ function testModels() model_dir = [fileparts(mfilename('fullpath')) '/../../models/']; cd(fileparts(mfilename('fullpath'))) - addpath(genpath('../../tests/cpputest')); + addpath(genpath('../../tests/cpp')); addpath(genpath('../examples')); % wrapTestModels() cd(fileparts(mfilename('fullpath'))) hdf5file = fullfile(fileparts(mfilename('fullpath')), ... - '../../tests/cpputest', 'expectedResults.h5'); - + '../../tests/cpp', 'expectedResults.h5'); + info = h5info(hdf5file); for imodel = 1:length(info.Groups) modelname = info.Groups(imodel).Name(2:end); - + if(~isempty(regexp(modelname,'^model_neuron'))) model_atol = 1e-9; model_rtol = 1e-4; @@ -42,18 +42,18 @@ function testModels() if(ismember(testname, ignoredTests)) continue end - + display(testname); [results,options,data,t,theta,kappa] = readDataFromHDF5(info.Groups(imodel).Groups(itest),hdf5file); - + % rebuild model old_path = addpath([model_dir modelname]); old_pwd = cd([model_dir modelname]); rebuild = str2func(['rebuild_' modelname]); rebuild(); cd(old_pwd); - + sol = getResults(modelname,options,data,t,theta,kappa); compareResults(sol,results); path(old_path); diff --git a/python/tests/test_pregenerated_models.py b/python/tests/test_pregenerated_models.py index 6bd63768b2..470cc8ed86 100755 --- a/python/tests/test_pregenerated_models.py +++ b/python/tests/test_pregenerated_models.py @@ -3,19 +3,18 @@ """Run simulations with Matlab-AMICI pre-generated models and verify using saved expectations.""" -import h5py -import amici import os -from amici.gradient_check import check_derivatives, check_results -import pytest +from pathlib import Path +import amici +import h5py import numpy as np +import pytest +from amici.gradient_check import check_derivatives, check_results - -options_file = os.path.join(os.path.dirname(__file__), '..', '..', - 'tests', 'cpputest', 'testOptions.h5') -expected_results_file = os.path.join(os.path.dirname(__file__), '..', '..', - 'tests', 'cpputest', 'expectedResults.h5') +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) @@ -43,10 +42,9 @@ def test_pregenerated_model(sub_test, case): else: model_name = sub_test - model_swig_folder = \ - os.path.join(os.path.dirname(__file__), '..', '..', 'build', 'tests', - 'cpputest', 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) @@ -64,7 +62,7 @@ def test_pregenerated_model(sub_test, case): edata = None if 'data' in expected_results[sub_test][case].keys(): edata = amici.readSimulationExpData( - expected_results_file, + str(expected_results_file), f'/{sub_test}/{case}/data', model.get() ) rdata = amici.runAmiciSimulation(model, solver, diff --git a/scripts/README.md b/scripts/README.md index 5434d511b9..67f64dca5c 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -14,12 +14,7 @@ This directory contains a number of build, installation, and CI scripts. Download and build [BioNetGen](https://www.csb.pitt.edu/Faculty/Faeder/?page_id=409) (required for some tests) - -* `buildCpputest.sh` - - Download and build [CppUTest](https://cpputest.github.io/) - (required for C++ test suite) - + * `buildSuiteSparse.sh` Build [SuiteSparse](http://faculty.cse.tamu.edu/davis/suitesparse.html) @@ -64,7 +59,7 @@ This directory contains a number of build, installation, and CI scripts. Run static code analysis -* `run-cpputest.sh` +* `run-cpp-tests.sh` Run C++ unit and integration tests diff --git a/scripts/buildAmici.sh b/scripts/buildAmici.sh index 446fe1d8bd..735e05fd3b 100755 --- a/scripts/buildAmici.sh +++ b/scripts/buildAmici.sh @@ -2,7 +2,8 @@ # # Build libamici # -set -e +set -eou pipefail + cmake=${CMAKE:-cmake} make=${MAKE:-make} @@ -12,28 +13,26 @@ amici_build_dir="${amici_path}/build" mkdir -p "${amici_build_dir}" cd "${amici_build_dir}" -cpputest_build_dir="${amici_path}/ThirdParty/cpputest-master/build/" - -if [[ $TRAVIS = true ]] || [[ $GITHUB_ACTIONS = true ]] || [[ $ENABLE_AMICI_DEBUGGING = TRUE ]]; -then +if [ "${TRAVIS:-}" = true ] || + [ "${GITHUB_ACTIONS:-}" = true ] || + [ "${ENABLE_AMICI_DEBUGGING:-}" = TRUE ]; then # Running on CI server build_type="Debug" else build_type="RelWithDebInfo" fi -CppUTest_DIR=${cpputest_build_dir} \ - ${cmake} \ - -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror" \ - -DCMAKE_BUILD_TYPE=$build_type \ - -DPython3_EXECUTABLE="$(command -v python3)" .. +${cmake} \ + -DCMAKE_CXX_FLAGS="-Wall -Wextra -Werror" \ + -DCMAKE_BUILD_TYPE=$build_type \ + -DPython3_EXECUTABLE="$(command -v python3)" .. # build, with or without sonarcloud wrapper -if [[ "$CI_SONARCLOUD" == "TRUE" ]]; then +if [ "${CI_SONARCLOUD:-}" = "TRUE" ]; then build-wrapper-linux-x86-64 \ --out-dir "${amici_path}/bw-output" \ cmake --build . --parallel -elif [[ "$TRAVIS" == "true" ]]; then +elif [ "${TRAVIS:-}" = "true" ]; then cmake --build . ${make} python-sdist else diff --git a/scripts/buildCpputest.sh b/scripts/buildCpputest.sh deleted file mode 100755 index 1d4a8f468c..0000000000 --- a/scripts/buildCpputest.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/bin/bash -# -# Build CppUTest -# -set -e - -script_path=$(dirname "$BASH_SOURCE") -amici_path=$(cd "$script_path/.." && pwd) - -cmake=${CMAKE:-cmake} -make=${MAKE:-make} - -# Cpputest -mkdir -p "${amici_path}/ThirdParty" -cd "${amici_path}/ThirdParty" -export CPPUTEST_BUILD_DIR="${amici_path}/ThirdParty/cpputest-master/" - -if [ ! -d "cpputest-master" ]; then - if [ ! -e "cpputest-master.zip" ]; then - wget -q -O cpputest-master.zip https://codeload.github.com/cpputest/cpputest/zip/master - fi - unzip -q cpputest-master.zip - #cd cpputest-master/ && ./autogen.sh && ./configure && make -fi - -cd cpputest-master -mkdir -p build -cd build -${cmake} -DTESTS=OFF -DBUILD_TESTING=OFF -DCMAKE_BUILD_TYPE=Release \ - -DC++11=ON -DMEMORY_LEAK_DETECTION=OFF .. -${make} -j4 - diff --git a/scripts/buildDependencies.sh b/scripts/buildDependencies.sh index 51a971a88d..1caaf0eb48 100755 --- a/scripts/buildDependencies.sh +++ b/scripts/buildDependencies.sh @@ -9,5 +9,4 @@ script_path=$(cd "$script_path" && pwd) "${script_path}/buildSuiteSparse.sh" "${script_path}/buildSundials.sh" -"${script_path}/buildCpputest.sh" "${script_path}/buildBNGL.sh" diff --git a/scripts/buildXcode.sh b/scripts/buildXcode.sh index d78edde4bd..97dfbf24e7 100755 --- a/scripts/buildXcode.sh +++ b/scripts/buildXcode.sh @@ -12,18 +12,16 @@ CMAKE=${CMAKE:-cmake} ${AMICI_PATH}/scripts/buildSuiteSparse.sh ${AMICI_PATH}/scripts/buildSundials.sh ${AMICI_PATH}/scripts/buildAmici.sh -${AMICI_PATH}/scripts/buildCpputest.sh -cp ${AMICI_PATH}/tests/cpputest/expectedResults.h5 ./expectedResults.h5 +cp ${AMICI_PATH}/tests/cpp/expectedResults.h5 ./expectedResults.h5 mkdir -p ${AMICI_PATH}/build_xcode cd ${AMICI_PATH}/build_xcode -CPPUTEST_BUILD_DIR=${AMICI_PATH}/ThirdParty/cpputest-master/build/ -CppUTest_DIR=${CPPUTEST_BUILD_DIR} ${CMAKE} -G"Xcode" -DCMAKE_BUILD_TYPE=Debug .. +${CMAKE} -G"Xcode" -DCMAKE_BUILD_TYPE=Debug .. for model in steadystate robertson neuron neuron_o2 jakstat_adjoint jakstat_adjoint_o2 dirac events nested_events do - cp ${AMICI_PATH}/build/tests/cpputest/external_model_${model}-prefix/src/external_model_${model}-build/libmodel_${model}.a \ - ${AMICI_PATH}/build_xcode/tests/cpputest/external_model_${model}-prefix/src/external_model_${model}-build/libmodel_${model}.a + cp ${AMICI_PATH}/build/tests/cpp/external_model_${model}-prefix/src/external_model_${model}-build/libmodel_${model}.a \ + ${AMICI_PATH}/build_xcode/tests/cpp/external_model_${model}-prefix/src/external_model_${model}-build/libmodel_${model}.a done diff --git a/scripts/run-cpp-tests.sh b/scripts/run-cpp-tests.sh new file mode 100755 index 0000000000..963ef3c51c --- /dev/null +++ b/scripts/run-cpp-tests.sh @@ -0,0 +1,20 @@ +#!/bin/bash +set -eou pipefail + +SCRIPT_PATH=$(dirname "$BASH_SOURCE") +AMICI_PATH=$(cd "$SCRIPT_PATH/.." && pwd) + +if [[ "${ENABLE_GCOV_COVERAGE:-}" == TRUE ]]; then + lcov --base-directory "${AMICI_PATH}" \ + --directory "${AMICI_PATH}/build/CMakeFiles/amici.dir/src" \ + --zerocounters -q +fi + +# run tests +cd "${AMICI_PATH}/build" + +ctest -V +ret=$? +if [[ $ret != 0 ]]; then exit $ret; fi +mv "${AMICI_PATH}/tests/cpp/writeResults.h5" \ + "${AMICI_PATH}/tests/cpp/writeResults.h5.bak" diff --git a/scripts/run-cppcheck.sh b/scripts/run-cppcheck.sh index 82018977ea..57c669439a 100755 --- a/scripts/run-cppcheck.sh +++ b/scripts/run-cppcheck.sh @@ -1,15 +1,18 @@ #!/bin/bash # Check test suite with valgrind -# Note: CppuTest memcheck should be disabled # Note: Consider using ctest -T memcheck instead +set -euo pipefail + SCRIPT_PATH=$(dirname "$BASH_SOURCE") AMICI_PATH=$(cd "$SCRIPT_PATH"/.. && pwd) -cd ${AMICI_PATH} +cd "${AMICI_PATH}" -cppcheck -i"${AMICI_PATH}"/src/doc "${AMICI_PATH}"/src \ - -I$"{AMICI_PATH}"/include/ \ - --enable=style \ - --exitcode-suppressions="${AMICI_PATH}"/.cppcheck-exitcode-suppressions +cppcheck \ + "-i${AMICI_PATH}/src/doc" \ + "${AMICI_PATH}/src" \ + "-I${AMICI_PATH}/include/" \ + --enable=style \ + "--exitcode-suppressions=${AMICI_PATH}/.cppcheck-exitcode-suppressions" diff --git a/scripts/run-cpputest.sh b/scripts/run-cpputest.sh deleted file mode 100755 index 781a31d32f..0000000000 --- a/scripts/run-cpputest.sh +++ /dev/null @@ -1,13 +0,0 @@ -#!/bin/bash -SCRIPT_PATH=$(dirname $BASH_SOURCE) -AMICI_PATH=$(cd $SCRIPT_PATH/.. && pwd) - -if [[ "$ENABLE_GCOV_COVERAGE" == TRUE ]]; then lcov --base-directory ${AMICI_PATH} --directory ${AMICI_PATH}/build/CMakeFiles/amici.dir/src --zerocounters -q; fi - -# run tests -cd ${AMICI_PATH}/build - -ctest -V -ret=$? -if [[ $ret != 0 ]]; then exit $ret; fi -mv ${AMICI_PATH}/tests/cpputest/writeResults.h5 ${AMICI_PATH}/tests/cpputest/writeResults.h5.bak diff --git a/scripts/run-valgrind-cpp.sh b/scripts/run-valgrind-cpp.sh index af0daa95b9..7d868d98ef 100755 --- a/scripts/run-valgrind-cpp.sh +++ b/scripts/run-valgrind-cpp.sh @@ -1,19 +1,19 @@ #!/bin/bash # Check test suite with valgrind -# 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) -set -e +set -eou pipefail # run tests -cd ${AMICI_PATH}/build/tests/cpputest/ +cd "${AMICI_PATH}/build/tests/cpp/" VALGRIND_OPTS="--leak-check=full --error-exitcode=1 --trace-children=yes --show-leak-kinds=definite" set -x for MODEL in $(ctest -N | grep "Test[ ]*#" | grep -v unittests | sed --regexp-extended 's/ *Test[ ]*#[0-9]+: model_(.*)_test/\1/') - do cd ${AMICI_PATH}/build/tests/cpputest/${MODEL}/ && valgrind ${VALGRIND_OPTS} ./model_${MODEL}_test + do cd "${AMICI_PATH}/build/tests/cpp/${MODEL}/" && valgrind ${VALGRIND_OPTS} "./model_${MODEL}_test" done -cd ${AMICI_PATH}/build/tests/cpputest/unittests/ && valgrind ${VALGRIND_OPTS} ./unittests +cd "${AMICI_PATH}/build/tests/cpp/unittests/" +valgrind ${VALGRIND_OPTS} ./unittests diff --git a/tests/README.md b/tests/README.md index 488d136f05..0758daf484 100644 --- a/tests/README.md +++ b/tests/README.md @@ -2,7 +2,7 @@ This directory contains: -- C++ unit tests and integration tests (`cpputest/`) +- C++ unit tests and integration tests (`cpp/`) - Scripts for running the SBML semantic test suite, exercising the Python interface - Scripts for running the PEtab test suite, exercising the Python interface diff --git a/tests/cpputest/CMakeLists.txt b/tests/cpp/CMakeLists.txt similarity index 60% rename from tests/cpputest/CMakeLists.txt rename to tests/cpp/CMakeLists.txt index fa441f3245..6586fb3ec4 100644 --- a/tests/cpputest/CMakeLists.txt +++ b/tests/cpp/CMakeLists.txt @@ -1,15 +1,43 @@ -project(amiciIntegrationTests) +# ------------------------------------------------------------------------------ +# Set up google test +# ------------------------------------------------------------------------------ + +# Download and unpack googletest at configure time +configure_file(CMakeLists.txt.in googletest-download/CMakeLists.txt) +execute_process( + COMMAND ${CMAKE_COMMAND} -G "${CMAKE_GENERATOR}" . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download) +if(result) + message(FATAL_ERROR "CMake step for googletest failed: ${result}") +endif() +execute_process( + COMMAND ${CMAKE_COMMAND} --build . + RESULT_VARIABLE result + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/googletest-download) +if(result) + message(FATAL_ERROR "Build step for googletest failed: ${result}") +endif() + +# Prevent overriding the parent project's compiler/linker settings on Windows +set(gtest_force_shared_crt + ON + CACHE BOOL "" FORCE) -find_package(CppUTest REQUIRED) -# because Cpputest doesn't seem to care about MEMORY_LEAK_DETECTION=OFF -add_definitions(-DD_MemoryLeakWarningPlugin_h) +# Add googletest directly to our build. This defines the gtest and gtest_main +# targets. +add_subdirectory(${CMAKE_CURRENT_BINARY_DIR}/googletest-src + ${CMAKE_CURRENT_BINARY_DIR}/googletest-build EXCLUDE_FROM_ALL) + +# ------------------------------------------------------------------------------ +# AMICI C++ tests +# ------------------------------------------------------------------------------ + +project(amiciIntegrationTests) # models depend on Upstream::amici add_library(Upstream::amici ALIAS amici) -set(CMAKE_CXX_FLAGS_OLD "${CMAKE_CXX_FLAGS}") -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -include sstream -include functional") - # Amici testing library add_library(amici-testing testfunctions.cpp) target_compile_definitions(amici-testing @@ -19,11 +47,10 @@ target_compile_definitions(amici-testing ) target_include_directories(amici-testing PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} - PUBLIC ${CppUTest_INCLUDE_DIRS} ) target_link_libraries(amici-testing PUBLIC Upstream::amici - PUBLIC ${CppUTest_LIBRARIES} + PUBLIC gtest_main ) # Names of models for which tests are to be run diff --git a/tests/cpp/CMakeLists.txt.in b/tests/cpp/CMakeLists.txt.in new file mode 100644 index 0000000000..6e80530a76 --- /dev/null +++ b/tests/cpp/CMakeLists.txt.in @@ -0,0 +1,15 @@ +cmake_minimum_required(VERSION 2.8.12) + +project(googletest-download NONE) + +include(ExternalProject) +ExternalProject_Add(googletest + GIT_REPOSITORY https://github.com/google/googletest.git + GIT_TAG master + SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-src" + BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}/googletest-build" + CONFIGURE_COMMAND "" + BUILD_COMMAND "" + INSTALL_COMMAND "" + TEST_COMMAND "" +) diff --git a/tests/cpputest/robertson/CMakeLists.txt b/tests/cpp/calvetti/CMakeLists.txt similarity index 67% rename from tests/cpputest/robertson/CMakeLists.txt rename to tests/cpp/calvetti/CMakeLists.txt index 388fdf9152..5f3db0773e 100644 --- a/tests/cpputest/robertson/CMakeLists.txt +++ b/tests/cpp/calvetti/CMakeLists.txt @@ -2,18 +2,20 @@ get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) project(model_${MODEL_NAME}_test) set(SRC_LIST - ../main.cpp tests1.cpp ) -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) target_link_libraries(${PROJECT_NAME} amici-testing model_${MODEL_NAME} + gtest_main ) -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/calvetti/tests1.cpp b/tests/cpp/calvetti/tests1.cpp similarity index 50% rename from tests/cpputest/calvetti/tests1.cpp rename to tests/cpp/calvetti/tests1.cpp index dfde30fd9d..fb093dc5bc 100644 --- a/tests/cpputest/calvetti/tests1.cpp +++ b/tests/cpp/calvetti/tests1.cpp @@ -1,13 +1,10 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "wrapfunctions.h" #include #include -TEST_GROUP(groupCalvetti){}; +#include -TEST(groupCalvetti, testSimulation) +TEST(ExampleCalvetti, Simulation) { amici::simulateVerifyWrite("/model_calvetti/nosensi/"); } diff --git a/tests/cpputest/calvetti/CMakeLists.txt b/tests/cpp/dirac/CMakeLists.txt similarity index 67% rename from tests/cpputest/calvetti/CMakeLists.txt rename to tests/cpp/dirac/CMakeLists.txt index 388fdf9152..5f3db0773e 100644 --- a/tests/cpputest/calvetti/CMakeLists.txt +++ b/tests/cpp/dirac/CMakeLists.txt @@ -2,18 +2,20 @@ get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) project(model_${MODEL_NAME}_test) set(SRC_LIST - ../main.cpp tests1.cpp ) -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) target_link_libraries(${PROJECT_NAME} amici-testing model_${MODEL_NAME} + gtest_main ) -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/dirac/tests1.cpp b/tests/cpp/dirac/tests1.cpp similarity index 54% rename from tests/cpputest/dirac/tests1.cpp rename to tests/cpp/dirac/tests1.cpp index 89d71f1f9b..d1d79af7fc 100644 --- a/tests/cpputest/dirac/tests1.cpp +++ b/tests/cpp/dirac/tests1.cpp @@ -1,19 +1,17 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include #include "wrapfunctions.h" #include -TEST_GROUP(groupDirac){}; +#include + -TEST(groupDirac, testSimulation) +TEST(ExampleDirac, Simulation) { amici::simulateVerifyWrite("/model_dirac/nosensi/"); } -TEST(groupDirac, testSensitivityForward) +TEST(ExampleDirac, SensitivityForward) { amici::simulateVerifyWrite("/model_dirac/sensiforward/"); } diff --git a/tests/cpputest/dirac/CMakeLists.txt b/tests/cpp/events/CMakeLists.txt similarity index 54% rename from tests/cpputest/dirac/CMakeLists.txt rename to tests/cpp/events/CMakeLists.txt index 99958ded68..5f3db0773e 100644 --- a/tests/cpputest/dirac/CMakeLists.txt +++ b/tests/cpp/events/CMakeLists.txt @@ -2,17 +2,20 @@ get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) project(model_${MODEL_NAME}_test) set(SRC_LIST - ../main.cpp tests1.cpp ) -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - add_executable(${PROJECT_NAME} ${SRC_LIST}) +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) + target_link_libraries(${PROJECT_NAME} amici-testing model_${MODEL_NAME} + gtest_main ) -add_test(NAME ${PROJECT_NAME} COMMAND ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME} -c) +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/events/tests1.cpp b/tests/cpp/events/tests1.cpp similarity index 55% rename from tests/cpputest/events/tests1.cpp rename to tests/cpp/events/tests1.cpp index f5ee53a7fb..a9b1ff8448 100644 --- a/tests/cpputest/events/tests1.cpp +++ b/tests/cpp/events/tests1.cpp @@ -1,24 +1,21 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "testfunctions.h" #include "wrapfunctions.h" #include -TEST_GROUP(groupEvents){}; +#include -TEST(groupEvents, testDefault) +TEST(ExampleEvents, Default) { amici::simulateWithDefaultOptions(); } -TEST(groupEvents, testSimulation) +TEST(ExampleEvents, Simulation) { amici::simulateVerifyWrite("/model_events/nosensi/"); } -TEST(groupEvents, testSensitivityForward) +TEST(ExampleEvents, SensitivityForward) { amici::simulateVerifyWrite("/model_events/sensiforward/"); } diff --git a/tests/cpputest/expectedResults.h5 b/tests/cpp/expectedResults.h5 similarity index 100% rename from tests/cpputest/expectedResults.h5 rename to tests/cpp/expectedResults.h5 diff --git a/tests/cpputest/jakstat_adjoint/CMakeLists.txt b/tests/cpp/jakstat_adjoint/CMakeLists.txt similarity index 54% rename from tests/cpputest/jakstat_adjoint/CMakeLists.txt rename to tests/cpp/jakstat_adjoint/CMakeLists.txt index 3197ed7a72..5f3db0773e 100644 --- a/tests/cpputest/jakstat_adjoint/CMakeLists.txt +++ b/tests/cpp/jakstat_adjoint/CMakeLists.txt @@ -2,17 +2,20 @@ get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) project(model_${MODEL_NAME}_test) set(SRC_LIST - ../main.cpp tests1.cpp ) -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - add_executable(${PROJECT_NAME} ${SRC_LIST}) +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) + target_link_libraries(${PROJECT_NAME} amici-testing model_${MODEL_NAME} + gtest_main ) -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/jakstat_adjoint/tests1.cpp b/tests/cpp/jakstat_adjoint/tests1.cpp similarity index 84% rename from tests/cpputest/jakstat_adjoint/tests1.cpp rename to tests/cpp/jakstat_adjoint/tests1.cpp index a69ed24a50..2ca15fdf6e 100644 --- a/tests/cpputest/jakstat_adjoint/tests1.cpp +++ b/tests/cpp/jakstat_adjoint/tests1.cpp @@ -1,46 +1,43 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "testfunctions.h" #include "wrapfunctions.h" #include -TEST_GROUP(groupJakstatAdjoint){}; +#include -TEST(groupJakstatAdjoint, testSimulation) +TEST(ExampleJakstatAdjoint, Simulation) { amici::simulateVerifyWrite("/model_jakstat_adjoint/nosensi/"); } -TEST(groupJakstatAdjoint, testSensitivityForward) +TEST(ExampleJakstatAdjoint, SensitivityForward) { amici::simulateVerifyWrite("/model_jakstat_adjoint/sensiforward/"); } -TEST(groupJakstatAdjoint, testSensitivityForwardLogParam) +TEST(ExampleJakstatAdjoint, SensitivityForwardLogParam) { amici::simulateVerifyWrite("/model_jakstat_adjoint/sensiforwardlogparam/"); } -TEST(groupJakstatAdjoint, testSensitivityAdjoint) +TEST(ExampleJakstatAdjoint, SensitivityAdjoint) { amici::simulateVerifyWrite("/model_jakstat_adjoint/sensiadjoint/"); } -TEST(groupJakstatAdjoint, testSensitivityForwardEmptySensInd) +TEST(ExampleJakstatAdjoint, SensitivityForwardEmptySensInd) { amici::simulateVerifyWrite( "/model_jakstat_adjoint/sensiforwardemptysensind/"); } -TEST(groupJakstatAdjoint, testSensitivityAdjointEmptySensInd) +TEST(ExampleJakstatAdjoint, SensitivityAdjointEmptySensInd) { amici::simulateVerifyWrite( "/model_jakstat_adjoint/sensiadjointemptysensind/"); } -IGNORE_TEST(groupJakstatAdjoint, testSensitivityAdjointUnusedNanOutputs) +TEST(ExampleJakstatAdjoint, DISABLED_SensitivityAdjointUnusedNanOutputs) { /* UN-IGNORE ONCE THIS MODEL HAS BEEN IMPORTED VIA PYTHON INTERFACE */ auto model = amici::generic_model::getModel(); @@ -71,10 +68,10 @@ IGNORE_TEST(groupJakstatAdjoint, testSensitivityAdjointUnusedNanOutputs) auto rdata = runAmiciSimulation(*solver, edata.get(), *model); for (int i = 0; i < model->nplist(); ++i) - CHECK_FALSE(std::isnan(rdata->sllh[i])); + ASSERT_FALSE(std::isnan(rdata->sllh[i])); } -TEST(groupJakstatAdjoint, testSensitivityReplicates) +TEST(ExampleJakstatAdjoint, SensitivityReplicates) { // Check that we can handle replicates correctly @@ -124,11 +121,11 @@ TEST(groupJakstatAdjoint, testSensitivityReplicates) solver->setSensitivityMethod(amici::SensitivityMethod::forward); auto rdata2 = runAmiciSimulation(*solver, &edata, *model); auto llh2 = rdata2->llh; - DOUBLES_EQUAL(2.0 * llh1, llh2, 1e-6); + ASSERT_NEAR(2.0 * llh1, llh2, 1e-6); // adjoint + replicates solver->setSensitivityMethod(amici::SensitivityMethod::adjoint); auto rdata3 = runAmiciSimulation(*solver, &edata, *model); auto llh3 = rdata3->llh; - DOUBLES_EQUAL(llh2, llh3, 1e-6); + ASSERT_NEAR(llh2, llh3, 1e-6); } diff --git a/tests/cpp/jakstat_adjoint_o2/CMakeLists.txt b/tests/cpp/jakstat_adjoint_o2/CMakeLists.txt new file mode 100644 index 0000000000..5f3db0773e --- /dev/null +++ b/tests/cpp/jakstat_adjoint_o2/CMakeLists.txt @@ -0,0 +1,21 @@ +get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +project(model_${MODEL_NAME}_test) + +set(SRC_LIST + tests1.cpp +) + +add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) + +target_link_libraries(${PROJECT_NAME} + amici-testing + model_${MODEL_NAME} + gtest_main +) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/jakstat_adjoint_o2/tests1.cpp b/tests/cpp/jakstat_adjoint_o2/tests1.cpp similarity index 53% rename from tests/cpputest/jakstat_adjoint_o2/tests1.cpp rename to tests/cpp/jakstat_adjoint_o2/tests1.cpp index 1331c0c369..b03b9da138 100644 --- a/tests/cpputest/jakstat_adjoint_o2/tests1.cpp +++ b/tests/cpp/jakstat_adjoint_o2/tests1.cpp @@ -1,24 +1,22 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "testfunctions.h" #include "wrapfunctions.h" #include -TEST_GROUP(groupJakstatAdjointO2){}; +#include + -TEST(groupJakstatAdjointO2, testSensitivityForward2) +TEST(ExampleJakstatAdjointO2, SensitivityForward2) { amici::simulateVerifyWrite("/model_jakstat_adjoint/sensi2forward/"); } -TEST(groupJakstatAdjointO2, testSensitivityForward2LogParam) +TEST(ExampleJakstatAdjointO2, SensitivityForward2LogParam) { amici::simulateVerifyWrite("/model_jakstat_adjoint/sensi2forwardlogparam/"); } -TEST(groupJakstatAdjointO2, testSensitivityAdjoint2) +TEST(ExampleJakstatAdjointO2, SensitivityAdjoint2) { amici::simulateVerifyWrite("/model_jakstat_adjoint/sensi2adjoint/"); } diff --git a/tests/cpp/nested_events/CMakeLists.txt b/tests/cpp/nested_events/CMakeLists.txt new file mode 100644 index 0000000000..5f3db0773e --- /dev/null +++ b/tests/cpp/nested_events/CMakeLists.txt @@ -0,0 +1,21 @@ +get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +project(model_${MODEL_NAME}_test) + +set(SRC_LIST + tests1.cpp +) + +add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) + +target_link_libraries(${PROJECT_NAME} + amici-testing + model_${MODEL_NAME} + gtest_main +) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/nested_events/tests1.cpp b/tests/cpp/nested_events/tests1.cpp similarity index 55% rename from tests/cpputest/nested_events/tests1.cpp rename to tests/cpp/nested_events/tests1.cpp index 7208b713de..7a74f5465b 100644 --- a/tests/cpputest/nested_events/tests1.cpp +++ b/tests/cpp/nested_events/tests1.cpp @@ -1,19 +1,16 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "testfunctions.h" #include "wrapfunctions.h" #include -TEST_GROUP(groupEvents){}; +#include -TEST(groupEvents, testSimulation) +TEST(ExampleNestedEvents, Simulation) { amici::simulateVerifyWrite("/model_nested_events/nosensi/"); } -TEST(groupEvents, testSensitivityForward) +TEST(ExampleNestedEvents, SensitivityForward) { amici::simulateVerifyWrite("/model_nested_events/sensiforward/"); } diff --git a/tests/cpp/neuron/CMakeLists.txt b/tests/cpp/neuron/CMakeLists.txt new file mode 100644 index 0000000000..5f3db0773e --- /dev/null +++ b/tests/cpp/neuron/CMakeLists.txt @@ -0,0 +1,21 @@ +get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +project(model_${MODEL_NAME}_test) + +set(SRC_LIST + tests1.cpp +) + +add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) + +target_link_libraries(${PROJECT_NAME} + amici-testing + model_${MODEL_NAME} + gtest_main +) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/neuron/tests1.cpp b/tests/cpp/neuron/tests1.cpp similarity index 62% rename from tests/cpputest/neuron/tests1.cpp rename to tests/cpp/neuron/tests1.cpp index f5d645b7af..cfdc04ef9c 100644 --- a/tests/cpputest/neuron/tests1.cpp +++ b/tests/cpp/neuron/tests1.cpp @@ -1,20 +1,17 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "testfunctions.h" #include "wrapfunctions.h" #include -TEST_GROUP(groupNeuron){}; +#include -TEST(groupNeuron, testSimulation) +TEST(ExampleNeuron, Simulation) { amici::simulateVerifyWrite( "/model_neuron/nosensi/", 100 * TEST_ATOL, 100 * TEST_RTOL); } -TEST(groupNeuron, testSensitivityForward) +TEST(ExampleNeuron, SensitivityForward) { amici::simulateVerifyWrite( "/model_neuron/sensiforward/", 10 * TEST_ATOL, 10 * TEST_RTOL); diff --git a/tests/cpp/neuron_o2/CMakeLists.txt b/tests/cpp/neuron_o2/CMakeLists.txt new file mode 100644 index 0000000000..5f3db0773e --- /dev/null +++ b/tests/cpp/neuron_o2/CMakeLists.txt @@ -0,0 +1,21 @@ +get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +project(model_${MODEL_NAME}_test) + +set(SRC_LIST + tests1.cpp +) + +add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) + +target_link_libraries(${PROJECT_NAME} + amici-testing + model_${MODEL_NAME} + gtest_main +) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/neuron_o2/tests1.cpp b/tests/cpp/neuron_o2/tests1.cpp similarity index 56% rename from tests/cpputest/neuron_o2/tests1.cpp rename to tests/cpp/neuron_o2/tests1.cpp index 57b9ac47ba..5be0c9ea54 100644 --- a/tests/cpputest/neuron_o2/tests1.cpp +++ b/tests/cpp/neuron_o2/tests1.cpp @@ -1,14 +1,11 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "testfunctions.h" #include "wrapfunctions.h" #include -TEST_GROUP(groupNeuronO2){}; +#include -TEST(groupNeuronO2, testSensitivity2) +TEST(ExampleNeuronO2, Sensitivity2) { amici::simulateVerifyWrite( "/model_neuron/sensi2forward/", 10 * TEST_ATOL, 10 * TEST_RTOL); diff --git a/tests/cpp/robertson/CMakeLists.txt b/tests/cpp/robertson/CMakeLists.txt new file mode 100644 index 0000000000..5f3db0773e --- /dev/null +++ b/tests/cpp/robertson/CMakeLists.txt @@ -0,0 +1,21 @@ +get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +project(model_${MODEL_NAME}_test) + +set(SRC_LIST + tests1.cpp +) + +add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) + +target_link_libraries(${PROJECT_NAME} + amici-testing + model_${MODEL_NAME} + gtest_main +) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/robertson/tests1.cpp b/tests/cpp/robertson/tests1.cpp similarity index 63% rename from tests/cpputest/robertson/tests1.cpp rename to tests/cpp/robertson/tests1.cpp index 441eac0b14..3cb74ada3a 100644 --- a/tests/cpputest/robertson/tests1.cpp +++ b/tests/cpp/robertson/tests1.cpp @@ -1,30 +1,27 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "wrapfunctions.h" #include #include -TEST_GROUP(groupRobertson){}; +#include -TEST(groupRobertson, testSimulation) +TEST(ExampleRobertson, Simulation) { amici::simulateVerifyWrite("/model_robertson/nosensi/"); } -TEST(groupRobertson, testSensitivityForward) +TEST(ExampleRobertson, SensitivityForward) { amici::simulateVerifyWrite( "/model_robertson/sensiforward/", 1e6 * TEST_ATOL, 1e2 * TEST_RTOL); } -TEST(groupRobertson, testSensitivityForwardDense) +TEST(ExampleRobertson, SensitivityForwardDense) { amici::simulateVerifyWrite( "/model_robertson/sensiforwarddense/", 1e6 * TEST_ATOL, 1e2 * TEST_RTOL); } -TEST(groupRobertson, testSensitivityForwardSPBCG) +TEST(ExampleRobertson, SensitivityForwardSPBCG) { amici::simulateVerifyWrite( "/model_robertson/sensiforwardSPBCG/", 1e7 * TEST_ATOL, 1e2 * TEST_RTOL); diff --git a/tests/cpp/steadystate/CMakeLists.txt b/tests/cpp/steadystate/CMakeLists.txt new file mode 100644 index 0000000000..5f3db0773e --- /dev/null +++ b/tests/cpp/steadystate/CMakeLists.txt @@ -0,0 +1,21 @@ +get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) +project(model_${MODEL_NAME}_test) + +set(SRC_LIST + tests1.cpp +) + +add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}) + +add_dependencies(${PROJECT_NAME} external_model_${MODEL_NAME}) + +target_link_libraries(${PROJECT_NAME} + amici-testing + model_${MODEL_NAME} + gtest_main +) + +include(GoogleTest) +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpputest/steadystate/tests1.cpp b/tests/cpp/steadystate/tests1.cpp similarity index 64% rename from tests/cpputest/steadystate/tests1.cpp rename to tests/cpp/steadystate/tests1.cpp index 78be0a2adc..505f8f15ed 100644 --- a/tests/cpputest/steadystate/tests1.cpp +++ b/tests/cpp/steadystate/tests1.cpp @@ -1,19 +1,16 @@ -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - #include "testfunctions.h" #include "wrapfunctions.h" #include -TEST_GROUP(groupSteadystate){}; +#include -TEST(groupSteadystate, testDefault) +TEST(ExampleSteadystate, Default) { amici::simulateWithDefaultOptions(); } -TEST(groupSteadystate, testModelFromHDF5) +TEST(ExampleSteadystate, ModelFromHDF5) { // Test reading some python-written options std::vector pExp{ 1, 0.5, 0.4, 2, 0.1 }; @@ -25,43 +22,43 @@ TEST(groupSteadystate, testModelFromHDF5) amici::checkEqualArray( kExp, model->getFixedParameters(), TEST_ATOL, TEST_RTOL, "k"); - CHECK_EQUAL(51, model->nt()); - CHECK_EQUAL(0.0, model->getTimepoint(0)); - CHECK_EQUAL(100.0, model->getTimepoint(model->nt() - 2)); - CHECK_EQUAL(INFINITY, model->getTimepoint(model->nt() - 1)); + ASSERT_EQ(51, model->nt()); + ASSERT_EQ(0.0, model->getTimepoint(0)); + ASSERT_EQ(100.0, model->getTimepoint(model->nt() - 2)); + ASSERT_EQ(INFINITY, model->getTimepoint(model->nt() - 1)); for (int i = 0; i < model->np(); ++i) { - CHECK_EQUAL(pExp[i], model->getUnscaledParameters()[i]); - CHECK_EQUAL(log10(pExp[i]), model->getParameters()[i]); + ASSERT_EQ(pExp[i], model->getUnscaledParameters()[i]); + ASSERT_EQ(log10(pExp[i]), model->getParameters()[i]); } } -TEST(groupSteadystate, testInequality) +TEST(ExampleSteadystate, Inequality) { auto modelA = amici::generic_model::getModel(); - auto modelB = std::unique_ptr(new amici::Model_Test()); + auto modelB = std::make_unique(); - CHECK_FALSE(*modelA == *modelB); + ASSERT_FALSE(*modelA == *modelB); } -TEST(groupSteadystate, testCopyModel) +TEST(ExampleSteadystate, CopyModel) { auto modelA = amici::generic_model::getModel(); auto modelB = std::unique_ptr(modelA->clone()); - CHECK_TRUE(*modelA == *modelB); + ASSERT_EQ(*modelA, *modelB); } -TEST(groupSteadystate, testCloneModel) +TEST(ExampleSteadystate, CloneModel) { auto modelA = amici::generic_model::getModel(); - auto modelB = std::unique_ptr( - new amici::model_model_steadystate::Model_model_steadystate()); + auto modelB = std::make_unique< + amici::model_model_steadystate::Model_model_steadystate>(); - CHECK_TRUE(*modelA == *modelB); + ASSERT_EQ(*modelA, *modelB); } -TEST(groupSteadystate, testExpDataFromReturnData) +TEST(ExampleSteadystate, ExpDataFromReturnData) { auto model = amici::generic_model::getModel(); auto solver = model->getSolver(); @@ -76,7 +73,7 @@ TEST(groupSteadystate, testExpDataFromReturnData) runAmiciSimulation(*solver, &edata, *model); } -TEST(groupSteadystate, testReuseSolver) +TEST(ExampleSteadystate, ReuseSolver) { auto model = amici::generic_model::getModel(); auto solver = model->getSolver(); @@ -90,7 +87,7 @@ TEST(groupSteadystate, testReuseSolver) runAmiciSimulation(*solver, nullptr, *model); } -TEST(groupSteadystate, testRethrow) +TEST(ExampleSteadystate, Rethrow) { auto model = amici::generic_model::getModel(); auto solver = model->getSolver(); @@ -109,12 +106,12 @@ TEST(groupSteadystate, testRethrow) runAmiciSimulation(*solver, nullptr, *model); // must throw - CHECK_THROWS(amici::IntegrationFailure, - runAmiciSimulation(*solver, nullptr, *model, true)); + ASSERT_THROW(runAmiciSimulation(*solver, nullptr, *model, true), + amici::IntegrationFailure); } -TEST(groupSteadystate, testMaxtime) +TEST(ExampleSteadystate, Maxtime) { auto model = amici::generic_model::getModel(); auto solver = model->getSolver(); @@ -125,100 +122,100 @@ TEST(groupSteadystate, testMaxtime) NEW_OPTION_FILE, *solver, "/model_steadystate/nosensi/options"); auto rdata = runAmiciSimulation(*solver, nullptr, *model); - CHECK_EQUAL(amici::AMICI_SUCCESS, rdata->status); + ASSERT_EQ(amici::AMICI_SUCCESS, rdata->status); solver->setMaxTime(0.000001); // must throw rdata = runAmiciSimulation(*solver, nullptr, *model); - CHECK_EQUAL(amici::AMICI_MAX_TIME_EXCEEDED, rdata->status); + ASSERT_EQ(amici::AMICI_MAX_TIME_EXCEEDED, rdata->status); } -TEST(groupSteadystate, testInitialStatesNonEmpty) +TEST(ExampleSteadystate, InitialStatesNonEmpty) { auto model = amici::generic_model::getModel(); - CHECK_FALSE(model->getInitialStates().empty()); + ASSERT_FALSE(model->getInitialStates().empty()); } -TEST(groupSteadystate, testInitialStateSensitivitiesNonEmpty) +TEST(ExampleSteadystate, InitialStateSensitivitiesNonEmpty) { auto model = amici::generic_model::getModel(); - CHECK_FALSE(model->getInitialStateSensitivities().empty()); + ASSERT_FALSE(model->getInitialStateSensitivities().empty()); } -TEST(groupSteadystate, testSimulation) +TEST(ExampleSteadystate, Simulation) { amici::simulateVerifyWrite( "/model_steadystate/nosensi/", 100 * TEST_ATOL, 100 * TEST_RTOL); } -TEST(groupSteadystate, testSensitivityForward) +TEST(ExampleSteadystate, SensitivityForward) { amici::simulateVerifyWrite("/model_steadystate/sensiforward/"); } -TEST(groupSteadystate, testSensitivityForwardPlist) +TEST(ExampleSteadystate, SensitivityForwardPlist) { amici::simulateVerifyWrite("/model_steadystate/sensiforwardplist/"); } -TEST(groupSteadystate, testSensitivityForwardErrorInt) +TEST(ExampleSteadystate, SensitivityForwardErrorInt) { amici::simulateVerifyWrite("/model_steadystate/sensiforwarderrorint/"); } -TEST(groupSteadystate, testSensitivityForwardErrorNewt) +TEST(ExampleSteadystate, SensitivityForwardErrorNewt) { amici::simulateVerifyWrite("/model_steadystate/sensiforwarderrornewt/"); } -TEST(groupSteadystate, testSensitivityForwardDense) +TEST(ExampleSteadystate, SensitivityForwardDense) { amici::simulateVerifyWrite("/model_steadystate/sensiforwarddense/"); } -TEST(groupSteadystate, testSensitivityForwardSPBCG) +TEST(ExampleSteadystate, SensitivityForwardSPBCG) { amici::simulateVerifyWrite( "/model_steadystate/nosensiSPBCG/", 10 * TEST_ATOL, 10 * TEST_RTOL); } -TEST(groupSteadystate, testSensiFwdNewtonPreeq) +TEST(ExampleSteadystate, SensiFwdNewtonPreeq) { amici::simulateVerifyWrite("/model_steadystate/sensifwdnewtonpreeq/"); } -TEST(groupSteadystate, testSensiAdjNewtonPreeq) +TEST(ExampleSteadystate, SensiAdjNewtonPreeq) { amici::simulateVerifyWrite("/model_steadystate/sensiadjnewtonpreeq/"); } -TEST(groupSteadystate, testSensiFwdSimPreeq) +TEST(ExampleSteadystate, SensiFwdSimPreeq) { amici::simulateVerifyWrite("/model_steadystate/sensifwdsimpreeq/"); } -TEST(groupSteadystate, testSensiFwdSimPreeqFSA) +TEST(ExampleSteadystate, SensiFwdSimPreeqFSA) { amici::simulateVerifyWrite("/model_steadystate/sensifwdsimpreeqFSA/"); } -TEST(groupSteadystate, testSensiAdjSimPreeq) +TEST(ExampleSteadystate, SensiAdjSimPreeq) { amici::simulateVerifyWrite("/model_steadystate/sensiadjsimpreeq/"); } -TEST(groupSteadystate, testSensiAdjSimPreeqFSA) +TEST(ExampleSteadystate, SensiAdjSimPreeqFSA) { amici::simulateVerifyWrite("/model_steadystate/sensiadjsimpreeqFSA/"); } -TEST(groupSteadystate, testSensiFwdByhandPreeq) +TEST(ExampleSteadystate, SensiFwdByhandPreeq) { amici::simulateVerifyWrite("/model_steadystate/sensifwdbyhandpreeq/"); } -TEST(groupSteadystate, testSensiAdjByhandPreeq) +TEST(ExampleSteadystate, SensiAdjByhandPreeq) { amici::simulateVerifyWrite("/model_steadystate/sensiadjbyhandpreeq/"); } diff --git a/tests/cpputest/testOptions.h5 b/tests/cpp/testOptions.h5 similarity index 100% rename from tests/cpputest/testOptions.h5 rename to tests/cpp/testOptions.h5 diff --git a/tests/cpputest/testfunctions.cpp b/tests/cpp/testfunctions.cpp similarity index 91% rename from tests/cpputest/testfunctions.cpp rename to tests/cpp/testfunctions.cpp index 382e7fa43d..3e03eedfc4 100644 --- a/tests/cpputest/testfunctions.cpp +++ b/tests/cpp/testfunctions.cpp @@ -9,8 +9,7 @@ #include #include -#include -#include +#include "gtest/gtest.h" namespace amici { @@ -120,12 +119,12 @@ bool withinTolerance(double expected, double actual, double atol, double rtol, i void checkEqualArray(std::vector const& expected, std::vector const& actual, double atol, double rtol, std::string const& name) { - CHECK_EQUAL(expected.size(), actual.size()); + ASSERT_EQ(expected.size(), actual.size()); for(int i = 0; (unsigned) i < expected.size(); ++i) { bool withinTol = withinTolerance(expected[i], actual[i], atol, rtol, i, name.c_str()); - CHECK_TRUE(withinTol); + ASSERT_TRUE(withinTol); } } @@ -135,12 +134,12 @@ void checkEqualArray(const double *expected, const double *actual, const int len if(!length) return; - CHECK_TRUE(expected && actual); + ASSERT_TRUE(expected && actual); for(int i = 0; i < length; ++i) { bool withinTol = withinTolerance(expected[i], actual[i], atol, rtol, i, name); - CHECK_TRUE(withinTol); + ASSERT_TRUE(withinTol); } } @@ -148,19 +147,19 @@ void checkEqualArrayStrided(const double *expected, const double *actual, int le if(!expected && !actual) return; - CHECK_TRUE(expected && actual); + ASSERT_TRUE(expected && actual); for(int i = 0; i < length; ++i) { bool withinTol = withinTolerance(expected[i * strideExpected], actual[i * strideActual], atol, rtol, i, name); - CHECK_TRUE(withinTol); + ASSERT_TRUE(withinTol); } } void verifyReturnData(std::string const& hdffile, std::string const& resultPath, const ReturnData *rdata, const Model *model, double atol, double rtol) { - CHECK_FALSE(rdata == nullptr); + ASSERT_FALSE(rdata == nullptr); if(!hdf5::locationExists(hdffile, resultPath)) { fprintf(stderr, "ERROR: No results available for %s!\n", @@ -176,19 +175,19 @@ void verifyReturnData(std::string const& hdffile, std::string const& resultPath, std::vector expected; auto statusExp = hdf5::getIntScalarAttribute(file, resultPath, "status"); - CHECK_EQUAL(statusExp, rdata->status); + ASSERT_EQ(statusExp, rdata->status); double llhExp = hdf5::getDoubleScalarAttribute(file, resultPath, "llh"); - CHECK_TRUE(withinTolerance(llhExp, rdata->llh, atol, rtol, 1, "llh")); + ASSERT_TRUE(withinTolerance(llhExp, rdata->llh, atol, rtol, 1, "llh")); double chi2Exp = hdf5::getDoubleScalarAttribute(file, resultPath, "chi2"); - CHECK_TRUE(withinTolerance(chi2Exp, rdata->chi2, atol, rtol, 1, "chi2")); + ASSERT_TRUE(withinTolerance(chi2Exp, rdata->chi2, atol, rtol, 1, "chi2")); if(hdf5::locationExists(file, resultPath + "/x")) { expected = hdf5::getDoubleDataset2D(file, resultPath + "/x", m, n); checkEqualArray(expected, rdata->x, atol, rtol, "x"); } else { - CHECK_TRUE(rdata->x.empty()); + ASSERT_TRUE(rdata->x.empty()); } // CHECK_EQUAL(AMICI_O2MODE_FULL, udata->o2mode); @@ -197,42 +196,42 @@ void verifyReturnData(std::string const& hdffile, std::string const& resultPath, expected = hdf5::getDoubleDataset2D(file, resultPath + "/diagnosis/J", m, n); checkEqualArray(expected, rdata->J, atol, rtol, "J"); } else { - CHECK_TRUE(rdata->J.empty()); + ASSERT_TRUE(rdata->J.empty()); } if(hdf5::locationExists(file, resultPath + "/y")) { expected = hdf5::getDoubleDataset2D(file, resultPath + "/y", m, n); checkEqualArray(expected, rdata->y, atol, rtol, "y"); } else { - CHECK_TRUE(rdata->y.empty()); + ASSERT_TRUE(rdata->y.empty()); } if(hdf5::locationExists(file, resultPath + "/res")) { expected = hdf5::getDoubleDataset1D(file, resultPath + "/res"); checkEqualArray(expected, rdata->res, atol, rtol, "res"); } else { - CHECK_TRUE(rdata->res.empty()); + ASSERT_TRUE(rdata->res.empty()); } if(hdf5::locationExists(file, resultPath + "/z")) { expected = hdf5::getDoubleDataset2D(file, resultPath + "/z", m, n); checkEqualArray(expected, rdata->z, atol, rtol, "z"); } else { - CHECK_TRUE(rdata->z.empty()); + ASSERT_TRUE(rdata->z.empty()); } if(hdf5::locationExists(file, resultPath + "/rz")) { expected = hdf5::getDoubleDataset2D(file, resultPath + "/rz", m, n); checkEqualArray(expected, rdata->rz, atol, rtol, "rz"); } else { - CHECK_TRUE(rdata->rz.empty()); + ASSERT_TRUE(rdata->rz.empty()); } if(hdf5::locationExists(file, resultPath + "/sigmaz")) { expected = hdf5::getDoubleDataset2D(file, resultPath + "/sigmaz", m, n); checkEqualArray(expected, rdata->sigmaz, atol, rtol, "sigmaz"); } else { - CHECK_TRUE(rdata->sigmaz.empty()); + ASSERT_TRUE(rdata->sigmaz.empty()); } expected = hdf5::getDoubleDataset1D(file, resultPath + "/diagnosis/xdot"); @@ -244,8 +243,8 @@ void verifyReturnData(std::string const& hdffile, std::string const& resultPath, if(rdata->sensi >= SensitivityOrder::first) { verifyReturnDataSensitivities(file, resultPath, rdata, model, atol, rtol); } else { - CHECK_EQUAL(0, rdata->sllh.size()); - CHECK_EQUAL(0, rdata->s2llh.size()); + ASSERT_EQ(0, rdata->sllh.size()); + ASSERT_EQ(0, rdata->s2llh.size()); } } @@ -257,7 +256,7 @@ void verifyReturnDataSensitivities(H5::H5File const& file, std::string const& re expected = hdf5::getDoubleDataset1D(file, resultPath + "/sllh"); checkEqualArray(expected, rdata->sllh, atol, rtol, "sllh"); } else { - CHECK_TRUE(rdata->sllh.empty()); + ASSERT_TRUE(rdata->sllh.empty()); } if(rdata->sensi_meth == SensitivityMethod::forward) { @@ -266,21 +265,21 @@ void verifyReturnDataSensitivities(H5::H5File const& file, std::string const& re expected = hdf5::getDoubleDataset2D(file, resultPath + "/sx0", m, n); checkEqualArray(expected, rdata->sx0, atol, rtol, "sx0"); } else { - CHECK_TRUE(rdata->sx0.empty()); + ASSERT_TRUE(rdata->sx0.empty()); } if(hdf5::locationExists(file, resultPath + "/sres")) { expected = hdf5::getDoubleDataset2D(file, resultPath + "/sres", m, n); checkEqualArray(expected, rdata->sres, atol, rtol, "sres"); } else { - CHECK_TRUE(rdata->sres.empty()); + ASSERT_TRUE(rdata->sres.empty()); } if(hdf5::locationExists(file, resultPath + "/FIM")) { expected = hdf5::getDoubleDataset2D(file, resultPath + "/FIM", m, n); checkEqualArray(expected, rdata->FIM, atol, rtol, "FIM"); } else { - CHECK_TRUE(rdata->FIM.empty()); + ASSERT_TRUE(rdata->FIM.empty()); } @@ -295,7 +294,7 @@ void verifyReturnDataSensitivities(H5::H5File const& file, std::string const& re &rdata->sx[ip * model->nt() * rdata->nx], model->nt() * model->nxtrue_rdata, atol, rtol, "sx"); } else { - CHECK_TRUE(rdata->sx.empty()); + ASSERT_TRUE(rdata->sx.empty()); } if(hdf5::locationExists(file, resultPath + "/sy")) { @@ -305,7 +304,7 @@ void verifyReturnDataSensitivities(H5::H5File const& file, std::string const& re &rdata->sy[ip * model->nt() * model->ny], model->nt() * model->nytrue, atol, rtol, "sy"); } else { - CHECK_TRUE(rdata->sy.empty()); + ASSERT_TRUE(rdata->sy.empty()); } @@ -349,8 +348,8 @@ void verifyReturnDataSensitivities(H5::H5File const& file, std::string const& re expected = hdf5::getDoubleDataset2D(file, resultPath + "/s2llh", m, n); checkEqualArray(expected, rdata->s2llh, atol, rtol, "s2llh"); } else { - CHECK_EQUAL(0, rdata->s2llh.size()); - CHECK_EQUAL(0, rdata->s2rz.size()); + ASSERT_EQ(0, rdata->s2llh.size()); + ASSERT_EQ(0, rdata->s2rz.size()); } } diff --git a/tests/cpputest/testfunctions.h b/tests/cpp/testfunctions.h similarity index 98% rename from tests/cpputest/testfunctions.h rename to tests/cpp/testfunctions.h index 2cb4658220..2d1d0f7325 100644 --- a/tests/cpputest/testfunctions.h +++ b/tests/cpp/testfunctions.h @@ -10,12 +10,9 @@ #include #endif -#include // make std::ostringstream available (needs to come before TestHarness.h) +#include #include -#include -#include - namespace amici { class ReturnData; diff --git a/tests/cpp/unittests/CMakeLists.txt b/tests/cpp/unittests/CMakeLists.txt new file mode 100644 index 0000000000..5e1f7e68ac --- /dev/null +++ b/tests/cpp/unittests/CMakeLists.txt @@ -0,0 +1,27 @@ +project(unittests) + +find_package(Boost COMPONENTS serialization) + +set(SRC_LIST + testMisc.cpp + testExpData.cpp +) + +add_executable(${PROJECT_NAME} ${SRC_LIST}) + +target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) + +if(Boost_FOUND) + target_sources(${PROJECT_NAME} PRIVATE testSerialization.cpp) + target_include_directories(${PROJECT_NAME} PRIVATE "${Boost_INCLUDE_DIR}") +endif() +target_link_libraries(${PROJECT_NAME} + amici-testing + Upstream::amici + ${Boost_LIBRARIES} + gtest_main + ) + +include(GoogleTest) + +gtest_discover_tests(${PROJECT_NAME}) diff --git a/tests/cpp/unittests/testExpData.cpp b/tests/cpp/unittests/testExpData.cpp new file mode 100644 index 0000000000..4cf947341c --- /dev/null +++ b/tests/cpp/unittests/testExpData.cpp @@ -0,0 +1,328 @@ +#include "testfunctions.h" + +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace amici { +namespace generic_model { +std::unique_ptr getModel(); +} // namespace generic_model +} // namespace amici + +using namespace amici; + +namespace { + +class ExpDataTest : public ::testing::Test { + protected: + void SetUp() override { + model->setTimepoints(timepoints); + model->setNMaxEvent(nmaxevent); + testModel.setTimepoints(timepoints); + testModel.setNMaxEvent(nmaxevent); + } + + int nx = 1, ny = 2, nz = 3, nmaxevent = 4; + std::vector timepoints = { 1, 2, 3, 4 }; + + std::unique_ptr model = generic_model::getModel(); + + Model_Test testModel = Model_Test( + ModelDimensions( + nx, // nx_rdata + nx, // nxtrue_rdata + nx, // nx_solver + nx, // nxtrue_solver + 0, // nx_solver_reinit + 1, // np + 3, // nk + ny, // ny + ny, // nytrue + nz, // nz + nz, // nztrue + nmaxevent, // ne + 0, // nJ + 0, // nw + 0, // ndwdx + 0, // ndwdp + 0, // dwdw + 0, // ndxdotdw + {}, // ndJydy + 0, // nnz + 0, // ubw + 0 // lbw + ), + SimulationParameters( + std::vector(3, 0.0), + std::vector(1, 0.0), + std::vector(2, 1) + ), + SecondOrderMode::none, + std::vector(), + std::vector()); +}; + +TEST_F(ExpDataTest, DefaultConstructable) +{ + ExpData edata{}; + ASSERT_EQ(edata.nytrue(), 0); + ASSERT_EQ(edata.nztrue(), 0); + ASSERT_EQ(edata.nmaxevent(), 0); +} +TEST_F(ExpDataTest, ModelCtor) +{ + ExpData edata(model->nytrue, model->nztrue, model->nMaxEvent()); + ASSERT_EQ(edata.nytrue(), model->nytrue); + ASSERT_EQ(edata.nztrue(), model->nztrue); + ASSERT_EQ(edata.nmaxevent(), model->nMaxEvent()); +} + +TEST_F(ExpDataTest, DimensionCtor) +{ + ExpData edata(model->nytrue, model->nztrue, model->nMaxEvent(), timepoints); + ASSERT_EQ(edata.nytrue(), model->nytrue); + ASSERT_EQ(edata.nztrue(), model->nztrue); + ASSERT_EQ(edata.nmaxevent(), model->nMaxEvent()); + ASSERT_EQ(edata.nt(), model->nt()); + checkEqualArray( + timepoints, edata.getTimepoints(), TEST_ATOL, TEST_RTOL, "ts"); +} + +TEST_F(ExpDataTest, MeasurementCtor) +{ + std::vector y(ny * timepoints.size(), 0.0); + std::vector y_std(ny * timepoints.size(), 0.1); + std::vector z(nz * nmaxevent, 0.0); + std::vector z_std(nz * nmaxevent, 0.1); + + ExpData edata(testModel.nytrue, + testModel.nztrue, + testModel.nMaxEvent(), + timepoints, + y, + y_std, + z, + z_std); + ASSERT_EQ(edata.nytrue(), testModel.nytrue); + ASSERT_EQ(edata.nztrue(), testModel.nztrue); + ASSERT_EQ(edata.nmaxevent(), testModel.nMaxEvent()); + ASSERT_EQ(edata.nt(), testModel.nt()); + checkEqualArray( + timepoints, edata.getTimepoints(), TEST_ATOL, TEST_RTOL, "ts"); + checkEqualArray( + y, edata.getObservedData(), TEST_ATOL, TEST_RTOL, "observedData"); + checkEqualArray(y_std, + edata.getObservedDataStdDev(), + TEST_ATOL, + TEST_RTOL, + "observedDataStdDev"); + checkEqualArray( + z, edata.getObservedEvents(), TEST_ATOL, TEST_RTOL, "observedEvents"); + checkEqualArray(z_std, + edata.getObservedEventsStdDev(), + TEST_ATOL, + TEST_RTOL, + "observedEventsStdDev"); + + ExpData edata_copy(edata); + ASSERT_EQ(edata.nytrue(), edata_copy.nytrue()); + ASSERT_EQ(edata.nztrue(), edata_copy.nztrue()); + ASSERT_EQ(edata.nmaxevent(), edata_copy.nmaxevent()); + ASSERT_EQ(edata.nt(), edata_copy.nt()); + checkEqualArray(edata_copy.getTimepoints(), + edata.getTimepoints(), + TEST_ATOL, + TEST_RTOL, + "ts"); + checkEqualArray(edata_copy.getObservedData(), + edata.getObservedData(), + TEST_ATOL, + TEST_RTOL, + "observedData"); + checkEqualArray(edata_copy.getObservedDataStdDev(), + edata.getObservedDataStdDev(), + TEST_ATOL, + TEST_RTOL, + "observedDataStdDev"); + checkEqualArray(edata_copy.getObservedEvents(), + edata.getObservedEvents(), + TEST_ATOL, + TEST_RTOL, + "observedEvents"); + checkEqualArray(edata_copy.getObservedEventsStdDev(), + edata.getObservedEventsStdDev(), + TEST_ATOL, + TEST_RTOL, + "observedEventsStdDev"); +} + +TEST_F(ExpDataTest, CopyConstructable) +{ + testModel.setTimepoints(timepoints); + auto edata = ExpData(testModel); + ASSERT_EQ(edata.nytrue(), testModel.nytrue); + ASSERT_EQ(edata.nztrue(), testModel.nztrue); + ASSERT_EQ(edata.nmaxevent(), testModel.nMaxEvent()); + ASSERT_EQ(edata.nt(), testModel.nt()); + checkEqualArray(testModel.getTimepoints(), + edata.getTimepoints(), + TEST_ATOL, + TEST_RTOL, + "ts"); +} + +TEST_F(ExpDataTest, DimensionChecks) +{ + std::vector bad_std(ny, -0.1); + std::vector y(ny * timepoints.size(), 0.0); + std::vector y_std(ny * timepoints.size(), 0.1); + std::vector z(nz * nmaxevent, 0.0); + std::vector z_std(nz * nmaxevent, 0.1); + + ASSERT_THROW(ExpData(testModel.nytrue, + testModel.nztrue, + testModel.nMaxEvent(), + timepoints, + z, + z_std, + z, + z_std), + AmiException); + + ASSERT_THROW(ExpData(testModel.nytrue, + testModel.nztrue, + testModel.nMaxEvent(), + timepoints, + z, + bad_std, + z, + z_std), + AmiException); + + ExpData edata(testModel); + + std::vector bad_y(ny * timepoints.size() + 1, 0.0); + std::vector bad_y_std(ny * timepoints.size() + 1, 0.1); + std::vector bad_z(nz * nmaxevent + 1, 0.0); + std::vector bad_z_std(nz * nmaxevent + 1, 0.1); + + ASSERT_THROW(edata.setObservedData(bad_y), AmiException); + ASSERT_THROW(edata.setObservedDataStdDev(bad_y_std), AmiException); + ASSERT_THROW(edata.setObservedEvents(bad_z), AmiException); + ASSERT_THROW(edata.setObservedEventsStdDev(bad_y_std), AmiException); + + std::vector bad_single_y(edata.nt() + 1, 0.0); + std::vector bad_single_y_std(edata.nt() + 1, 0.1); + std::vector bad_single_z(edata.nmaxevent() + 1, 0.0); + std::vector bad_single_z_std(edata.nmaxevent() + 1, 0.1); + + ASSERT_THROW(edata.setObservedData(bad_single_y, 0), + AmiException); + ASSERT_THROW(edata.setObservedDataStdDev(bad_single_y_std, 0), + AmiException); + ASSERT_THROW(edata.setObservedEvents(bad_single_z, 0), + AmiException); + ASSERT_THROW(edata.setObservedEventsStdDev(bad_single_y_std, 0), + AmiException); + + ASSERT_THROW(edata.setTimepoints(std::vector{ 0.0, 1.0, 0.5 }), + AmiException); +} + +TEST_F(ExpDataTest, SettersGetters) +{ + ExpData edata(testModel); + + std::vector y(ny * timepoints.size(), 0.0); + std::vector y_std(ny * timepoints.size(), 0.1); + std::vector z(nz * nmaxevent, 0.0); + std::vector z_std(nz * nmaxevent, 0.1); + + edata.setObservedData(y); + checkEqualArray( + edata.getObservedData(), y, TEST_ATOL, TEST_RTOL, "ObservedData"); + edata.setObservedDataStdDev(y_std); + checkEqualArray(edata.getObservedDataStdDev(), + y_std, + TEST_ATOL, + TEST_RTOL, + "ObservedDataStdDev"); + edata.setObservedEvents(z); + checkEqualArray( + edata.getObservedEvents(), z, TEST_ATOL, TEST_RTOL, "ObservedEvents"); + edata.setObservedEventsStdDev(z_std); + checkEqualArray(edata.getObservedEventsStdDev(), + z_std, + TEST_ATOL, + TEST_RTOL, + "ObservedEventsStdDev"); + + std::vector single_y(edata.nt(), 0.0); + std::vector single_y_std(edata.nt(), 0.1); + + for (int iy = 0; iy < ny; ++iy) { + edata.setObservedData(single_y, iy); + edata.setObservedDataStdDev(single_y_std, iy); + } + ASSERT_THROW(edata.setObservedData(single_y, ny), std::exception); + ASSERT_THROW(edata.setObservedData(single_y, -1), std::exception); + ASSERT_THROW(edata.setObservedDataStdDev(single_y_std, ny), std::exception); + ASSERT_THROW(edata.setObservedDataStdDev(single_y_std, -1), std::exception); + + std::vector single_z(edata.nmaxevent(), 0.0); + std::vector single_z_std(edata.nmaxevent(), 0.1); + + for (int iz = 0; iz < nz; ++iz) { + edata.setObservedEvents(single_z, iz); + edata.setObservedEventsStdDev(single_z_std, iz); + } + + ASSERT_THROW(edata.setObservedEvents(single_z, nz), std::exception); + ASSERT_THROW(edata.setObservedEvents(single_z, -1), std::exception); + ASSERT_THROW(edata.setObservedEventsStdDev(single_z_std, nz), + std::exception); + ASSERT_THROW(edata.setObservedEventsStdDev(single_z_std, -1), + std::exception); + + ASSERT_TRUE(edata.getObservedDataPtr(0)); + ASSERT_TRUE(edata.getObservedDataStdDevPtr(0)); + ASSERT_TRUE(edata.getObservedEventsPtr(0)); + ASSERT_TRUE(edata.getObservedEventsStdDevPtr(0)); + + std::vector empty(0, 0.0); + + edata.setObservedData(empty); + edata.setObservedDataStdDev(empty); + edata.setObservedEvents(empty); + edata.setObservedEventsStdDev(empty); + + ASSERT_TRUE(!edata.getObservedDataPtr(0)); + ASSERT_TRUE(!edata.getObservedDataStdDevPtr(0)); + ASSERT_TRUE(!edata.getObservedEventsPtr(0)); + ASSERT_TRUE(!edata.getObservedEventsStdDevPtr(0)); + + checkEqualArray( + edata.getObservedData(), empty, TEST_ATOL, TEST_RTOL, "ObservedData"); + checkEqualArray(edata.getObservedDataStdDev(), + empty, + TEST_ATOL, + TEST_RTOL, + "ObservedDataStdDev"); + checkEqualArray( + edata.getObservedEvents(), empty, TEST_ATOL, TEST_RTOL, "ObservedEvents"); + checkEqualArray(edata.getObservedEventsStdDev(), + empty, + TEST_ATOL, + TEST_RTOL, + "ObservedEventsStdDev"); +} + +} // namespace diff --git a/tests/cpp/unittests/testMisc.cpp b/tests/cpp/unittests/testMisc.cpp new file mode 100644 index 0000000000..4106742dbd --- /dev/null +++ b/tests/cpp/unittests/testMisc.cpp @@ -0,0 +1,597 @@ +#include "testfunctions.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include + +namespace amici { +namespace generic_model { + +std::unique_ptr getModel() +{ + return std::make_unique(); +} + +} // namespace generic_model +} // namespace amici + +using namespace amici; + +namespace { + +void +testSolverGetterSetters(CVodeSolver solver, + SensitivityMethod sensi_meth, + SensitivityOrder sensi, + InternalSensitivityMethod ism, + InterpolationType interp, + NonlinearSolverIteration iter, + LinearMultistepMethod lmm, + int steps, + int badsteps, + double tol, + double badtol); + +class ModelTest : public ::testing::Test { + protected: + int nx = 1, ny = 2, nz = 3, nmaxevent = 4; + std::vector p{ 1.0 }; + std::vector k{ 0.5, 0.4, 0.7 }; + std::vector plist{ 1 }; + std::vector idlist{ 0 }; + std::vector z2event{ 0, 0, 0 }; + Model_Test model = Model_Test( + ModelDimensions( + nx, // nx_rdata + nx, // nxtrue_rdata + nx, // nx_solver + nx, // nxtrue_solver + 0, // nx_solver_reinit + static_cast(p.size()), // np + static_cast(k.size()), // nk + ny, // ny + ny, // nytrue + nz, // nz + nz, // nztrue + nmaxevent, // ne + 0, // nJ + 0, // nw + 0, // ndwdx + 0, // ndwdp + 0, // dwdw + 0, // ndxdotdw + {}, // ndJydy + 0, // nnz + 0, // ubw + 0 // lbw + ), + SimulationParameters(k, p, plist), + SecondOrderMode::none, + idlist, + z2event); + std::vector unscaled{ NAN }; +}; + + +TEST_F(ModelTest, LinScaledParameterIsNotTransformed) +{ + model.setParameterScale(ParameterScaling::none); + + ASSERT_EQ(p[0], model.getParameters()[0]); +} + +TEST_F(ModelTest, LogScaledParameterIsTransformed) +{ + model.setParameterScale(ParameterScaling::ln); + + ASSERT_NEAR(std::log(p[0]), model.getParameters()[0], 1e-16); +} + +TEST_F(ModelTest, Log10ScaledParameterIsTransformed) +{ + model.setParameterScale(ParameterScaling::log10); + + ASSERT_NEAR(std::log10(p[0]), model.getParameters()[0], 1e-16); +} + +TEST_F(ModelTest, ParameterScaleTooShort) +{ + std::vector pscale(p.size() - 1, + ParameterScaling::log10); + ASSERT_THROW(model.setParameterScale(pscale), AmiException); + +} + +TEST_F(ModelTest, ParameterScaleTooLong) +{ + std::vector pscale (p.size() + 1, + ParameterScaling::log10); + ASSERT_THROW(model.setParameterScale(pscale), AmiException); +} + +TEST_F(ModelTest, UnsortedTimepointsThrow){ + ASSERT_THROW(model.setTimepoints(std::vector{ 0.0, 1.0, 0.5 }), + AmiException); +} + +TEST_F(ModelTest, ParameterNameIdGetterSetter) +{ + model.setParameterById("p0", 3.0); + ASSERT_NEAR(model.getParameterById("p0"), 3.0, 1e-16); + ASSERT_THROW(model.getParameterById("p1"), AmiException); + ASSERT_NEAR( + model.setParametersByIdRegex("p[\\d]+", 5.0), p.size(), 1e-16); + for (const auto& ip : model.getParameters()) + ASSERT_NEAR(ip, 5.0, 1e-16); + ASSERT_THROW(model.setParametersByIdRegex("k[\\d]+", 5.0), AmiException); + + model.setParameterByName("p0", 3.0); + ASSERT_NEAR(model.getParameterByName("p0"), 3.0, 1e-16); + ASSERT_THROW(model.getParameterByName("p1"), AmiException); + ASSERT_NEAR( + model.setParametersByNameRegex("p[\\d]+", 5.0), p.size(), 1e-16); + for (const auto& ip : model.getParameters()) + ASSERT_NEAR(ip, 5.0, 1e-16); + ASSERT_THROW(model.setParametersByNameRegex("k[\\d]+", 5.0), AmiException); + + model.setFixedParameterById("k0", 3.0); + ASSERT_NEAR(model.getFixedParameterById("k0"), 3.0, 1e-16); + ASSERT_THROW(model.getFixedParameterById("k4"), AmiException); + ASSERT_NEAR( + model.setFixedParametersByIdRegex("k[\\d]+", 5.0), k.size(), 1e-16); + for (const auto& ik : model.getFixedParameters()) + ASSERT_NEAR(ik, 5.0, 1e-16); + ASSERT_THROW(model.setFixedParametersByIdRegex("p[\\d]+", 5.0), AmiException); + + model.setFixedParameterByName("k0", 3.0); + ASSERT_NEAR(model.getFixedParameterByName("k0"), 3.0, 1e-16); + ASSERT_THROW(model.getFixedParameterByName("k4"), AmiException); + ASSERT_NEAR( + model.setFixedParametersByNameRegex("k[\\d]+", 5.0), k.size(), 1e-16); + for (const auto& ik : model.getFixedParameters()) + ASSERT_NEAR(ik, 5.0, 1e-16); + ASSERT_THROW(model.setFixedParametersByNameRegex("p[\\d]+", 5.0), + AmiException); +} + +TEST_F(ModelTest, ReinitializeFixedParameterInitialStates) +{ + ASSERT_THROW(model.setReinitializeFixedParameterInitialStates(true), + AmiException); + model.setReinitializeFixedParameterInitialStates(false); + ASSERT_TRUE(!model.getReinitializeFixedParameterInitialStates()); + AmiVector x(nx); + AmiVectorArray sx(model.np(), nx); +} + +TEST(SymbolicFunctionsTest, Sign) +{ + ASSERT_EQ(-1, sign(-2)); + ASSERT_EQ(0, sign(0)); + ASSERT_EQ(1, sign(2)); +} + +TEST(SymbolicFunctionsTest, Heaviside) +{ + ASSERT_EQ(0, heaviside(-1)); + ASSERT_EQ(1, heaviside(0)); + ASSERT_EQ(1, heaviside(1)); +} + +TEST(SymbolicFunctionsTest, Min) +{ + ASSERT_EQ(-1, min(-1, 2, 0)); + ASSERT_EQ(-2, min(1, -2, 0)); + ASSERT_TRUE(isNaN(min(getNaN(), getNaN(), 0))); + ASSERT_EQ(-1, min(-1, getNaN(), 0)); + ASSERT_EQ(-1, min(getNaN(), -1, 0)); +} + +TEST(SymbolicFunctionsTest, Max) +{ + ASSERT_EQ(2, max(-1, 2, 0)); + ASSERT_EQ(1, max(1, -2, 0)); + ASSERT_TRUE(isNaN(max(getNaN(), getNaN(), 0))); + ASSERT_EQ(-1, max(-1, getNaN(), 0)); + ASSERT_EQ(-1, max(getNaN(), -1, 0)); +} + +TEST(SymbolicFunctionsTest, DMin) +{ + ASSERT_EQ(0, Dmin(1, -1, -2, 0)); + ASSERT_EQ(1, Dmin(1, -1, 2, 0)); + ASSERT_EQ(1, Dmin(2, -1, -2, 0)); + ASSERT_EQ(0, Dmin(2, -1, 2, 0)); +} + +TEST(SymbolicFunctionsTest, DMax) +{ + ASSERT_EQ(1, Dmax(1, -1, -2, 0)); + ASSERT_EQ(0, Dmax(1, -1, 2, 0)); + ASSERT_EQ(0, Dmax(2, -1, -2, 0)); + ASSERT_EQ(1, Dmax(2, -1, 2, 0)); +} + +TEST(SymbolicFunctionsTest, pos_pow) +{ + ASSERT_EQ(0, pos_pow(-0.1, 3)); + ASSERT_EQ(pow(0.1, 3), pos_pow(0.1, 3)); +} + +TEST(SolverTestBasic, Equality) +{ + IDASolver i1, i2; + CVodeSolver c1, c2; + + ASSERT_EQ(i1, i2); + ASSERT_EQ(c1, c2); + ASSERT_FALSE(i1 == c1); +} + +TEST(SolverTestBasic, Clone) +{ + IDASolver i1; + std::unique_ptr i2(i1.clone()); + ASSERT_EQ(i1, *i2); + + CVodeSolver c1; + std::unique_ptr c2(c1.clone()); + ASSERT_TRUE(c1 == *c2); + ASSERT_FALSE(*i2 == *c2); +} + +TEST(SolverIdasTest, DefaultConstructableAndNotLeaky) +{ + IDASolver solver; +} + + +class SolverTest : public ::testing::Test { + protected: + void SetUp() override { + tol = 0.01; + badtol = -0.01; + sensi_meth = SensitivityMethod::adjoint; + sensi = SensitivityOrder::first; + steps = 1000; + badsteps = -1; + lmm = LinearMultistepMethod::adams; + iter = NonlinearSolverIteration::fixedpoint; + ism = InternalSensitivityMethod::staggered1; + interp = InterpolationType::polynomial; + } + + int nx = 1, ny = 2, nz = 3, ne = 0; + double tol, badtol; + std::vector timepoints = { 1, 2, 3, 4 }; + + std::unique_ptr model = generic_model::getModel(); + SensitivityMethod sensi_meth; + SensitivityOrder sensi; + int steps, badsteps; + LinearMultistepMethod lmm; + NonlinearSolverIteration iter; + InternalSensitivityMethod ism; + InterpolationType interp; + + Model_Test testModel = Model_Test( + ModelDimensions( + nx, // nx_rdata + nx, // nxtrue_rdata + nx, // nx_solver + nx, // nxtrue_solver + 0, // nx_solver_reinit + 1, // np + 3, // nk + ny, // ny + ny, // nytrue + nz, // nz + nz, // nztrue + ne, // ne + 0, // nJ + 0, // nw + 0, // ndwdx + 0, // ndwdp + 0, // dwdw + 0, // ndxdotdw + {}, // ndJydy + 1, // nnz + 0, // ubw + 0 // lbw + ), + SimulationParameters( + std::vector(3, 0.0), + std::vector(1, 0.0), + std::vector(2, 1) + ), + SecondOrderMode::none, + std::vector(0, 0.0), + std::vector()); + + CVodeSolver solver = CVodeSolver(); +}; + +TEST_F(SolverTest, SettersGettersNoSetup) +{ + testSolverGetterSetters(solver, + sensi_meth, + sensi, + ism, + interp, + iter, + lmm, + steps, + badsteps, + tol, + badtol); +} + +TEST_F(SolverTest, SettersGettersWithSetup) +{ + + solver.setSensitivityMethod(sensi_meth); + ASSERT_EQ(static_cast(solver.getSensitivityMethod()), + static_cast(sensi_meth)); + + auto rdata = std::make_unique(solver, testModel); + AmiVector x(nx), dx(nx); + AmiVectorArray sx(nx, 1), sdx(nx, 1); + + testModel.setInitialStates(std::vector{ 0 }); + + solver.setup(0, &testModel, x, dx, sx, sdx); + + testSolverGetterSetters(solver, + sensi_meth, + sensi, + ism, + interp, + iter, + lmm, + steps, + badsteps, + tol, + badtol); +} + +void +testSolverGetterSetters(CVodeSolver solver, + SensitivityMethod sensi_meth, + SensitivityOrder sensi, + InternalSensitivityMethod ism, + InterpolationType interp, + NonlinearSolverIteration iter, + LinearMultistepMethod lmm, + int steps, + int badsteps, + double tol, + double badtol) +{ + + solver.setSensitivityMethod(sensi_meth); + ASSERT_EQ(static_cast(solver.getSensitivityMethod()), + static_cast(sensi_meth)); + + solver.setSensitivityOrder(sensi); + ASSERT_EQ(static_cast(solver.getSensitivityOrder()), + static_cast(sensi)); + + solver.setInternalSensitivityMethod(ism); + ASSERT_EQ(static_cast(solver.getInternalSensitivityMethod()), + static_cast(ism)); + + solver.setInterpolationType(interp); + ASSERT_EQ(static_cast(solver.getInterpolationType()), + static_cast(interp)); + + solver.setNonlinearSolverIteration(iter); + ASSERT_EQ(static_cast(solver.getNonlinearSolverIteration()), + static_cast(iter)); + + solver.setLinearMultistepMethod(lmm); + ASSERT_EQ(static_cast(solver.getLinearMultistepMethod()), + static_cast(lmm)); + + solver.setPreequilibration(true); + ASSERT_EQ(solver.getPreequilibration(), true); + + solver.setStabilityLimitFlag(true); + ASSERT_EQ(solver.getStabilityLimitFlag(), true); + + ASSERT_THROW(solver.setNewtonMaxSteps(badsteps), AmiException); + solver.setNewtonMaxSteps(steps); + ASSERT_EQ(solver.getNewtonMaxSteps(), steps); + + ASSERT_THROW(solver.setNewtonMaxLinearSteps(badsteps), AmiException); + solver.setNewtonMaxLinearSteps(steps); + ASSERT_EQ(solver.getNewtonMaxLinearSteps(), steps); + + ASSERT_THROW(solver.setMaxSteps(badsteps), AmiException); + solver.setMaxSteps(steps); + ASSERT_EQ(solver.getMaxSteps(), steps); + + ASSERT_THROW(solver.setMaxStepsBackwardProblem(badsteps), AmiException); + solver.setMaxStepsBackwardProblem(steps); + ASSERT_EQ(solver.getMaxStepsBackwardProblem(), steps); + + ASSERT_THROW(solver.setRelativeTolerance(badtol), AmiException); + solver.setRelativeTolerance(tol); + ASSERT_EQ(solver.getRelativeTolerance(), tol); + + ASSERT_THROW(solver.setAbsoluteTolerance(badtol), AmiException); + solver.setAbsoluteTolerance(tol); + ASSERT_EQ(solver.getAbsoluteTolerance(), tol); + + ASSERT_THROW(solver.setRelativeToleranceQuadratures(badtol), AmiException); + solver.setRelativeToleranceQuadratures(tol); + ASSERT_EQ(solver.getRelativeToleranceQuadratures(), tol); + + ASSERT_THROW(solver.setAbsoluteToleranceQuadratures(badtol), AmiException); + solver.setAbsoluteToleranceQuadratures(tol); + ASSERT_EQ(solver.getAbsoluteToleranceQuadratures(), tol); + + ASSERT_THROW(solver.setRelativeToleranceSteadyState(badtol), AmiException); + solver.setRelativeToleranceSteadyState(tol); + ASSERT_EQ(solver.getRelativeToleranceSteadyState(), tol); + + ASSERT_THROW(solver.setAbsoluteToleranceSteadyState(badtol), AmiException); + solver.setAbsoluteToleranceSteadyState(tol); + ASSERT_EQ(solver.getAbsoluteToleranceSteadyState(), tol); +} + +class AmiVectorTest : public ::testing::Test { + protected: + std::vector vec1{ 1, 2, 4, 3 }; + std::vector vec2{ 4, 1, 2, 3 }; + std::vector vec3{ 4, 4, 2, 1 }; +}; + +TEST_F(AmiVectorTest, Vector) +{ + AmiVector av(vec1); + N_Vector nvec = av.getNVector(); + for (int i = 0; i < av.getLength(); ++i) + ASSERT_EQ(av.at(i), NV_Ith_S(nvec, i)); +} + +TEST_F(AmiVectorTest, VectorArray) +{ + AmiVectorArray ava(4, 3); + AmiVector av1(vec1), av2(vec2), av3(vec3); + std::vector avs{ av1, av2, av3 }; + for (int i = 0; i < ava.getLength(); ++i) + ava[i] = avs.at(i); + + std::vector badLengthVector(13, 0.0); + std::vector flattened(12, 0.0); + + ASSERT_THROW(ava.flatten_to_vector(badLengthVector), AmiException); + ava.flatten_to_vector(flattened); + for (int i = 0; i < ava.getLength(); ++i) { + const AmiVector av = ava[i]; + for (int j = 0; j < av.getLength(); ++j) + ASSERT_EQ(flattened.at(i * av.getLength() + j), av.at(j)); + } +} + +class SunMatrixWrapperTest : public ::testing::Test { + protected: + void SetUp() override { + A.set_data(0, 0, 0.69); + A.set_data(1, 0, 0.32); + A.set_data(2, 0, 0.95); + A.set_data(0, 1, 0.03); + A.set_data(1, 1, 0.44); + A.set_data(2, 1, 0.38); + + B.set_indexptr(0, 0); + B.set_indexptr(1, 2); + B.set_indexptr(2, 4); + B.set_indexptr(3, 5); + B.set_indexptr(4, 7); + B.set_data(0, 3); + B.set_data(1, 1); + B.set_data(2, 3); + B.set_data(3, 7); + B.set_data(4, 1); + B.set_data(5, 2); + B.set_data(6, 9); + B.set_indexval(0, 1); + B.set_indexval(1, 3); + B.set_indexval(2, 0); + B.set_indexval(3, 2); + B.set_indexval(4, 0); + B.set_indexval(5, 1); + B.set_indexval(6, 3); + } + + //inputs + std::vector a{0.82, 0.91, 0.13}; + std::vector b{0.77, 0.80}; + SUNMatrixWrapper A = SUNMatrixWrapper(3, 2); + SUNMatrixWrapper B = SUNMatrixWrapper(4, 4, 7, CSC_MAT); + // result + std::vector d{1.3753, 1.5084, 1.1655}; +}; + +TEST_F(SunMatrixWrapperTest, SparseMultiply) +{ + + auto A_sparse = SUNMatrixWrapper(A, 0.0, CSC_MAT); + auto c(a); //copy c + A_sparse.multiply(c, b); + checkEqualArray(d, c, TEST_ATOL, TEST_RTOL, "multiply"); +} + +TEST_F(SunMatrixWrapperTest, SparseMultiplyEmpty) +{ + // Ensure empty Matrix vector multiplication succeeds + auto A_sparse = SUNMatrixWrapper(1, 1, 0, CSR_MAT); + std::vector b {0.1}; + std::vector c {0.1}; + A_sparse.multiply(c, b); + ASSERT_TRUE(c[0] == 0.1); + + A_sparse = SUNMatrixWrapper(1, 1, 0, CSC_MAT); + A_sparse.multiply(c, b); + ASSERT_TRUE(c[0] == 0.1); +} + +TEST_F(SunMatrixWrapperTest, DenseMultiply) +{ + auto c(a); //copy c + A.multiply(c, b); + checkEqualArray(d, c, TEST_ATOL, TEST_RTOL, "multiply"); +} + +TEST_F(SunMatrixWrapperTest, StdVectorCtor) +{ + auto b_amivector = AmiVector(b); + auto a_amivector = AmiVector(a); +} + +TEST_F(SunMatrixWrapperTest, TransformThrows) +{ + ASSERT_THROW(SUNMatrixWrapper(A, 0.0, 13), std::invalid_argument); + auto A_sparse = SUNMatrixWrapper(A, 0.0, CSR_MAT); + ASSERT_THROW(SUNMatrixWrapper(A_sparse, 0.0, CSR_MAT), + std::invalid_argument); +} + +TEST_F(SunMatrixWrapperTest, BlockTranspose) +{ + SUNMatrixWrapper B_sparse(4, 4, 7, CSR_MAT); + ASSERT_THROW(B.transpose(B_sparse, 1.0, 4), std::domain_error); + + B_sparse = SUNMatrixWrapper(4, 4, 7, CSC_MAT); + B.transpose(B_sparse, -1.0, 2); + for (int idx = 0; idx < 7; idx++) { + ASSERT_EQ(SM_INDEXVALS_S(B.get())[idx], + SM_INDEXVALS_S(B_sparse.get())[idx]); + if (idx == 1) { + ASSERT_EQ(SM_DATA_S(B.get())[idx], + -SM_DATA_S(B_sparse.get())[3]); + } else if (idx == 3) { + ASSERT_EQ(SM_DATA_S(B.get())[idx], + -SM_DATA_S(B_sparse.get())[1]); + } else { + ASSERT_EQ(SM_DATA_S(B.get())[idx], + -SM_DATA_S(B_sparse.get())[idx]); + } + } + for (int icol = 0; icol <= 4; icol++) + ASSERT_EQ(SM_INDEXPTRS_S(B.get())[icol], + SM_INDEXPTRS_S(B_sparse.get())[icol]); +} + +} // namespace diff --git a/tests/cpp/unittests/testSerialization.cpp b/tests/cpp/unittests/testSerialization.cpp new file mode 100644 index 0000000000..15c8df9d84 --- /dev/null +++ b/tests/cpp/unittests/testSerialization.cpp @@ -0,0 +1,257 @@ +#include +#include +#include + +#include "testfunctions.h" + +#include + +#include + +void +checkReturnDataEqual(amici::ReturnData const& r, amici::ReturnData const& s) +{ + ASSERT_EQ(r.np, s.np); + ASSERT_EQ(r.nk, s.nk); + ASSERT_EQ(r.nx, s.nx); + ASSERT_EQ(r.nxtrue, s.nxtrue); + ASSERT_EQ(r.nx_solver, s.nx_solver); + ASSERT_EQ(r.nx_solver_reinit, s.nx_solver_reinit); + ASSERT_EQ(r.ny, s.ny); + ASSERT_EQ(r.nytrue, s.nytrue); + ASSERT_EQ(r.nz, s.nz); + ASSERT_EQ(r.nztrue, s.nztrue); + ASSERT_EQ(r.ne, s.ne); + ASSERT_EQ(r.nJ, s.nJ); + ASSERT_EQ(r.nplist, s.nplist); + ASSERT_EQ(r.nmaxevent, s.nmaxevent); + ASSERT_EQ(r.nt, s.nt); + ASSERT_EQ(r.newton_maxsteps, s.newton_maxsteps); + ASSERT_EQ(r.pscale, s.pscale); + ASSERT_EQ(static_cast(r.o2mode), static_cast(s.o2mode)); + ASSERT_EQ(static_cast(r.sensi), static_cast(s.sensi)); + ASSERT_EQ(static_cast(r.sensi_meth), static_cast(s.sensi_meth)); + + using amici::checkEqualArray; + checkEqualArray(r.ts, s.ts, 1e-16, 1e-16, "ts"); + checkEqualArray(r.xdot, s.xdot, 1e-16, 1e-16, "xdot"); + checkEqualArray(r.J, s.J, 1e-16, 1e-16, "J"); + checkEqualArray(r.z, s.z, 1e-16, 1e-16, "z"); + checkEqualArray(r.sigmaz, s.sigmaz, 1e-16, 1e-16, "sigmaz"); + checkEqualArray(r.sz, s.sz, 1e-16, 1e-16, "sz"); + checkEqualArray(r.ssigmaz, s.ssigmaz, 1e-16, 1e-16, "ssigmaz"); + checkEqualArray(r.rz, s.rz, 1e-16, 1e-16, "rz"); + checkEqualArray(r.srz, s.srz, 1e-16, 1e-16, "srz"); + checkEqualArray(r.s2rz, s.s2rz, 1e-16, 1e-16, "s2rz"); + checkEqualArray(r.x, s.x, 1e-16, 1e-16, "x"); + checkEqualArray(r.sx, s.sx, 1e-16, 1e-16, "sx"); + + checkEqualArray(r.y, s.y, 1e-16, 1e-16, "y"); + checkEqualArray(r.sigmay, s.sigmay, 1e-16, 1e-16, "sigmay"); + checkEqualArray(r.sy, s.sy, 1e-16, 1e-16, "sy"); + checkEqualArray(r.ssigmay, s.ssigmay, 1e-16, 1e-16, "ssigmay"); + + ASSERT_EQ(r.numsteps, s.numsteps); + ASSERT_EQ(r.numstepsB, s.numstepsB); + ASSERT_EQ(r.numrhsevals, s.numrhsevals); + ASSERT_EQ(r.numrhsevalsB, s.numrhsevalsB); + ASSERT_EQ(r.numerrtestfails, s.numerrtestfails); + ASSERT_EQ(r.numerrtestfailsB, s.numerrtestfailsB); + ASSERT_EQ(r.numnonlinsolvconvfails, s.numnonlinsolvconvfails); + ASSERT_EQ(r.numnonlinsolvconvfailsB, s.numnonlinsolvconvfailsB); + ASSERT_EQ(r.order, s.order); + ASSERT_EQ(r.cpu_time, s.cpu_time); + ASSERT_EQ(r.cpu_timeB, s.cpu_timeB); + + ASSERT_EQ(r.preeq_status, s.preeq_status); + ASSERT_TRUE(r.preeq_t == s.preeq_t || + (std::isnan(r.preeq_t) && std::isnan(s.preeq_t))); + ASSERT_TRUE(r.preeq_wrms == s.preeq_wrms || + (std::isnan(r.preeq_wrms) && std::isnan(s.preeq_wrms))); + ASSERT_EQ(r.preeq_numsteps, s.preeq_numsteps); + ASSERT_EQ(r.preeq_numlinsteps, s.preeq_numlinsteps); + EXPECT_NEAR(r.preeq_cpu_time, s.preeq_cpu_time, 1e-16); + + ASSERT_EQ(r.posteq_status, s.posteq_status); + ASSERT_TRUE(r.posteq_t == s.posteq_t || + (std::isnan(r.posteq_t) && std::isnan(s.posteq_t))); + ASSERT_TRUE(r.posteq_wrms == s.posteq_wrms || + (std::isnan(r.posteq_wrms) && std::isnan(s.posteq_wrms))); + ASSERT_EQ(r.posteq_numsteps, s.posteq_numsteps); + ASSERT_EQ(r.posteq_numlinsteps, s.posteq_numlinsteps); + EXPECT_NEAR(r.posteq_cpu_time, s.posteq_cpu_time, 1e-16); + + checkEqualArray(r.x0, s.x0, 1e-16, 1e-16, "x0"); + checkEqualArray(r.sx0, s.sx0, 1e-16, 1e-16, "sx0"); + + ASSERT_TRUE(r.llh == s.llh || (std::isnan(r.llh) && std::isnan(s.llh))); + ASSERT_TRUE(r.chi2 == s.chi2 || (std::isnan(r.llh) && std::isnan(s.llh))); + ASSERT_EQ(r.status, s.status); + + checkEqualArray(r.sllh, s.sllh, 1e-5, 1e-5, "sllh"); + checkEqualArray(r.s2llh, s.s2llh, 1e-5, 1e-5, "s2llh"); +} + +class SolverSerializationTest : public ::testing::Test { + protected: + void SetUp() override { + // set non-default values for all members + solver.setAbsoluteTolerance(1e-4); + solver.setRelativeTolerance(1e-5); + solver.setAbsoluteToleranceQuadratures(1e-6); + solver.setRelativeToleranceQuadratures(1e-7); + solver.setAbsoluteToleranceSteadyState(1e-8); + solver.setRelativeToleranceSteadyState(1e-9); + solver.setSensitivityMethod(amici::SensitivityMethod::adjoint); + solver.setSensitivityOrder(amici::SensitivityOrder::second); + solver.setMaxSteps(1e1); + solver.setMaxStepsBackwardProblem(1e2); + solver.setNewtonMaxSteps(1e3); + solver.setNewtonMaxLinearSteps(1e4); + solver.setPreequilibration(true); + solver.setStateOrdering(static_cast(amici::SUNLinSolKLU::StateOrdering::COLAMD)); + solver.setInterpolationType(amici::InterpolationType::polynomial); + solver.setStabilityLimitFlag(false); + solver.setLinearSolver(amici::LinearSolver::dense); + solver.setLinearMultistepMethod(amici::LinearMultistepMethod::adams); + solver.setNonlinearSolverIteration(amici::NonlinearSolverIteration::newton); + solver.setInternalSensitivityMethod(amici::InternalSensitivityMethod::staggered); + solver.setReturnDataReportingMode(amici::RDataReporting::likelihood); + } + + amici::CVodeSolver solver; +}; + +TEST(ModelSerializationTest, ToFile) +{ + int np = 1; + int nk = 2; + int nx = 3; + int ny = 4; + int nz = 5; + int ne = 6; + amici::CVodeSolver solver; + amici::Model_Test m = amici::Model_Test( + amici::ModelDimensions( + nx, // nx_rdata + nx, // nxtrue_rdata + nx, // nx_solver + nx, // nxtrue_solver + 0, // nx_solver_reinit + np, // np + nk, // nk + ny, // ny + ny, // nytrue + nz, // nz + nz, // nztrue + ne, // ne + 0, // nJ + 9, // nw + 2, // ndwdx + 2, // ndwdp + 2, // dwdw + 13, // ndxdotdw + {}, // ndJydy + 15, // nnz + 16, // ubw + 17 // lbw + ), + amici::SimulationParameters( + std::vector(nk, 0.0), + std::vector(np, 0.0), + std::vector(np, 0) + ), + amici::SecondOrderMode::none, + std::vector(nx, 0.0), + std::vector(nz, 0)); + + { + std::ofstream ofs("sstore.dat"); + boost::archive::text_oarchive oar(ofs); + // oar & static_cast(solver); + oar& static_cast(m); + } + { + std::ifstream ifs("sstore.dat"); + boost::archive::text_iarchive iar(ifs); + amici::CVodeSolver v; + amici::Model_Test n; + // iar &static_cast(v); + iar& static_cast(n); + // CHECK_TRUE(solver == v); + ASSERT_EQ(m, n); + } +} + +TEST(ReturnDataSerializationTest, ToString) +{ + int np = 1; + int nk = 2; + int nx = 3; + int ny = 4; + int nz = 5; + int ne = 6; + amici::CVodeSolver solver; + amici::Model_Test m = amici::Model_Test( + amici::ModelDimensions( + nx, // nx_rdata + nx, // nxtrue_rdata + nx, // nx_solver + nx, // nxtrue_solver + 0, // nx_solver_reinit + np, // np + nk, // nk + ny, // ny + ny, // nytrue + nz, // nz + nz, // nztrue + ne, // ne + 0, // nJ + 9, // nw + 10, // ndwdx + 2, // ndwdp + 12, // dwdw + 13, // ndxdotdw + {}, // ndJydy + 15, // nnz + 16, // ubw + 17 // lbw + ), + amici::SimulationParameters( + std::vector(nk, 0.0), + std::vector(np, 0.0), + std::vector(np, 0) + ), + amici::SecondOrderMode::none, + std::vector(nx, 0.0), + std::vector(nz, 0)); + + amici::ReturnData r(solver, m); + + std::string serialized = amici::serializeToString(r); + + checkReturnDataEqual( + r, amici::deserializeFromString(serialized)); +} + +TEST_F(SolverSerializationTest, ToChar) +{ + int length; + char* buf = amici::serializeToChar(solver, &length); + + amici::CVodeSolver v = + amici::deserializeFromChar(buf, length); + + delete[] buf; + ASSERT_EQ(solver, v); +} + +TEST_F(SolverSerializationTest, ToStdVec) +{ + + auto buf = amici::serializeToStdVec(solver); + amici::CVodeSolver v = + amici::deserializeFromChar(buf.data(), buf.size()); + + ASSERT_EQ(solver, v); +} diff --git a/tests/cpputest/wrapTestModels.m b/tests/cpp/wrapTestModels.m similarity index 100% rename from tests/cpputest/wrapTestModels.m rename to tests/cpp/wrapTestModels.m diff --git a/tests/cpputest/events/CMakeLists.txt b/tests/cpputest/events/CMakeLists.txt deleted file mode 100644 index 3197ed7a72..0000000000 --- a/tests/cpputest/events/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) -project(model_${MODEL_NAME}_test) - -set(SRC_LIST - ../main.cpp - tests1.cpp -) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - -add_executable(${PROJECT_NAME} ${SRC_LIST}) - -target_link_libraries(${PROJECT_NAME} - amici-testing - model_${MODEL_NAME} -) - -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) diff --git a/tests/cpputest/jakstat_adjoint_o2/CMakeLists.txt b/tests/cpputest/jakstat_adjoint_o2/CMakeLists.txt deleted file mode 100644 index 3197ed7a72..0000000000 --- a/tests/cpputest/jakstat_adjoint_o2/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) -project(model_${MODEL_NAME}_test) - -set(SRC_LIST - ../main.cpp - tests1.cpp -) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - -add_executable(${PROJECT_NAME} ${SRC_LIST}) - -target_link_libraries(${PROJECT_NAME} - amici-testing - model_${MODEL_NAME} -) - -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) diff --git a/tests/cpputest/main.cpp b/tests/cpputest/main.cpp deleted file mode 100644 index e588e8ed53..0000000000 --- a/tests/cpputest/main.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include - -#include "CppUTest/CommandLineTestRunner.h" -#include "CppUTest/TestHarness.h" - -int main(int argc, char** argv) -{ - return CommandLineTestRunner::RunAllTests(argc, argv); -} diff --git a/tests/cpputest/nested_events/CMakeLists.txt b/tests/cpputest/nested_events/CMakeLists.txt deleted file mode 100644 index 3197ed7a72..0000000000 --- a/tests/cpputest/nested_events/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) -project(model_${MODEL_NAME}_test) - -set(SRC_LIST - ../main.cpp - tests1.cpp -) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - -add_executable(${PROJECT_NAME} ${SRC_LIST}) - -target_link_libraries(${PROJECT_NAME} - amici-testing - model_${MODEL_NAME} -) - -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) diff --git a/tests/cpputest/neuron/CMakeLists.txt b/tests/cpputest/neuron/CMakeLists.txt deleted file mode 100644 index 3197ed7a72..0000000000 --- a/tests/cpputest/neuron/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) -project(model_${MODEL_NAME}_test) - -set(SRC_LIST - ../main.cpp - tests1.cpp -) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - -add_executable(${PROJECT_NAME} ${SRC_LIST}) - -target_link_libraries(${PROJECT_NAME} - amici-testing - model_${MODEL_NAME} -) - -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) diff --git a/tests/cpputest/neuron_o2/CMakeLists.txt b/tests/cpputest/neuron_o2/CMakeLists.txt deleted file mode 100644 index 3197ed7a72..0000000000 --- a/tests/cpputest/neuron_o2/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) -project(model_${MODEL_NAME}_test) - -set(SRC_LIST - ../main.cpp - tests1.cpp -) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - -add_executable(${PROJECT_NAME} ${SRC_LIST}) - -target_link_libraries(${PROJECT_NAME} - amici-testing - model_${MODEL_NAME} -) - -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) diff --git a/tests/cpputest/steadystate/CMakeLists.txt b/tests/cpputest/steadystate/CMakeLists.txt deleted file mode 100644 index 3197ed7a72..0000000000 --- a/tests/cpputest/steadystate/CMakeLists.txt +++ /dev/null @@ -1,18 +0,0 @@ -get_filename_component(MODEL_NAME ${CMAKE_CURRENT_LIST_DIR} NAME) -project(model_${MODEL_NAME}_test) - -set(SRC_LIST - ../main.cpp - tests1.cpp -) - -include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CppUTest_INCLUDE_DIRS}) - -add_executable(${PROJECT_NAME} ${SRC_LIST}) - -target_link_libraries(${PROJECT_NAME} - amici-testing - model_${MODEL_NAME} -) - -add_test(NAME ${PROJECT_NAME} COMMAND ./${PROJECT_NAME} -c) diff --git a/tests/cpputest/unittests/CMakeLists.txt b/tests/cpputest/unittests/CMakeLists.txt deleted file mode 100644 index 376a2dbe96..0000000000 --- a/tests/cpputest/unittests/CMakeLists.txt +++ /dev/null @@ -1,27 +0,0 @@ -project(unittests) - -# cannot mix CppuTest new override togeter with Boost -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_OLD}") -find_package(Boost COMPONENTS serialization) - -set(SRC_LIST - ../main.cpp - tests1.cpp -) - -if(Boost_FOUND) - set(SRC_LIST ${SRC_LIST} testsSerialization.cpp) - include_directories("${Boost_INCLUDE_DIR}") -endif() - -include_directories(${CMAKE_CURRENT_SOURCE_DIR}) - -add_executable(${PROJECT_NAME} ${SRC_LIST}) - -target_link_libraries(${PROJECT_NAME} - amici-testing - Upstream::amici - ${Boost_LIBRARIES} - ) - -add_test(NAME unittests COMMAND ./unittests -c) diff --git a/tests/cpputest/unittests/tests1.cpp b/tests/cpputest/unittests/tests1.cpp deleted file mode 100644 index 869c6dc9fd..0000000000 --- a/tests/cpputest/unittests/tests1.cpp +++ /dev/null @@ -1,911 +0,0 @@ -#include "testfunctions.h" - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - -namespace amici { -namespace generic_model { - -std::unique_ptr getModel() -{ - return std::make_unique(); -} - -} // namespace generic_model -} // namespace amici - -using namespace amici; - -void -testSolverGetterSetters(CVodeSolver solver, - SensitivityMethod sensi_meth, - SensitivityOrder sensi, - InternalSensitivityMethod ism, - InterpolationType interp, - NonlinearSolverIteration iter, - LinearMultistepMethod lmm, - int steps, - int badsteps, - double tol, - double badtol); - -TEST_GROUP(amici){}; - -TEST_GROUP(model) -{ - int nx = 1, ny = 2, nz = 3, nmaxevent = 4; - std::vector p{ 1.0 }; - std::vector k{ 0.5, 0.4, 0.7 }; - std::vector plist{ 1 }; - std::vector idlist{ 0 }; - std::vector z2event{ 0, 0, 0 }; - Model_Test model = Model_Test( - ModelDimensions( - nx, // nx_rdata - nx, // nxtrue_rdata - nx, // nx_solver - nx, // nxtrue_solver - 0, // nx_solver_reinit - static_cast(p.size()), // np - static_cast(k.size()), // nk - ny, // ny - ny, // nytrue - nz, // nz - nz, // nztrue - nmaxevent, // ne - 0, // nJ - 0, // nw - 0, // ndwdx - 0, // ndwdp - 0, // dwdw - 0, // ndxdotdw - {}, // ndJydy - 0, // nnz - 0, // ubw - 0 // lbw - ), - SimulationParameters(k, p, plist), - SecondOrderMode::none, - idlist, - z2event); - - std::vector unscaled{ NAN }; -}; - -TEST(model, testScalingLin) -{ - model.setParameterScale(ParameterScaling::none); - - CHECK_EQUAL(p[0], model.getParameters()[0]); -} - -TEST(model, testScalingLog) -{ - model.setParameterScale(ParameterScaling::ln); - - DOUBLES_EQUAL(std::log(p[0]), model.getParameters()[0], 1e-16); -} - -TEST(model, testScalingLog10) -{ - model.setParameterScale(ParameterScaling::log10); - - DOUBLES_EQUAL(std::log10(p[0]), model.getParameters()[0], 1e-16); -} - -TEST(model, testParameterScalingLengthMismatch) -{ - // too short - auto pscale = - std::vector(p.size() - 1, ParameterScaling::log10); - CHECK_THROWS(AmiException, model.setParameterScale(pscale)); - - // too long - pscale = - std::vector(p.size() + 1, ParameterScaling::log10); - CHECK_THROWS(AmiException, model.setParameterScale(pscale)); -} - -TEST(model, testSetTimepoints){ - CHECK_THROWS(AmiException, - model.setTimepoints(std::vector{ 0.0, 1.0, 0.5 })); -} - -TEST(model, testNameIdGetterSetter) -{ - model.setParameterById("p0", 3.0); - DOUBLES_EQUAL(model.getParameterById("p0"), 3.0, 1e-16); - CHECK_THROWS(AmiException, model.getParameterById("p1")); - DOUBLES_EQUAL( - model.setParametersByIdRegex("p[\\d]+", 5.0), p.size(), 1e-16); - for (const auto& ip : model.getParameters()) - DOUBLES_EQUAL(ip, 5.0, 1e-16); - CHECK_THROWS(AmiException, model.setParametersByIdRegex("k[\\d]+", 5.0)); - - model.setParameterByName("p0", 3.0); - DOUBLES_EQUAL(model.getParameterByName("p0"), 3.0, 1e-16); - CHECK_THROWS(AmiException, model.getParameterByName("p1")); - DOUBLES_EQUAL( - model.setParametersByNameRegex("p[\\d]+", 5.0), p.size(), 1e-16); - for (const auto& ip : model.getParameters()) - DOUBLES_EQUAL(ip, 5.0, 1e-16); - CHECK_THROWS(AmiException, model.setParametersByNameRegex("k[\\d]+", 5.0)); - - model.setFixedParameterById("k0", 3.0); - DOUBLES_EQUAL(model.getFixedParameterById("k0"), 3.0, 1e-16); - CHECK_THROWS(AmiException, model.getFixedParameterById("k4")); - DOUBLES_EQUAL( - model.setFixedParametersByIdRegex("k[\\d]+", 5.0), k.size(), 1e-16); - for (const auto& ik : model.getFixedParameters()) - DOUBLES_EQUAL(ik, 5.0, 1e-16); - CHECK_THROWS(AmiException, - model.setFixedParametersByIdRegex("p[\\d]+", 5.0)); - - model.setFixedParameterByName("k0", 3.0); - DOUBLES_EQUAL(model.getFixedParameterByName("k0"), 3.0, 1e-16); - CHECK_THROWS(AmiException, model.getFixedParameterByName("k4")); - DOUBLES_EQUAL( - model.setFixedParametersByNameRegex("k[\\d]+", 5.0), k.size(), 1e-16); - for (const auto& ik : model.getFixedParameters()) - DOUBLES_EQUAL(ik, 5.0, 1e-16); - CHECK_THROWS(AmiException, - model.setFixedParametersByNameRegex("p[\\d]+", 5.0)); -} - -TEST(model, reinitializeFixedParameterInitialStates) -{ - CHECK_THROWS(AmiException, - model.setReinitializeFixedParameterInitialStates(true)); - model.setReinitializeFixedParameterInitialStates(false); - CHECK_TRUE(!model.getReinitializeFixedParameterInitialStates()); - AmiVector x(nx); - AmiVectorArray sx(model.np(), nx); -} - -TEST_GROUP(symbolicFunctions){}; - -TEST(symbolicFunctions, testSign) -{ - CHECK_EQUAL(-1, sign(-2)); - CHECK_EQUAL(0, sign(0)); - CHECK_EQUAL(1, sign(2)); -} - -TEST(symbolicFunctions, testHeaviside) -{ - CHECK_EQUAL(0, heaviside(-1)); - CHECK_EQUAL(1, heaviside(0)); - CHECK_EQUAL(1, heaviside(1)); -} - -TEST(symbolicFunctions, testMin) -{ - CHECK_EQUAL(-1, amici::min(-1, 2, 0)); - CHECK_EQUAL(-2, amici::min(1, -2, 0)); - CHECK_TRUE(amici::isNaN(amici::min(amici::getNaN(), amici::getNaN(), 0))); - CHECK_EQUAL(-1, amici::min(-1, amici::getNaN(), 0)); - CHECK_EQUAL(-1, amici::min(amici::getNaN(), -1, 0)); -} - -TEST(symbolicFunctions, testMax) -{ - CHECK_EQUAL(2, amici::max(-1, 2, 0)); - CHECK_EQUAL(1, amici::max(1, -2, 0)); - CHECK_TRUE(amici::isNaN(amici::max(amici::getNaN(), amici::getNaN(), 0))); - CHECK_EQUAL(-1, amici::max(-1, amici::getNaN(), 0)); - CHECK_EQUAL(-1, amici::max(amici::getNaN(), -1, 0)); -} - -TEST(symbolicFunctions, testDMin) -{ - CHECK_EQUAL(0, amici::Dmin(1, -1, -2, 0)); - CHECK_EQUAL(1, amici::Dmin(1, -1, 2, 0)); - CHECK_EQUAL(1, amici::Dmin(2, -1, -2, 0)); - CHECK_EQUAL(0, amici::Dmin(2, -1, 2, 0)); -} - -TEST(symbolicFunctions, testDMax) -{ - CHECK_EQUAL(1, amici::Dmax(1, -1, -2, 0)); - CHECK_EQUAL(0, amici::Dmax(1, -1, 2, 0)); - CHECK_EQUAL(0, amici::Dmax(2, -1, -2, 0)); - CHECK_EQUAL(1, amici::Dmax(2, -1, 2, 0)); -} - -TEST(symbolicFunctions, testpos_pow) -{ - CHECK_EQUAL(0, amici::pos_pow(-0.1, 3)); - CHECK_EQUAL(pow(0.1, 3), amici::pos_pow(0.1, 3)); -} - -TEST_GROUP(amiciSolver){ }; - -TEST(amiciSolver, testEquality) -{ - IDASolver i1, i2; - CVodeSolver c1, c2; - - CHECK_TRUE(i1 == i2); - CHECK_TRUE(c1 == c2); - CHECK_FALSE(i1 == c1); -} - -TEST(amiciSolver, testClone) -{ - IDASolver i1; - auto i2 = std::unique_ptr(i1.clone()); - CHECK_TRUE(i1 == *i2); - - CVodeSolver c1; - auto c2 = std::unique_ptr(c1.clone()); - CHECK_TRUE(c1 == *c2); - CHECK_FALSE(*i2 == *c2); -} - -TEST_GROUP(amiciSolverIdas){}; - -TEST(amiciSolverIdas, testConstructionDestruction) -{ - IDASolver solver; -} - -TEST_GROUP(edata) -{ - int nx = 1, ny = 2, nz = 3, nmaxevent = 4; - std::vector timepoints = { 1, 2, 3, 4 }; - - std::unique_ptr model = amici::generic_model::getModel(); - - Model_Test testModel = Model_Test( - ModelDimensions( - nx, // nx_rdata - nx, // nxtrue_rdata - nx, // nx_solver - nx, // nxtrue_solver - 0, // nx_solver_reinit - 1, // np - 3, // nk - ny, // ny - ny, // nytrue - nz, // nz - nz, // nztrue - nmaxevent, // ne - 0, // nJ - 0, // nw - 0, // ndwdx - 0, // ndwdp - 0, // dwdw - 0, // ndxdotdw - {}, // ndJydy - 0, // nnz - 0, // ubw - 0 // lbw - ), - SimulationParameters( - std::vector(3, 0.0), - std::vector(1, 0.0), - std::vector(2, 1) - ), - SecondOrderMode::none, - std::vector(), - std::vector()); - void setup() override - { - model->setTimepoints(timepoints); - model->setNMaxEvent(nmaxevent); - testModel.setTimepoints(timepoints); - testModel.setNMaxEvent(nmaxevent); - } - - void teardown() override {} -}; - -TEST(edata, testConstructors1) -{ - auto edata = ExpData(); - CHECK_TRUE(edata.nytrue() == 0); - CHECK_TRUE(edata.nztrue() == 0); - CHECK_TRUE(edata.nmaxevent() == 0); -} -TEST(edata, testConstructors2) -{ - auto edata = ExpData(model->nytrue, model->nztrue, model->nMaxEvent()); - CHECK_TRUE(edata.nytrue() == model->nytrue); - CHECK_TRUE(edata.nztrue() == model->nztrue); - CHECK_TRUE(edata.nmaxevent() == model->nMaxEvent()); -} - -TEST(edata, testConstructors3) -{ - auto edata = - ExpData(model->nytrue, model->nztrue, model->nMaxEvent(), timepoints); - CHECK_TRUE(edata.nytrue() == model->nytrue); - CHECK_TRUE(edata.nztrue() == model->nztrue); - CHECK_TRUE(edata.nmaxevent() == model->nMaxEvent()); - CHECK_TRUE(edata.nt() == model->nt()); - checkEqualArray( - timepoints, edata.getTimepoints(), TEST_ATOL, TEST_RTOL, "ts"); -} - -TEST(edata, testConstructors4) -{ - std::vector y(ny * timepoints.size(), 0.0); - std::vector y_std(ny * timepoints.size(), 0.1); - std::vector z(nz * nmaxevent, 0.0); - std::vector z_std(nz * nmaxevent, 0.1); - - auto edata = ExpData(testModel.nytrue, - testModel.nztrue, - testModel.nMaxEvent(), - timepoints, - y, - y_std, - z, - z_std); - CHECK_TRUE(edata.nytrue() == testModel.nytrue); - CHECK_TRUE(edata.nztrue() == testModel.nztrue); - CHECK_TRUE(edata.nmaxevent() == testModel.nMaxEvent()); - CHECK_TRUE(edata.nt() == testModel.nt()); - checkEqualArray( - timepoints, edata.getTimepoints(), TEST_ATOL, TEST_RTOL, "ts"); - checkEqualArray( - y, edata.getObservedData(), TEST_ATOL, TEST_RTOL, "observedData"); - checkEqualArray(y_std, - edata.getObservedDataStdDev(), - TEST_ATOL, - TEST_RTOL, - "observedDataStdDev"); - checkEqualArray( - z, edata.getObservedEvents(), TEST_ATOL, TEST_RTOL, "observedEvents"); - checkEqualArray(z_std, - edata.getObservedEventsStdDev(), - TEST_ATOL, - TEST_RTOL, - "observedEventsStdDev"); - - auto edata_copy = ExpData(edata); - CHECK_TRUE(edata.nytrue() == edata_copy.nytrue()); - CHECK_TRUE(edata.nztrue() == edata_copy.nztrue()); - CHECK_TRUE(edata.nmaxevent() == edata_copy.nmaxevent()); - CHECK_TRUE(edata.nt() == edata_copy.nt()); - checkEqualArray(edata_copy.getTimepoints(), - edata.getTimepoints(), - TEST_ATOL, - TEST_RTOL, - "ts"); - checkEqualArray(edata_copy.getObservedData(), - edata.getObservedData(), - TEST_ATOL, - TEST_RTOL, - "observedData"); - checkEqualArray(edata_copy.getObservedDataStdDev(), - edata.getObservedDataStdDev(), - TEST_ATOL, - TEST_RTOL, - "observedDataStdDev"); - checkEqualArray(edata_copy.getObservedEvents(), - edata.getObservedEvents(), - TEST_ATOL, - TEST_RTOL, - "observedEvents"); - checkEqualArray(edata_copy.getObservedEventsStdDev(), - edata.getObservedEventsStdDev(), - TEST_ATOL, - TEST_RTOL, - "observedEventsStdDev"); -} - -TEST(edata, testConstructors5) -{ - testModel.setTimepoints(timepoints); - auto edata = ExpData(testModel); - CHECK_TRUE(edata.nytrue() == testModel.nytrue); - CHECK_TRUE(edata.nztrue() == testModel.nztrue); - CHECK_TRUE(edata.nmaxevent() == testModel.nMaxEvent()); - CHECK_TRUE(edata.nt() == testModel.nt()); - checkEqualArray(testModel.getTimepoints(), - edata.getTimepoints(), - TEST_ATOL, - TEST_RTOL, - "ts"); -} - -TEST(edata, testDimensionChecks) -{ - - std::vector bad_std(ny, -0.1); - - std::vector y(ny * timepoints.size(), 0.0); - std::vector y_std(ny * timepoints.size(), 0.1); - std::vector z(nz * nmaxevent, 0.0); - std::vector z_std(nz * nmaxevent, 0.1); - - CHECK_THROWS(AmiException, - ExpData(testModel.nytrue, - testModel.nztrue, - testModel.nMaxEvent(), - timepoints, - z, - z_std, - z, - z_std)); - - CHECK_THROWS(AmiException, - ExpData(testModel.nytrue, - testModel.nztrue, - testModel.nMaxEvent(), - timepoints, - z, - bad_std, - z, - z_std)); - - auto edata = ExpData(testModel); - - std::vector bad_y(ny * timepoints.size() + 1, 0.0); - std::vector bad_y_std(ny * timepoints.size() + 1, 0.1); - std::vector bad_z(nz * nmaxevent + 1, 0.0); - std::vector bad_z_std(nz * nmaxevent + 1, 0.1); - - CHECK_THROWS(AmiException, edata.setObservedData(bad_y)); - CHECK_THROWS(AmiException, edata.setObservedDataStdDev(bad_y_std)); - CHECK_THROWS(AmiException, edata.setObservedEvents(bad_z)); - CHECK_THROWS(AmiException, edata.setObservedEventsStdDev(bad_y_std)); - - std::vector bad_single_y(edata.nt() + 1, 0.0); - std::vector bad_single_y_std(edata.nt() + 1, 0.1); - std::vector bad_single_z(edata.nmaxevent() + 1, 0.0); - std::vector bad_single_z_std(edata.nmaxevent() + 1, 0.1); - - CHECK_THROWS(AmiException, edata.setObservedData(bad_single_y, 0)); - CHECK_THROWS(AmiException, - edata.setObservedDataStdDev(bad_single_y_std, 0)); - CHECK_THROWS(AmiException, edata.setObservedEvents(bad_single_z, 0)); - CHECK_THROWS(AmiException, - edata.setObservedEventsStdDev(bad_single_y_std, 0)); - - CHECK_THROWS(AmiException, - edata.setTimepoints(std::vector{ 0.0, 1.0, 0.5 })); -} - -TEST(edata, testSettersGetters) -{ - auto edata = ExpData(testModel); - - std::vector y(ny * timepoints.size(), 0.0); - std::vector y_std(ny * timepoints.size(), 0.1); - std::vector z(nz * nmaxevent, 0.0); - std::vector z_std(nz * nmaxevent, 0.1); - - edata.setObservedData(y); - checkEqualArray( - edata.getObservedData(), y, TEST_ATOL, TEST_RTOL, "ObservedData"); - edata.setObservedDataStdDev(y_std); - checkEqualArray(edata.getObservedDataStdDev(), - y_std, - TEST_ATOL, - TEST_RTOL, - "ObservedDataStdDev"); - edata.setObservedEvents(z); - checkEqualArray( - edata.getObservedEvents(), z, TEST_ATOL, TEST_RTOL, "ObservedEvents"); - edata.setObservedEventsStdDev(z_std); - checkEqualArray(edata.getObservedEventsStdDev(), - z_std, - TEST_ATOL, - TEST_RTOL, - "ObservedEventsStdDev"); - - std::vector single_y(edata.nt(), 0.0); - std::vector single_y_std(edata.nt(), 0.1); - - for (int iy = 0; iy < ny; ++iy) { - edata.setObservedData(single_y, iy); - edata.setObservedDataStdDev(single_y_std, iy); - } - CHECK_THROWS(std::exception, edata.setObservedData(single_y, ny)); - CHECK_THROWS(std::exception, edata.setObservedData(single_y, -1)); - CHECK_THROWS(std::exception, edata.setObservedDataStdDev(single_y_std, ny)); - CHECK_THROWS(std::exception, edata.setObservedDataStdDev(single_y_std, -1)); - - std::vector single_z(edata.nmaxevent(), 0.0); - std::vector single_z_std(edata.nmaxevent(), 0.1); - - for (int iz = 0; iz < nz; ++iz) { - edata.setObservedEvents(single_z, iz); - edata.setObservedEventsStdDev(single_z_std, iz); - } - - CHECK_THROWS(std::exception, edata.setObservedEvents(single_z, nz)); - CHECK_THROWS(std::exception, edata.setObservedEvents(single_z, -1)); - CHECK_THROWS(std::exception, - edata.setObservedEventsStdDev(single_z_std, nz)); - CHECK_THROWS(std::exception, - edata.setObservedEventsStdDev(single_z_std, -1)); - - CHECK_TRUE(edata.getObservedDataPtr(0)); - CHECK_TRUE(edata.getObservedDataStdDevPtr(0)); - CHECK_TRUE(edata.getObservedEventsPtr(0)); - CHECK_TRUE(edata.getObservedEventsStdDevPtr(0)); - - std::vector empty(0, 0.0); - - edata.setObservedData(empty); - edata.setObservedDataStdDev(empty); - edata.setObservedEvents(empty); - edata.setObservedEventsStdDev(empty); - - CHECK_TRUE(!edata.getObservedDataPtr(0)); - CHECK_TRUE(!edata.getObservedDataStdDevPtr(0)); - CHECK_TRUE(!edata.getObservedEventsPtr(0)); - CHECK_TRUE(!edata.getObservedEventsStdDevPtr(0)); - - checkEqualArray( - edata.getObservedData(), empty, TEST_ATOL, TEST_RTOL, "ObservedData"); - checkEqualArray(edata.getObservedDataStdDev(), - empty, - TEST_ATOL, - TEST_RTOL, - "ObservedDataStdDev"); - checkEqualArray( - edata.getObservedEvents(), empty, TEST_ATOL, TEST_RTOL, "ObservedEvents"); - checkEqualArray(edata.getObservedEventsStdDev(), - empty, - TEST_ATOL, - TEST_RTOL, - "ObservedEventsStdDev"); -} - -TEST_GROUP(solver) -{ - int nx = 1, ny = 2, nz = 3, ne = 0; - double tol, badtol; - std::vector timepoints = { 1, 2, 3, 4 }; - - std::unique_ptr model = amici::generic_model::getModel(); - SensitivityMethod sensi_meth; - SensitivityOrder sensi; - int steps, badsteps; - LinearMultistepMethod lmm; - NonlinearSolverIteration iter; - InternalSensitivityMethod ism; - InterpolationType interp; - - Model_Test testModel = Model_Test( - ModelDimensions( - nx, // nx_rdata - nx, // nxtrue_rdata - nx, // nx_solver - nx, // nxtrue_solver - 0, // nx_solver_reinit - 1, // np - 3, // nk - ny, // ny - ny, // nytrue - nz, // nz - nz, // nztrue - ne, // ne - 0, // nJ - 0, // nw - 0, // ndwdx - 0, // ndwdp - 0, // dwdw - 0, // ndxdotdw - {}, // ndJydy - 1, // nnz - 0, // ubw - 0 // lbw - ), - SimulationParameters( - std::vector(3, 0.0), - std::vector(1, 0.0), - std::vector(2, 1) - ), - SecondOrderMode::none, - std::vector(0, 0.0), - std::vector()); - - CVodeSolver solver = CVodeSolver(); - - void setup() - { - tol = 0.01; - badtol = -0.01; - sensi_meth = SensitivityMethod::adjoint; - sensi = SensitivityOrder::first; - steps = 1000; - badsteps = -1; - lmm = LinearMultistepMethod::adams; - iter = NonlinearSolverIteration::fixedpoint; - ism = InternalSensitivityMethod::staggered1; - interp = InterpolationType::polynomial; - } - - void teardown() {} -}; - -TEST(solver, testSettersGettersNoSetup) -{ - testSolverGetterSetters(solver, - sensi_meth, - sensi, - ism, - interp, - iter, - lmm, - steps, - badsteps, - tol, - badtol); -} - -TEST(solver, testSettersGettersWithSetup) -{ - - solver.setSensitivityMethod(sensi_meth); - CHECK_EQUAL(static_cast(solver.getSensitivityMethod()), - static_cast(sensi_meth)); - - auto rdata = - std::unique_ptr(new ReturnData(solver, testModel)); - AmiVector x(nx), dx(nx); - AmiVectorArray sx(nx, 1), sdx(nx, 1); - - testModel.setInitialStates(std::vector{ 0 }); - - solver.setup(0, &testModel, x, dx, sx, sdx); - - testSolverGetterSetters(solver, - sensi_meth, - sensi, - ism, - interp, - iter, - lmm, - steps, - badsteps, - tol, - badtol); -} - -void -testSolverGetterSetters(CVodeSolver solver, - SensitivityMethod sensi_meth, - SensitivityOrder sensi, - InternalSensitivityMethod ism, - InterpolationType interp, - NonlinearSolverIteration iter, - LinearMultistepMethod lmm, - int steps, - int badsteps, - double tol, - double badtol) -{ - - solver.setSensitivityMethod(sensi_meth); - CHECK_EQUAL(static_cast(solver.getSensitivityMethod()), - static_cast(sensi_meth)); - - solver.setSensitivityOrder(sensi); - CHECK_EQUAL(static_cast(solver.getSensitivityOrder()), - static_cast(sensi)); - - solver.setInternalSensitivityMethod(ism); - CHECK_EQUAL(static_cast(solver.getInternalSensitivityMethod()), - static_cast(ism)); - - solver.setInterpolationType(interp); - CHECK_EQUAL(static_cast(solver.getInterpolationType()), - static_cast(interp)); - - solver.setNonlinearSolverIteration(iter); - CHECK_EQUAL(static_cast(solver.getNonlinearSolverIteration()), - static_cast(iter)); - - solver.setLinearMultistepMethod(lmm); - CHECK_EQUAL(static_cast(solver.getLinearMultistepMethod()), - static_cast(lmm)); - - solver.setPreequilibration(true); - CHECK_EQUAL(solver.getPreequilibration(), true); - - solver.setStabilityLimitFlag(true); - CHECK_EQUAL(solver.getStabilityLimitFlag(), true); - - CHECK_THROWS(AmiException, solver.setNewtonMaxSteps(badsteps)); - solver.setNewtonMaxSteps(steps); - CHECK_EQUAL(solver.getNewtonMaxSteps(), steps); - - CHECK_THROWS(AmiException, solver.setNewtonMaxLinearSteps(badsteps)); - solver.setNewtonMaxLinearSteps(steps); - CHECK_EQUAL(solver.getNewtonMaxLinearSteps(), steps); - - CHECK_THROWS(AmiException, solver.setMaxSteps(badsteps)); - solver.setMaxSteps(steps); - CHECK_EQUAL(solver.getMaxSteps(), steps); - - CHECK_THROWS(AmiException, solver.setMaxStepsBackwardProblem(badsteps)); - solver.setMaxStepsBackwardProblem(steps); - CHECK_EQUAL(solver.getMaxStepsBackwardProblem(), steps); - - CHECK_THROWS(AmiException, solver.setRelativeTolerance(badtol)); - solver.setRelativeTolerance(tol); - CHECK_EQUAL(solver.getRelativeTolerance(), tol); - - CHECK_THROWS(AmiException, solver.setAbsoluteTolerance(badtol)); - solver.setAbsoluteTolerance(tol); - CHECK_EQUAL(solver.getAbsoluteTolerance(), tol); - - CHECK_THROWS(AmiException, solver.setRelativeToleranceQuadratures(badtol)); - solver.setRelativeToleranceQuadratures(tol); - CHECK_EQUAL(solver.getRelativeToleranceQuadratures(), tol); - - CHECK_THROWS(AmiException, solver.setAbsoluteToleranceQuadratures(badtol)); - solver.setAbsoluteToleranceQuadratures(tol); - CHECK_EQUAL(solver.getAbsoluteToleranceQuadratures(), tol); - - CHECK_THROWS(AmiException, solver.setRelativeToleranceSteadyState(badtol)); - solver.setRelativeToleranceSteadyState(tol); - CHECK_EQUAL(solver.getRelativeToleranceSteadyState(), tol); - - CHECK_THROWS(AmiException, solver.setAbsoluteToleranceSteadyState(badtol)); - solver.setAbsoluteToleranceSteadyState(tol); - CHECK_EQUAL(solver.getAbsoluteToleranceSteadyState(), tol); -} - -TEST_GROUP(amivector) -{ - std::vector vec1{ 1, 2, 4, 3 }; - std::vector vec2{ 4, 1, 2, 3 }; - std::vector vec3{ 4, 4, 2, 1 }; -}; - -TEST(amivector, vector) -{ - AmiVector av(vec1); - N_Vector nvec = av.getNVector(); - for (int i = 0; i < av.getLength(); ++i) - CHECK_EQUAL(av.at(i), NV_Ith_S(nvec, i)); -} - -TEST(amivector, vectorArray) -{ - AmiVectorArray ava(4, 3); - AmiVector av1(vec1), av2(vec2), av3(vec3); - std::vector avs{ av1, av2, av3 }; - for (int i = 0; i < ava.getLength(); ++i) - ava[i] = avs.at(i); - - std::vector badLengthVector(13, 0.0); - std::vector flattened(12, 0.0); - - CHECK_THROWS(AmiException, ava.flatten_to_vector(badLengthVector)); - ava.flatten_to_vector(flattened); - for (int i = 0; i < ava.getLength(); ++i) { - const AmiVector av = ava[i]; - for (int j = 0; j < av.getLength(); ++j) - CHECK_EQUAL(flattened.at(i * av.getLength() + j), av.at(j)); - } -} - -TEST_GROUP(sunmatrixwrapper) -{ - //inputs - std::vector a{0.82, 0.91, 0.13}; - std::vector b{0.77, 0.80}; - SUNMatrixWrapper A = SUNMatrixWrapper(3, 2); - SUNMatrixWrapper B = SUNMatrixWrapper(4, 4, 7, CSC_MAT); - // result - std::vector d{1.3753, 1.5084, 1.1655}; - - void setup() override { - A.set_data(0, 0, 0.69); - A.set_data(1, 0, 0.32); - A.set_data(2, 0, 0.95); - A.set_data(0, 1, 0.03); - A.set_data(1, 1, 0.44); - A.set_data(2, 1, 0.38); - - B.set_indexptr(0, 0); - B.set_indexptr(1, 2); - B.set_indexptr(2, 4); - B.set_indexptr(3, 5); - B.set_indexptr(4, 7); - B.set_data(0, 3); - B.set_data(1, 1); - B.set_data(2, 3); - B.set_data(3, 7); - B.set_data(4, 1); - B.set_data(5, 2); - B.set_data(6, 9); - B.set_indexval(0, 1); - B.set_indexval(1, 3); - B.set_indexval(2, 0); - B.set_indexval(3, 2); - B.set_indexval(4, 0); - B.set_indexval(5, 1); - B.set_indexval(6, 3); - - } -}; - -TEST(sunmatrixwrapper, sparse_multiply) -{ - - auto A_sparse = SUNMatrixWrapper(A, 0.0, CSC_MAT); - auto c(a); //copy c - A_sparse.multiply(c, b); - checkEqualArray(d, c, TEST_ATOL, TEST_RTOL, "multiply"); -} - -TEST(sunmatrixwrapper, sparse_multiply_empty) -{ - // Ensure empty Matrix vector multiplication succeeds - auto A_sparse = SUNMatrixWrapper(1, 1, 0, CSR_MAT); - std::vector b {0.1}; - std::vector c {0.1}; - A_sparse.multiply(c, b); - CHECK_TRUE(c[0] == 0.1); - - A_sparse = SUNMatrixWrapper(1, 1, 0, CSC_MAT); - A_sparse.multiply(c, b); - CHECK_TRUE(c[0] == 0.1); -} - -TEST(sunmatrixwrapper, dense_multiply) -{ - auto c(a); //copy c - A.multiply(c, b); - checkEqualArray(d, c, TEST_ATOL, TEST_RTOL, "multiply"); -} - -TEST(sunmatrixwrapper, multiply_throws) -{ - auto b_amivector = AmiVector(b); - auto a_amivector = AmiVector(a); -} - -TEST(sunmatrixwrapper, transform_throws) -{ - CHECK_THROWS(std::invalid_argument, SUNMatrixWrapper(A, 0.0, 13)); - auto A_sparse = SUNMatrixWrapper(A, 0.0, CSR_MAT); - CHECK_THROWS(std::invalid_argument, SUNMatrixWrapper(A_sparse, 0.0, CSR_MAT)); -} - -TEST(sunmatrixwrapper, block_transpose) -{ - auto B_sparse = SUNMatrixWrapper(4, 4, 7, CSR_MAT); - CHECK_THROWS(std::domain_error, B.transpose(B_sparse, 1.0, 4)); - - B_sparse = SUNMatrixWrapper(4, 4, 7, CSC_MAT); - B.transpose(B_sparse, -1.0, 2); - for (int idx = 0; idx < 7; idx++) { - CHECK_TRUE(SM_INDEXVALS_S(B.get())[idx] - == SM_INDEXVALS_S(B_sparse.get())[idx]); - if (idx == 1) { - CHECK_TRUE(SM_DATA_S(B.get())[idx] - == -SM_DATA_S(B_sparse.get())[3]); - } else if (idx == 3) { - CHECK_TRUE(SM_DATA_S(B.get())[idx] - == -SM_DATA_S(B_sparse.get())[1]); - } else { - CHECK_TRUE(SM_DATA_S(B.get())[idx] - == -SM_DATA_S(B_sparse.get())[idx]); - } - } - for (int icol = 0; icol <= 4; icol++) - CHECK_TRUE(SM_INDEXPTRS_S(B.get())[icol] - == SM_INDEXPTRS_S(B_sparse.get())[icol]); -} diff --git a/tests/cpputest/unittests/testsSerialization.cpp b/tests/cpputest/unittests/testsSerialization.cpp deleted file mode 100644 index 520f3b34e6..0000000000 --- a/tests/cpputest/unittests/testsSerialization.cpp +++ /dev/null @@ -1,258 +0,0 @@ -#include -#include // needs to be included before cpputest -#include - -#include "testfunctions.h" - -#include - -#include "CppUTest/TestHarness.h" -#include "CppUTestExt/MockSupport.h" - -void -checkReturnDataEqual(amici::ReturnData const& r, amici::ReturnData const& s) -{ - CHECK_EQUAL(r.np, s.np); - CHECK_EQUAL(r.nk, s.nk); - CHECK_EQUAL(r.nx, s.nx); - CHECK_EQUAL(r.nxtrue, s.nxtrue); - CHECK_EQUAL(r.nx_solver, s.nx_solver); - CHECK_EQUAL(r.nx_solver_reinit, s.nx_solver_reinit); - CHECK_EQUAL(r.ny, s.ny); - CHECK_EQUAL(r.nytrue, s.nytrue); - CHECK_EQUAL(r.nz, s.nz); - CHECK_EQUAL(r.nztrue, s.nztrue); - CHECK_EQUAL(r.ne, s.ne); - CHECK_EQUAL(r.nJ, s.nJ); - CHECK_EQUAL(r.nplist, s.nplist); - CHECK_EQUAL(r.nmaxevent, s.nmaxevent); - CHECK_EQUAL(r.nt, s.nt); - CHECK_EQUAL(r.newton_maxsteps, s.newton_maxsteps); - CHECK_TRUE(r.pscale == s.pscale); - CHECK_EQUAL(static_cast(r.o2mode), static_cast(s.o2mode)); - CHECK_EQUAL(static_cast(r.sensi), static_cast(s.sensi)); - CHECK_EQUAL(static_cast(r.sensi_meth), static_cast(s.sensi_meth)); - - using amici::checkEqualArray; - checkEqualArray(r.ts, s.ts, 1e-16, 1e-16, "ts"); - checkEqualArray(r.xdot, s.xdot, 1e-16, 1e-16, "xdot"); - checkEqualArray(r.J, s.J, 1e-16, 1e-16, "J"); - checkEqualArray(r.z, s.z, 1e-16, 1e-16, "z"); - checkEqualArray(r.sigmaz, s.sigmaz, 1e-16, 1e-16, "sigmaz"); - checkEqualArray(r.sz, s.sz, 1e-16, 1e-16, "sz"); - checkEqualArray(r.ssigmaz, s.ssigmaz, 1e-16, 1e-16, "ssigmaz"); - checkEqualArray(r.rz, s.rz, 1e-16, 1e-16, "rz"); - checkEqualArray(r.srz, s.srz, 1e-16, 1e-16, "srz"); - checkEqualArray(r.s2rz, s.s2rz, 1e-16, 1e-16, "s2rz"); - checkEqualArray(r.x, s.x, 1e-16, 1e-16, "x"); - checkEqualArray(r.sx, s.sx, 1e-16, 1e-16, "sx"); - - checkEqualArray(r.y, s.y, 1e-16, 1e-16, "y"); - checkEqualArray(r.sigmay, s.sigmay, 1e-16, 1e-16, "sigmay"); - checkEqualArray(r.sy, s.sy, 1e-16, 1e-16, "sy"); - checkEqualArray(r.ssigmay, s.ssigmay, 1e-16, 1e-16, "ssigmay"); - - CHECK_TRUE(r.numsteps == s.numsteps); - CHECK_TRUE(r.numstepsB == s.numstepsB); - CHECK_TRUE(r.numrhsevals == s.numrhsevals); - CHECK_TRUE(r.numrhsevalsB == s.numrhsevalsB); - CHECK_TRUE(r.numerrtestfails == s.numerrtestfails); - CHECK_TRUE(r.numerrtestfailsB == s.numerrtestfailsB); - CHECK_TRUE(r.numnonlinsolvconvfails == s.numnonlinsolvconvfails); - CHECK_TRUE(r.numnonlinsolvconvfailsB == s.numnonlinsolvconvfailsB); - CHECK_TRUE(r.order == s.order); - CHECK_TRUE(r.cpu_time == s.cpu_time); - CHECK_TRUE(r.cpu_timeB == s.cpu_timeB); - - CHECK_TRUE(r.preeq_status == s.preeq_status); - CHECK_TRUE(r.preeq_t == s.preeq_t || - (std::isnan(r.preeq_t) && std::isnan(s.preeq_t))); - CHECK_TRUE(r.preeq_wrms == s.preeq_wrms || - (std::isnan(r.preeq_wrms) && std::isnan(s.preeq_wrms))); - CHECK_TRUE(r.preeq_numsteps == s.preeq_numsteps); - CHECK_TRUE(r.preeq_numlinsteps == s.preeq_numlinsteps); - DOUBLES_EQUAL(r.preeq_cpu_time, s.preeq_cpu_time, 1e-16); - - CHECK_TRUE(r.posteq_status == s.posteq_status); - CHECK_TRUE(r.posteq_t == s.posteq_t || - (std::isnan(r.posteq_t) && std::isnan(s.posteq_t))); - CHECK_TRUE(r.posteq_wrms == s.posteq_wrms || - (std::isnan(r.posteq_wrms) && std::isnan(s.posteq_wrms))); - CHECK_TRUE(r.posteq_numsteps == s.posteq_numsteps); - CHECK_TRUE(r.posteq_numlinsteps == s.posteq_numlinsteps); - DOUBLES_EQUAL(r.posteq_cpu_time, s.posteq_cpu_time, 1e-16); - - checkEqualArray(r.x0, s.x0, 1e-16, 1e-16, "x0"); - checkEqualArray(r.sx0, s.sx0, 1e-16, 1e-16, "sx0"); - - CHECK_TRUE(r.llh == s.llh || (std::isnan(r.llh) && std::isnan(s.llh))); - CHECK_TRUE(r.chi2 == s.chi2 || (std::isnan(r.llh) && std::isnan(s.llh))); - CHECK_EQUAL(r.status, s.status); - - checkEqualArray(r.sllh, s.sllh, 1e-5, 1e-5, "sllh"); - checkEqualArray(r.s2llh, s.s2llh, 1e-5, 1e-5, "s2llh"); -} - -// clang-format off -TEST_GROUP(dataSerialization){ - amici::CVodeSolver solver; - void setup() override { - // set non-default values for all members - solver.setAbsoluteTolerance(1e-4); - solver.setRelativeTolerance(1e-5); - solver.setAbsoluteToleranceQuadratures(1e-6); - solver.setRelativeToleranceQuadratures(1e-7); - solver.setAbsoluteToleranceSteadyState(1e-8); - solver.setRelativeToleranceSteadyState(1e-9); - solver.setSensitivityMethod(amici::SensitivityMethod::adjoint); - solver.setSensitivityOrder(amici::SensitivityOrder::second); - solver.setMaxSteps(1e1); - solver.setMaxStepsBackwardProblem(1e2); - solver.setNewtonMaxSteps(1e3); - solver.setNewtonMaxLinearSteps(1e4); - solver.setPreequilibration(true); - solver.setStateOrdering(static_cast(amici::SUNLinSolKLU::StateOrdering::COLAMD)); - solver.setInterpolationType(amici::InterpolationType::polynomial); - solver.setStabilityLimitFlag(false); - solver.setLinearSolver(amici::LinearSolver::dense); - solver.setLinearMultistepMethod(amici::LinearMultistepMethod::adams); - solver.setNonlinearSolverIteration(amici::NonlinearSolverIteration::newton); - solver.setInternalSensitivityMethod(amici::InternalSensitivityMethod::staggered); - solver.setReturnDataReportingMode(amici::RDataReporting::likelihood); - } -}; -// clang-format on - -TEST(dataSerialization, testFile) -{ - int np = 1; - int nk = 2; - int nx = 3; - int ny = 4; - int nz = 5; - int ne = 6; - amici::CVodeSolver solver; - amici::Model_Test m = amici::Model_Test( - amici::ModelDimensions( - nx, // nx_rdata - nx, // nxtrue_rdata - nx, // nx_solver - nx, // nxtrue_solver - 0, // nx_solver_reinit - np, // np - nk, // nk - ny, // ny - ny, // nytrue - nz, // nz - nz, // nztrue - ne, // ne - 0, // nJ - 9, // nw - 2, // ndwdx - 2, // ndwdp - 2, // dwdw - 13, // ndxdotdw - {}, // ndJydy - 15, // nnz - 16, // ubw - 17 // lbw - ), - amici::SimulationParameters( - std::vector(nk, 0.0), - std::vector(np, 0.0), - std::vector(np, 0) - ), - amici::SecondOrderMode::none, - std::vector(nx, 0.0), - std::vector(nz, 0)); - - { - std::ofstream ofs("sstore.dat"); - boost::archive::text_oarchive oar(ofs); - // oar & static_cast(solver); - oar& static_cast(m); - } - { - std::ifstream ifs("sstore.dat"); - boost::archive::text_iarchive iar(ifs); - amici::CVodeSolver v; - amici::Model_Test n; - // iar &static_cast(v); - iar& static_cast(n); - // CHECK_TRUE(solver == v); - CHECK_TRUE(m == n); - } -} - -TEST(dataSerialization, testString) -{ - int np = 1; - int nk = 2; - int nx = 3; - int ny = 4; - int nz = 5; - int ne = 6; - amici::CVodeSolver solver; - amici::Model_Test m = amici::Model_Test( - amici::ModelDimensions( - nx, // nx_rdata - nx, // nxtrue_rdata - nx, // nx_solver - nx, // nxtrue_solver - 0, // nx_solver_reinit - np, // np - nk, // nk - ny, // ny - ny, // nytrue - nz, // nz - nz, // nztrue - ne, // ne - 0, // nJ - 9, // nw - 10, // ndwdx - 2, // ndwdp - 12, // dwdw - 13, // ndxdotdw - {}, // ndJydy - 15, // nnz - 16, // ubw - 17 // lbw - ), - amici::SimulationParameters( - std::vector(nk, 0.0), - std::vector(np, 0.0), - std::vector(np, 0) - ), - amici::SecondOrderMode::none, - std::vector(nx, 0.0), - std::vector(nz, 0)); - - amici::ReturnData r(solver, m); - - std::string serialized = amici::serializeToString(r); - - checkReturnDataEqual( - r, amici::deserializeFromString(serialized)); -} - -TEST(dataSerialization, testChar) -{ - int length; - char* buf = amici::serializeToChar(solver, &length); - - amici::CVodeSolver v = - amici::deserializeFromChar(buf, length); - - delete[] buf; - CHECK_TRUE(solver == v); -} - -TEST(dataSerialization, testStdVec) -{ - - auto buf = amici::serializeToStdVec(solver); - amici::CVodeSolver v = - amici::deserializeFromChar(buf.data(), buf.size()); - - CHECK_TRUE(solver == v); -} diff --git a/tests/generateTestConfigurationForExamples.sh b/tests/generateTestConfigurationForExamples.sh index 578228ee31..e54f6f5ecc 100755 --- a/tests/generateTestConfigurationForExamples.sh +++ b/tests/generateTestConfigurationForExamples.sh @@ -1,22 +1,24 @@ #!/bin/bash # Generate AMICI configuration for test models +set -eou pipefail + # AMICI root directory -AMICI_PATH="`dirname \"$BASH_SOURCE\"`" -AMICI_PATH="`( cd \"$AMICI_PATH/..\" && pwd )`" +AMICI_PATH=$(dirname "$BASH_SOURCE") +AMICI_PATH=$( cd "$AMICI_PATH/.." && pwd ) # File with test configuration -TEST_FILE="${AMICI_PATH}/tests/cpputest/testOptions.h5" +TEST_FILE="${AMICI_PATH}/tests/cpp/testOptions.h5" # Delete old config -rm ${TEST_FILE} +rm "${TEST_FILE}" -cd ${AMICI_PATH}/tests/generateTestConfig -./example_dirac.py ${TEST_FILE} -./example_events.py ${TEST_FILE} -./example_jakstat.py ${TEST_FILE} -./example_nested_events.py ${TEST_FILE} -./example_neuron.py ${TEST_FILE} -./example_robertson.py ${TEST_FILE} -./example_steadystate.py ${TEST_FILE} -./example_calvetti.py ${TEST_FILE} +cd "${AMICI_PATH}/tests/generateTestConfig" +./example_dirac.py "${TEST_FILE}" +./example_events.py "${TEST_FILE}" +./example_jakstat.py "${TEST_FILE}" +./example_nested_events.py "${TEST_FILE}" +./example_neuron.py "${TEST_FILE}" +./example_robertson.py "${TEST_FILE}" +./example_steadystate.py "${TEST_FILE}" +./example_calvetti.py "${TEST_FILE}" From e981d7491b70626ad8b51c3daa32cfbb27ef0da7 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 28 Jul 2021 13:06:02 +0200 Subject: [PATCH 02/17] Fixup doc: build script order for C++ build (#1536) * First SuiteSparse, then SUNDIALS. Not vice versa. * fix typo --- documentation/cpp_installation.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/cpp_installation.rst b/documentation/cpp_installation.rst index bd0ada57bb..f0c5ed1c03 100644 --- a/documentation/cpp_installation.rst +++ b/documentation/cpp_installation.rst @@ -24,8 +24,8 @@ To use AMICI from C++, run the .. code-block:: bash + ./scripts/buildSuiteSparse.sh ./scripts/buildSundials.sh - ./scripts/buildSuitesparse.sh ./scripts/buildAmici.sh script to build the AMICI library. From 36a86f309df36c5a3084ea64f6f5bdbceb5f6b35 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 30 Jul 2021 19:27:45 +0200 Subject: [PATCH 03/17] Optimize SUNMatrixWrapper functions (#1538) * Avoid repeated std::vector::at calls in SUNMatrixWrapper::transpose * Enable inlining of various tiny functions * Save intermediate results and avoid repeated function calls --- include/amici/sundials_matrix_wrapper.h | 102 +++++++++-- src/sundials_matrix_wrapper.cpp | 229 +++++++++--------------- 2 files changed, 177 insertions(+), 154 deletions(-) diff --git a/include/amici/sundials_matrix_wrapper.h b/include/amici/sundials_matrix_wrapper.h index 2ccc7622ab..43c1af9842 100644 --- a/include/amici/sundials_matrix_wrapper.h +++ b/include/amici/sundials_matrix_wrapper.h @@ -9,6 +9,8 @@ #include +#include + #include "amici/vector.h" namespace amici { @@ -118,13 +120,25 @@ class SUNMatrixWrapper { * @brief Get the number of rows * @return number of rows */ - sunindextype rows() const; + sunindextype rows() const { + assert(!matrix_ || + (matrix_id() == SUNMATRIX_SPARSE ? + num_rows_ == SM_ROWS_S(matrix_) : + num_rows_ == SM_ROWS_D(matrix_))); + return num_rows_; + } /** * @brief Get the number of columns * @return number of columns */ - sunindextype columns() const; + sunindextype columns() const { + assert(!matrix_ || + (matrix_id() == SUNMATRIX_SPARSE ? + num_columns_ == SM_COLUMNS_S(matrix_) : + num_columns_ == SM_COLUMNS_D(matrix_))); + return num_columns_; + } /** * @brief Get the number of specified non-zero elements (sparse matrices only) @@ -162,7 +176,13 @@ class SUNMatrixWrapper { * @param idx data index * @return idx-th data entry */ - realtype get_data(sunindextype idx) const; + realtype get_data(sunindextype idx) const{ + assert(matrix_); + assert(matrix_id() == SUNMATRIX_SPARSE); + assert(idx < capacity()); + assert(SM_DATA_S(matrix_) == data_); + return data_[idx]; + } /** * @brief Get data entry for a dense matrix @@ -170,14 +190,26 @@ class SUNMatrixWrapper { * @param icol col * @return A(irow,icol) */ - realtype get_data(sunindextype irow, sunindextype icol) const; + realtype get_data(sunindextype irow, sunindextype icol) const{ + assert(matrix_); + assert(matrix_id() == SUNMATRIX_DENSE); + assert(irow < rows()); + assert(icol < columns()); + return SM_ELEMENT_D(matrix_, irow, icol); + } /** * @brief Set data entry for a sparse matrix * @param idx data index * @param data data for idx-th entry */ - void set_data(sunindextype idx, realtype data); + void set_data(sunindextype idx, realtype data) { + assert(matrix_); + assert(matrix_id() == SUNMATRIX_SPARSE); + assert(idx < capacity()); + assert(SM_DATA_S(matrix_) == data_); + data_[idx] = data; + } /** * @brief Set data entry for a dense matrix @@ -185,47 +217,93 @@ class SUNMatrixWrapper { * @param icol col * @param data data for idx-th entry */ - void set_data(sunindextype irow, sunindextype icol, realtype data); + void set_data(sunindextype irow, sunindextype icol, realtype data) { + assert(matrix_); + assert(matrix_id() == SUNMATRIX_DENSE); + assert(irow < rows()); + assert(icol < columns()); + SM_ELEMENT_D(matrix_, irow, icol) = data; + } /** * @brief Get the index value of a sparse matrix * @param idx data index * @return row (CSC) or column (CSR) for idx-th data entry */ - sunindextype get_indexval(sunindextype idx) const; + sunindextype get_indexval(sunindextype idx) const { + assert(matrix_); + assert(matrix_id() == SUNMATRIX_SPARSE); + assert(idx < capacity()); + assert(indexvals_ == SM_INDEXVALS_S(matrix_)); + return indexvals_[idx]; + } /** * @brief Set the index value of a sparse matrix * @param idx data index * @param val row (CSC) or column (CSR) for idx-th data entry */ - void set_indexval(sunindextype idx, sunindextype val); + void set_indexval(sunindextype idx, sunindextype val) { + assert(matrix_); + assert(matrix_id() == SUNMATRIX_SPARSE); + assert(idx < capacity()); + assert(indexvals_ == SM_INDEXVALS_S(matrix_)); + indexvals_[idx] = val; + } /** * @brief Set the index values of a sparse matrix * @param vals rows (CSC) or columns (CSR) for data entries */ - void set_indexvals(const gsl::span vals); + void set_indexvals(const gsl::span vals) { + assert(matrix_); + assert(matrix_id() == SUNMATRIX_SPARSE); + assert(static_cast(vals.size()) == capacity()); + assert(indexvals_ == SM_INDEXVALS_S(matrix_)); + std::copy_n(vals.begin(), capacity(), indexvals_); + } /** * @brief Get the index pointer of a sparse matrix * @param ptr_idx pointer index * @return index where the ptr_idx-th column (CSC) or row (CSR) starts */ - sunindextype get_indexptr(sunindextype ptr_idx) const; + sunindextype get_indexptr(sunindextype ptr_idx) const { + assert(matrix_); + assert(matrix_id() == SUNMATRIX_SPARSE); + assert(ptr_idx <= num_indexptrs()); + assert(indexptrs_ == SM_INDEXPTRS_S(matrix_)); + return indexptrs_[ptr_idx]; + } /** * @brief Set the index pointer of a sparse matrix * @param ptr_idx pointer index * @param ptr data-index where the ptr_idx-th column (CSC) or row (CSR) starts */ - void set_indexptr(sunindextype ptr_idx, sunindextype ptr); + void set_indexptr(sunindextype ptr_idx, sunindextype ptr) { + assert(matrix_); + assert(matrix_id() == SUNMATRIX_SPARSE); + assert(ptr_idx <= num_indexptrs()); + assert(ptr <= capacity()); + assert(indexptrs_ == SM_INDEXPTRS_S(matrix_)); + indexptrs_[ptr_idx] = ptr; + if (ptr_idx == num_indexptrs()) + num_nonzeros_ = ptr; + } /** * @brief Set the index pointers of a sparse matrix * @param ptrs starting data-indices where the columns (CSC) or rows (CSR) start */ - void set_indexptrs(const gsl::span ptrs); + void set_indexptrs(const gsl::span ptrs) { + assert(matrix_); + assert(matrix_id() == SUNMATRIX_SPARSE); + assert(static_cast(ptrs.size()) == num_indexptrs() + 1); + assert(indexptrs_ == SM_INDEXPTRS_S(matrix_)); + std::copy_n(ptrs.begin(), num_indexptrs() + 1, indexptrs_); + num_nonzeros_ = indexptrs_[num_indexptrs()]; + } /** * @brief Get the type of sparse matrix diff --git a/src/sundials_matrix_wrapper.cpp b/src/sundials_matrix_wrapper.cpp index 4e080958c6..f8413e569b 100644 --- a/src/sundials_matrix_wrapper.cpp +++ b/src/sundials_matrix_wrapper.cpp @@ -6,7 +6,6 @@ #include // bad_alloc #include #include // invalid_argument and domain_error -#include namespace amici { @@ -140,7 +139,7 @@ void SUNMatrixWrapper::reallocate(sunindextype NNZ) { update_ptrs(); capacity_ = NNZ; - assert((NNZ && columns() && rows()) ^ !matrix_); + assert((NNZ && columns() && rows()) || !matrix_); } void SUNMatrixWrapper::realloc() { @@ -153,24 +152,10 @@ void SUNMatrixWrapper::realloc() { update_ptrs(); capacity_ = num_nonzeros_; - assert(capacity() ^ !matrix_); + assert(capacity() || !matrix_); } -sunindextype SUNMatrixWrapper::rows() const { - assert(!matrix_ || - (matrix_id() == SUNMATRIX_SPARSE ? - num_rows_ == SM_ROWS_S(matrix_) : - num_rows_ == SM_ROWS_D(matrix_))); - return num_rows_; -} -sunindextype SUNMatrixWrapper::columns() const { - assert(!matrix_ || - (matrix_id() == SUNMATRIX_SPARSE ? - num_columns_ == SM_COLUMNS_S(matrix_) : - num_columns_ == SM_COLUMNS_D(matrix_))); - return num_columns_; -} sunindextype SUNMatrixWrapper::num_indexptrs() const { assert(matrix_id() == SUNMATRIX_SPARSE); @@ -195,40 +180,6 @@ sunindextype SUNMatrixWrapper::num_nonzeros() const { return num_nonzeros_; } -realtype SUNMatrixWrapper::get_data(sunindextype idx) const{ - assert(matrix_); - assert(matrix_id() == SUNMATRIX_SPARSE); - assert(idx < capacity()); - assert(SM_DATA_S(matrix_) == data_); - return data_[idx]; -}; - -realtype SUNMatrixWrapper::get_data(sunindextype irow, sunindextype icol) const{ - assert(matrix_); - assert(matrix_id() == SUNMATRIX_DENSE); - assert(irow < rows()); - assert(icol < columns()); - return SM_ELEMENT_D(matrix_, irow, icol); -}; - - -void SUNMatrixWrapper::set_data(sunindextype idx, realtype data) { - assert(matrix_); - assert(matrix_id() == SUNMATRIX_SPARSE); - assert(idx < capacity()); - assert(SM_DATA_S(matrix_) == data_); - data_[idx] = data; -} - -void SUNMatrixWrapper::set_data(sunindextype irow, sunindextype icol, - realtype data) { - assert(matrix_); - assert(matrix_id() == SUNMATRIX_DENSE); - assert(irow < rows()); - assert(icol < columns()); - SM_ELEMENT_D(matrix_, irow, icol) = data; -} - const realtype *SUNMatrixWrapper::data() const { return data_; } @@ -237,58 +188,6 @@ realtype *SUNMatrixWrapper::data() { return data_; } -sunindextype SUNMatrixWrapper::get_indexval(sunindextype idx) const { - assert(matrix_); - assert(matrix_id() == SUNMATRIX_SPARSE); - assert(idx < capacity()); - assert(indexvals_ == SM_INDEXVALS_S(matrix_)); - return indexvals_[idx]; -} - -void SUNMatrixWrapper::set_indexval(sunindextype idx, sunindextype val) { - assert(matrix_); - assert(matrix_id() == SUNMATRIX_SPARSE); - assert(idx < capacity()); - assert(indexvals_ == SM_INDEXVALS_S(matrix_)); - indexvals_[idx] = val; -} - -void SUNMatrixWrapper::set_indexvals(const gsl::span vals) { - assert(matrix_); - assert(matrix_id() == SUNMATRIX_SPARSE); - assert(static_cast(vals.size()) == capacity()); - assert(indexvals_ == SM_INDEXVALS_S(matrix_)); - std::copy_n(vals.begin(), capacity(), indexvals_); -} - -sunindextype SUNMatrixWrapper::get_indexptr(sunindextype ptr_idx) const { - assert(matrix_); - assert(matrix_id() == SUNMATRIX_SPARSE); - assert(ptr_idx <= num_indexptrs()); - assert(indexptrs_ == SM_INDEXPTRS_S(matrix_)); - return indexptrs_[ptr_idx]; -} - -void SUNMatrixWrapper::set_indexptr(sunindextype ptr_idx, sunindextype ptr) { - assert(matrix_); - assert(matrix_id() == SUNMATRIX_SPARSE); - assert(ptr_idx <= num_indexptrs()); - assert(ptr <= capacity()); - assert(indexptrs_ == SM_INDEXPTRS_S(matrix_)); - indexptrs_[ptr_idx] = ptr; - if (ptr_idx == num_indexptrs()) - num_nonzeros_ = ptr; -} - -void SUNMatrixWrapper::set_indexptrs(const gsl::span ptrs) { - assert(matrix_); - assert(matrix_id() == SUNMATRIX_SPARSE); - assert(static_cast(ptrs.size()) == num_indexptrs() + 1); - assert(indexptrs_ == SM_INDEXPTRS_S(matrix_)); - std::copy_n(ptrs.begin(), num_indexptrs() + 1, indexptrs_); - num_nonzeros_ = indexptrs_[num_indexptrs()]; -} - int SUNMatrixWrapper::sparsetype() const { assert(matrix_); assert(matrix_id() == SUNMATRIX_SPARSE); @@ -297,7 +196,8 @@ int SUNMatrixWrapper::sparsetype() const { void SUNMatrixWrapper::scale(realtype a) { if (matrix_) { - for (sunindextype idx = 0; idx < capacity(); ++idx) + auto cap = capacity(); + for (sunindextype idx = 0; idx < cap; ++idx) data_[idx] *= a; } } @@ -382,17 +282,38 @@ void SUNMatrixWrapper::multiply(gsl::span c, return; /* Carry out actual multiplication */ - sunindextype idx; + auto c_ptr = c.data(); + auto b_ptr = b.data(); + if (transpose) { - for (int icols = 0; icols < (int)cols.size(); ++icols) - for (idx = get_indexptr(cols.at(icols)); - idx < get_indexptr(cols.at(icols) + 1); ++idx) - c.at(icols) += get_data(idx) * b.at(get_indexval(idx)); + auto cols_size = cols.size(); + for (std::size_t icols = 0; icols < cols_size; ++icols) { + auto idx_next_col = get_indexptr(cols.at(icols) + 1); + for (sunindextype idx = get_indexptr(cols.at(icols)); + idx < idx_next_col; ++idx) { + + auto idx_val = get_indexval(idx); + assert(icols > 0 && icols < c.size()); + assert(idx_val > 0 && static_cast(idx_val) < b.size()); + + c_ptr[icols] += get_data(idx) * b_ptr[idx_val]; + } + } } else { - for (sunindextype icols = 0; icols < columns(); ++icols) - for (idx = get_indexptr(cols.at(icols)); - idx < get_indexptr(cols.at(icols)+1); ++idx) - c.at(get_indexval(idx)) += get_data(idx) * b.at(icols); + auto num_cols = static_cast(columns()); + for (std::size_t icols = 0; icols < num_cols; ++icols) { + auto idx_next_col = get_indexptr(cols.at(icols) + 1); + + for (sunindextype idx = get_indexptr(cols.at(icols)); + idx < idx_next_col; ++idx) { + auto idx_val = get_indexval(idx); + + assert(icols > 0 && icols < b.size()); + assert(idx_val > 0 && static_cast(idx_val) < c.size()); + + c_ptr[idx_val] += get_data(idx) * b_ptr[icols]; + } + } } } @@ -512,7 +433,9 @@ void SUNMatrixWrapper::sparse_add(const SUNMatrixWrapper &A, realtype alpha, nnz); // no reallocation should happen here for (cidx = get_indexptr(ccol); cidx < nnz; cidx++) { - set_data(cidx, x.at(get_indexval(cidx))); // copy data to C + auto x_idx = get_indexval(cidx); + assert(x_idx >= 0 && static_cast(x_idx) < x.size()); + set_data(cidx, x[x_idx]); // copy data to C } } set_indexptr(num_indexptrs(), nnz); @@ -522,7 +445,11 @@ void SUNMatrixWrapper::sparse_add(const SUNMatrixWrapper &A, realtype alpha, void SUNMatrixWrapper::sparse_sum(const std::vector &mats) { // matrix_ == nullptr is allowed on the first call - if (std::all_of(mats.begin(), mats.end(), [](const SUNMatrixWrapper &m){return !m.matrix_;})) + auto all_empty = std::all_of(mats.begin(), mats.end(), + [](const SUNMatrixWrapper &m){ + return !m.matrix_; + }); + if (all_empty) return; check_csc(this); @@ -558,13 +485,15 @@ void SUNMatrixWrapper::sparse_sum(const std::vector &mats) { for (acol = 0; acol < columns(); acol++) { - set_indexptr(acol, nnz); /* column j of A starts here */ + set_indexptr(acol, nnz); /* column j of A starts here */ for (auto & mat : mats) nnz = mat.scatter(acol, 1.0, w.data(), gsl::make_span(x), acol+1, this, nnz); // no reallocation should happen here for (aidx = get_indexptr(acol); aidx < nnz; aidx++) { - set_data(aidx, x.at(get_indexval(aidx))); // copy data to C + auto x_idx = get_indexval(aidx); + assert(x_idx >= 0 && static_cast(x_idx) < x.size()); + set_data(aidx, x[x_idx]); // copy data to C } } set_indexptr(num_indexptrs(), nnz); @@ -572,8 +501,6 @@ void SUNMatrixWrapper::sparse_sum(const std::vector &mats) { realloc(); // resize if necessary } -static const std::string scatter_name = "scatter"; - sunindextype SUNMatrixWrapper::scatter(const sunindextype acol, const realtype beta, sunindextype *w, @@ -597,13 +524,15 @@ sunindextype SUNMatrixWrapper::scatter(const sunindextype acol, for (aidx = get_indexptr(acol); aidx < get_indexptr(acol+1); aidx++) { auto arow = get_indexval(aidx); /* A(arow,acol) is nonzero */ + assert(arow >= 0 && static_cast(arow) <= x.size()); if (w && w[arow] < mark) { w[arow] = mark; /* arow is new entry in C(:,*) */ if (C) C->set_indexval(nnz++, arow); /* add arow to pattern of C(:,*) */ - x.at(arow) = beta * get_data(aidx); /* x(arow) = beta*A(arow,acol) */ - } else - x.at(arow) += beta * get_data(aidx); /* arow exists in C(:,*) already */ + x[arow] = beta * get_data(aidx); /* x(arow) = beta*A(arow,acol) */ + } else { + x[arow] += beta * get_data(aidx); /* arow exists in C(:,*) already */ + } } assert(!C || nnz <= C->capacity()); return nnz; @@ -612,10 +541,9 @@ sunindextype SUNMatrixWrapper::scatter(const sunindextype acol, // https://github.com/DrTimothyAldenDavis/SuiteSparse/blob/master/CSparse/Source/cs_cumsum.c /* p [0..n] = cumulative sum of c[0..n-1], and then copy p [0..n-1] into c */ static void cumsum(gsl::span p, std::vector &c) { - sunindextype i; sunindextype nz = 0; assert(p.size() == c.size() + 1); - for (i = 0; i < static_cast(c.size()); i++) + for (sunindextype i = 0; i < static_cast(c.size()); i++) { p[i] = nz; nz += c[i]; @@ -652,45 +580,62 @@ void SUNMatrixWrapper::transpose(SUNMatrixWrapper &C, const realtype alpha, // see https://github.com/DrTimothyAldenDavis/SuiteSparse/blob/master/CSparse/Source/cs_transpose.c - std::vector w; auto nrows = rows(); + if (C_matrix_id == SUNMATRIX_SPARSE) { - w = std::vector(columns()); + std::vector w(columns()); + auto w_data = w.data(); + for (sunindextype acol = 0; acol < nrows; acol++) { /* row counts */ auto next_indexptr = get_indexptr(acol+1); + auto widx_offset = (acol/blocksize)*blocksize; for (sunindextype aidx = get_indexptr(acol); aidx < next_indexptr; aidx++) { - sunindextype widx = (acol/blocksize)*blocksize + get_indexval(aidx) % blocksize; + sunindextype widx = widx_offset + get_indexval(aidx) % blocksize; assert(widx >= 0 && widx < (sunindextype)w.size()); - w[widx]++; - assert(w[widx] <= nrows); + w_data[widx]++; + assert(w_data[widx] <= nrows); } } /* row pointers */ cumsum(gsl::make_span(C.indexptrs_, C.columns()+1), w); - } - - for (sunindextype acol = 0; acol < nrows; acol++) - { - auto next_indexptr = get_indexptr(acol+1); - for (sunindextype aidx = get_indexptr(acol); aidx < next_indexptr; aidx++) + for (sunindextype acol = 0; acol < nrows; acol++) { - sunindextype ccol = (acol/blocksize)*blocksize + get_indexval(aidx) % blocksize; - sunindextype crow = (get_indexval(aidx)/blocksize)*blocksize + acol % blocksize; - assert(crow < nrows); - assert(ccol < columns()); - if (C_matrix_id == SUNMATRIX_SPARSE) { + auto next_indexptr = get_indexptr(acol+1); + auto ccol_offset = (acol/blocksize)*blocksize; + auto crow_offset = acol % blocksize; + for (sunindextype aidx = get_indexptr(acol); aidx < next_indexptr; aidx++) + { + auto indexval_aidx = get_indexval(aidx); + sunindextype ccol = ccol_offset + indexval_aidx % blocksize; + sunindextype crow = (indexval_aidx/blocksize)*blocksize + crow_offset; + assert(crow < nrows); + assert(ccol < columns()); assert(aidx < capacity()); assert(ccol >= 0 && ccol < (sunindextype)w.size()); - sunindextype cidx = w[ccol]++; + sunindextype cidx = w_data[ccol]++; C.set_indexval(cidx, crow); /* place A(i,j) as entry C(j,i) */ C.set_data(cidx, alpha * get_data(aidx)); - } else { + } + } + } else { + + for (sunindextype acol = 0; acol < nrows; acol++) + { + auto next_indexptr = get_indexptr(acol+1); + + for (sunindextype aidx = get_indexptr(acol); aidx < next_indexptr; aidx++) + { + sunindextype ccol = (acol/blocksize)*blocksize + get_indexval(aidx) % blocksize; + sunindextype crow = (get_indexval(aidx)/blocksize)*blocksize + acol % blocksize; + assert(crow < nrows); + assert(ccol < columns()); C.set_data(crow, ccol, alpha * get_data(aidx)); } } } + } void SUNMatrixWrapper::to_dense(SUNMatrixWrapper &D) const { From e7cd31b0cdcfda80852ff88916308e11481d79f6 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 24 Sep 2021 16:27:30 +0200 Subject: [PATCH 04/17] =?UTF-8?q?Fix:=20Argument=20"outdir"=20in=20ODEExpo?= =?UTF-8?q?rter.=5F=5Finit=5F=5F=20only=20stored=20but=20never=20=E2=80=A6?= =?UTF-8?q?=20(#1543)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix: Argument "outdir" in ODEExporter.__init__ only stored but never used * Honor user-provided output directory (Fixes #1542) * Remove unused instance variable ODEExporter.output_dir * Remove redundant logic for setting model_path and model_swig path * Handle default directory in ODEExporter.set_paths * Allow setting ODEExporter.model_name via __init__ * Create dir only when needed --- python/amici/ode_export.py | 39 +++++++++++++++++++++---------------- python/amici/pysb_import.py | 3 +-- python/amici/sbml_import.py | 3 +-- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index b5ec800af4..838764117d 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -2575,9 +2575,6 @@ class ODEExporter: :ivar model: ODE definition - :ivar outdir: - see :meth:`amici.ode_export.ODEExporter.set_paths` - :ivar verbose: more verbose output if True @@ -2621,7 +2618,8 @@ def __init__( assume_pow_positivity: Optional[bool] = False, compiler: Optional[str] = None, allow_reinit_fixpar_initcond: Optional[bool] = True, - generate_sensitivity_code: Optional[bool] = True + generate_sensitivity_code: Optional[bool] = True, + model_name: Optional[str] = 'model' ): """ Generate AMICI C++ files for the ODE provided to the constructor. @@ -2649,19 +2647,21 @@ def __init__( :param generate_sensitivity_code specifies whether code required for sensitivity computation will be generated + + :param model_name: + name of the model to be used during code generation """ set_log_level(logger, verbose) - self.outdir: str = outdir self.verbose: bool = logger.getEffectiveLevel() <= logging.DEBUG self.assume_pow_positivity: bool = assume_pow_positivity self.compiler: str = compiler - self.model_name: str = 'model' - output_dir = os.path.join(os.getcwd(), - f'amici-{self.model_name}') - self.model_path: str = os.path.abspath(output_dir) - self.model_swig_path: str = os.path.join(self.model_path, 'swig') + self.model_path: str = '' + self.model_swig_path: str = '' + + self.set_name(model_name) + self.set_paths(outdir) # Signatures and properties of generated model functions (see # include/amici/model.h for details) @@ -2702,8 +2702,11 @@ def compile_model(self) -> None: def _prepare_model_folder(self) -> None: """ - Remove all files from the model folder. + Create model directory or remove all files if the output directory + already exists. """ + os.makedirs(self.model_path, exist_ok=True) + for file in os.listdir(self.model_path): file_path = os.path.join(self.model_path, file) if os.path.isfile(file_path): @@ -3472,22 +3475,24 @@ def _write_module_setup(self) -> None: template_data ) - def set_paths(self, output_dir: str) -> None: + def set_paths(self, output_dir: Optional[str] = None) -> None: """ Set output paths for the model and create if necessary :param output_dir: relative or absolute path where the generated model - code is to be placed. will be created if does not exists. + code is to be placed. If ``None``, this will default to + `amici-{self.model_name}` in the current working directory. + will be created if does not exists. """ + if output_dir is None: + 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') - for directory in [self.model_path, self.model_swig_path]: - if not os.path.exists(directory): - os.makedirs(directory) - def set_name(self, model_name: str) -> None: """ Sets the model name diff --git a/python/amici/pysb_import.py b/python/amici/pysb_import.py index 603eb3ef23..c33b9a2339 100644 --- a/python/amici/pysb_import.py +++ b/python/amici/pysb_import.py @@ -139,13 +139,12 @@ def pysb2amici( exporter = ODEExporter( ode_model, outdir=output_dir, + model_name=model.name, verbose=verbose, assume_pow_positivity=assume_pow_positivity, compiler=compiler, generate_sensitivity_code=generate_sensitivity_code ) - exporter.set_name(model.name) - exporter.set_paths(output_dir) exporter.generate_model_code() if compile: diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py index ab9cfd9ba6..70c04e9915 100644 --- a/python/amici/sbml_import.py +++ b/python/amici/sbml_import.py @@ -364,6 +364,7 @@ def sbml2amici(self, self, compute_cls=compute_conservation_laws) exporter = ODEExporter( ode_model, + model_name=model_name, outdir=output_dir, verbose=verbose, assume_pow_positivity=assume_pow_positivity, @@ -371,8 +372,6 @@ def sbml2amici(self, allow_reinit_fixpar_initcond=allow_reinit_fixpar_initcond, generate_sensitivity_code=generate_sensitivity_code ) - exporter.set_name(model_name) - exporter.set_paths(output_dir) exporter.generate_model_code() if compile: From 1d359e8676192fd41b64dd59b88f554af38a3e86 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 24 Sep 2021 21:46:43 +0200 Subject: [PATCH 05/17] SBML import: Use more descriptive IDs for flux expressions (#1551) Previously, those were flux_r0, flux_r1, ...; now they include the reaction ID if it was set in the imported SBML model. --- python/amici/ode_export.py | 17 ++++++++++++----- python/amici/sbml_import.py | 11 +++++++++++ 2 files changed, 23 insertions(+), 5 deletions(-) diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index 838764117d..158e9aa5da 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -1050,10 +1050,9 @@ def import_from_sbml_importer(self, nexpr = len(symbols[SymbolId.EXPRESSION]) # assemble fluxes and add them as expressions to the model - fluxes = [] - for ir, flux in enumerate(si.flux_vector): - flux_id = generate_flux_symbol(ir) - fluxes.append(flux_id) + 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)] nr = len(fluxes) # correct time derivatives for compartment changes @@ -3878,7 +3877,10 @@ def generate_measurement_symbol(observable_id: Union[str, sp.Symbol]): return symbol_with_assumptions(f'm{observable_id}') -def generate_flux_symbol(reaction_index: int) -> 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 @@ -3886,9 +3888,14 @@ def generate_flux_symbol(reaction_index: int) -> sp.Symbol: :param reaction_index: index of the reaction to which the flux corresponds + :param name: + an optional identifier of the reaction to which the flux corresponds :return: identifier symbol """ + if name is not None: + return symbol_with_assumptions(name) + return symbol_with_assumptions(f'flux_r{reaction_index}') diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py index 70c04e9915..16eb82cf28 100644 --- a/python/amici/sbml_import.py +++ b/python/amici/sbml_import.py @@ -87,6 +87,9 @@ class SbmlImporter: :ivar flux_vector: reaction kinetic laws + :ivar flux_ids: + identifiers for elements of flux_vector + :ivar _local_symbols: model symbols for sympy to consider during sympification see `locals`argument in `sympy.sympify` @@ -848,6 +851,14 @@ def _process_reactions(self): # stoichiometric matrix self.stoichiometric_matrix = sp.SparseMatrix(sp.zeros(nx, nr)) self.flux_vector = sp.zeros(nr, 1) + # Use reaction IDs as IDs for flux expressions (note that prior to SBML + # level 3 version 2 the ID attribute was not mandatory and may be + # unset) + self.flux_ids = [ + f"flux_{reaction.getId()}" if reaction.isSetId() + else f"flux_r{reaction_idx}" + for reaction_idx, reaction in enumerate(reactions) + ] or ['flux_r0'] reaction_ids = [ reaction.getId() for reaction in reactions From 9ddb859cd6bb492b8f9a35c71f9cedbee45b413e Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Sun, 26 Sep 2021 13:32:34 +0200 Subject: [PATCH 06/17] Improve checking support for SBML extensions (#1546) Ignore enabled extensions that do not affect model equations Closes #1545 * Plugin 'required' attribute only available in L3; remove unnecessary lists * ignore layout extension --- python/amici/sbml_import.py | 33 +++++++++++++++++++++++++++------ 1 file changed, 27 insertions(+), 6 deletions(-) diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py index 16eb82cf28..4928ee324e 100644 --- a/python/amici/sbml_import.py +++ b/python/amici/sbml_import.py @@ -408,28 +408,49 @@ def check_support(self) -> None: Also ensures that the SBML contains at least one reaction, or rate rule, or assignment rule, to produce change in the system over time. """ - if hasattr(self.sbml, 'all_elements_from_plugins') \ + + # 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 any([not rule.isAssignment() and not isinstance( + 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 plugin.getPackageName() in ('layout',): + # 'layout' plugin does not have the 'required' attribute + continue + if hasattr(plugin, 'getRequired') and not plugin.getRequired(): + # if not "required", this has no impact on model + # simulation, and we can safely ignore it + continue + # Check if there are extension elements. If not, we can safely + # ignore the enabled package + if plugin.getListOfAllElements(): + raise SBMLException( + f'Required SBML extension {plugin.getPackageName()} ' + f'is currently not supported!') + + if any(not rule.isAssignment() and not isinstance( self.sbml.getElementBySId(rule.getVariable()), (sbml.Compartment, sbml.Species, sbml.Parameter) - ) for rule in self.sbml.getListOfRules()]): + ) for rule in self.sbml.getListOfRules()): raise SBMLException('Algebraic rules are currently not supported, ' 'and rate rules are only supported for ' 'species, compartments, and parameters.') - if any([not (rule.isAssignment() or rule.isRate()) + if any(not (rule.isAssignment() or rule.isRate()) and isinstance( self.sbml.getElementBySId(rule.getVariable()), (sbml.Compartment, sbml.Species, sbml.Parameter) - ) for rule in self.sbml.getListOfRules()]): + ) for rule in self.sbml.getListOfRules()): raise SBMLException('Only assignment and rate rules are ' 'currently supported for compartments, ' 'species, and parameters!') - if any([r.getFast() for r in self.sbml.getListOfReactions()]): + if any(r.getFast() for r in self.sbml.getListOfReactions()): raise SBMLException('Fast reactions are currently not supported!') # Check events for unsupported functionality From d92fdf3ede296e4f1363cc79094eb0bb58e0649b Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 4 Oct 2021 12:10:21 +0200 Subject: [PATCH 07/17] Include license file in python source distribution (#1556) --- python/sdist/LICENSE.md | 1 + python/sdist/MANIFEST.in | 1 + 2 files changed, 2 insertions(+) create mode 120000 python/sdist/LICENSE.md diff --git a/python/sdist/LICENSE.md b/python/sdist/LICENSE.md new file mode 120000 index 0000000000..f0608a63ae --- /dev/null +++ b/python/sdist/LICENSE.md @@ -0,0 +1 @@ +../../LICENSE.md \ No newline at end of file diff --git a/python/sdist/MANIFEST.in b/python/sdist/MANIFEST.in index d522f9cf98..f53efb3e68 100644 --- a/python/sdist/MANIFEST.in +++ b/python/sdist/MANIFEST.in @@ -9,6 +9,7 @@ include amici/src/hdf5.cpp include amici/swig/CMakeLists_model.cmake include setup_clibs.py include version.txt +include LICENSE.md exclude amici/*.so exclude amici/*.dll From 1e25eaf596e96baa73c95cef0af629616c027646 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 4 Oct 2021 13:33:48 +0200 Subject: [PATCH 08/17] Doc: Update reference list (#1554) Closes #1552, closes #1534 --- documentation/amici_refs.bib | 61 ++++++++++++++++++++++++++++++++++++ documentation/references.md | 19 +++++++++-- 2 files changed, 78 insertions(+), 2 deletions(-) diff --git a/documentation/amici_refs.bib b/documentation/amici_refs.bib index 6803ee7e6f..bf72b85dfd 100644 --- a/documentation/amici_refs.bib +++ b/documentation/amici_refs.bib @@ -885,6 +885,67 @@ @article {Froehlich2021.05.20.445065 journal = {bioRxiv} } +@Article{ErdemMut2021, + author = {Erdem, Cemal and Mutsuddy, Arnab and Bensman, Ethan M. and Dodd, William B. and Saint-Antoine, Michael M. and Bouhaddou, Mehdi and Blake, Robert C. and Gross, Sean M. and Heiser, Laura M. and Alex Feltus, F. and Birtwistle, Marc R.}, + journal = {bioRxiv}, + title = {A Scalable, Open-Source Implementation of a Large-Scale Mechanistic Model for Single Cell Proliferation and Death Signaling}, + year = {2021}, + abstract = {Mechanistic models of how single cells respond to different perturbagens can help integrate disparate big data sets or predict response to varied drug combinations. However, the construction and simulation of such models have proved challenging. Our lab previously constructed one of the largest mechanistic models for single mammalian cell regulation of proliferation and death (774 species, 141 genes, 8 ligands, 2400 reactions). However, this, as many other large-scale models, was written using licensed software (MATLAB) with intricate programming structure, impeding alteration, expansion, and sharing. Here, we generated a new foundation for this model, which includes a python-based creation and simulation pipeline converting a few structured text files into an SBML-compatible format. This new open-source model (named SPARCED) is high-performance- and cloud-computing compatible and enables the study of virtual cell population responses at the single-cell level. We applied this new model to a subset of the LINCS MCF10A Data Cube, which observed that IFNγ acts as an anti-proliferative factor, but the reasons why were unknown. After expanding the SPARCED model with an IFNγ signaling module (to 950 species, 150 genes, 9 ligands, 2500 reactions), we ran stochastic single-cell simulations for two different putative crosstalk mechanisms and looked at the number of cycling cells in each case. Our model-based analysis suggested, and experiments support that these observations are better explained by IFNγ-induced SOCS1 expression sequestering activated EGF receptors, thereby downregulating AKT activity, as opposed to direct IFNγ-induced upregulation of p21 expression. This work forms a foundation for increased mechanistic model-based data integration on a single-cell level, an important building block for clinically predictive mechanistic models.Competing Interest StatementThe authors have declared no competing interest.}, + doi = {10.1101/2020.11.09.373407}, + elocation-id = {2020.11.09.373407}, + eprint = {https://www.biorxiv.org/content/early/2021/07/15/2020.11.09.373407.full.pdf}, + publisher = {Cold Spring Harbor Laboratory}, + url = {https://www.biorxiv.org/content/early/2021/07/15/2020.11.09.373407}, +} + +@Article{BastBuc2021, + author = {Lisa Bast and Michèle C. Buck and Judith S. Hecker and Robert A.J. Oostendorp and Katharina S. Götze and Carsten Marr}, + journal = {iScience}, + title = {Computational modeling of stem and progenitor cell kinetics identifies plausible hematopoietic lineage hierarchies}, + year = {2021}, + issn = {2589-0042}, + number = {2}, + pages = {102120}, + volume = {24}, + abstract = {Summary +Classically, hematopoietic stem cell (HSC) differentiation is assumed to occur via progenitor compartments of decreasing plasticity and increasing maturity in a specific, hierarchical manner. The classical hierarchy has been challenged in the past by alternative differentiation pathways. We abstracted experimental evidence into 10 differentiation hierarchies, each comprising 7 cell type compartments. By fitting ordinary differential equation models with realistic waiting time distributions to time-resolved data of differentiating HSCs from 10 healthy human donors, we identified plausible lineage hierarchies and rejected others. We found that, for most donors, the classical model of hematopoiesis is preferred. Surprisingly, multipotent lymphoid progenitor differentiation into granulocyte-monocyte progenitors is plausible in 90% of samples. An in silico analysis confirmed that, even for strong noise, the classical model can be identified robustly. Our computational approach infers differentiation hierarchies in a personalized fashion and can be used to gain insights into kinetic alterations of diseased hematopoiesis.}, + doi = {https://doi.org/10.1016/j.isci.2021.102120}, + keywords = {stem cells research, in silico biology, systems biology}, + url = {https://www.sciencedirect.com/science/article/pii/S2589004221000882}, +} + +@Article{TomasoniPar2021, + author = {Tomasoni, Danilo and Paris, Alessio and Giampiccolo, Stefano and Reali, Federico and Simoni, Giulia and Marchetti, Luca and Kaddi, Chanchala and Neves-Zaph, Susana and Priami, Corrado and Azer, Karim and Lombardo, Rosario}, + journal = {Communications Biology}, + title = {{QSPcc} reduces bottlenecks in computational model simulations}, + year = {2021}, + issn = {2399-3642}, + number = {1}, + pages = {1022}, + volume = {4}, + abstract = {Mathematical models have grown in size and complexity becoming often computationally intractable. In sensitivity analysis and optimization phases, critical for tuning, validation and qualification, these models may be run thousands of times. Scientific programming languages popular for prototyping, such as MATLAB and R, can be a bottleneck in terms of performance. Here we show a compiler-based approach, designed to be universal at handling engineering and life sciences modeling styles, that automatically translates models into fast C code. At first QSPcc is demonstrated to be crucial in enabling the research on otherwise intractable Quantitative Systems Pharmacology models, such as in rare Lysosomal Storage Disorders. To demonstrate the full value in seamlessly accelerating, or enabling, the R&D efforts in natural sciences, we then benchmark QSPcc against 8 solutions on 24 real-world projects from different scientific fields. With speed-ups of 22000x peak, and 1605x arithmetic mean, our results show consistent superior performances.}, + doi = {10.1038/s42003-021-02553-9}, + refid = {Tomasoni2021}, + url = {https://doi.org/10.1038/s42003-021-02553-9}, +} + +@Misc{MaierHar2020, + author = {Corinna Maier and Niklas Hartung and Charlotte Kloft and Wilhelm Huisinga and Jana de Wiljes}, + title = {Reinforcement learning and Bayesian data assimilation for model-informed precision dosing in oncology}, + year = {2020}, + archiveprefix = {arXiv}, + eprint = {2006.01061}, + primaryclass = {stat.ML}, +} +@phdthesis{Maier2021, + author = {Corinna Maier}, + title = {Bayesian data assimilation and reinforcement learning for model-informed precision dosing in oncology}, + type = {doctoralthesis}, + pages = {x, 138}, + school = {Universit{\"a}t Potsdam}, + doi = {10.25932/publishup-51587}, + year = {2021}, +} @Comment{jabref-meta: databaseType:bibtex;} @Comment{jabref-meta: grouping: diff --git a/documentation/references.md b/documentation/references.md index 3f624bdac4..7ef0254192 100644 --- a/documentation/references.md +++ b/documentation/references.md @@ -1,17 +1,26 @@ # References -List of publications using AMICI. Total number is 57. +List of publications using AMICI. Total number is 62. If you applied AMICI in your work and your publication is missing, please let us know via a new Github issue.

2021

+
+

Bast, Lisa, Michèle C. Buck, Judith S. Hecker, Robert A. J. Oostendorp, Katharina S. Götze, and Carsten Marr. 2021. “Computational Modeling of Stem and Progenitor Cell Kinetics Identifies Plausible Hematopoietic Lineage Hierarchies.” iScience 24 (2): 102120. https://doi.org/https://doi.org/10.1016/j.isci.2021.102120.

+
+
+

Erdem, Cemal, Arnab Mutsuddy, Ethan M. Bensman, William B. Dodd, Michael M. Saint-Antoine, Mehdi Bouhaddou, Robert C. Blake, et al. 2021. “A Scalable, Open-Source Implementation of a Large-Scale Mechanistic Model for Single Cell Proliferation and Death Signaling.” bioRxiv. https://doi.org/10.1101/2020.11.09.373407.

+

Fröhlich, Fabian, and Peter K. Sorger. 2021. “Fides: Reliable Trust-Region Optimization for Parameter Estimation of Ordinary Differential Equation Models.” bioRxiv. https://doi.org/10.1101/2021.05.20.445065.

Gaspari, Erika. 2021. “Model-Driven Design of Mycoplasma as a Vaccine Chassis.” PhD thesis, Wageningen: Wageningen University. https://doi.org/10.18174/539593.

+
+

Maier, Corinna. 2021. “Bayesian Data Assimilation and Reinforcement Learning for Model-Informed Precision Dosing in Oncology.” Doctoralthesis, Universität Potsdam. https://doi.org/10.25932/publishup-51587.

+

Raimúndez, Elba, Erika Dudkin, Jakob Vanhoefer, Emad Alamoudi, Simon Merkt, Lara Fuhrmann, Fan Bai, and Jan Hasenauer. 2021. “COVID-19 Outbreak in Wuhan Demonstrates the Limitations of Publicly Available Case Numbers for Epidemiological Modeling.” Epidemics 34: 100439. https://doi.org/https://doi.org/10.1016/j.epidem.2021.100439.

@@ -24,8 +33,11 @@ If you applied AMICI in your work and your publication is missing, please let us

Sten, Sebastian, Henrik Podéus, Nicolas Sundqvist, Fredrik Elinder, Maria Engström, and Gunnar Cedersund. 2021. “A Multi-Data Based Quantitative Model for the Neurovascular Coupling in the Brain.” bioRxiv. https://doi.org/10.1101/2021.03.25.437053.

+
+

Tomasoni, Danilo, Alessio Paris, Stefano Giampiccolo, Federico Reali, Giulia Simoni, Luca Marchetti, Chanchala Kaddi, et al. 2021. “QSPcc Reduces Bottlenecks in Computational Model Simulations.” Communications Biology 4 (1): 1022. https://doi.org/10.1038/s42003-021-02553-9.

+
-

Vanhoefer, Jakob, Marta R. a. Matos, Dilan Pathirana, Yannik Schälte, and Jan Hasenauer. 2021. “Yaml2sbml: Human-Readable and -Writable Specification of Ode Models and Their Conversion to Sbml.” Journal of Open Source Software 6 (61): 3215. https://doi.org/10.21105/joss.03215.

+

Vanhoefer, Jakob, Marta R. A. Matos, Dilan Pathirana, Yannik Schälte, and Jan Hasenauer. 2021. “Yaml2sbml: Human-Readable and -Writable Specification of Ode Models and Their Conversion to Sbml.” Journal of Open Source Software 6 (61): 3215. https://doi.org/10.21105/joss.03215.

van Rosmalen, R. P., R. W. Smith, V. A. P. Martins dos Santos, C. Fleck, and M. Suarez-Diez. 2021. “Model Reduction of Genome-Scale Metabolic Models as a Basis for Targeted Kinetic Models.” Metabolic Engineering 64: 74–84. https://doi.org/https://doi.org/10.1016/j.ymben.2021.01.008.

@@ -48,6 +60,9 @@ If you applied AMICI in your work and your publication is missing, please let us

Kuritz, Karsten, Alain R Bonny, João Pedro Fonseca, and Frank Allgöwer. 2020. “PDE-Constrained Optimization for Estimating Population Dynamics over Cell Cycle from Static Single Cell Measurements.” bioRxiv. https://doi.org/10.1101/2020.03.30.015909.

+
+

Maier, Corinna, Niklas Hartung, Charlotte Kloft, Wilhelm Huisinga, and Jana de Wiljes. 2020. “Reinforcement Learning and Bayesian Data Assimilation for Model-Informed Precision Dosing in Oncology.” http://arxiv.org/abs/2006.01061.

+

Schälte, Yannik, and Jan Hasenauer. 2020. “Efficient Exact Inference for Dynamical Systems with Noisy Measurements Using Sequential Approximate Bayesian Computation.” bioRxiv. https://doi.org/10.1101/2020.01.30.927004.

From ecffa081998b43a370910d5be61b564357e2c346 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Sat, 9 Oct 2021 21:55:57 +0200 Subject: [PATCH 09/17] Require sympy<1.9 Until pysb symbols are deepcopy-able with sympy 1.9 Currently results in: ``` cls = , args = ('PROT_0', 2150901) kwargs = {'commutative': True, 'complex': True, 'composite': False, 'even': False, ...} def __newobj_ex__(cls, args, kwargs): """Used by pickle protocol 4, instead of __newobj__ to allow classes with keyword-only arguments to be pickled correctly. """ > return cls.__new__(cls, *args, **kwargs) E TypeError: __new__() got an unexpected keyword argument 'real' ``` --- python/sdist/setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/sdist/setup.cfg b/python/sdist/setup.cfg index 8e8fdbaeaf..40dac7e288 100644 --- a/python/sdist/setup.cfg +++ b/python/sdist/setup.cfg @@ -27,7 +27,7 @@ package_dir = amici = amici python_requires = >=3.7 install_requires = - sympy>=1.7.1 + sympy>=1.7.1,<1.9 numpy>=1.14.5; python_version=='3.7' numpy>=1.17.5; python_version=='3.8' numpy>=1.19.3; python_version>='3.9' From 9d2f906de3b8f5628db03f40e3e663caa258d411 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Sat, 9 Oct 2021 22:57:47 +0200 Subject: [PATCH 10/17] Use BioNetGen-2.7.0 in tests (#1561) Use the most recent version of BioNetGen --- .github/workflows/test_petab_test_suite.yml | 2 +- .github/workflows/test_python_cplusplus.yml | 4 ++-- .github/workflows/test_python_ver_matrix.yml | 2 +- scripts/README.md | 2 +- scripts/buildBNGL.sh | 6 +++--- scripts/run-codecov.sh | 2 +- scripts/run-python-tests.sh | 2 +- scripts/run-valgrind-py.sh | 2 +- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test_petab_test_suite.yml b/.github/workflows/test_petab_test_suite.yml index e39e80016d..26148154a1 100644 --- a/.github/workflows/test_petab_test_suite.yml +++ b/.github/workflows/test_petab_test_suite.yml @@ -44,7 +44,7 @@ jobs: - run: | echo "${HOME}/.local/bin/" >> $GITHUB_PATH echo "${GITHUB_WORKSPACE}/tests/performance/" >> $GITHUB_PATH - echo "BNGPATH=${GITHUB_WORKSPACE}/ThirdParty/BioNetGen-2.5.2" >> $GITHUB_ENV + echo "BNGPATH=${GITHUB_WORKSPACE}/ThirdParty/BioNetGen-2.7.0" >> $GITHUB_ENV # install AMICI - name: Install python package diff --git a/.github/workflows/test_python_cplusplus.yml b/.github/workflows/test_python_cplusplus.yml index 81d32223cd..0451ab88fb 100644 --- a/.github/workflows/test_python_cplusplus.yml +++ b/.github/workflows/test_python_cplusplus.yml @@ -17,7 +17,7 @@ jobs: - run: git fetch --prune --unshallow - run: echo "AMICI_DIR=$(pwd)" >> $GITHUB_ENV - - run: echo "BNGPATH=${GITHUB_WORKSPACE}/ThirdParty/BioNetGen-2.5.2" >> $GITHUB_ENV + - run: echo "BNGPATH=${GITHUB_WORKSPACE}/ThirdParty/BioNetGen-2.7.0" >> $GITHUB_ENV # sonar cloud - run: echo "SONAR_SCANNER_VERSION=4.5.0.2216" >> $GITHUB_ENV @@ -138,7 +138,7 @@ jobs: - run: git fetch --prune --unshallow - run: echo "AMICI_DIR=$(pwd)" >> $GITHUB_ENV - - run: echo "BNGPATH=${AMICI_DIR}/ThirdParty/BioNetGen-2.5.2" >> $GITHUB_ENV + - run: echo "BNGPATH=${AMICI_DIR}/ThirdParty/BioNetGen-2.7.0" >> $GITHUB_ENV # install amici dependencies - name: homebrew diff --git a/.github/workflows/test_python_ver_matrix.yml b/.github/workflows/test_python_ver_matrix.yml index 17771c2ae9..4e730126d8 100644 --- a/.github/workflows/test_python_ver_matrix.yml +++ b/.github/workflows/test_python_ver_matrix.yml @@ -31,7 +31,7 @@ jobs: steps: - run: echo "AMICI_DIR=$(pwd)" >> $GITHUB_ENV - - run: echo "BNGPATH=${AMICI_DIR}/ThirdParty/BioNetGen-2.5.2" >> $GITHUB_ENV + - run: echo "BNGPATH=${AMICI_DIR}/ThirdParty/BioNetGen-2.7.0" >> $GITHUB_ENV - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v2 diff --git a/scripts/README.md b/scripts/README.md index 67f64dca5c..c7a9be0bfd 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -13,7 +13,7 @@ This directory contains a number of build, installation, and CI scripts. * `buildBNGL.sh` Download and build - [BioNetGen](https://www.csb.pitt.edu/Faculty/Faeder/?page_id=409) (required for some tests) + [BioNetGen](https://github.com/RuleWorld/bionetgen) (required for some tests) * `buildSuiteSparse.sh` diff --git a/scripts/buildBNGL.sh b/scripts/buildBNGL.sh index 6429be7aae..ff2bed8ba2 100755 --- a/scripts/buildBNGL.sh +++ b/scripts/buildBNGL.sh @@ -10,12 +10,12 @@ amici_path=$(cd "$script_path/.." && pwd) mkdir -p "${amici_path}/ThirdParty" cd "${amici_path}/ThirdParty" -if [ ! -d "BioNetGen-2.5.2" ]; then +if [ ! -d "BioNetGen-2.7.0" ]; then if [ ! -e "bionetgen.tar.gz" ]; then if [[ "$OSTYPE" == "linux-gnu" || "$OSTYPE" == "linux" ]]; then - wget -q -O bionetgen.tar.gz https://github.com/RuleWorld/bionetgen/releases/download/BioNetGen-2.5.2/BioNetGen-2.5.2-linux.tgz + wget -q -O bionetgen.tar.gz https://github.com/RuleWorld/bionetgen/releases/download/BioNetGen-2.7.0/BioNetGen-2.7.0-linux.tgz elif [[ "$OSTYPE" == "darwin"* ]]; then - wget -q -O bionetgen.tar.gz https://github.com/RuleWorld/bionetgen/releases/download/BioNetGen-2.5.2/BioNetGen-2.5.2-mac.tgz + wget -q -O bionetgen.tar.gz https://github.com/RuleWorld/bionetgen/releases/download/BioNetGen-2.7.0/BioNetGen-2.7.0-mac.tgz fi fi tar -xf bionetgen.tar.gz diff --git a/scripts/run-codecov.sh b/scripts/run-codecov.sh index 3db9f0dd8c..42d76f7a0e 100755 --- a/scripts/run-codecov.sh +++ b/scripts/run-codecov.sh @@ -8,7 +8,7 @@ source "${amici_path}"/build/venv/bin/activate pip install coverage pytest pytest-cov if [[ -z "${BNGPATH}" ]]; then - export BNGPATH="${amici_path}"/ThirdParty/BioNetGen-2.5.2 + export BNGPATH="${amici_path}"/ThirdParty/BioNetGen-2.7.0 fi pytest \ diff --git a/scripts/run-python-tests.sh b/scripts/run-python-tests.sh index 045148c63a..e0b33eb47a 100755 --- a/scripts/run-python-tests.sh +++ b/scripts/run-python-tests.sh @@ -7,7 +7,7 @@ amici_path=$(cd "$script_path"/.. && pwd) set -e if [[ -z "${BNGPATH}" ]]; then - export BNGPATH=${amici_path}/ThirdParty/BioNetGen-2.5.2 + export BNGPATH=${amici_path}/ThirdParty/BioNetGen-2.7.0 fi cd "${amici_path}"/python/tests diff --git a/scripts/run-valgrind-py.sh b/scripts/run-valgrind-py.sh index 7d43861a2d..241a6bd23e 100755 --- a/scripts/run-valgrind-py.sh +++ b/scripts/run-valgrind-py.sh @@ -7,7 +7,7 @@ amici_path=$(cd "$script_path"/.. && pwd) set -e if [[ -z "${BNGPATH}" ]]; then - export BNGPATH=${amici_path}/ThirdParty/BioNetGen-2.5.2 + export BNGPATH=${amici_path}/ThirdParty/BioNetGen-2.7.0 fi cd "${amici_path}"/python/tests From 49a2898691f8aef0b7d1b926610b535e02d1f1a9 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Sun, 10 Oct 2021 11:02:49 +0200 Subject: [PATCH 11/17] Replace distutils imports (#1557) * Replace distutils imports * import setuptools._distutils.ccompiler --- python/amici/custom_commands.py | 25 ++++++++++++------------- python/amici/setup.template.py | 6 +++--- python/amici/setuptools.py | 27 +++++++++++++-------------- python/sdist/setup.cfg | 1 + 4 files changed, 29 insertions(+), 30 deletions(-) diff --git a/python/amici/custom_commands.py b/python/amici/custom_commands.py index e7f36ebd2d..d4c25a348a 100644 --- a/python/amici/custom_commands.py +++ b/python/amici/custom_commands.py @@ -15,7 +15,6 @@ from setuptools.command.install import install from setuptools.command.install_lib import install_lib from setuptools.command.sdist import sdist -from distutils import log # typehints Library = Tuple[str, Dict[str, List[str]]] @@ -24,7 +23,7 @@ class AmiciInstall(install): """Custom install to handle extra arguments""" - log.debug("running AmiciInstall") + print("running AmiciInstall") # Passing --no-clibs allows to install the Python-only part of AMICI user_options = install.user_options + [ @@ -81,7 +80,7 @@ class AmiciBuildCLib(build_clib): """Custom build_clib""" def run(self): - log.debug("running AmiciBuildCLib") + print("running AmiciBuildCLib") # Always force recompilation. The way setuptools/distutils check for # whether sources require recompilation is not reliable and may lead @@ -91,7 +90,7 @@ def run(self): build_clib.run(self) def build_libraries(self, libraries: List[Library]): - log.debug("running AmiciBuildCLib.build_libraries") + print("running AmiciBuildCLib.build_libraries") no_clibs = 'develop' in self.distribution.command_obj \ and self.get_finalized_command('develop').no_clibs @@ -139,7 +138,7 @@ def finalize_options(self): develop.finalize_options(self) def run(self): - log.debug("running AmiciDevelop") + print("running AmiciDevelop") if not self.no_clibs: self.get_finalized_command('build_clib').run() @@ -156,7 +155,7 @@ def run(self): Returns: """ - log.debug("running AmiciInstallLib") + print("running AmiciInstallLib") if 'ENABLE_AMICI_DEBUGGING' in os.environ \ and os.environ['ENABLE_AMICI_DEBUGGING'] == 'TRUE' \ @@ -187,7 +186,7 @@ def run(self): the wheel """ - log.debug("running AmiciBuildExt") + print("running AmiciBuildExt") no_clibs = 'develop' in self.distribution.command_obj \ and self.get_finalized_command('develop').no_clibs @@ -220,13 +219,13 @@ def run(self): f"Found unexpected number of files: {libfilenames}" src = libfilenames[0] dest = os.path.join(target_dir, os.path.basename(src)) - log.info(f"copying {src} -> {dest}") + print(f"copying {src} -> {dest}") copyfile(src, dest) swig_outdir = os.path.join(os.path.abspath(build_dir), "amici") generate_swig_interface_files(swig_outdir=swig_outdir) swig_py_module_path = os.path.join(swig_outdir, 'amici.py') - log.debug("updating typehints") + print("updating typehints") fix_typehints(swig_py_module_path, swig_py_module_path) # Always force recompilation. The way setuptools/distutils check for @@ -244,7 +243,7 @@ class AmiciSDist(sdist): def run(self): """Setuptools entry-point""" - log.debug("running AmiciSDist") + print("running AmiciSDist") save_git_version() @@ -266,7 +265,7 @@ def save_git_version(): '--always', '--tags'] subprocess.run(cmd, stdout=f) except Exception as e: - log.warn(e) + print(e) def set_compiler_specific_library_options( @@ -298,7 +297,7 @@ def set_compiler_specific_library_options( for field in ['cflags', 'sources', 'macros']: try: lib[1][field] += lib[1][f'{field}_{compiler_type}'] - log.info(f"Changed {field} for {lib[0]} with {compiler_type} " + print(f"Changed {field} for {lib[0]} with {compiler_type} " f"to {lib[1][field]}") except KeyError: # No compiler-specific options set @@ -322,7 +321,7 @@ def set_compiler_specific_extension_options( new_value = getattr(ext, attr) + \ getattr(ext, f'{attr}_{compiler_type}') setattr(ext, attr, new_value) - log.info(f"Changed {attr} for {compiler_type} to {new_value}") + print(f"Changed {attr} for {compiler_type} to {new_value}") except AttributeError: # No compiler-specific options set pass diff --git a/python/amici/setup.template.py b/python/amici/setup.template.py index b9b6f69c44..990b5ea232 100644 --- a/python/amici/setup.template.py +++ b/python/amici/setup.template.py @@ -30,9 +30,9 @@ def build_extension(self, ext): # except for Windows, where this seems to be incompatible with # providing swig files. Not investigated further... if sys.platform != 'win32': - import distutils.ccompiler + import setuptools._distutils.ccompiler self.compiler.compile = compile_parallel.__get__( - self.compiler, distutils.ccompiler.CCompiler) + self.compiler, setuptools._distutils.ccompiler.CCompiler) build_ext.build_extension(self, ext) @@ -64,7 +64,7 @@ def get_amici_libs() -> List[str]: def get_extension() -> Extension: - """Get distutils extension object for this AMICI model package""" + """Get setuptools extension object for this AMICI model package""" cxx_flags = [] linker_flags = [] diff --git a/python/amici/setuptools.py b/python/amici/setuptools.py index 4c998e318d..7cfcf61fe1 100644 --- a/python/amici/setuptools.py +++ b/python/amici/setuptools.py @@ -9,7 +9,6 @@ import shlex import subprocess -from distutils import log from .swig import find_swig, get_swig_version try: @@ -143,7 +142,7 @@ def get_hdf5_config() -> PackageInfo: hdf5_include_dir_found = os.path.isfile( os.path.join(hdf5_include_dir_hint, 'hdf5.h')) if hdf5_include_dir_found: - log.info('hdf5.h found in %s' % hdf5_include_dir_hint) + print(f"hdf5.h found in {hdf5_include_dir_hint}") h5pkgcfg['include_dirs'] = [hdf5_include_dir_hint] break @@ -153,7 +152,7 @@ def get_hdf5_config() -> PackageInfo: hdf5_library_dir_found = os.path.isfile( os.path.join(hdf5_library_dir_hint, lib_filename)) if hdf5_library_dir_found: - log.info(f'{lib_filename} found in {hdf5_library_dir_hint}') + print(f'{lib_filename} found in {hdf5_library_dir_hint}') h5pkgcfg['library_dirs'] = [hdf5_library_dir_hint] break if hdf5_library_dir_found: @@ -192,8 +191,8 @@ def add_coverage_flags_if_required(cxx_flags: List[str], """ if 'ENABLE_GCOV_COVERAGE' in os.environ and \ os.environ['ENABLE_GCOV_COVERAGE'].upper() == 'TRUE': - log.info("ENABLE_GCOV_COVERAGE was set to TRUE." - " Building AMICI with coverage symbols.") + print("ENABLE_GCOV_COVERAGE was set to TRUE." + " Building AMICI with coverage symbols.") cxx_flags.extend(['-g', '-O0', '--coverage']) linker_flags.extend(['--coverage', '-g']) @@ -212,8 +211,8 @@ def add_debug_flags_if_required(cxx_flags: List[str], """ if 'ENABLE_AMICI_DEBUGGING' in os.environ \ and os.environ['ENABLE_AMICI_DEBUGGING'] == 'TRUE': - log.info("ENABLE_AMICI_DEBUGGING was set to TRUE." - " Building AMICI with debug symbols.") + print("ENABLE_AMICI_DEBUGGING was set to TRUE." + " Building AMICI with debug symbols.") cxx_flags.extend(['-g', '-O0', '-UNDEBUG']) linker_flags.extend(['-g']) @@ -237,7 +236,7 @@ def generate_swig_interface_files(swig_outdir: str = None, f'-Iamici{os.sep}include', ] - log.info(f"Found SWIG version {swig_version}") + print(f"Found SWIG version {swig_version}") # Are HDF5 includes available to generate the wrapper? if with_hdf5 is None: @@ -258,7 +257,7 @@ def generate_swig_interface_files(swig_outdir: str = None, '-o', os.path.join("amici", "amici_wrap.cxx"), os.path.join("amici", "swig", "amici.i")] - log.info(f"Running SWIG: {' '.join(swig_cmd)}") + print(f"Running SWIG: {' '.join(swig_cmd)}") sp = subprocess.run(swig_cmd, stdout=subprocess.PIPE, stderr=sys.stdout.buffer) if not sp.returncode == 0: @@ -271,15 +270,15 @@ def add_openmp_flags(cxx_flags: List, ldflags: List) -> None: # Enable OpenMP support for Linux / OSX: if sys.platform == 'linux': - log.info("Adding OpenMP flags...") + print("Adding OpenMP flags...") cxx_flags.insert(0, "-fopenmp") ldflags.insert(0, "-fopenmp") elif sys.platform == 'darwin': if os.path.exists('/usr/local/lib/libomp.a'): - log.info("Adding OpenMP flags...") + print("Adding OpenMP flags...") cxx_flags[0:0] = ["-Xpreprocessor", "-fopenmp"] ldflags[0:0] = ["-Xpreprocessor", "-fopenmp", "-lomp"] else: - log.info("Not adding OpenMP flags, because /usr/local/lib/libomp.a" - " does not exist. To enable, run `brew install libomp` " - "or add flags manually.") + print("Not adding OpenMP flags, because /usr/local/lib/libomp.a" + " does not exist. To enable, run `brew install libomp` " + "or add flags manually.") diff --git a/python/sdist/setup.cfg b/python/sdist/setup.cfg index 40dac7e288..e37885e29c 100644 --- a/python/sdist/setup.cfg +++ b/python/sdist/setup.cfg @@ -37,6 +37,7 @@ install_requires = pkgconfig wurlitzer toposort + setuptools>=48 include_package_data = True zip_safe = False From 04053244bacdf6f3b7372069deec34ec02b32a20 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 11 Oct 2021 17:40:17 +0200 Subject: [PATCH 12/17] Remove Travis CI leftovers (#1564) --- README.md | 2 -- documentation/index.rst | 3 --- matlab/mtoc/config/Doxyfile.template | 1 - scripts/README.md | 4 ---- scripts/travis_wrap.sh | 15 --------------- 5 files changed, 25 deletions(-) delete mode 100755 scripts/travis_wrap.sh diff --git a/README.md b/README.md index 1b05c7050d..52856ae214 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,6 @@ constrained optimization problems. PyPI version PyPI installation - - Build Status Code coverage diff --git a/documentation/index.rst b/documentation/index.rst index 39a6b4b7ed..8bfa399ef5 100644 --- a/documentation/index.rst +++ b/documentation/index.rst @@ -2,9 +2,6 @@ Welcome to AMICI's documentation! ================================= -.. image:: https://travis-ci.com/AMICI-dev/AMICI.svg?branch=master - :target: https://travis-ci.com/AMICI-dev/AMICI - :alt: Build status .. image:: https://codecov.io/gh/AMICI-dev/AMICI/branch/master/graph/badge.svg :target: https://codecov.io/gh/AMICI-dev/AMICI :alt: Code coverage diff --git a/matlab/mtoc/config/Doxyfile.template b/matlab/mtoc/config/Doxyfile.template index a50605b07c..ed2f623f17 100644 --- a/matlab/mtoc/config/Doxyfile.template +++ b/matlab/mtoc/config/Doxyfile.template @@ -875,7 +875,6 @@ WARN_LOGFILE = INPUT = "_SourceDir_/README.md" \ "_SourceDir_/LICENSE.md" \ - "_SourceDir_/INSTALL.md" \ "_SourceDir_/documentation/MATLAB_.md" \ "_SourceDir_/documentation/CPP_.md" \ "_SourceDir_/include" \ diff --git a/scripts/README.md b/scripts/README.md index c7a9be0bfd..d656d9d499 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -85,7 +85,3 @@ This directory contains a number of build, installation, and CI scripts. Run memory leak check using valgrind for all unit and integration tests. Assumes they have been built before in the default location. - -* `travis_wrap.sh` - - Wrapper script for Travis CI to enable output folding in Travis CI logs diff --git a/scripts/travis_wrap.sh b/scripts/travis_wrap.sh deleted file mode 100755 index 58314beeac..0000000000 --- a/scripts/travis_wrap.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash -# wrapper script to enable code folding on travis-ci -# -# Note: Expects travis functions exported in before_script -# -# Usage: travis_wrap.sh fold-block-id script [script args] -# fold-block-id should not contain special characters, blanks newlines, ... - -set -e -travis_time_finish -travis_fold start "$1" - travis_time_start - bash -c "${@:2:$#}" - travis_time_finish -travis_fold end "$1" From c738a6c1b797f7ad64be8a8cf97b034dcf1c2687 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 11 Oct 2021 17:41:02 +0200 Subject: [PATCH 13/17] Doc: Remove outdated/redundant INSTALL.md (#1563) More up-to-date installation instructions are available in readthedocs. Applied changes from https://github.com/AMICI-dev/AMICI/pull/1496 --- INSTALL.md | 515 -------------------------- documentation/python_installation.rst | 10 +- 2 files changed, 5 insertions(+), 520 deletions(-) delete mode 100755 INSTALL.md diff --git a/INSTALL.md b/INSTALL.md deleted file mode 100755 index 61140d2e2c..0000000000 --- a/INSTALL.md +++ /dev/null @@ -1,515 +0,0 @@ -# Installation - -## Table of Contents -1. [Availability](#availability) -2. [Python](#python) -3. [MATLAB](#matlab) -4. [C++ only](#cpp) -5. [Dependencies](#dependencies) - - -## Availability - -The sources for AMICI are available as -- Source [tarball](https://github.com/AMICI-dev/AMICI/tarball/master) -- Source [zip](https://github.com/AMICI-dev/AMICI/zipball/master) -- GIT repository on [github](https://github.com/AMICI-dev/AMICI) - -A Python package is available on pypi, see below. - -If AMICI was downloaded as a zip, it needs to be unpacked in a -convenient directory. If AMICI was obtained via cloning of the git -repository, no further unpacking is necessary. - -### Obtaining AMICI via the GIT version control system -In order to always stay up-to-date with the latest AMICI versions, -simply pull it from our GIT repository and recompile it when a new -release is available. For more information about GIT checkout their -[website](http://git-scm.com/) - -The GIT repository can currently be found at -[https://github.com/AMICI-dev/AMICI](https://github.com/AMICI-dev/AMICI) -and a direct clone is possible via - - git clone https://github.com/AMICI-dev/AMICI.git AMICI - - -## Python - -To use AMICI from python, install the module and all other requirements -using pip: - - pip3 install amici - -You can now import it as python module: - - import amici - -For cases where this installation fails, check below for special setups -and custom installations. -For Python-AMICI usage see -[https://github.com/AMICI-dev/AMICI/blob/master/documentation/PYTHON.md](https://github.com/AMICI-dev/AMICI/blob/master/documentation/PYTHON.md). - -### Installation of development versions - -To install development versions which have not been released to pypi yet, -you can install AMICI with pip directly from GitHub using: - - pip3 install -e git+https://github.com/AMICI-dev/amici.git@develop#egg=amici\&subdirectory=python/sdist - -Replace `develop` by the branch or commit you want to install. - -Note that this will probably not work on Windows which does not support -symlinks by default -(https://stackoverflow.com/questions/5917249/git-symlinks-in-windows/49913019#49913019). - -### Light installation - -In case you only want to use the AMICI Python package for generating model code -for use with Matlab or C++ and don't want to bothered with any unnecessary -dependencies, you can run - - pip3 install --install-option --no-clibs amici - -Note, however, that you will not be able to compile any model into a -Python extension with this installation. - -NOTE: If you run into an error with above installation command, install -all AMICI dependencies listed in -[`setup.py`](https://github.com/AMICI-dev/AMICI/blob/master/python/sdist/setup.py) -manually, and try again. (This is because `pip` `--install-option`s are -applied to *all* installed packages, including dependencies.) - -### Anaconda - -To use an Anaconda installation of python -([https://www.anaconda.com/distribution/](https://www.anaconda.com/distribution/), -Python>=3.6), proceed as follows: - -Since Anaconda provides own versions of some packages which might not -work with amici (in particular the gcc compiler), create a minimal -virtual environment via: - - conda create --name ENV_NAME pip python - -Here, replace ENV_NAME by some name for the environment. To activate the -environment, do: - - source activate ENV_NAME - -(and `conda deactivate` later to deactivate it again). - -SWIG must be installed and available in your `PATH`, and a -CBLAS-compatible BLAS must be available. You can also use conda to -install the latter locally, using: - - conda install -c conda-forge openblas - -To install AMICI, now do: - - pip install amici - -The option `--no-cache` may be helpful here to make sure the -installation is done completely anew. - -Now, you are ready to use AMICI in the virtual environment. - -#### Anaconda on Mac - -If the above installation does not work for you, try installing AMICI -via: - - CFLAGS="-stdlib=libc++" CC=clang CXX=clang pip3 install --verbose amici - -This will use the `clang` compiler. - -You will have to pass the same options when compiling any model later -on. This can be done by inserting the following code before calling -`sbml2amici`: - - import os - os.environ['CC'] = 'clang' - os.environ['CXX'] = 'clang' - os.environ['CFLAGS'] = '-stdlib=libc++' - -(For further discussion see https://github.com/AMICI-dev/AMICI/issues/357) - -### Windows using GCC (mingw) - -To install AMICI on Windows using python, you can proceed as follows: - -Some general remarks: - -* Install all libraries in a path not containing white spaces, - e.g. directly under C:. -* Replace the following paths according to your installation. -* Slashes can be preferable to backslashes for some environment - variables. -* See also [#425](https://github.com/AMICI-dev/amici/issues/425) for - further discussion. - -Then, follow these steps: - -* A python environment for Windows is required. We recommend - [Anaconda](https://www.anaconda.com/distribution/) with python >=3.7. -* Install [MinGW-W64](https://sourceforge.net/projects/mingw-w64/files/) - (32bit will succeed to compile, but fail during linking). - MinGW-W64 GCC-8.1.0 for `x86_64-posix-sjlj` - ([direct link](https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/8.1.0/threads-posix/sjlj/x86_64-8.1.0-release-posix-sjlj-rt_v6-rev0.7z/download) has been shown to work on Windows 7 and 10 test systems. -* Add the following directory to `PATH`: - + `C:\mingw-w64\x86_64-8.1.0-posix-sjlj-rt_v6-rev0\mingw64\bin` -* Make sure that this is the compiler that is found by the system - (e.g. `where gcc` in a `cmd` should point to this installation). -* Download CBLAS headers and libraries, e.g. - [OpenBLAS](https://sourceforge.net/projects/openblas/files/v0.2.19/), - binary distribution 0.2.19. Set the following environment variables: - + `BLAS_CFLAGS=-IC:/OpenBLAS-v0.2.19-Win64-int32/include` - + `BLAS_LIBS=-Wl,-Bstatic -LC:/OpenBLAS-v0.2.19-Win64-int32/lib -lopenblas -Wl,-Bdynamic` -* Install [SWIG](http://www.swig.org/download.html) - (version swigwin-3.0.12 worked) and add the following directory to - `PATH`: - + `C:\swigwin-3.0.12` -* Install AMICI using: - - `pip install --global-option="build_clib" --global-option="--compiler=mingw32" - --global-option="build_ext" --global-option="--compiler=mingw32" - amici --no-cache-dir --verbose` - -Possible sources of errors: - -* On recent Windows versions, - `anaconda3\Lib\distutils\cygwinccompiler.py` fails linking - `msvcr140.dll` with - `[...] x86_64-w64-mingw32/bin/ld.exe: cannot find -lmsvcr140`. - This is not required for amici, so in `cygwinccompiler.py` - `return ['msvcr140']` can be changed to `return []`. -* If you use a python version where - [python/cpython#880](https://github.com/python/cpython/pull/880) - has not been fixed yet, you need to disable - `define hypot _hypot in anaconda3\include/pyconfig.h` yourself. -* `import amici` in python resulting in the very informative - - > ImportError: DLL load failed: The specified module could not be found. - - means that some amici module dependencies were not found (not the - AMICI module itself). - [DependencyWalker](http://www.dependencywalker.com/) will show you - which ones. - - Support for msvc is experimental. - [installOpenBLAS.ps1](https://github.com/AMICI-dev/AMICI/blob/master/scripts/installOpenBLAS.ps1) - and [compileBLAS.cmd](https://github.com/AMICI-dev/AMICI/blob/master/scripts/compileBLAS.cmd) - may serve as guidance on how to install openBLAS using msvc. - -### Windows using MSVC (Visual Studio) - -#### Visual Studio - -We assume that Visual Studio (not to be confused with Visual Studio Code) is already installed. Using Visual Studio Installer, the following components need to be included: - -* Microsoft Visual C++ (MSVC). This is part of multiple packages, including Desktop Development with C++. -* Windows Universal C Runtime. This is an individual component and installs some DLLs that we need. - -#### openBLAS - -Installation of AMICI using MSVC also requires installation of Basic Linear Algebra Subprograms (BLAS) and OpenBLAS version 0.3.12 is known to work. To install open BLAS, download the following scripts from AMICI: - -https://github.com/AMICI-dev/AMICI/blob/master/scripts/installOpenBLAS.ps1 -https://github.com/AMICI-dev/AMICI/blob/master/scripts/compileBLAS.cmd - -The first script needs to be called in Powershell, and it needs to call `compileBLAS.cmd`, so you will need to modify line 11: - - C:\Users\travis\build\AMICI\scripts\compileBLAS.cmd - -so that it matches your directory structure. It may also be necessary to modify line 3 of the second script (call to `vcvars64.bat`) in order to match your installation of MSVC. - -This will download openBLAS and compile it, creating - - C:\BLAS\lib\openblas.lib - C:\BLAS\bin\openblas.dll - -You will also need to define two environment variables: - - BLAS_LIBS="/LIBPATH:C:/BLAS/lib openblas.lib" - BLAS_CFLAGS="/IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12" - -One way to do that is to run a PowerShell script with the following commands: - - [System.Environment]::SetEnvironmentVariable("BLAS_LIBS", "/LIBPATH:C:/BLAS/lib openblas.lib", [System.EnvironmentVariableTarget]::User) - [System.Environment]::SetEnvironmentVariable("BLAS_LIBS", "/LIBPATH:C:/BLAS/lib openblas.lib", [System.EnvironmentVariableTarget]::Process) - [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12", [System.EnvironmentVariableTarget]::User) - [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12", [System.EnvironmentVariableTarget]::Process) - -The call ending in `Process` sets the environment variable in the current process, and it is no longer in effect in the next process. The call ending in `User` is permanent, and takes effect the next time the user logs on. - -#### PATH -Now we need to make sure that all required DLLs are within the scope of the PATH variable. In particular, the following directories need to be included in PATH: - - C:\BLAS\bin - C:\Program Files (x86)\Windows Kits\10\Redist\ucrt\DLLs\x64 - -The first one is needed for `openblas.dll` and the second is needed for the Windows Universal C Runtime. -If any DLLs are missing in the PATH variable, Python will return the following error: - - ImportError: DLL load failed: The specified module could not be found. - -This can be tested using the "where" command. For example - - where openblas.dll - -should return - - C:\BLAS\bin\openblas.dll - -Almost all of the DLLs are standard Windows DLLs and should be included in either Windows or Visual Studio. But, in case it is necessary to test this, here is a list of some DLLs required by AMICI (when compiled with MSVC): - -* `openblas.dll` -* `python37.dll` -* `MSVCP140.dll` -* `KERNEL32.dll` -* `VCRUNTIME140_1.dll` -* `VCRUNTIME140.dll` -* `api-ms-win-crt-convert-l1-1-0.dll` -* `api-ms-win-crt-heap-l1-1-0.dll` -* `api-ms-win-crt-stdio-l1-1-0.dll` -* `api-ms-win-crt-string-l1-1-0.dll` -* `api-ms-win-crt-runtime-l1-1-0.dll` -* `api-ms-win-crt-time-l1-1-0.dll` -* `api-ms-win-crt-math-l1-1-0.dll` - -`MSVCP140.dll`, `VCRUNTIME140.dll`, and `VCRUNTIME140_1.dll` are needed by MSVC (see Visual Studio above). `KERNEL32.dll` is part of Windows and in `C:\Windows\System32`. The `api-ms-win-crt-XXX-l1-1-0.dll` are needed by `openblas.dll` and are part of the Windows Universal C Runtime (see Visual Studio above). - -### Custom installation - -AMICI Python package installation can be customized using a number of -environment variables: - -|Variable | Purpose | Example | -|---|---|---| -|`CC`| Setting the C(++) compiler | `CC=/usr/bin/g++`| -|`CFLAGS`| Extra compiler flags used in every compiler call | | -|`BLAS_CFLAGS`| Compiler flags for, e.g. BLAS include directories | | -|`BLAS_LIBS`| Flags for linking BLAS | | -|`ENABLE_GCOV_COVERAGE`| Set to build AMICI to provide code coverage information | `ENABLE_GCOV_COVERAGE=TRUE`| -|`ENABLE_AMICI_DEBUGGING`| Set to build AMICI with debugging symbols | `ENABLE_AMICI_DEBUGGING=TRUE`| -|`AMICI_PARALLEL_COMPILE`| Set to the number of parallel processes to be used for C(++) file compilation (defaults to 1)| `AMICI_PARALLEL_COMPILE=4`| - - -## MATLAB - -To use AMICI from MATLAB, start MATLAB and add the `AMICI/matlab` -directory to the MATLAB path. To add all toolbox directories to the -MATLAB path, execute the matlab script - - installAMICI.m - -To store the installation for further MATLAB session, the path can be -saved via - - savepath - -For the compilation of .mex files, MATLAB needs to be configured with a -working C++ compiler. The C++ compiler needs to be installed and -configured via: - - mex -setup c++ - -For a list of supported compilers we refer to the mathworks -documentation: -[mathworks.com](http://mathworks.com/support/compilers/R2018b/index.html) -Note that Microsoft Visual Studio compilers are currently not supported. - - -## C++ only - -To use AMICI from C++, run the - - ./scripts/buildSundials.sh - ./scripts/buildSuitesparse.sh - ./scripts/buildAmici.sh - -script to compile AMICI library. - -**NOTE**: On some systems, the CMake executable may be named something -other than `cmake`. In this case, set the `CMAKE` environment variable -to the correct name (e.g. `export CMAKE=cmake3`, in case you have CMake -available as `cmake3`). - -The static library file can then be linked from - - ./build/libamici.a - -In CMake-based packages, amici can be linked via - - find_package(Amici) - -### Optional SuperLU_MT support - -To build AMICI with SuperLU_MT support, run - - ./scripts/buildSuperLUMT.sh - ./scripts/buildSundials.sh - cd build/ - cmake -DSUNDIALS_SUPERLUMT_ENABLE=ON .. - make - - -## Dependencies - -### General - -The tools SUNDIALS and SuiteSparse shipped with AMICI do __not__ require -explicit installation. - -AMICI uses the following packages from SUNDIALS: - -__CVODES__: the sensitivity-enabled ODE solver in SUNDIALS. Radu Serban -and Alan C. Hindmarsh. _ASME 2005 International Design Engineering -Technical Conferences and Computers and Information in Engineering -Conference._ American Society of Mechanical Engineers, 2005. -[PDF](http://proceedings.asmedigitalcollection.asme.org/proceeding.aspx?articleid=1588657) - -__IDAS__ - -AMICI uses the following packages from SuiteSparse: - -__Algorithm 907: KLU__, A Direct Sparse Solver for Circuit Simulation -Problems. Timothy A. Davis, Ekanathan Palamadai Natarajan, -_ACM Transactions on Mathematical Software_, Vol 37, Issue 6, 2010, -pp 36:1 - 36:17. [PDF](http://dl.acm.org/authorize?305534) - -__Algorithm 837: AMD__, an approximate minimum degree ordering -algorithm, Patrick R. Amestoy, Timothy A. Davis, Iain S. Duff, -_ACM Transactions on Mathematical Software_, Vol 30, Issue 3, 2004, -pp 381 - 388. [PDF](http://dl.acm.org/authorize?733169) - -__Algorithm 836: COLAMD__, a column approximate minimum degree ordering -algorithm, Timothy A. Davis, John R. Gilbert, Stefan I. Larimore, -Esmond G. Ng _ACM Transactions on Mathematical Software_, Vol 30, -Issue 3, 2004, pp 377 - 380. [PDF](http://dl.acm.org/authorize?734450) - -#### libsbml - -To import Systems Biology Markup Language ([SBML](http://sbml.org/)) -models, AMICI relies on the Python or MATLAB SBML library. - -#### Math Kernel Library (MKL) - -The python and C++ interfaces require a system installation of a `BLAS`. -AMICI has been tested with various native and general purpose MKL -implementations such as Accelerate, Intel MKL, cblas, openblas, atlas. -The matlab interface uses the MATLAB MKL, which requires no separate -installation. - -On Ubuntu, this requirement can be satisfied with - - apt install libatlas-base-dev - -On Fedora (32): - - sudo dnf install blas-devel - -#### C++ compiler - -All AMICI installations require a C++11-compatible C++ compiler. -AMICI has been tested with g++, mingw, clang and the Intel compiler. -Visual C++ is not officially supported, but may work. - -#### HDF5 - -The python and C++ interfaces provide routines to read and write options -and results in [hdf5](https://support.hdfgroup.org/HDF5/) format. -For the python interface, the installation of hdf5 is optional, but for -the C++ interace it is currently required. - -HDF5 can be installed using package managers such as -[brew](https://brew.sh) or [apt](https://wiki.debian.org/Apt): - - brew install hdf5 - -or - - apt-get install libhdf5-serial-dev - -#### SWIG - -The python interface requires [SWIG](http://www.swig.org), which has to -be installed by the user. As root user, SWIG can be installed using -package managers such as [brew](https://brew.sh) or -[apt](https://wiki.debian.org/Apt): - - brew install swig - -or - - apt-get install swig3.0 - -Or by non-root users, using `scripts/downloadAndBuildSwig.sh` from the -AMICI repository (not included in the PyPI package). The binary -directory has to be added to the `PATH` environment variable, or `SWIG` -has to be set as described in the following section. - -##### Using a non-default SWIG executable - -We note here that some linux package managers may provide swig -executables as `swig3.0`, but installation as `swig` is required. This -can be fixed as root user using, e.g., symbolic links: - - mkdir -p ~/bin/ && ln -s $(which swig3.0) ~/bin/swig && export PATH=~/bin/:$PATH - -Non-root users can set the `SWIG` environment variable to the full -path of the desired SWIG executable. This variable has be set during -AMICI package installation as well as during model compilation. - -### Matlab - -The MATLAB interface requires the Mathworks Symbolic Toolbox for model -generation via `amiwrap(...)`, but not for execution of precompiled -models. Currently MATLAB R2018a or newer is not supported (see -[https://github.com/AMICI-dev/AMICI/issues/307](https://github.com/AMICI-dev/AMICI/issues/307)). - -The Symbolic Toolbox requirement can be circumvented by performing model -import using the Python interface. The result code can then be used from -Matlab. - -### Python - -The python interface requires python 3.6 or newer and a cblas-compatible -BLAS library to be installed. Windows installations via pip are -currently not supported, but users may try to install amici using the -build scripts provided for the C++ interface (these will by default -automatically install the python module). - -The python interface depends on some additional packages, e.g. `numpy`. -They are automatically installed when installing the python package. - -### C++ - -The C++ interface requires `cmake` and a cblas-compatible BLAS to be -installed. - -### Optional - -#### SuperLU_MT - -"A general purpose library for the direct solution of large, -sparse, nonsymmetric systems of linear equations" -(https://crd-legacy.lbl.gov/~xiaoye/SuperLU/#superlu_mt). -SuperLU_MT is optional and is so far only available from the C++ interface. - - -#### Boost - -[Boost](https://www.boost.org/) is an optional C++ dependency only required for -special functions (including e.g. gamma derivatives) in the python interface. -It can be installed via package managers via - - apt-get install libboost-math-dev - -or - - brew install boost - -As only headers are required, also a -[source code](https://www.boost.org/doc/libs/1_66_0/more/getting_started/unix-variants.html) -download suffices. The compiler must be able to find the module in the search path. diff --git a/documentation/python_installation.rst b/documentation/python_installation.rst index 127309fd6d..0526f6133b 100644 --- a/documentation/python_installation.rst +++ b/documentation/python_installation.rst @@ -205,16 +205,16 @@ You will also need to define two environment variables: .. code-block:: text BLAS_LIBS="/LIBPATH:C:\BLAS\lib openblas.lib" - BLAS_CFLAGS="/IC:\BLAS\OpenBLAS-v0.3.10\OpenBLAS-0.3.10" + BLAS_CFLAGS="/IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12" One way to do that is to run a PowerShell script with the following commands: .. code-block:: text - [System.Environment]::SetEnvironmentVariable("BLAS_LIBS", "/LIBPATH:C:\BLAS\lib openblas.lib", [System.EnvironmentVariableTarget]::User) - [System.Environment]::SetEnvironmentVariable("BLAS_LIBS", "/LIBPATH:C:\BLAS\lib openblas.lib", [System.EnvironmentVariableTarget]::Process) - [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:\BLAS\OpenBLAS-v0.3.10\OpenBLAS-0.3.10", [System.EnvironmentVariableTarget]::User) - [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:\BLAS\OpenBLAS-v0.3.10\OpenBLAS-0.3.10", [System.EnvironmentVariableTarget]::Process) + [System.Environment]::SetEnvironmentVariable("BLAS_LIBS", "/LIBPATH:C:/BLAS/lib openblas.lib", [System.EnvironmentVariableTarget]::User) + [System.Environment]::SetEnvironmentVariable("BLAS_LIBS", "/LIBPATH:C:/BLAS/lib openblas.lib", [System.EnvironmentVariableTarget]::Process) + [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12", [System.EnvironmentVariableTarget]::User) + [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12", [System.EnvironmentVariableTarget]::Process) The call ending in ``Process`` sets the environment variable in the current process, and it is no longer in effect in the next process. The call ending in From e20b0db2f6659b93b7366655e02a0463a882946c Mon Sep 17 00:00:00 2001 From: Stephan Grein Date: Mon, 11 Oct 2021 17:43:03 +0200 Subject: [PATCH 14/17] Add CITATION.cff (#1559) --- CITATION.cff | 79 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 CITATION.cff diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 0000000000..c5e6d8d6ff --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,79 @@ +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" +title: "AMICI: High-Performance Sensitivity Analysis for Large Ordinary Differential Equation Models" + +preferred-citation: + type: article + title: "AMICI: High-Performance Sensitivity Analysis for Large Ordinary Differential Equation Models" + doi: 10.1093/bioinformatics/btab227 + journal: "Bioinformatics" + year: 2021 + month: 4 + 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" +cff-version: 1.2.0 +message: "If you use this software, please cite both the article from preferred-citation and the software itself." +url: "https://github.com/AMICI-dev/AMICI" +doi: 10.5281/zenodo.597928 From 31344e78e814cfd82675c93421a9ebd53b1bae7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Tue, 12 Oct 2021 12:01:47 -0400 Subject: [PATCH 15/17] implement observable transformations, fixes #963 (#1567) * implement observable transformations, fixes #963 * fixup * Apply suggestions from code review Co-authored-by: Daniel Weindl * address review comments * fix cpp * fix doxygen * fix doxy * fix swig * fix pysb import * fix blank throws * fix sphinx * fix sphinx Co-authored-by: Daniel Weindl --- include/amici/defines.h | 7 ++++ include/amici/model.h | 7 ++++ python/amici/import_utils.py | 33 +++++++++++++++- python/amici/ode_export.py | 38 ++++++++++++++++-- python/amici/pysb_import.py | 14 ++++--- python/amici/sbml_import.py | 10 +++-- src/model.ODE_template.cpp | 4 ++ src/model.cpp | 4 ++ src/model_header.ODE_template.h | 5 +++ src/rdata.cpp | 45 +++++++++++++++++----- swig/amici.i | 1 + tests/petab_test_suite/test_petab_suite.py | 11 ++---- 12 files changed, 150 insertions(+), 29 deletions(-) diff --git a/include/amici/defines.h b/include/amici/defines.h index 7e69576179..852b0c4370 100644 --- a/include/amici/defines.h +++ b/include/amici/defines.h @@ -109,6 +109,13 @@ enum class ParameterScaling { log10 }; +/** modes for observable scaling */ +enum class ObservableScaling { + lin, + log, + log10 +}; + /** modes for second order sensitivity analysis */ enum class SecondOrderMode { none, diff --git a/include/amici/model.h b/include/amici/model.h index 1e47808443..1de282d206 100644 --- a/include/amici/model.h +++ b/include/amici/model.h @@ -802,6 +802,13 @@ class Model : public AbstractModel, public ModelDimensions { */ void getObservable(gsl::span y, const realtype t, const AmiVector &x); + + /** + * @brief Get scaling type for observable + * @param iy observable index + * @return scaling type + */ + virtual ObservableScaling getObservableScaling(int iy) const; /** * @brief Get sensitivity of time-resolved observables. diff --git a/python/amici/import_utils.py b/python/amici/import_utils.py index 833e622c4a..52c9c793c0 100644 --- a/python/amici/import_utils.py +++ b/python/amici/import_utils.py @@ -4,13 +4,44 @@ from typing import Dict, Union, Optional, Callable import sympy as sp +import enum from toposort import toposort SymbolDef = Dict[sp.Symbol, Union[Dict[str, sp.Expr], sp.Expr]] +class ObservableTransformation(str, enum.Enum): + """ + Different modes of observable transformation. + """ + LOG10 = 'log10' + LOG = 'log' + LIN = 'lin' + + +def noise_distribution_to_observable_transformation( + noise_distribution: Union[str, Callable] +) -> ObservableTransformation: + """ + Parse noise distribution string and extract observable transformation + + :param noise_distribution: + see :func:`noise_distribution_to_cost_function` + + :return: + observable transformation + """ + if isinstance(noise_distribution, str): + if noise_distribution.startswith('log-'): + return ObservableTransformation.LOG + if noise_distribution.startswith('log10-'): + return ObservableTransformation.LOG10 + + return ObservableTransformation.LIN + + def noise_distribution_to_cost_function( - noise_distribution: str + noise_distribution: Union[str, Callable] ) -> Callable[[str], str]: """ Parse noise distribution string to a cost function definition amici can diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index 158e9aa5da..e0376b8791 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -45,7 +45,8 @@ ) from .logging import get_logger, log_execution_time, set_log_level from .constants import SymbolId -from .import_utils import smart_subs_dict, toposort_symbols +from .import_utils import smart_subs_dict, toposort_symbols, \ + ObservableTransformation # Template for model simulation main.cpp file CXX_MAIN_TEMPLATE_FILE = os.path.join(amiciSrcPath, 'main.template.cpp') @@ -555,6 +556,10 @@ class Observable(ModelQuantity): :ivar _measurement_symbol: sympy symbol used in the objective function to represent measurements to this observable + + :ivar trafo: + observable transformation, only applies when evaluating objective + function or residuals """ _measurement_symbol: Union[sp.Symbol, None] = None @@ -563,7 +568,8 @@ def __init__(self, identifier: sp.Symbol, name: str, value: sp.Expr, - measurement_symbol: Optional[sp.Symbol] = None): + measurement_symbol: Optional[sp.Symbol] = None, + transformation: Optional[ObservableTransformation] = 'lin'): """ Create a new Observable instance. @@ -575,9 +581,14 @@ def __init__(self, :param value: formula + + :param transformation: + observable transformation, only applies when evaluating objective + function or residuals """ super(Observable, self).__init__(identifier, name, value) self._measurement_symbol = measurement_symbol + self.trafo = transformation def get_measurement_symbol(self) -> sp.Symbol: if self._measurement_symbol is None: @@ -1160,6 +1171,8 @@ def transform_dxdt_to_concentration(species_id, dxdt): args += ['value'] if symbol_name == SymbolId.EVENT: args += ['state_update', 'event_observable'] + if symbol_name == SymbolId.OBSERVABLE: + args += ['transformation'] protos = [ { @@ -1215,7 +1228,8 @@ def transform_dxdt_to_concentration(species_id, dxdt): # fill in 'self._sym' based on prototypes and components in ode_model self.generate_basic_variables(from_sbml=True) self._has_quadratic_nllh = all( - llh['dist'] in ['normal', 'lin-normal'] + llh['dist'] in ['normal', 'lin-normal', 'log-normal', + 'log10-normal'] for llh in si.symbols[SymbolId.LLHY].values() ) @@ -1299,6 +1313,17 @@ def add_conservation_law(self, self._states[ix].set_conservation_law(state_expr) + + def get_observable_transformations(self) -> List[ObservableTransformation]: + """ + List of observable transformations + + :return: + list of transformations + """ + return [obs.trafo for obs in self._observables] + + def num_states_rdata(self) -> int: """ Number of states. @@ -3292,6 +3317,13 @@ def _write_model_header_cpp(self) -> None: 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}, // 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': diff --git a/python/amici/pysb_import.py b/python/amici/pysb_import.py index c33b9a2339..6dab3f8a73 100644 --- a/python/amici/pysb_import.py +++ b/python/amici/pysb_import.py @@ -11,7 +11,8 @@ ) from .import_utils import ( - noise_distribution_to_cost_function, _get_str_symbol_identifiers + noise_distribution_to_cost_function, _get_str_symbol_identifiers, + noise_distribution_to_observable_transformation ) import logging from .logging import get_logger, log_execution_time, set_log_level @@ -217,7 +218,7 @@ def ode_model_from_pysb_importer( _process_pysb_expressions(model, ode, observables, sigmas, noise_distributions) ode._has_quadratic_nllh = not noise_distributions or all( - noise_distr in ['normal', 'lin-normal'] + noise_distr in ['normal', 'lin-normal', 'log-normal', 'log10-normal'] for noise_distr in noise_distributions.values() ) @@ -373,8 +374,12 @@ def _add_expression( ) if name in observables: + noise_dist = noise_distributions.get(name, 'normal') \ + if noise_distributions else 'normal' + y = sp.Symbol(f'{name}') - obs = Observable(y, name, sym) + 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( @@ -384,8 +389,7 @@ def _add_expression( sigma = sp.Symbol(sigma_name) ode_model.add_component(SigmaY(sigma, f'{sigma_name}', sigma_value)) - noise_dist = noise_distributions.get(name, 'normal') \ - if noise_distributions else 'normal' + 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, diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py index 4928ee324e..c851384d76 100644 --- a/python/amici/sbml_import.py +++ b/python/amici/sbml_import.py @@ -20,8 +20,8 @@ from .import_utils import ( smart_subs, smart_subs_dict, toposort_symbols, - _get_str_symbol_identifiers, - noise_distribution_to_cost_function + _get_str_symbol_identifiers, noise_distribution_to_cost_function, + noise_distribution_to_observable_transformation ) from .ode_export import ( ODEExporter, ODEModel, generate_measurement_symbol, @@ -1209,7 +1209,11 @@ def _process_observables( # former. 'value': self._sympy_from_sbml_math( definition['formula'] - ) + ), + 'transformation': + noise_distribution_to_observable_transformation( + noise_distributions.get(obs, 'normal') + ) } for iobs, (obs, definition) in enumerate(observables.items()) } diff --git a/src/model.ODE_template.cpp b/src/model.ODE_template.cpp index af1e5845cf..5c9f69b6aa 100644 --- a/src/model.ODE_template.cpp +++ b/src/model.ODE_template.cpp @@ -21,6 +21,10 @@ std::array observableNames = { TPL_OBSERVABLE_NAMES_INITIALIZER_LIST }; +std::array observableScalings = { + TPL_OBSERVABLE_TRAFO_INITIALIZER_LIST +}; + std::array expressionNames = { TPL_EXPRESSION_NAMES_INITIALIZER_LIST }; diff --git a/src/model.cpp b/src/model.cpp index 2123be7427..e37b7409db 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -791,6 +791,10 @@ void Model::getObservable(gsl::span y, const realtype t, writeSlice(derived_state_.y_, y); } +ObservableScaling Model::getObservableScaling(int /*iy*/) const { + return ObservableScaling::lin; +} + void Model::getObservableSensitivity(gsl::span sy, const realtype t, const AmiVector &x, const AmiVectorArray &sx) { diff --git a/src/model_header.ODE_template.h b/src/model_header.ODE_template.h index 79eec1d51c..c35d07918a 100644 --- a/src/model_header.ODE_template.h +++ b/src/model_header.ODE_template.h @@ -19,6 +19,7 @@ extern std::array parameterNames; extern std::array fixedParameterNames; extern std::array stateNames; extern std::array observableNames; +extern std::array observableScalings; extern std::array expressionNames; extern std::array parameterIds; extern std::array fixedParameterIds; @@ -565,6 +566,10 @@ class Model_TPL_MODELNAME : public amici::Model_ODE { virtual bool hasQuadraticLLH() const override { return TPL_QUADRATIC_LLH; } + + virtual ObservableScaling getObservableScaling(int iy) const override { + return observableScalings.at(iy); + } }; diff --git a/src/rdata.cpp b/src/rdata.cpp index df64d76eb0..38fcf8562c 100644 --- a/src/rdata.cpp +++ b/src/rdata.cpp @@ -777,8 +777,18 @@ void ReturnData::initializeObjectiveFunction(bool enable_chi2) { chi2 = 0.0; } -static realtype fres(realtype y, realtype my, realtype sigma_y) { - return (y - my) / sigma_y; +static realtype fres(realtype y, realtype my, realtype sigma_y, + ObservableScaling scale) { + switch (scale) { + case amici::ObservableScaling::lin: + return (y - my) / sigma_y; + case amici::ObservableScaling::log: + return (std::log(y) - std::log(my)) / sigma_y; + case amici::ObservableScaling::log10: + return (std::log10(y) - std::log10(my)) / sigma_y; + default: + throw std::invalid_argument("only lin, log, log10 allowed."); + } } static realtype fres_error(realtype sigma_y, realtype sigma_offset) { @@ -800,8 +810,11 @@ void ReturnData::fres(const int it, Model &model, const ExpData &edata) { int iyt = iy + it * edata.nytrue(); if (!edata.isSetObservedData(it, iy)) continue; + res.at(iyt) = amici::fres(y_it.at(iy), observedData[iy], - sigmay_it.at(iy)); + sigmay_it.at(iy), + model.getObservableScaling(iy)); + if (sigma_res) res.at(iyt + nt * nytrue) = fres_error(sigmay_it.at(iy), sigma_offset); @@ -821,10 +834,20 @@ void ReturnData::fchi2(const int it, const ExpData &edata) { } static realtype fsres(realtype y, realtype sy, realtype my, - realtype sigma_y, realtype ssigma_y) { - return (sy - ssigma_y * fres(y, my, sigma_y)) / sigma_y; + realtype sigma_y, realtype ssigma_y, + ObservableScaling scale) { + auto res = fres(y, my, sigma_y, scale); + switch (scale) { + case amici::ObservableScaling::lin: + return (sy - ssigma_y * res) / sigma_y; + case amici::ObservableScaling::log: + return (sy / y - ssigma_y * res) / sigma_y; + case amici::ObservableScaling::log10: + return (sy / (y * std::log(10)) - ssigma_y * res) / sigma_y; + default: + throw std::invalid_argument("only lin, log, log10 allowed."); + } } - static realtype fsres_error(realtype sigma_y, realtype ssigma_y, realtype sigma_offset) { return ssigma_y / ( fres_error(sigma_y, sigma_offset) * sigma_y); @@ -851,9 +874,12 @@ void ReturnData::fsres(const int it, Model &model, const ExpData &edata) { continue; for (int ip = 0; ip < nplist; ++ip) { int idx = (iy + it * edata.nytrue()) * nplist + ip; + sres.at(idx) = amici::fsres(y_it.at(iy), sy_it.at(iy + ny * ip), observedData[iy], sigmay_it.at(iy), - ssigmay_it.at(iy + ny * ip)); + ssigmay_it.at(iy + ny * ip), + model.getObservableScaling(iy)); + if (sigma_res) { int idx_res = (iy + it * edata.nytrue() + edata.nytrue() * edata.nt()) * @@ -941,18 +967,19 @@ void ReturnData::fFIM(int it, Model &model, const ExpData &edata) { auto y = y_it.at(iy); auto m = observedData[iy]; auto s = sigmay_it.at(iy); + auto os = model.getObservableScaling(iy); // auto r = amici::fres(y, m, s); for (int ip = 0; ip < nplist; ++ip) { auto dy_i = sy_it.at(iy + ny * ip); auto ds_i = ssigmay_it.at(iy + ny * ip); - auto sr_i = amici::fsres(y, dy_i, m, s, ds_i); + auto sr_i = amici::fsres(y, dy_i, m, s, ds_i, os); realtype sre_i = 0.0; if (sigma_res) sre_i = amici::fsres_error(s, ds_i, sigma_offset); for (int jp = 0; jp < nplist; ++jp) { auto dy_j = sy_it.at(iy + ny * jp); auto ds_j = ssigmay_it.at(iy + ny * jp); - auto sr_j = amici::fsres(y, dy_j, m, s, ds_j); + auto sr_j = amici::fsres(y, dy_j, m, s, ds_j, os); FIM.at(ip + nplist * jp) += sr_i*sr_j; if (sigma_res) { auto sre_j = amici::fsres_error(s, ds_j, sigma_offset); diff --git a/swig/amici.i b/swig/amici.i index 80a7c0d4e8..7253545d60 100644 --- a/swig/amici.i +++ b/swig/amici.i @@ -161,6 +161,7 @@ def enum(prefix): values = {k[len(prefix)+1:]:v for k,v in values.items()} return IntEnum(prefix, values) ParameterScaling = enum('ParameterScaling') +ObservableScaling = enum('ObservableScaling') SecondOrderMode = enum('SecondOrderMode') SensitivityOrder = enum('SensitivityOrder') SensitivityMethod = enum('SensitivityMethod') diff --git a/tests/petab_test_suite/test_petab_suite.py b/tests/petab_test_suite/test_petab_suite.py index 73c7a58db3..5e0695c3e1 100755 --- a/tests/petab_test_suite/test_petab_suite.py +++ b/tests/petab_test_suite/test_petab_suite.py @@ -108,14 +108,9 @@ def _test_case(case, model_type): logger.log(logging.DEBUG if simulations_match else logging.ERROR, f"Simulations: match = {simulations_match}") - # FIXME case 7 fails due to #963 - if case not in ['0007', '0016']: - check_derivatives(problem, model) - - # FIXME case 7 fails due to #963 - if not all([llhs_match, simulations_match]) \ - or (not chi2s_match and case not in ['0007', '0016']): - # chi2s_match ignored until fixed in amici + check_derivatives(problem, model) + + 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") From c89108a7d38db828803b085f0f10987d9d9f4383 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Tue, 12 Oct 2021 13:29:47 -0400 Subject: [PATCH 16/17] Small steadystate fixes (#1541) * fix convergence check * only check early convergence for first newton try * fixup? * update docstr * unsilence errors during preequilibration * change doc * disable sensitivity check for adjoint * fix asserts * fixup Co-authored-by: Daniel Weindl --- include/amici/steadystateproblem.h | 4 ++-- python/tests/test_preequilibration.py | 14 +++++++------- src/steadystateproblem.cpp | 16 +++++++++++----- src/sundials_matrix_wrapper.cpp | 8 ++++---- 4 files changed, 24 insertions(+), 18 deletions(-) diff --git a/include/amici/steadystateproblem.h b/include/amici/steadystateproblem.h index bd7efe47c4..550433e797 100644 --- a/include/amici/steadystateproblem.h +++ b/include/amici/steadystateproblem.h @@ -144,8 +144,8 @@ class SteadystateProblem { * @brief Computes the weighted root mean square of xdot * the weights are computed according to x: * w_i = 1 / ( rtol * x_i + atol ) - * @param x current state - * @param xdot current rhs + * @param x current state (sx[ip] for sensitivities) + * @param xdot current rhs (sxdot[ip] for sensitivities) * @param atol absolute tolerance * @param rtol relative tolerance * @param ewt error weight vector diff --git a/python/tests/test_preequilibration.py b/python/tests/test_preequilibration.py index 1129c89857..2af6da888b 100644 --- a/python/tests/test_preequilibration.py +++ b/python/tests/test_preequilibration.py @@ -236,7 +236,7 @@ def test_parameter_in_expdata(preeq_fixture): def test_raise_presimulation_with_adjoints(preeq_fixture): - """Test data replicates""" + """Test simulation failures with adjoin+presimulation""" model, solver, edata, edata_preeq, \ edata_presim, edata_sim, pscales, plists = preeq_fixture @@ -248,16 +248,15 @@ def test_raise_presimulation_with_adjoints(preeq_fixture): rdata = amici.runAmiciSimulation(model, solver, edata) assert rdata['status'] == amici.AMICI_ERROR - # presimulation and postequilibration with adjoints: - # this also needs to fail + # add postequilibration y = edata.getObservedData() stdy = edata.getObservedDataStdDev() - - # add infty timepoint ts = np.hstack([*edata.getTimepoints(), np.inf]) - edata.setTimepoints(sorted(ts)) + edata.setTimepoints(ts) edata.setObservedData(np.hstack([y, y[0]])) edata.setObservedDataStdDev(np.hstack([stdy, stdy[0]])) + + # remove presimulation edata.t_presim = 0 edata.fixedParametersPresimulation = () @@ -267,7 +266,8 @@ def test_raise_presimulation_with_adjoints(preeq_fixture): def test_equilibration_methods_with_adjoints(preeq_fixture): - """Test data replicates""" + """Test different combinations of equilibration and simulation + sensitivity methods""" model, solver, edata, edata_preeq, \ edata_presim, edata_sim, pscales, plists = preeq_fixture diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index 969c5723f8..8c433efcc4 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -198,9 +198,15 @@ void SteadystateProblem::findSteadyStateBySimulation(const Solver *solver, steady_state_status_[1] = SteadyStateStatus::failed_too_long_simulation; break; default: + model->app->warningF("AMICI:newton", + "AMICI newton method failed: %s\n", + ex.what()); steady_state_status_[1] = SteadyStateStatus::failed; } - } catch (AmiException const &) { + } catch (AmiException const &ex) { + model->app->warningF("AMICI:equilibration", + "AMICI equilibration failed: %s\n", + ex.what()); steady_state_status_[1] = SteadyStateStatus::failed; } } @@ -462,7 +468,7 @@ bool SteadystateProblem::checkConvergence(const Solver *solver, sx_ = solver->getStateSensitivity(t_); model->fsxdot(t_, x_, dx_, ip, sx_[ip], dx_, xdot_); wrms_ = getWrmsNorm( - x_, xdot_, solver->getAbsoluteToleranceSteadyStateSensi(), + sx_[ip], xdot_, solver->getAbsoluteToleranceSteadyStateSensi(), solver->getRelativeToleranceSteadyStateSensi(), ewt_); converged = wrms_ < RCONST(1.0); } @@ -504,8 +510,8 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, xdot_old_ = xdot_; wrms_ = getWrmsNorm(x_newton_, xdot_, newtonSolver->atol_, - newtonSolver->rtol_, ewt_); - bool converged = wrms_ < RCONST(1.0); + newtonSolver->rtol_, ewt_); + bool converged = newton_retry ? false : wrms_ < RCONST(1.0); while (!converged && i_newtonstep < newtonSolver->max_steps) { /* If Newton steps are necessary, compute the initial search direction */ @@ -594,7 +600,7 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, /* Do we also have to check for convergence of sensitivities? */ SensitivityMethod sensitivityFlag = SensitivityMethod::none; if (solver->getSensitivityOrder() > SensitivityOrder::none && - solver->getSensitivityMethod() > SensitivityMethod::none) + solver->getSensitivityMethod() == SensitivityMethod::forward) sensitivityFlag = SensitivityMethod::forward; /* If flag for forward sensitivity computation by simulation is not set, disable forward sensitivity integration. Sensitivities will be computed diff --git a/src/sundials_matrix_wrapper.cpp b/src/sundials_matrix_wrapper.cpp index f8413e569b..9fb2fee025 100644 --- a/src/sundials_matrix_wrapper.cpp +++ b/src/sundials_matrix_wrapper.cpp @@ -293,8 +293,8 @@ void SUNMatrixWrapper::multiply(gsl::span c, idx < idx_next_col; ++idx) { auto idx_val = get_indexval(idx); - assert(icols > 0 && icols < c.size()); - assert(idx_val > 0 && static_cast(idx_val) < b.size()); + assert(icols < c.size()); + assert(static_cast(idx_val) < b.size()); c_ptr[icols] += get_data(idx) * b_ptr[idx_val]; } @@ -308,8 +308,8 @@ void SUNMatrixWrapper::multiply(gsl::span c, idx < idx_next_col; ++idx) { auto idx_val = get_indexval(idx); - assert(icols > 0 && icols < b.size()); - assert(idx_val > 0 && static_cast(idx_val) < c.size()); + assert(icols < b.size()); + assert(static_cast(idx_val) < c.size()); c_ptr[idx_val] += get_data(idx) * b_ptr[icols]; } From f869b19fbd92163ba809c1be55870c6e388be1bc Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 13 Oct 2021 11:32:46 +0200 Subject: [PATCH 17/17] Update changelog; bump version number: 0.11.19 --- CHANGELOG.md | 20 ++++++++++++++++++++ version.txt | 2 +- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 90649a6fc0..f2aeeb35b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,26 @@ ## v0.X Series +### v0.11.19 (2021-10-13) + +New: +* Added support for observable transformations (lin/log/log10) (#1567). Thereby supporting additional noise distributions in combination with least squares solvers. + +Fixes: +* Fixed a bug when Newton sensitivity computation was activated despite specifying newton_steps == 0. The error occurs when simulation converges to a steadystate but simulation sensitivities are not converged according to convergence criteria. In that case simulation returned failure, but the newton rootfinding "finds" a steadystate even before the iteration check, leading to the erroneous computation of sensitivities via Newton/IFT. For singular jacobians this means the overall simulation still fails, but a different, more informative error message is displayed. (#1541) +* Fixed a bug where argument "outdir" in ODEExporter.__init__ would not be used (#1543) + +Other: +* Improve checking support for SBML extensions (#1546) +* SBML import: Use more descriptive IDs for flux expressions (#1551) +* Optimized SUNMatrixWrapper functions (#1538) +* C++: Changed test suite from CppUTest to gtest (#1532) +* Add CITATION.cff (#1559) +* Updated documentation (#1563, #1554, #1536) +* Removed distutils dependency (#1557) +* Require sympy<1.9 + + ### v0.11.18 (2021-07-12) New: diff --git a/version.txt b/version.txt index 168a8f63a5..8f78d386f2 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.11.18 +0.11.19