From 8a5795275e4040be25d058cef4a3179fc019a378 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Sun, 5 May 2019 21:09:31 -0400 Subject: [PATCH 01/26] reuse solver, fixes #541 (#630) * cvodes implementation * idas implementation * overhaul solver interface and actually reuse solver memory * improve cvodes and idas solver interfaces * bugfixes * refactor solver interfaces * cleanup * bugfixes * bugfixes * fix doc * fix undefined symbol * refactor solver code * refactor solver, forwardproblem, backwardproblem * make compilable * fix initialization of xB * fix synchronisation of NVectors and reset t0 on setup * fixes for steadystateproblem and post discontinuity reinitialization * refactor passing of AmiVectors from pointers to by-references * add more consts to AmiVector in Model and improve solver interface * fix storage of solver time for adjoint problem * fix DAE evaluation * fix extraction of results from steadystate solver * fix timepoint extraction in forwardproblem * fix solver documentation * loosen tolerances for steadystate and neuron * fix reInitPostProcessB * Add test for replicate data handling with forward and adjoint sensitivities * fix test tolerances * adress review comments * fix cppcheck issues * fix folding * Some refactoring, cleanup, doc; part 1 * Some refactoring, cleanup, doc; done * Re-add required python/examples/example_presimulation/model_presimulation.xml * fix passing of initials to ss problem and extraction of variables from solver * fixes doxygen * clang format heavily refactored code * Fix merge: const * Tidy up * Tidy up * Make backtrace code reusable * Fix checking of wrong parameter scaling vector * Use gsl::span, add make_span, cleanup checkFinite * Change const members to non-const avoid const_casts which potentially result in UB * Cleanup * Refactor(core) Move static Solver_* members (SUNDIALS callbacks) out of class * Cleanup * Show location of used AMICI library * remove calcIC in from IDASolver:reInit * fix doxygen and clang format respective headers * fix reinitialization for DAE problems, update expected results --- .travis.yml | 2 +- CMakeLists.txt | 2 + include/amici/abstract_model.h | 126 +- include/amici/amici.h | 58 +- include/amici/backwardproblem.h | 28 +- include/amici/cblas.h | 16 +- include/amici/defines.h | 8 +- include/amici/edata.h | 22 +- include/amici/exception.h | 353 ++--- include/amici/forwardproblem.h | 24 +- include/amici/misc.h | 53 +- include/amici/model.h | 302 ++-- include/amici/model_dae.h | 699 +++++---- include/amici/model_ode.h | 809 +++++----- include/amici/newton_solver.h | 29 +- include/amici/rdata.h | 73 +- include/amici/serialization.h | 87 +- include/amici/solver.h | 891 +++++++---- include/amici/solver_cvodes.h | 235 ++- include/amici/solver_idas.h | 237 ++- include/amici/steadystateproblem.h | 46 +- include/amici/sundials_linsol_wrapper.h | 4 +- include/amici/sundials_matrix_wrapper.h | 25 +- include/amici/vector.h | 481 +++--- matlab/@amimodel/compileAndLinkModel.m | 5 +- models/model_calvetti/CMakeLists.txt | 1 + python/amici/gradient_check.py | 2 +- src/CMakeLists.template.cmake | 1 + src/abstract_model.cpp | 6 +- src/amici.cpp | 25 +- src/backwardproblem.cpp | 66 +- src/edata.cpp | 18 +- src/exception.cpp | 55 + src/forwardproblem.cpp | 151 +- src/misc.cpp | 126 +- src/model.cpp | 1395 ++++++++--------- src/model_dae.cpp | 488 +++--- src/model_ode.cpp | 594 +++---- src/newton_solver.cpp | 74 +- src/rdata.cpp | 20 +- src/solver.cpp | 814 ++++++---- src/solver_cvodes.cpp | 1099 +++++++------ src/solver_idas.cpp | 1188 ++++++++------ src/steadystateproblem.cpp | 77 +- src/sundials_linsol_wrapper.cpp | 4 +- src/sundials_matrix_wrapper.cpp | 7 +- src/vector.cpp | 149 ++ tests/cpputest/expectedResults.h5 | Bin 4134156 -> 4843500 bytes tests/cpputest/jakstat_adjoint/tests1.cpp | 63 +- tests/cpputest/neuron/tests1.cpp | 6 +- tests/cpputest/steadystate/tests1.cpp | 3 +- tests/cpputest/testfunctions.h | 32 +- tests/cpputest/unittests/tests1.cpp | 10 +- .../cpputest/unittests/testsSerialization.cpp | 6 +- tests/testModels.py | 45 +- 55 files changed, 6174 insertions(+), 4966 deletions(-) create mode 100644 src/exception.cpp create mode 100644 src/vector.cpp diff --git a/.travis.yml b/.travis.yml index 3e82aebbaa..3c59121593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -113,7 +113,7 @@ script: - if [[ "$CI_MODE" == "test" ]]; then $FOLD notebooks "cd $BASE_DIR && scripts/runNotebook.sh python/examples/example_*/"; fi - if [[ "$TRAVIS_OS_NAME" != "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD cpputest ./scripts/run-cpputest.sh; fi - if [[ "$TRAVIS_OS_NAME" != "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD python-tests ./scripts/run-python-tests.sh; fi - - if [[ "$TRAVIS_OS_NAME" != "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD python-tests ./tests/testCMakeCompilation.sh; fi + - if [[ "$TRAVIS_OS_NAME" != "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD cmake ./tests/testCMakeCompilation.sh; fi - if [[ "$CI_MODE" == "deploy" ]]; then $FOLD doxygen ./scripts/run-doxygen.sh; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD cppcheck ./scripts/run-cppcheck.sh; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD valgrind ./scripts/run-valgrind.sh; fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 336615b52e..b6f0d78d7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ set(AMICI_SRC_LIST ${CMAKE_SOURCE_DIR}/src/misc.cpp ${CMAKE_SOURCE_DIR}/src/rdata.cpp ${CMAKE_SOURCE_DIR}/src/edata.cpp + ${CMAKE_SOURCE_DIR}/src/exception.cpp ${CMAKE_SOURCE_DIR}/src/hdf5.cpp ${CMAKE_SOURCE_DIR}/src/spline.cpp ${CMAKE_SOURCE_DIR}/src/solver.cpp @@ -123,6 +124,7 @@ set(AMICI_SRC_LIST ${CMAKE_SOURCE_DIR}/src/sundials_matrix_wrapper.cpp ${CMAKE_SOURCE_DIR}/src/sundials_linsol_wrapper.cpp ${CMAKE_SOURCE_DIR}/src/abstract_model.cpp + ${CMAKE_SOURCE_DIR}/src/vector.cpp ) add_library(${PROJECT_NAME} ${AMICI_SRC_LIST}) diff --git a/include/amici/abstract_model.h b/include/amici/abstract_model.h index 7ed668e2fe..0db1fba0a0 100644 --- a/include/amici/abstract_model.h +++ b/include/amici/abstract_model.h @@ -28,34 +28,34 @@ class AbstractModel { virtual ~AbstractModel() = default; /** - * Retrieves the solver object + * @brief Retrieves the solver object * @return The Solver instance */ virtual std::unique_ptr getSolver() = 0; /** - * Root function + * @brief Root function * @param t time * @param x state * @param dx time derivative of state (DAE only) * @param root array to which values of the root function will be written */ - virtual void froot(realtype t, AmiVector *x, AmiVector *dx, - realtype *root) = 0; + virtual void froot(const realtype t, const AmiVector &x, + const AmiVector &dx, gsl::span root) = 0; /** - * Residual function + * @brief Residual function * @param t time * @param x state * @param dx time derivative of state (DAE only) * @param xdot array to which values of the residual function will be * written */ - virtual void fxdot(realtype t, AmiVector *x, AmiVector *dx, - AmiVector *xdot) = 0; + virtual void fxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, AmiVector &xdot) = 0; /** - * Sensitivity Residual function + * @brief Sensitivity Residual function * @param t time * @param x state * @param dx time derivative of state (DAE only) @@ -65,11 +65,12 @@ class AbstractModel { * @param sxdot array to which values of the sensitivity residual function * will be written */ - virtual void fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, - AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) = 0; + virtual void fsxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, int ip, const AmiVector &sx, + const AmiVector &sdx, AmiVector &sxdot) = 0; /** - * Dense Jacobian function + * @brief Dense Jacobian function * @param t time * @param cj scaling factor (inverse of timestep, DAE only) * @param x state @@ -77,11 +78,12 @@ class AbstractModel { * @param xdot values of residual function (unused) * @param J dense matrix to which values of the jacobian will be written */ - virtual void fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) = 0; + virtual void fJ(const realtype t, realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, + SUNMatrix J) = 0; /** - * Sparse Jacobian function + * @brief Sparse Jacobian function * @param t time * @param cj scaling factor (inverse of timestep, DAE only) * @param x state @@ -89,11 +91,12 @@ class AbstractModel { * @param xdot values of residual function (unused) * @param J sparse matrix to which values of the Jacobian will be written */ - virtual void fJSparse(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) = 0; + virtual void fJSparse(const realtype t, realtype cj, + const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, SUNMatrix J) = 0; /** - * Diagonal Jacobian function + * @brief Diagonal Jacobian function * @param t time * @param Jdiag array to which the diagonal of the Jacobian will be written * @param cj scaling factor (inverse of timestep, DAE only) @@ -101,20 +104,22 @@ class AbstractModel { * @param dx time derivative of state (DAE only) * @return flag indicating successful evaluation */ - virtual void fJDiag(realtype t, AmiVector *Jdiag, realtype cj, AmiVector *x, - AmiVector *dx) = 0; + virtual void fJDiag(const realtype t, AmiVector &Jdiag, + realtype cj, const AmiVector &x, + const AmiVector &dx) = 0; /** - * Parameter derivative of residual function + * @brief Parameter derivative of residual function * @param t time * @param x state * @param dx time derivative of state (DAE only) * @return flag indicating successful evaluation */ - virtual void fdxdotdp(realtype t, AmiVector *x, AmiVector *dx) = 0; + virtual void fdxdotdp(const realtype t, const AmiVector &x, + const AmiVector &dx) = 0; /** - * Jacobian multiply function + * @brief Jacobian multiply function * @param t time * @param x state * @param dx time derivative of state (DAE only) @@ -123,8 +128,9 @@ class AbstractModel { * @param nJv array to which result of multiplication will be written * @param cj scaling factor (inverse of timestep, DAE only) */ - virtual void fJv(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot, - AmiVector *v, AmiVector *nJv, realtype cj) = 0; + virtual void fJv(const realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, const AmiVector &v, AmiVector &nJv, + realtype cj) = 0; /** * @brief Returns the amici version that was used to generate the model @@ -149,7 +155,7 @@ class AbstractModel { const realtype *k); /** - * Function indicating whether reinitialization of states depending on + * @brief Function indicating whether reinitialization of states depending on * fixed parameters is permissible * @return flag inidication whether reinitialization of states depending on * fixed parameters is permissible @@ -177,7 +183,7 @@ class AbstractModel { **/ virtual void fsx0_fixedParameters(realtype *sx0, const realtype t, const realtype *x0, const realtype *p, - const realtype *k, const int ip); + const realtype *k, int ip); /** * @brief Model specific implementation of fsx0 @@ -189,15 +195,15 @@ class AbstractModel { * @param ip sensitivity index **/ virtual void fsx0(realtype *sx0, const realtype t, const realtype *x0, - const realtype *p, const realtype *k, const int ip); + const realtype *p, const realtype *k, int ip); /** - * Initial value for time derivative of states (only necessary for DAEs) + * @brief Initial value for time derivative of states (only necessary for DAEs) * @param x0 Vector with the initial states * @param dx0 Vector to which the initial derivative states will be * written (only DAE) **/ - virtual void fdx0(AmiVector *x0, AmiVector *dx0); + virtual void fdx0(AmiVector &x0, AmiVector &dx0); /** * @brief Model specific implementation of fstau @@ -213,7 +219,7 @@ class AbstractModel { **/ virtual void fstau(realtype *stau, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *sx, const int ip, const int ie); + const realtype *sx, int ip, int ie); /** * @brief Model specific implementation of fy @@ -243,7 +249,7 @@ class AbstractModel { **/ virtual void fdydp(realtype *dydp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const int ip, const realtype *w, const realtype *dwdp); + int ip, const realtype *w, const realtype *dwdp); /** * @brief Model specific implementation of fdydx @@ -270,7 +276,7 @@ class AbstractModel { * @param k constant vector * @param h heavyside vector **/ - virtual void fz(realtype *z, const int ie, const realtype t, + virtual void fz(realtype *z, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h); @@ -286,9 +292,9 @@ class AbstractModel { * @param sx current state sensitivity * @param ip sensitivity index **/ - virtual void fsz(realtype *sz, const int ie, const realtype t, + virtual void fsz(realtype *sz, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const realtype *sx, const int ip); + const realtype *h, const realtype *sx, int ip); /** * @brief Model specific implementation of frz @@ -301,7 +307,7 @@ class AbstractModel { * @param k constant vector * @param h heavyside vector **/ - virtual void frz(realtype *rz, const int ie, const realtype t, + virtual void frz(realtype *rz, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h); @@ -317,9 +323,9 @@ class AbstractModel { * @param h heavyside vector * @param ip sensitivity index **/ - virtual void fsrz(realtype *srz, const int ie, const realtype t, + virtual void fsrz(realtype *srz, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const realtype *sx, const int ip); + const realtype *h, const realtype *sx, int ip); /** * @brief Model specific implementation of fdzdp @@ -333,9 +339,9 @@ class AbstractModel { * @param h heavyside vector * @param ip parameter index w.r.t. which the derivative is requested **/ - virtual void fdzdp(realtype *dzdp, const int ie, const realtype t, + virtual void fdzdp(realtype *dzdp, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const int ip); + const realtype *h, int ip); /** * @brief Model specific implementation of fdzdx @@ -348,7 +354,7 @@ class AbstractModel { * @param k constant vector * @param h heavyside vector **/ - virtual void fdzdx(realtype *dzdx, const int ie, const realtype t, + virtual void fdzdx(realtype *dzdx, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h); @@ -364,9 +370,9 @@ class AbstractModel { * @param h heavyside vector * @param ip parameter index w.r.t. which the derivative is requested **/ - virtual void fdrzdp(realtype *drzdp, const int ie, const realtype t, + virtual void fdrzdp(realtype *drzdp, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const int ip); + const realtype *h, int ip); /** * @brief Model specific implementation of fdrzdx @@ -378,7 +384,7 @@ class AbstractModel { * @param k constant vector * @param h heavyside vector **/ - virtual void fdrzdx(realtype *drzdx, const int ie, const realtype t, + virtual void fdrzdx(realtype *drzdx, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h); @@ -396,7 +402,7 @@ class AbstractModel { **/ virtual void fdeltax(realtype *deltax, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const int ie, const realtype *xdot, + const realtype *h, int ie, const realtype *xdot, const realtype *xdot_old); /** @@ -418,7 +424,7 @@ class AbstractModel { virtual void fdeltasx(realtype *deltasx, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *w, const int ip, const int ie, + const realtype *w, int ip, int ie, const realtype *xdot, const realtype *xdot_old, const realtype *sx, const realtype *stau); @@ -437,7 +443,7 @@ class AbstractModel { **/ virtual void fdeltaxB(realtype *deltaxB, const realtype t, const realtype *x, const realtype *p, - const realtype *k, const realtype *h, const int ie, + const realtype *k, const realtype *h, int ie, const realtype *xdot, const realtype *xdot_old, const realtype *xB); @@ -457,8 +463,8 @@ class AbstractModel { **/ virtual void fdeltaqB(realtype *deltaqB, const realtype t, const realtype *x, const realtype *p, - const realtype *k, const realtype *h, const int ip, - const int ie, const realtype *xdot, + const realtype *k, const realtype *h, int ip, + int ie, const realtype *xdot, const realtype *xdot_old, const realtype *xB); /** @@ -480,7 +486,7 @@ class AbstractModel { * @param ip sensitivity index **/ virtual void fdsigmaydp(realtype *dsigmaydp, const realtype t, - const realtype *p, const realtype *k, const int ip); + const realtype *p, const realtype *k, int ip); /** * @brief Model specific implementation of fsigmaz @@ -502,7 +508,7 @@ class AbstractModel { * @param ip sensitivity index **/ virtual void fdsigmazdp(realtype *dsigmazdp, const realtype t, - const realtype *p, const realtype *k, const int ip); + const realtype *p, const realtype *k, int ip); /** * @brief Model specific implementation of fJy @@ -514,7 +520,7 @@ class AbstractModel { * @param sigmay measurement standard deviation at timepoint * @param my measurements at timepoint **/ - virtual void fJy(realtype *nllh, const int iy, const realtype *p, + virtual void fJy(realtype *nllh, int iy, const realtype *p, const realtype *k, const realtype *y, const realtype *sigmay, const realtype *my); @@ -528,7 +534,7 @@ class AbstractModel { * @param sigmaz event measurement standard deviation at timepoint * @param mz event measurements at timepoint **/ - virtual void fJz(realtype *nllh, const int iz, const realtype *p, + virtual void fJz(realtype *nllh, int iz, const realtype *p, const realtype *k, const realtype *z, const realtype *sigmaz, const realtype *mz); @@ -541,7 +547,7 @@ class AbstractModel { * @param z model event output at timepoint * @param sigmaz event measurement standard deviation at timepoint **/ - virtual void fJrz(realtype *nllh, const int iz, const realtype *p, + virtual void fJrz(realtype *nllh, int iz, const realtype *p, const realtype *k, const realtype *z, const realtype *sigmaz); @@ -556,7 +562,7 @@ class AbstractModel { * @param sigmay measurement standard deviation at timepoint * @param my measurement at timepoint **/ - virtual void fdJydy(realtype *dJydy, const int iy, const realtype *p, + virtual void fdJydy(realtype *dJydy, int iy, const realtype *p, const realtype *k, const realtype *y, const realtype *sigmay, const realtype *my); @@ -571,7 +577,7 @@ class AbstractModel { * @param sigmay measurement standard deviation at timepoint * @param my measurement at timepoint **/ - virtual void fdJydsigma(realtype *dJydsigma, const int iy, + virtual void fdJydsigma(realtype *dJydsigma, int iy, const realtype *p, const realtype *k, const realtype *y, const realtype *sigmay, const realtype *my); @@ -587,7 +593,7 @@ class AbstractModel { * @param sigmaz event measurement standard deviation at timepoint * @param mz event measurement at timepoint **/ - virtual void fdJzdz(realtype *dJzdz, const int iz, const realtype *p, + virtual void fdJzdz(realtype *dJzdz, int iz, const realtype *p, const realtype *k, const realtype *z, const realtype *sigmaz, const realtype *mz); @@ -602,7 +608,7 @@ class AbstractModel { * @param sigmaz event measurement standard deviation at timepoint * @param mz event measurement at timepoint **/ - virtual void fdJzdsigma(realtype *dJzdsigma, const int iz, + virtual void fdJzdsigma(realtype *dJzdsigma, int iz, const realtype *p, const realtype *k, const realtype *z, const realtype *sigmaz, const realtype *mz); @@ -616,7 +622,7 @@ class AbstractModel { * @param rz model root output at timepoint * @param sigmaz event measurement standard deviation at timepoint **/ - virtual void fdJrzdz(realtype *dJrzdz, const int iz, const realtype *p, + virtual void fdJrzdz(realtype *dJrzdz, int iz, const realtype *p, const realtype *k, const realtype *rz, const realtype *sigmaz); @@ -630,7 +636,7 @@ class AbstractModel { * @param rz model root output at timepoint * @param sigmaz event measurement standard deviation at timepoint **/ - virtual void fdJrzdsigma(realtype *dJrzdsigma, const int iz, + virtual void fdJrzdsigma(realtype *dJrzdsigma, int iz, const realtype *p, const realtype *k, const realtype *rz, const realtype *sigmaz); @@ -681,7 +687,7 @@ class AbstractModel { virtual void fdwdp(realtype *dwdp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w, const realtype *tcl, - const realtype *stcl, const int ip); + const realtype *stcl, int ip); /** * @brief Model specific implementation of dwdx, data part diff --git a/include/amici/amici.h b/include/amici/amici.h index 2252608d33..2bddad9252 100644 --- a/include/amici/amici.h +++ b/include/amici/amici.h @@ -1,23 +1,45 @@ #ifndef amici_h #define amici_h -#include "amici/model.h" -#include "amici/solver.h" -#include "amici/exception.h" +#include "amici/cblas.h" #include "amici/defines.h" -#include "amici/rdata.h" #include "amici/edata.h" +#include "amici/exception.h" +#include "amici/model.h" +#include "amici/rdata.h" +#include "amici/solver.h" #include "amici/symbolic_functions.h" -#include "amici/cblas.h" namespace amici { -void printErrMsgIdAndTxt(const char *identifier, const char *format, ...); +/*! + * @brief printErrMsgIdAndTxt prints a specified error message associated to the + * specified identifier + * + * @param[in] identifier error identifier @type char + * @param[in] format string with error message printf-style format + * @param ... arguments to be formatted + * @return void + */ +void +printErrMsgIdAndTxt(const char* identifier, const char* format, ...); -void printWarnMsgIdAndTxt(const char *identifier, const char *format, ...); +/*! + * @brief printErrMsgIdAndTxt prints a specified warning message associated to + * the specified identifier + * + * @param[in] identifier warning identifier @type char + * @param[in] format string with error message printf-style format + * @param ... arguments to be formatted + * @return void + */ +void +printWarnMsgIdAndTxt(const char* identifier, const char* format, ...); -// function pointers to process errors / warnings +/** errMsgIdAndTxt is a function pointer for printErrMsgIdAndTxt */ extern msgIdAndTxtFp errMsgIdAndTxt; + +/** warnMsgIdAndTxt is a function pointer for printWarnMsgIdAndTxt */ extern msgIdAndTxtFp warnMsgIdAndTxt; /*! @@ -30,10 +52,15 @@ extern msgIdAndTxtFp warnMsgIdAndTxt; * @param rethrow rethrow integration exceptions? * @return rdata pointer to return data object */ -std::unique_ptr runAmiciSimulation(Solver &solver, const ExpData *edata, Model &model, bool rethrow=false); +std::unique_ptr +runAmiciSimulation(Solver& solver, + const ExpData* edata, + Model& model, + bool rethrow = false); /*! - * runAmiciSimulations does the same as runAmiciSimulation, but for multiple ExpData instances. + * runAmiciSimulations does the same as runAmiciSimulation, but for multiple + * ExpData instances. * * @param solver Solver instance * @param edatas experimental data objects @@ -42,11 +69,12 @@ std::unique_ptr runAmiciSimulation(Solver &solver, const ExpData *ed * @param num_threads number of threads for parallel execution * @return vector of pointers to return data objects */ -std::vector> runAmiciSimulations(Solver const& solver, - const std::vector &edatas, - Model const& model, - const bool failfast, - int num_threads); +std::vector> +runAmiciSimulations(Solver const& solver, + const std::vector& edatas, + Model const& model, + bool failfast, + int num_threads); } // namespace amici diff --git a/include/amici/backwardproblem.h b/include/amici/backwardproblem.h index 90e93e5739..540655ef0f 100644 --- a/include/amici/backwardproblem.h +++ b/include/amici/backwardproblem.h @@ -62,28 +62,6 @@ class BackwardProblem { return &which; } - /** accessor for pointer to xB - * @return &xB - */ - AmiVector *getxBptr() { - return &xB; - } - - /** - * @brief accessor for pointer to xQB - * @return &xQB - */ - AmiVector *getxQBptr() { - return &xQB; - } - - /** accessor for pointer to dxB - * @return &dxB - */ - AmiVector *getdxBptr() { - return &dxB; - } - /** * @brief Accessor for dJydx * @return dJydx @@ -107,7 +85,7 @@ class BackwardProblem { * * @param it index of data point @type int */ - void handleDataPointB(const int it); + void handleDataPointB(int it); /** @@ -122,7 +100,7 @@ class BackwardProblem { * @param model pointer to model specification object @type Model * @return tnext next timepoint @type realtype */ - realtype getTnext(std::vector const& troot, const int iroot, const int it); + realtype getTnext(std::vector const& troot, int iroot, int it); /** * @brief Compute likelihood sensitivities. @@ -150,7 +128,7 @@ class BackwardProblem { /** array of old differential state vectors at discontinuities*/ const AmiVectorArray xdot_old_disc; /** sensitivity state vector array */ - AmiVectorArray sx; + AmiVectorArray sx0; /** array of number of found roots for a certain event type */ std::vector nroots; /** array containing the time-points of discontinuities*/ diff --git a/include/amici/cblas.h b/include/amici/cblas.h index 5f08a20d19..597bcf84ef 100644 --- a/include/amici/cblas.h +++ b/include/amici/cblas.h @@ -6,17 +6,17 @@ namespace amici { void amici_dgemv(BLASLayout layout, BLASTranspose TransA, - const int M, const int N, const double alpha, const double *A, - const int lda, const double *X, const int incX, - const double beta, double *Y, const int incY); + int M, int N, double alpha, const double *A, + int lda, const double *X, int incX, + double beta, double *Y, int incY); void amici_dgemm(BLASLayout layout, BLASTranspose TransA, - BLASTranspose TransB, const int M, const int N, - const int K, const double alpha, const double *A, - const int lda, const double *B, const int ldb, - const double beta, double *C, const int ldc); + BLASTranspose TransB, int M, int N, + int K, double alpha, const double *A, + int lda, const double *B, int ldb, + double beta, double *C, int ldc); -void amici_daxpy(int n, double alpha, const double *x, const int incx, double *y, int incy); +void amici_daxpy(int n, double alpha, const double *x, int incx, double *y, int incy); } // namespace amici diff --git a/include/amici/defines.h b/include/amici/defines.h index 15fe280acc..7482578645 100644 --- a/include/amici/defines.h +++ b/include/amici/defines.h @@ -1,5 +1,6 @@ #ifndef AMICI_DEFINES_H #define AMICI_DEFINES_H + #include namespace amici { @@ -48,8 +49,9 @@ constexpr double pi = 3.14159265358979323846; #define TRUE 1 #endif -/** defines variable type for simulation variables (determines numerical accuracy) */ -typedef double realtype; +/** defines variable type for simulation variables + * (determines numerical accuracy) */ +using realtype = double; /** BLAS Matrix Layout, affects dgemm and gemv calls */ enum class BLASLayout{ @@ -152,7 +154,7 @@ enum class NewtonStatus { * @param format string with error message printf-style format * @param ... arguments to be formatted */ -typedef void (*msgIdAndTxtFp)(const char *identifier, const char *format, ...); +using msgIdAndTxtFp = void (*)(const char *, const char *, ...); // clang-format on diff --git a/include/amici/edata.h b/include/amici/edata.h index 31db0c88ba..fdc659d8d7 100644 --- a/include/amici/edata.h +++ b/include/amici/edata.h @@ -16,7 +16,7 @@ class ExpData { public: /** @brief default constructor */ - ExpData(); + ExpData() = default; /** @brief Copy constructor, needs to be declared to be generated in * swig*/ @@ -208,7 +208,7 @@ class ExpData { * * @param stdDev standard deviation (dimension: scalar) */ - void setObservedDataStdDev(const realtype stdDev); + void setObservedDataStdDev(realtype stdDev); /** * @brief set function that copies standard deviation of observed data for @@ -228,7 +228,7 @@ class ExpData { * @param stdDev standard deviation (dimension: scalar) * @param iy observed data index */ - void setObservedDataStdDev(const realtype stdDev, int iy); + void setObservedDataStdDev(realtype stdDev, int iy); /** * @brief get function that checks whether standard deviation of data at @@ -316,7 +316,7 @@ class ExpData { * * @param stdDev standard deviation (dimension: scalar) */ - void setObservedEventsStdDev(const realtype stdDev); + void setObservedEventsStdDev(realtype stdDev); /** * @brief set function that copies standard deviation of observed data for @@ -337,7 +337,7 @@ class ExpData { * @param stdDev standard deviation (dimension: scalar) * @param iz observed data index */ - void setObservedEventsStdDev(const realtype stdDev, int iz); + void setObservedEventsStdDev(realtype stdDev, int iz); /** * @brief get function that checks whether standard deviation of even data @@ -396,7 +396,7 @@ class ExpData { std::vector pscale; /** @brief condition-specific parameter list */ std::vector plist; - + /** * @brief duration of pre-simulation * if this is > 0, presimualation will be performed from @@ -442,13 +442,13 @@ class ExpData { const char *fieldname) const; /** @brief number of observables */ - int nytrue_; + int nytrue_{0}; /** @brief number of event observables */ - int nztrue_; + int nztrue_{0}; /** @brief maximal number of event occurences */ - int nmaxevent_; + int nmaxevent_{0}; /** @brief observation timepoints (dimension: nt) */ std::vector ts; @@ -482,7 +482,7 @@ void checkSigmaPositivity(std::vector const &sigmaVector, * @param sigma input to be checked * @param sigmaName name of the input */ -void checkSigmaPositivity(const realtype sigma, const char *sigmaName); +void checkSigmaPositivity(realtype sigma, const char *sigmaName); /** * @brief The ConditionContext class applies condition-specific amici::Model @@ -526,7 +526,7 @@ class ConditionContext { std::vector originalTimepoints; std::vector originalParameterList; std::vector originalScaling; - + }; } // namespace amici diff --git a/include/amici/exception.h b/include/amici/exception.h index 648ebd8b8c..5417304692 100644 --- a/include/amici/exception.h +++ b/include/amici/exception.h @@ -4,217 +4,166 @@ #include "amici/defines.h" // necessary for realtype #include -#include -#include -#include -#include -#if defined(_WIN32) - #define PLATFORM_WINDOWS // Windows -#elif defined(_WIN64) - #define PLATFORM_WINDOWS // Windows -#elif defined(__CYGWIN__) && !defined(_WIN32) - #define PLATFORM_WINDOWS // Windows (Cygwin POSIX under Microsoft Window) -#else - #include - #include // for dladdr - #include // for __cxa_demangle -#endif namespace amici { - /** @brief amici exception handler class - * - * has a printf style interface to allow easy generation of error messages +/** + * @brief AMICI exception class + * + * Has a printf style interface to allow easy generation of error messages + */ +class AmiException : public std::exception { +public: + /** + * @brief Constructor with printf style interface + * @param fmt error message with printf format + * @param ... printf formating variables */ - class AmiException : public std::exception { - public: - AmiException(char const* fmt, ...) : std::exception() { - /** constructor with printf style interface - * @param fmt error message with printf format - * @param ... printf formating variables - */ - va_list ap; - va_start(ap, fmt); - vsnprintf(msg, sizeof(msg), fmt, ap); - va_end(ap); - storeBacktrace(12); - } - - AmiException(const AmiException& old) { - /** copy constructor - * @param old object to copy from - */ - snprintf(msg, sizeof(msg), "%s", old.msg); - snprintf(trace, sizeof(trace), "%s", old.trace); - } - - /** override of default error message function - * @return msg error message - */ - const char* what() const throw() { - return msg; - } - - /** returns the stored backtrace - * @return trace backtrace - */ - const char *getBacktrace() const { - return trace; - } - - /** stores the current backtrace - * @param nMaxFrames number of frams to go back in stacktrace - */ - void storeBacktrace(const int nMaxFrames) { - std::ostringstream trace_buf; - - #ifdef PLATFORM_WINDOWS - - trace_buf << "stacktrace not available on windows platforms\n"; - - #else - - void *callstack[nMaxFrames]; - char buf[1024]; - int nFrames = backtrace(callstack, nMaxFrames); - char **symbols = backtrace_symbols(callstack, nFrames); - - for (int i = 2; i < nFrames; - i++) { // start at 2 to omit AmiException and storeBacktrace - // call - Dl_info info; - if (dladdr(callstack[i], &info) && info.dli_sname) { - char *demangled = NULL; - int status = -1; - if (info.dli_sname[0] == '_') - demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, - &status); - snprintf(buf, sizeof(buf), "%-3d %*p %s + %zd\n", i - 2, - int(2 + sizeof(void *) * 2), callstack[i], - status == 0 ? demangled - : info.dli_sname == 0 ? symbols[i] - : info.dli_sname, - static_cast((char *)callstack[i] - - (char *)info.dli_saddr)); - free(demangled); - } else { - snprintf(buf, sizeof(buf), "%-3d %*p %s\n", i - 2, - int(2 + sizeof(void *) * 2), callstack[i], - symbols[i]); - } - trace_buf << buf; - } - free(symbols); - if (nFrames == nMaxFrames) - trace_buf << "[truncated]\n"; - - #endif - - snprintf(trace, sizeof(trace), "%s", trace_buf.str().c_str()); - } - - private: - char msg[500]; - char trace[500]; - }; - - /** @brief cvode exception handler class + AmiException(char const* fmt, ...); + + /** + * @brief Copy constructor + * @param old object to copy from + */ + AmiException(const AmiException& old); + + /** + * @brief Override of default error message function + * @return msg error message + */ + const char* what() const noexcept override; + + /** + * @brief Returns the stored backtrace + * @return trace backtrace */ - class CvodeException : public AmiException { - public: - /** constructor - * @param error_code error code returned by cvode function - * @param function cvode function name - */ - CvodeException(const int error_code, const char *function) : - AmiException("Cvode routine %s failed with error code %i",function,error_code){} - }; - - /** @brief ida exception handler class + const char *getBacktrace() const; + + /** + * @brief Stores the current backtrace + * @param nMaxFrames number of frames to go back in stacktrace + */ + void storeBacktrace(int nMaxFrames); + +private: + char msg[500]{}; + char trace[500]{}; +}; + + +/** + * @brief cvode exception handler class + */ +class CvodeException : public AmiException { +public: + /** + * @brief Constructor + * @param error_code error code returned by cvode function + * @param function cvode function name + */ + CvodeException(int error_code, const char *function); +}; + + +/** + * @brief ida exception handler class + */ +class IDAException : public AmiException { +public: + /** + * @brief Constructor + * @param error_code error code returned by ida function + * @param function ida function name */ - class IDAException : public AmiException { - public: - /** constructor - * @param error_code error code returned by ida function - * @param function ida function name - */ - IDAException(const int error_code, const char *function) : - AmiException("IDA routine %s failed with error code %i",function,error_code){} - }; - - /** @brief integration failure exception for the forward problem - * this exception should be thrown when an integration failure occured - * for this exception we can assume that we can recover from the exception - * and return a solution struct to the user + IDAException(int error_code, const char *function); +}; + + +/** + * @brief Integration failure exception for the forward problem + * + * This exception should be thrown when an integration failure occured + * for this exception we can assume that we can recover from the exception + * and return a solution struct to the user + */ +class IntegrationFailure : public AmiException { + public: + /** + * @brief Constructor + * @param code error code returned by cvode/ida + * @param t time of integration failure */ - class IntegrationFailure : public AmiException { - public: - /** error code returned by cvode/ida */ - int error_code; - /** time of integration failure */ - realtype time; - /** constructor - * @param code error code returned by cvode/ida - * @param t time of integration failure - */ - IntegrationFailure(int code, realtype t) : - AmiException("AMICI failed to integrate the forward problem"), - error_code(code), time(t) {} - }; - - /** @brief integration failure exception for the backward problem - * this exception should be thrown when an integration failure occured - * for this exception we can assume that we can recover from the exception - * and return a solution struct to the user + IntegrationFailure(int code, realtype t); + + /** error code returned by cvodes/idas */ + int error_code; + + /** time of integration failure */ + realtype time; +}; + + +/** + * @brief Integration failure exception for the backward problem + * + * This exception should be thrown when an integration failure occured + * for this exception we can assume that we can recover from the exception + * and return a solution struct to the user + */ +class IntegrationFailureB : public AmiException { + public: + /** + * @brief Constructor + * @param code error code returned by cvode/ida + * @param t time of integration failure */ - class IntegrationFailureB : public AmiException { - public: - /** error code returned by cvode/ida */ - int error_code; - /** time of integration failure */ - realtype time; - /** constructor - * @param code error code returned by cvode/ida - * @param t time of integration failure - */ - IntegrationFailureB(int code, realtype t) : - AmiException("AMICI failed to integrate the backward problem"), - error_code(code), time(t) {} - }; - - /** @brief setup failure exception - * this exception should be thrown when the solver setup failed - * for this exception we can assume that we cannot recover from the exception - * and an error will be thrown + IntegrationFailureB(int code, realtype t); + + /** error code returned by cvode/ida */ + int error_code; + + /** time of integration failure */ + realtype time; +}; + + +/** + * @brief Setup failure exception + * + * This exception should be thrown when the solver setup failed + * for this exception we can assume that we cannot recover from the exception + * and an error will be thrown + */ +class SetupFailure : public AmiException { +public: + /** + * @brief Constructor, simply calls AmiException constructor + * @param msg */ - class SetupFailure : public AmiException { - public: - /** constructor, simply calls AmiException constructor - * @param msg - */ - explicit SetupFailure(const char *msg) : AmiException(msg) {} - }; - - /** @brief newton failure exception - * this exception should be thrown when the steady state computation - * failed to converge for this exception we can assume that we can - * recover from the exception and return a solution struct to the user + explicit SetupFailure(const char *msg) : AmiException(msg) {} +}; + + +/** + * @brief Newton failure exception + * + * This exception should be thrown when the steady state computation + * failed to converge for this exception we can assume that we can + * recover from the exception and return a solution struct to the user + */ +class NewtonFailure : public AmiException { +public: + /** + * @brief Constructor, simply calls AmiException constructor + * @param function name of the function in which the error occured + * @param code error code */ - class NewtonFailure : public AmiException { - public: - /** error code returned by solver */ - int error_code; - /** constructor, simply calls AmiException constructor - * @param function name of the function in which the error occured - * @param code error code - */ - NewtonFailure(int code, const char *function) : - AmiException("NewtonSolver routine %s failed with error code %i",function,code) { - error_code = code; - } - }; - - -} + NewtonFailure(int code, const char *function); + + /** error code returned by solver */ + int error_code; +}; + +} // namespace amici #endif /* amici_exception_h */ diff --git a/include/amici/forwardproblem.h b/include/amici/forwardproblem.h index 05687b1adf..612a7a6448 100644 --- a/include/amici/forwardproblem.h +++ b/include/amici/forwardproblem.h @@ -163,10 +163,13 @@ class ForwardProblem { /** pointer to model instance */ Model *model; + /** pointer to return data instance */ ReturnData *rdata; + /** pointer to solver instance */ Solver *solver; + /** pointer to experimental data instance */ const ExpData *edata; @@ -178,7 +181,7 @@ class ForwardProblem { void updateAndReinitStatesAndSensitivities(bool isSteadystate); - void handlePresimulation(int *ncheck); + void handlePresimulation(); /** * @brief Execute everything necessary for the handling of events @@ -186,7 +189,7 @@ class ForwardProblem { * @param tlastroot pointer to the timepoint of the last event */ - void handleEvent(realtype *tlastroot,const bool seflag); + void handleEvent(realtype *tlastroot,bool seflag); /** * @brief Evaluates the Jacobian and differential equation right hand side, @@ -258,11 +261,14 @@ class ForwardProblem { /** array of index which root has been found * (dimension: ne * ne * nmaxevent, ordering = ?) */ std::vector rootidx; + /** array of number of found roots for a certain event type * (dimension: ne) */ std::vector nroots; + /** array of values of the root function (dimension: ne) */ std::vector rootvals; + /** temporary rootval storage to check crossing in secondary event * (dimension: ne) */ std::vector rvaltmp; @@ -270,6 +276,7 @@ class ForwardProblem { /** array containing the time-points of discontinuities * (dimension: nmaxevent x ne, ordering = ?) */ std::vector discs; + /** array containing the index of discontinuities * (dimension: nmaxevent x ne, ordering = ?) */ std::vector irdiscs; @@ -281,9 +288,11 @@ class ForwardProblem { /** array of state vectors at discontinuities * (dimension nx x nMaxEvent * ne, ordering =?) */ AmiVectorArray x_disc; + /** array of differential state vectors at discontinuities * (dimension nx x nMaxEvent * ne, ordering =?) */ AmiVectorArray xdot_disc; + /** array of old differential state vectors at discontinuities * (dimension nx x nMaxEvent * ne, ordering =?) */ AmiVectorArray xdot_old_disc; @@ -291,6 +300,7 @@ class ForwardProblem { /** state derivative of data likelihood * (dimension nJ x nx x nt, ordering =?) */ std::vector dJydx; + /** state derivative of event likelihood * (dimension nJ x nx x nMaxEvent, ordering =?) */ std::vector dJzdx; @@ -312,27 +322,33 @@ class ForwardProblem { /** state vector (dimension: nx_solver) */ AmiVector x; + /** state vector, including states eliminated from conservation laws * (dimension: nx) */ AmiVector x_rdata; + /** old state vector (dimension: nx_solver) */ AmiVector x_old; + /** differential state vector (dimension: nx_solver) */ AmiVector dx; + /** old differential state vector (dimension: nx_solver) */ AmiVector dx_old; + /** time derivative state vector (dimension: nx_solver) */ AmiVector xdot; + /** old time derivative state vector (dimension: nx_solver) */ AmiVector xdot_old; - - /** sensitivity state vector array (dimension: nx_cl x nplist, row-major) */ AmiVectorArray sx; + /** full sensitivity state vector array, including states eliminated from * conservation laws (dimension: nx x nplist, row-major) */ AmiVectorArray sx_rdata; + /** differential sensitivity state vector array * (dimension: nx_cl x nplist, row-major) */ AmiVectorArray sdx; diff --git a/include/amici/misc.h b/include/amici/misc.h index 629703aad5..dc2aca7a3d 100644 --- a/include/amici/misc.h +++ b/include/amici/misc.h @@ -8,53 +8,34 @@ #include #include -namespace amici { +#include -/** Checks the values in an array for NaNs and Infs - * - * @param array array - * @param fun name of calling function - * @return AMICI_RECOVERABLE_ERROR if a NaN/Inf value was found, AMICI_SUCCESS otherwise - */ -int checkFinite(std::vector const& array, const char* fun); +namespace amici { -/** Checks the values in an array for NaNs and Infs +/** + * @brief Checks the values in an array for NaNs and Infs * - * @param N number of elements in array * @param array array * @param fun name of calling function * @return AMICI_RECOVERABLE_ERROR if a NaN/Inf value was found, AMICI_SUCCESS otherwise */ -int checkFinite(const int N, const realtype *array, const char* fun); +int checkFinite(gsl::span array, const char* fun); /** * @brief Remove parameter scaling according to the parameter scaling in pscale * + * All vectors must be of same length. + * * @param bufferScaled scaled parameters * @param pscale parameter scaling - * @param n number of elements in bufferScaled, pscale and bufferUnscaled * @param bufferUnscaled unscaled parameters are written to the array * * @return status flag indicating success of execution @type int */ -void unscaleParameters(const double *bufferScaled, - const ParameterScaling *pscale, - int n, - double *bufferUnscaled); - -/** - * @brief Remove parameter scaling according to the parameter scaling in pscale. - * - * All vectors must be of same length - * - * @param bufferScaled scaled parameters - * @param pscale parameter scaling - * @param bufferUnscaled unscaled parameters are written to the array - */ -void unscaleParameters(std::vector const& bufferScaled, - std::vector const& pscale, - std::vector & bufferUnscaled); +void unscaleParameters(gsl::span bufferScaled, + gsl::span pscale, + gsl::span bufferUnscaled); /** * @brief Remove parameter scaling according to `scaling` @@ -82,9 +63,17 @@ double getScaledParameter(double unscaledParameter, ParameterScaling scaling); * @param pscale parameter scaling * @param bufferScaled destination */ -void scaleParameters(const std::vector &bufferUnscaled, - const std::vector &pscale, - std::vector &bufferScaled); +void scaleParameters(gsl::span bufferUnscaled, + gsl::span pscale, + gsl::span bufferScaled); + +/** + * @brief Returns the current backtrace as std::string + * @param maxFrames Number of frames to include + * @return Backtrace + */ +std::string backtraceString(int maxFrames); + } // namespace amici diff --git a/include/amici/model.h b/include/amici/model.h index 9e2169761c..3b954dae10 100644 --- a/include/amici/model.h +++ b/include/amici/model.h @@ -22,7 +22,7 @@ class Solver; namespace boost { namespace serialization { template -void serialize(Archive &ar, amici::Model &u, const unsigned int version); +void serialize(Archive &ar, amici::Model &u, unsigned int version); } } // namespace boost @@ -39,7 +39,7 @@ class Model : public AbstractModel { Model(); /** - * Constructor with model dimensions + * @brief Constructor with model dimensions * @param nx_rdata number of state variables * @param nxtrue_rdata number of state variables of the non-augmented model * @param nx_solver number of state variables with conservation laws applied @@ -69,18 +69,18 @@ class Model : public AbstractModel { * @param idlist indexes indicating algebraic components (DAE only) * @param z2event mapping of event outputs to events */ - Model(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, - const int nxtrue_solver, const int ny, const int nytrue, const int nz, - const int nztrue, const int ne, const int nJ, const int nw, - const int ndwdx, const int ndwdp, const int ndxdotdw, - std::vector ndJydy, const int nnz, - const int ubw, const int lbw, amici::SecondOrderMode o2mode, + Model(int nx_rdata, int nxtrue_rdata, int nx_solver, + int nxtrue_solver, int ny, int nytrue, int nz, + int nztrue, int ne, int nJ, int nw, + int ndwdx, int ndwdp, int ndxdotdw, + std::vector ndJydy, int nnz, + int ubw, int lbw, amici::SecondOrderMode o2mode, const std::vector &p, std::vector k, const std::vector &plist, std::vector idlist, std::vector z2event); /** destructor */ - virtual ~Model() = default; + ~Model() override = default; /** * Copy assignment is disabled until const members are removed @@ -135,29 +135,6 @@ class Model : public AbstractModel { using AbstractModel::fy; using AbstractModel::fz; - /** - * Model specific implementation of fdJydy colptrs - * @param indexptrs column pointers - * @param index ytrue index - */ - virtual void fdJydy_colptrs(sunindextype *indexptrs, int index) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** - * Model specific implementation of fdxdotdw row vals - * @param indexptrs row val pointers - * @param index ytrue index - */ - virtual void fdJydy_rowvals(sunindextype *indexptrs, int index) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** * Expands conservation law for states * @param x_rdata pointer to state variables with conservation laws @@ -165,7 +142,7 @@ class Model : public AbstractModel { * @param x_solver pointer to state variables with conservation laws * applied (solver returns this) */ - void fx_rdata(AmiVector *x_rdata, const AmiVector *x_solver); + void fx_rdata(AmiVector &x_rdata, const AmiVector &x_solver); /** * Expands conservation law for state sensitivities @@ -174,26 +151,26 @@ class Model : public AbstractModel { * @param sx_solver pointer to state variable sensitivities with * conservation laws applied (solver returns this) */ - void fsx_rdata(AmiVectorArray *sx_rdata, const AmiVectorArray *sx_solver); + void fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx_solver); /** * Initial states * @param x pointer to state variables */ - void fx0(AmiVector *x); + void fx0(AmiVector &x); /** * Sets only those initial states that are specified via fixedParmeters * @param x pointer to state variables */ - void fx0_fixedParameters(AmiVector *x); + void fx0_fixedParameters(AmiVector &x); /** * Initial value for initial state sensitivities * @param sx pointer to state sensitivity variables * @param x pointer to state variables **/ - void fsx0(AmiVectorArray *sx, const AmiVector *x); + void fsx0(AmiVectorArray &sx, const AmiVector &x); /** * Sets only those initial states sensitivities that are affected from fx0 @@ -201,7 +178,7 @@ class Model : public AbstractModel { * @param sx pointer to state sensitivity variables * @param x pointer to state variables **/ - void fsx0_fixedParameters(AmiVectorArray *sx, const AmiVector *x); + void fsx0_fixedParameters(AmiVectorArray &sx, const AmiVector &x); /** * Sensitivity of derivative initial states sensitivities sdx0 (only @@ -216,8 +193,8 @@ class Model : public AbstractModel { * @param x pointer to state variables * @param sx pointer to state sensitivity variables */ - void fstau(const realtype t, const int ie, const AmiVector *x, - const AmiVectorArray *sx); + void fstau(realtype t, int ie, const AmiVector &x, + const AmiVectorArray &sx); /** * Observables / measurements @@ -226,7 +203,7 @@ class Model : public AbstractModel { * @param x current state * @param rdata pointer to return data instance */ - void fy(const realtype t, const int it, const AmiVector *x, + void fy(realtype t, int it, const AmiVector &x, ReturnData *rdata); /** @@ -234,14 +211,14 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdydp(const realtype t, const AmiVector *x); + void fdydp(realtype t, const AmiVector &x); /** * Partial derivative of observables y w.r.t. state variables x * @param t current timepoint * @param x current state */ - void fdydx(const realtype t, const AmiVector *x); + void fdydx(realtype t, const AmiVector &x); /** Event-resolved output * @param nroots number of events for event index @@ -250,8 +227,8 @@ class Model : public AbstractModel { * @param x current state * @param rdata pointer to return data instance */ - void fz(const int nroots, const int ie, const realtype t, - const AmiVector *x, ReturnData *rdata); + void fz(int nroots, int ie, realtype t, + const AmiVector &x, ReturnData *rdata); /** Sensitivity of z, total derivative * @param nroots number of events for event index @@ -261,8 +238,8 @@ class Model : public AbstractModel { * @param sx current state sensitivities * @param rdata pointer to return data instance */ - void fsz(const int nroots, const int ie, const realtype t, - const AmiVector *x, const AmiVectorArray *sx, ReturnData *rdata); + void fsz(int nroots, int ie, realtype t, + const AmiVector &x, const AmiVectorArray &sx, ReturnData *rdata); /** * Event root function of events (equal to froot but does not include @@ -273,8 +250,8 @@ class Model : public AbstractModel { * @param x current state * @param rdata pointer to return data instance */ - void frz(const int nroots, const int ie, const realtype t, - const AmiVector *x, ReturnData *rdata); + void frz(int nroots, int ie, realtype t, + const AmiVector &x, ReturnData *rdata); /** * Sensitivity of rz, total derivative @@ -285,8 +262,8 @@ class Model : public AbstractModel { * @param sx current state sensitivities * @param rdata pointer to return data instance */ - void fsrz(const int nroots, const int ie, const realtype t, - const AmiVector *x, const AmiVectorArray *sx, ReturnData *rdata); + void fsrz(int nroots, int ie, realtype t, + const AmiVector &x, const AmiVectorArray &sx, ReturnData *rdata); /** * Partial derivative of event-resolved output z w.r.t. to model parameters @@ -295,7 +272,7 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdzdp(const realtype t, const int ie, const AmiVector *x); + void fdzdp(realtype t, int ie, const AmiVector &x); /** * Partial derivative of event-resolved output z w.r.t. to model states x @@ -303,7 +280,7 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdzdx(const realtype t, const int ie, const AmiVector *x); + void fdzdx(realtype t, int ie, const AmiVector &x); /** * Sensitivity of event-resolved root output w.r.t. to model parameters p @@ -311,7 +288,7 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdrzdp(const realtype t, const int ie, const AmiVector *x); + void fdrzdp(realtype t, int ie, const AmiVector &x); /** * Sensitivity of event-resolved measurements rz w.r.t. to model states x @@ -319,7 +296,7 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdrzdx(const realtype t, const int ie, const AmiVector *x); + void fdrzdx(realtype t, int ie, const AmiVector &x); /** * State update functions for events @@ -329,8 +306,8 @@ class Model : public AbstractModel { * @param xdot current residual function values * @param xdot_old value of residual function before event */ - void fdeltax(const int ie, const realtype t, const AmiVector *x, - const AmiVector *xdot, const AmiVector *xdot_old); + void fdeltax(int ie, realtype t, const AmiVector &x, + const AmiVector &xdot, const AmiVector &xdot_old); /** * Sensitivity update functions for events, total derivative @@ -341,9 +318,9 @@ class Model : public AbstractModel { * @param xdot current residual function values * @param xdot_old value of residual function before event */ - void fdeltasx(const int ie, const realtype t, const AmiVector *x, - const AmiVectorArray *sx, const AmiVector *xdot, - const AmiVector *xdot_old); + void fdeltasx(int ie, realtype t, const AmiVector &x, + const AmiVectorArray &sx, const AmiVector &xdot, + const AmiVector &xdot_old); /** * Adjoint state update functions for events @@ -354,9 +331,9 @@ class Model : public AbstractModel { * @param xdot current residual function values * @param xdot_old value of residual function before event */ - void fdeltaxB(const int ie, const realtype t, const AmiVector *x, - const AmiVector *xB, const AmiVector *xdot, - const AmiVector *xdot_old); + void fdeltaxB(int ie, realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, + const AmiVector &xdot_old); /** * Quadrature state update functions for events @@ -367,9 +344,9 @@ class Model : public AbstractModel { * @param xdot current residual function values * @param xdot_old value of residual function before event */ - void fdeltaqB(const int ie, const realtype t, const AmiVector *x, - const AmiVector *xB, const AmiVector *xdot, - const AmiVector *xdot_old); + void fdeltaqB(int ie, realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, + const AmiVector &xdot_old); /** * Standard deviation of measurements @@ -377,7 +354,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fsigmay(const int it, ReturnData *rdata, const ExpData *edata); + void fsigmay(int it, ReturnData *rdata, const ExpData *edata); /** * Partial derivative of standard deviation of measurements w.r.t. model @@ -385,7 +362,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to ExpData data instance holding sigma values */ - void fdsigmaydp(const int it, ReturnData *rdata, const ExpData *edata); + void fdsigmaydp(int it, ReturnData *rdata, const ExpData *edata); /** * Standard deviation of events @@ -395,7 +372,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fsigmaz(const realtype t, const int ie, const int *nroots, + void fsigmaz(realtype t, int ie, const int *nroots, ReturnData *rdata, const ExpData *edata); /** @@ -407,7 +384,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdsigmazdp(const realtype t, const int ie, const int *nroots, + void fdsigmazdp(realtype t, int ie, const int *nroots, ReturnData *rdata, const ExpData *edata); /** @@ -416,7 +393,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fJy(const int it, ReturnData *rdata, const ExpData *edata); + void fJy(int it, ReturnData *rdata, const ExpData *edata); /** * Negative log-likelihood of event-resolved measurements z @@ -424,7 +401,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fJz(const int nroots, ReturnData *rdata, const ExpData *edata); + void fJz(int nroots, ReturnData *rdata, const ExpData *edata); /** * Regularization of negative log-likelihood with roots of event-resolved @@ -433,7 +410,21 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fJrz(const int nroots, ReturnData *rdata, const ExpData *edata); + void fJrz(int nroots, ReturnData *rdata, const ExpData *edata); + + /** + * Model specific implementation of fdJydy colptrs + * @param indexptrs column pointers + * @param index ytrue index + */ + virtual void fdJydy_colptrs(sunindextype *indexptrs, int index); + + /** + * Model specific implementation of fdxdotdw row vals + * @param indexptrs row val pointers + * @param index ytrue index + */ + virtual void fdJydy_rowvals(sunindextype *indexptrs, int index); /** * Partial derivative of time-resolved measurement negative log-likelihood @@ -442,7 +433,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJydy(const int it, const ReturnData *rdata, const ExpData *edata); + void fdJydy(int it, const ReturnData *rdata, const ExpData *edata); /** * Sensitivity of time-resolved measurement negative log-likelihood Jy @@ -451,7 +442,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJydsigma(const int it, const ReturnData *rdata, + void fdJydsigma(int it, const ReturnData *rdata, const ExpData *edata); /** @@ -460,7 +451,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJzdz(const int nroots, const ReturnData *rdata, + void fdJzdz(int nroots, const ReturnData *rdata, const ExpData *edata); /** @@ -470,7 +461,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJzdsigma(const int nroots, const ReturnData *rdata, + void fdJzdsigma(int nroots, const ReturnData *rdata, const ExpData *edata); /** @@ -479,7 +470,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJrzdz(const int nroots, const ReturnData *rdata, + void fdJrzdz(int nroots, const ReturnData *rdata, const ExpData *edata); /** @@ -489,7 +480,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJrzdsigma(const int nroots, const ReturnData *rdata, + void fdJrzdsigma(int nroots, const ReturnData *rdata, const ExpData *edata); /** @@ -498,7 +489,7 @@ class Model : public AbstractModel { * @param sx pointer to state sensitivities * @param rdata pointer to return data instance */ - void fsy(const int it, const AmiVectorArray *sx, ReturnData *rdata); + void fsy(int it, const AmiVectorArray &sx, ReturnData *rdata); /** * Sensitivity of z at final timepoint (ignores sensitivity of timepoint), @@ -507,7 +498,7 @@ class Model : public AbstractModel { * @param ie event index * @param rdata pointer to return data instance */ - void fsz_tf(const int *nroots, const int ie, ReturnData *rdata); + void fsz_tf(const int *nroots, int ie, ReturnData *rdata); /** * Sensitivity of time-resolved measurement negative log-likelihood Jy, @@ -517,8 +508,8 @@ class Model : public AbstractModel { * @param dJydx vector with values of state derivative of Jy * @param rdata pointer to return data instance */ - void fsJy(const int it, const std::vector &dJydx, - const AmiVectorArray *sx, ReturnData *rdata); + void fsJy(int it, const std::vector &dJydx, + const AmiVectorArray &sx, ReturnData *rdata); /** * Compute sensitivity of time-resolved measurement negative log-likelihood @@ -528,7 +519,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fdJydp(const int it, ReturnData *rdata, const ExpData *edata); + void fdJydp(int it, ReturnData *rdata, const ExpData *edata); /** * Sensitivity of time-resolved measurement negative log-likelihood Jy @@ -537,7 +528,7 @@ class Model : public AbstractModel { * @param it timepoint index * @param edata pointer to experimental data instance */ - void fdJydx(std::vector &dJydx, const int it, + void fdJydx(std::vector &dJydx, int it, const ExpData *edata); /** @@ -548,8 +539,8 @@ class Model : public AbstractModel { * @param sx pointer to state sensitivities * @param rdata pointer to return data instance */ - void fsJz(const int nroots, const std::vector &dJzdx, - const AmiVectorArray *sx, ReturnData *rdata); + void fsJz(int nroots, const std::vector &dJzdx, + const AmiVectorArray &sx, ReturnData *rdata); /** * Sensitivity of event-resolved measurement negative log-likelihood Jz @@ -559,7 +550,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fdJzdp(const int nroots, realtype t, const ExpData *edata, + void fdJzdp(int nroots, realtype t, const ExpData *edata, const ReturnData *rdata); /** @@ -571,7 +562,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fdJzdx(std::vector *dJzdx, const int nroots, realtype t, + void fdJzdx(std::vector *dJzdx, int nroots, realtype t, const ExpData *edata, const ReturnData *rdata); /** @@ -584,21 +575,29 @@ class Model : public AbstractModel { * @param computeSensitivities flag indicating whether sensitivities * are to be computed */ - void initialize(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, - AmiVectorArray *sdx, bool computeSensitivities); + void initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx, + AmiVectorArray &sdx, bool computeSensitivities); + + /** + * Initialization of model properties + * @param xB adjoint state variables + * @param dxB time derivative of adjoint states (DAE only) + * @param xQB adjoint quadratures + */ + void initializeB(AmiVector &xB, AmiVector &dxB, AmiVector &xQB); /** * Initialization of initial states * @param x pointer to state variables */ - void initializeStates(AmiVector *x); + void initializeStates(AmiVector &x); /** * Initialization of initial state sensitivities * @param sx pointer to state variable sensititivies * @param x pointer to state variables */ - void initializeStateSensitivities(AmiVectorArray *sx, AmiVector *x); + void initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x); /** * Initialises the heaviside variables h at the intial time t0 @@ -606,7 +605,7 @@ class Model : public AbstractModel { * @param x pointer to state variables * @param dx pointer to time derivative of states (DAE only) */ - void initHeaviside(AmiVector *x, AmiVector *dx); + void initHeaviside(AmiVector &x, AmiVector &dx); /** * @brief Number of parameters wrt to which sensitivities are computed @@ -871,7 +870,7 @@ class Model : public AbstractModel { * @param t timepoint * @param x array with the states */ - void fw(const realtype t, const realtype *x); + void fw(realtype t, const realtype *x); /** * @brief Recurring terms in xdot, parameter derivative @@ -880,14 +879,14 @@ class Model : public AbstractModel { * @return flag indicating whether dwdp will be returned in dense storage * dense: true, sparse: false */ - void fdwdp(const realtype t, const realtype *x); + void fdwdp(realtype t, const realtype *x); /** * @brief Recurring terms in xdot, state derivative * @param t timepoint * @param x array with the states */ - void fdwdx(const realtype t, const realtype *x); + void fdwdx(realtype t, const realtype *x); /** * Residual function @@ -895,14 +894,14 @@ class Model : public AbstractModel { * @param rdata ReturnData instance to which result will be written * @param edata ExpData instance containing observable data */ - void fres(const int it, ReturnData *rdata, const ExpData *edata); + void fres(int it, ReturnData *rdata, const ExpData *edata); /** * Chi-squared function * @param it time index * @param rdata ReturnData instance to which result will be written */ - void fchi2(const int it, ReturnData *rdata); + void fchi2(int it, ReturnData *rdata); /** * Residual sensitivity function @@ -910,14 +909,14 @@ class Model : public AbstractModel { * @param rdata ReturnData instance to which result will be written * @param edata ExpData instance containing observable data */ - void fsres(const int it, ReturnData *rdata, const ExpData *edata); + void fsres(int it, ReturnData *rdata, const ExpData *edata); /** * Fisher information matrix function * @param it time index * @param rdata ReturnData instance to which result will be written */ - void fFIM(const int it, ReturnData *rdata); + void fFIM(int it, ReturnData *rdata); /** * Update the heaviside variables h on event occurences @@ -940,12 +939,12 @@ class Model : public AbstractModel { /** * @brief Serialize Model (see boost::serialization::serialize) * @param ar Archive to serialize to - * @param r Data to serialize + * @param u Data to serialize * @param version Version number */ template - friend void boost::serialization::serialize(Archive &ar, Model &r, - const unsigned int version); + friend void boost::serialization::serialize(Archive &ar, Model &u, + unsigned int version); /** * @brief Check equality of data members @@ -961,18 +960,19 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @return current timepoint */ - realtype gett(const int it, const ReturnData *rdata) const; + realtype gett(int it, const ReturnData *rdata) const; /** * @brief Check if the given array has only finite elements. + * * If not try to give hints by which other fields this could be caused. - * @param N number of datapoints in array + * * @param array arrays of values * @param fun name of the fucntion that generated the values * @return AMICI_RECOVERABLE_ERROR if a NaN/Inf value was found, * AMICI_SUCCESS otherwise */ - int checkFinite(const int N, const realtype *array, const char *fun) const; + int checkFinite(gsl::span array, const char *fun) const; /** * @brief Reports whether the model has parameter names set. @@ -1130,7 +1130,7 @@ class Model : public AbstractModel { * simulation * @param mode steadyStateSensitivityMode */ - void setSteadyStateSensitivityMode(const SteadyStateSensitivityMode mode); + void setSteadyStateSensitivityMode(SteadyStateSensitivityMode mode); /** * @brief Gets the mode how sensitivities are computed in the steadystate @@ -1176,49 +1176,69 @@ class Model : public AbstractModel { } /** number of states */ - const int nx_rdata; + int nx_rdata{0}; + /** number of states in the unaugmented system */ - const int nxtrue_rdata; + int nxtrue_rdata{0}; + /** number of states with conservation laws applied */ - const int nx_solver; + int nx_solver{0}; + /** number of states in the unaugmented system with conservation laws * applied */ - const int nxtrue_solver; + int nxtrue_solver{0}; + /** number of observables */ - const int ny; + int ny{0}; + /** number of observables in the unaugmented system */ - const int nytrue; + int nytrue{0}; + /** number of event outputs */ - const int nz; + int nz{0}; + /** number of event outputs in the unaugmented system */ - const int nztrue; + int nztrue{0}; + /** number of events */ - const int ne; + int ne{0}; + /** number of common expressions */ - const int nw; + int nw{0}; + /** number of derivatives of common expressions wrt x */ - const int ndwdx; + int ndwdx{0}; + /** number of derivatives of common expressions wrt p */ - const int ndwdp; + int ndwdp{0}; + /** number of nonzero entries in dxdotdw */ - const int ndxdotdw; + int ndxdotdw{0}; + /** number of nonzero entries in dJydy */ std::vector ndJydy; + /** number of nonzero entries in jacobian */ - const int nnz; + int nnz{0}; + /** dimension of the augmented objective function for 2nd order ASA */ - const int nJ; + int nJ{0}; + /** upper bandwith of the jacobian */ - const int ubw; + int ubw{0}; + /** lower bandwith of the jacobian */ - const int lbw; + int lbw{0}; + /** flag indicating whether for sensi == AMICI_SENSI_ORDER_SECOND * directional or full second order derivative will be computed */ - const SecondOrderMode o2mode; + SecondOrderMode o2mode{SecondOrderMode::none}; + /** index indicating to which event an event output belongs */ - const std::vector z2event; + std::vector z2event; + /** flag array for DAE equations */ - const std::vector idlist; + std::vector idlist; /** data standard deviation for current timepoint (dimension: ny) */ std::vector sigmay; @@ -1285,7 +1305,7 @@ class Model : public AbstractModel { * @param ip sensitivity index **/ virtual void fsx_rdata(realtype *sx_rdata, const realtype *sx_solver, - const realtype *stcl, const int ip); + const realtype *stcl, int ip); /** * Model specific implementation of fx_solver @@ -1319,40 +1339,40 @@ class Model : public AbstractModel { * @param ip sensitivity index **/ virtual void fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, - const int ip); + int ip); /** create my slice at timepoint * @param it timepoint index * @param edata pointer to experimental data instance */ - void getmy(const int it, const ExpData *edata); + void getmy(int it, const ExpData *edata); /** create mz slice at event * @param nroots event occurence * @param edata pointer to experimental data instance */ - void getmz(const int nroots, const ExpData *edata); + void getmz(int nroots, const ExpData *edata); /** create y slice at timepoint * @param it timepoint index * @param rdata pointer to return data instance * @return y y-slice from rdata instance */ - const realtype *gety(const int it, const ReturnData *rdata) const; + const realtype *gety(int it, const ReturnData *rdata) const; /** create z slice at event * @param nroots event occurence * @param rdata pointer to return data instance * @return z slice */ - const realtype *getz(const int nroots, const ReturnData *rdata) const; + const realtype *getz(int nroots, const ReturnData *rdata) const; /** create rz slice at event * @param nroots event occurence * @param rdata pointer to return data instance * @return rz slice */ - const realtype *getrz(const int nroots, const ReturnData *rdata) const; + const realtype *getrz(int nroots, const ReturnData *rdata) const; /** create sz slice at event * @param nroots event occurence @@ -1360,7 +1380,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @return z slice */ - const realtype *getsz(const int nroots, const int ip, + const realtype *getsz(int nroots, int ip, const ReturnData *rdata) const; /** create srz slice at event @@ -1369,7 +1389,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @return rz slice */ - const realtype *getsrz(const int nroots, const int ip, + const realtype *getsrz(int nroots, int ip, const ReturnData *rdata) const; /** @@ -1383,7 +1403,7 @@ class Model : public AbstractModel { * @return state vector with negative values replaced by 0 according to * stateIsNonNegative */ - N_Vector computeX_pos(N_Vector x); + N_Vector computeX_pos(const_N_Vector x); /** Sparse Jacobian (dimension: nnz)*/ SUNMatrixWrapper J; @@ -1494,11 +1514,11 @@ class Model : public AbstractModel { std::vector total_cl; /** sensitivities of total abundances for conservation laws - (dimension: (nx_rdata-nx_solver) * np, ordering = row-major)*/ + (dimension: (nx_rdata-nx_solver) * np, ordering = row-major) */ std::vector stotal_cl; - /** indexes of parameters wrt to which sensitivities are computed (dimension - * nplist) */ + /** indexes of parameters wrt to which sensitivities are computed + * (dimension nplist) */ std::vector plist_; /** state initialisation (size nx_solver) */ diff --git a/include/amici/model_dae.h b/include/amici/model_dae.h index 177eab99f2..e5f4738bf7 100644 --- a/include/amici/model_dae.h +++ b/include/amici/model_dae.h @@ -6,300 +6,433 @@ #include #include -#include #include +#include +#include #include namespace amici { - extern msgIdAndTxtFp warnMsgIdAndTxt; +extern msgIdAndTxtFp warnMsgIdAndTxt; + +class ExpData; +class IDASolver; + +/** + * @brief The Model class represents an AMICI DAE model. + * + * The model does not contain any data, but represents the state + * of the model at a specific time t. The states must not always be + * in sync, but may be updated asynchroneously. + */ +class Model_DAE : public Model { + public: + /** default constructor */ + Model_DAE() = default; + + /** + * @brief Constructor with model dimensions + * @param nx_rdata number of state variables + * @param nxtrue_rdata number of state variables of the non-augmented model + * @param nx_solver number of state variables with conservation laws applied + * @param nxtrue_solver number of state variables of the non-augmented model + with conservation laws applied + * @param ny number of observables + * @param nytrue number of observables of the non-augmented model + * @param nz number of event observables + * @param nztrue number of event observables of the non-augmented model + * @param ne number of events + * @param nJ number of objective functions + * @param nw number of repeating elements + * @param ndwdx number of nonzero elements in the x derivative of the + * repeating elements + * @param ndwdp number of nonzero elements in the p derivative of the + * repeating elements + * @param ndxdotdw number of nonzero elements dxdotdw + * @param ndJydy number of nonzero elements dJydy + * @param nnz number of nonzero elements in Jacobian + * @param ubw upper matrix bandwidth in the Jacobian + * @param lbw lower matrix bandwidth in the Jacobian + * @param o2mode second order sensitivity mode + * @param p parameters + * @param k constants + * @param plist indexes wrt to which sensitivities are to be computed + * @param idlist indexes indicating algebraic components (DAE only) + * @param z2event mapping of event outputs to events + */ + Model_DAE(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, + const int nxtrue_solver, const int ny, const int nytrue, + const int nz, const int nztrue, const int ne, const int nJ, + const int nw, const int ndwdx, const int ndwdp, + const int ndxdotdw, std::vector ndJydy, const int nnz, + const int ubw, const int lbw, const SecondOrderMode o2mode, + std::vector const &p, std::vector const &k, + std::vector const &plist, + std::vector const &idlist, + std::vector const &z2event) + : Model(nx_rdata, nxtrue_rdata, nx_solver, nxtrue_solver, ny, nytrue, + nz, nztrue, ne, nJ, nw, ndwdx, ndwdp, ndxdotdw, + std::move(ndJydy), nnz, ubw, lbw, o2mode, p, k, plist, idlist, + z2event) {} + + void fJ(realtype t, realtype cj, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, SUNMatrix J) override; - class ExpData; - class IDASolver; + /** + * @brief Jacobian of xdot with respect to states x + * @param t timepoint + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + * @param J Matrix to which the Jacobian will be written + **/ + void fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xdot, + SUNMatrix J); /** - * @brief The Model class represents an AMICI DAE model. - * The model does not contain any data, but represents the state - * of the model at a specific time t. The states must not always be - * in sync, but may be updated asynchroneously. + * @brief Jacobian of xBdot with respect to adjoint state xB + * @param t timepoint + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param JB Matrix to which the Jacobian will be written + **/ + + void fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, SUNMatrix JB); + + void fJSparse(realtype t, realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, + SUNMatrix J) override; + + /** + * @brief J in sparse form (for sparse solvers from the SuiteSparse Package) + * @param t timepoint + * @param cj scalar in Jacobian (inverse stepsize) + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param J Matrix to which the Jacobian will be written + */ + void fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, + SUNMatrix J); + + /** JB in sparse form (for sparse solvers from the SuiteSparse Package) + * @param t timepoint + * @param cj scalar in Jacobian + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param JB Matrix to which the Jacobian will be written */ - class Model_DAE : public Model { - public: - /** default constructor */ - Model_DAE() : Model() {} - - /** constructor with model dimensions - * @param nx_rdata number of state variables - * @param nxtrue_rdata number of state variables of the non-augmented model - * @param nx_solver number of state variables with conservation laws applied - * @param nxtrue_solver number of state variables of the non-augmented model - with conservation laws applied - * @param ny number of observables - * @param nytrue number of observables of the non-augmented model - * @param nz number of event observables - * @param nztrue number of event observables of the non-augmented model - * @param ne number of events - * @param nJ number of objective functions - * @param nw number of repeating elements - * @param ndwdx number of nonzero elements in the x derivative of the - * repeating elements - * @param ndwdp number of nonzero elements in the p derivative of the - * repeating elements - * @param ndxdotdw number of nonzero elements dxdotdw - * @param ndJydy number of nonzero elements dJydy - * @param nnz number of nonzero elements in Jacobian - * @param ubw upper matrix bandwidth in the Jacobian - * @param lbw lower matrix bandwidth in the Jacobian - * @param o2mode second order sensitivity mode - * @param p parameters - * @param k constants - * @param plist indexes wrt to which sensitivities are to be computed - * @param idlist indexes indicating algebraic components (DAE only) - * @param z2event mapping of event outputs to events - */ - Model_DAE(const int nx_rdata, const int nxtrue_rdata, - const int nx_solver, const int nxtrue_solver, const int ny, - const int nytrue, const int nz, const int nztrue, - const int ne, const int nJ, const int nw, const int ndwdx, - const int ndwdp, const int ndxdotdw, std::vector ndJydy, - const int nnz, - const int ubw, const int lbw, const SecondOrderMode o2mode, - std::vector const &p, - std::vector const &k, std::vector const &plist, - std::vector const &idlist, - std::vector const &z2event) - : Model(nx_rdata, nxtrue_rdata, nx_solver, nxtrue_solver, ny, - nytrue, nz, nztrue, ne, nJ, nw, ndwdx, ndwdp, ndxdotdw, - ndJydy, nnz, - ubw, lbw, o2mode, p, k, plist, idlist, z2event) {} - - virtual void fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) override; - void fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xdot, - SUNMatrix J); - - void fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, SUNMatrix JB); - - virtual void fJSparse(realtype t, realtype cj, AmiVector *x, - AmiVector *dx, AmiVector *xdot, - SUNMatrix J) override; - void fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, - SUNMatrix J); - - void fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, SUNMatrix JB); - - virtual void fJDiag(realtype t, AmiVector *JDiag, realtype cj, - AmiVector *x, AmiVector *dx) override; - - virtual void fJv(realtype t, AmiVector *x, AmiVector *dx, - AmiVector *xdot, AmiVector *v, AmiVector *nJv, - realtype cj) override; - void fJv(realtype t, N_Vector x, N_Vector dx, N_Vector v, N_Vector Jv, - realtype cj); - - void fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector vB, N_Vector JvB, realtype cj); - - virtual void froot(realtype t, AmiVector *x, AmiVector *dx, realtype *root) override; - void froot(realtype t, N_Vector x, N_Vector dx, realtype *root); - - virtual void fxdot(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot) override; - void fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot); - - void fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector xBdot); - - void fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector qBdot); - - void fdxdotdp(const realtype t, const N_Vector x, const N_Vector dx); - virtual void fdxdotdp(realtype t, AmiVector *x, AmiVector *dx) override { - fdxdotdp(t,x->getNVector(),dx->getNVector()); - }; - - void fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, - AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) override; - void fsxdot(realtype t, N_Vector x, N_Vector dx, int ip, N_Vector sx, N_Vector sdx, N_Vector sxdot); - - void fM(realtype t, const N_Vector x); - - - - virtual std::unique_ptr getSolver() override; - protected: - - /** model specific implementation for fJ - * @param J Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param dx Vector with the derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJ(realtype *J, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype cj, const realtype *dx, const realtype *w, const realtype *dwdx) = 0; - - /** model specific implementation for fJB - * @param JB Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param xB Vector with the adjoint states - * @param dx Vector with the derivative states - * @param dxB Vector with the adjoint derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJB(realtype *JB, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype cj, const realtype *xB, const realtype *dx, const realtype *dxB, - const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - } - - /** model specific implementation for fJSparse - * @param JSparse Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param dx Vector with the derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparse(SUNMatrixContent_Sparse JSparse, const realtype t, - const realtype *x, const double *p, - const double *k, const realtype *h, - const realtype cj, const realtype *dx, - const realtype *w, const realtype *dwdx) = 0; - - /** model specific implementation for fJSparseB - * @param JSparseB Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param xB Vector with the adjoint states - * @param dx Vector with the derivative states - * @param dxB Vector with the adjoint derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparseB(SUNMatrixContent_Sparse JSparseB, - const realtype t, const realtype *x, - const double *p, const double *k, - const realtype *h, const realtype cj, - const realtype *xB, const realtype *dx, - const realtype *dxB, const realtype *w, - const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); - } - - /** model specific implementation for fJDiag - * @param JDiag array to which the Jacobian diagonal will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param dx Vector with the derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJDiag(realtype *JDiag, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype cj, const realtype *dx, const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - } - - /** model specific implementation for fJvB - * @param JvB Matrix vector product of JB with a vector v - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param xB Vector with the adjoint states - * @param dx Vector with the derivative states - * @param dxB Vector with the adjoint derivative states - * @param vB Vector with which the Jacobian is multiplied - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJvB(realtype *JvB, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype cj, const realtype *xB, const realtype *dx, const realtype *dxB, - const realtype *vB, const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); // not implemented - } - - /** model specific implementation for froot - * @param root values of the trigger function - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param dx Vector with the derivative states - **/ - virtual void froot(realtype *root, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype *dx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); // not implemented - } - - /** model specific implementation for fxdot - * @param xdot residual function - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dx Vector with the derivative states - **/ - virtual void fxdot(realtype *xdot, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype *dx, const realtype *w) = 0; - - /** model specific implementation of fdxdotdp - * @param dxdotdp partial derivative xdot wrt p - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param ip parameter index - * @param dx Vector with the derivative states - * @param w vector with helper variables - * @param dwdp derivative of w wrt p - */ - virtual void fdxdotdp(realtype *dxdotdp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const int ip, const realtype *dx, const realtype *w, const realtype *dwdp) { - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - }; - - /** model specific implementation of fM - * @param M mass matrix - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - */ - virtual void fM(realtype *M, const realtype t, const realtype *x, const realtype *p, - const realtype *k) {}; + void fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, SUNMatrix JB); + + /** diagonalized Jacobian (for preconditioning) + * @param t timepoint + * @param JDiag Vector to which the Jacobian diagonal will be written + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @return status flag indicating successful execution + **/ + + void fJDiag(realtype t, AmiVector &JDiag, realtype cj, const AmiVector &x, + const AmiVector &dx) override; + + void fJv(realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, const AmiVector &v, AmiVector &nJv, + realtype cj) override; + + /** Matrix vector product of J with a vector v (for iterative solvers) + * @param t timepoint @type realtype + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param v Vector with which the Jacobian is multiplied + * @param Jv Vector to which the Jacobian vector product will be + * written + **/ + void fJv(realtype t, N_Vector x, N_Vector dx, N_Vector v, N_Vector Jv, + realtype cj); + + /** Matrix vector product of JB with a vector v (for iterative solvers) + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param vB Vector with which the Jacobian is multiplied + * @param JvB Vector to which the Jacobian vector product will be + *written + * @param cj scalar in Jacobian (inverse stepsize) + **/ + + void fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, + N_Vector vB, N_Vector JvB, realtype cj); + + void froot(realtype t, const AmiVector &x, const AmiVector &dx, + gsl::span root) override; + + /** Event trigger function for events + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param root array with root function values + */ + void froot(realtype t, N_Vector x, N_Vector dx, gsl::span root); + + void fxdot(realtype t, const AmiVector &x, const AmiVector &dx, + AmiVector &xdot) override; + /** + * @brief Residual function of the DAE + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + */ + void fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot); + + /** Right hand side of differential equation for adjoint state xB + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param xBdot Vector with the adjoint right hand side + */ + void fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, + N_Vector xBdot); + + /** Right hand side of integral equation for quadrature states qB + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param qBdot Vector with the adjoint quadrature right hand side + */ + void fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, + N_Vector qBdot); + + /** Sensitivity of dx/dt wrt model parameters p + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @return status flag indicating successful execution + */ + void fdxdotdp(realtype t, const N_Vector x, const N_Vector dx); + void fdxdotdp(const realtype t, const AmiVector &x, + const AmiVector &dx) override { + fdxdotdp(t, x.getNVector(), dx.getNVector()); }; + + void fsxdot(realtype t, const AmiVector &x, const AmiVector &dx, int ip, + const AmiVector &sx, const AmiVector &sdx, + AmiVector &sxdot) override; + /** Right hand side of differential equation for state sensitivities sx + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param ip parameter index + * @param sx Vector with the state sensitivities + * @param sdx Vector with the derivative state sensitivities + * @param sxdot Vector with the sensitivity right hand side + */ + void fsxdot(realtype t, N_Vector x, N_Vector dx, int ip, N_Vector sx, + N_Vector sdx, N_Vector sxdot); + + /** + * @brief Mass matrix for DAE systems + * @param t timepoint + * @param x Vector with the states + */ + void fM(realtype t, const N_Vector x); + + std::unique_ptr getSolver() override; + + protected: + /** + * @brief Model specific implementation for fJ + * @param J Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param dx Vector with the derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJ(realtype *J, realtype t, const realtype *x, const double *p, + const double *k, const realtype *h, realtype cj, + const realtype *dx, const realtype *w, + const realtype *dwdx) = 0; + + /** + * @brief model specific implementation for fJB + * @param JB Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param xB Vector with the adjoint states + * @param dx Vector with the derivative states + * @param dxB Vector with the adjoint derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJB(realtype *JB, realtype t, const realtype *x, + const double *p, const double *k, const realtype *h, + realtype cj, const realtype *xB, const realtype *dx, + const realtype *dxB, const realtype *w, + const realtype *dwdx); + + /** + * @brief model specific implementation for fJSparse + * @param JSparse Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param dx Vector with the derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparse(SUNMatrixContent_Sparse JSparse, realtype t, + const realtype *x, const double *p, const double *k, + const realtype *h, realtype cj, const realtype *dx, + const realtype *w, const realtype *dwdx) = 0; + + /** + * @brief Model specific implementation for fJSparseB + * @param JSparseB Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param xB Vector with the adjoint states + * @param dx Vector with the derivative states + * @param dxB Vector with the adjoint derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparseB(SUNMatrixContent_Sparse JSparseB, const realtype t, + const realtype *x, const double *p, const double *k, + const realtype *h, const realtype cj, + const realtype *xB, const realtype *dx, + const realtype *dxB, const realtype *w, + const realtype *dwdx); + + /** + * @brief Model specific implementation for fJDiag + * @param JDiag array to which the Jacobian diagonal will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param dx Vector with the derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJDiag(realtype *JDiag, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + realtype cj, const realtype *dx, const realtype *w, + const realtype *dwdx); + + /** + * Model specific implementation for fJvB + * @param JvB Matrix vector product of JB with a vector v + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param xB Vector with the adjoint states + * @param dx Vector with the derivative states + * @param dxB Vector with the adjoint derivative states + * @param vB Vector with which the Jacobian is multiplied + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJvB(realtype *JvB, realtype t, const realtype *x, + const double *p, const double *k, const realtype *h, + realtype cj, const realtype *xB, const realtype *dx, + const realtype *dxB, const realtype *vB, + const realtype *w, const realtype *dwdx); + + /** + * @brief Model specific implementation for froot + * @param root values of the trigger function + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param dx Vector with the derivative states + **/ + virtual void froot(realtype *root, realtype t, const realtype *x, + const double *p, const double *k, const realtype *h, + const realtype *dx); + + /** + * @brief Model specific implementation for fxdot + * @param xdot residual function + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dx Vector with the derivative states + **/ + virtual void fxdot(realtype *xdot, realtype t, const realtype *x, + const double *p, const double *k, const realtype *h, + const realtype *dx, const realtype *w) = 0; + + /** + * @brief model specific implementation of fdxdotdp + * @param dxdotdp partial derivative xdot wrt p + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param ip parameter index + * @param dx Vector with the derivative states + * @param w vector with helper variables + * @param dwdp derivative of w wrt p + */ + virtual void fdxdotdp(realtype *dxdotdp, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, int ip, const realtype *dx, + const realtype *w, const realtype *dwdp); + + /** + * @brief model specific implementation of fM + * @param M mass matrix + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + */ + virtual void fM(realtype *M, const realtype t, const realtype *x, + const realtype *p, const realtype *k){}; +}; } // namespace amici #endif // MODEL_H diff --git a/include/amici/model_ode.h b/include/amici/model_ode.h index 798e948a27..0f0d44bfab 100644 --- a/include/amici/model_ode.h +++ b/include/amici/model_ode.h @@ -7,383 +7,456 @@ #include #include -#include #include +#include +#include #include namespace amici { - extern msgIdAndTxtFp warnMsgIdAndTxt; +extern msgIdAndTxtFp warnMsgIdAndTxt; + +class CVodeSolver; + +/** + * @brief The Model class represents an AMICI ODE model. + * + * The model does not contain any data, but represents the state + * of the model at a specific time t. The states must not always be + * in sync, but may be updated asynchroneously. + */ +class Model_ODE : public Model { + public: + /** default constructor */ + Model_ODE() = default; + + /** + * @brief Constructor with model dimensions + * @param nx_rdata number of state variables + * @param nxtrue_rdata number of state variables of the non-augmented model + * @param nx_solver number of state variables with conservation laws applied + * @param nxtrue_solver number of state variables of the non-augmented model + with conservation laws applied + * @param ny number of observables + * @param nytrue number of observables of the non-augmented model + * @param nz number of event observables + * @param nztrue number of event observables of the non-augmented model + * @param ne number of events + * @param nJ number of objective functions + * @param nw number of repeating elements + * @param ndwdx number of nonzero elements in the x derivative of the + * repeating elements + * @param ndwdp number of nonzero elements in the p derivative of the + * repeating elements + * @param ndxdotdw number of nonzero elements dxdotdw + * @param ndJydy number of nonzero elements dJydy + * @param nnz number of nonzero elements in Jacobian + * @param ubw upper matrix bandwidth in the Jacobian + * @param lbw lower matrix bandwidth in the Jacobian + * @param o2mode second order sensitivity mode + * @param p parameters + * @param k constants + * @param plist indexes wrt to which sensitivities are to be computed + * @param idlist indexes indicating algebraic components (DAE only) + * @param z2event mapping of event outputs to events + */ + Model_ODE(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, + const int nxtrue_solver, const int ny, const int nytrue, + const int nz, const int nztrue, const int ne, const int nJ, + const int nw, const int ndwdx, const int ndwdp, + const int ndxdotdw, std::vector ndJydy, const int nnz, + const int ubw, const int lbw, const SecondOrderMode o2mode, + std::vector const &p, std::vector const &k, + std::vector const &plist, + std::vector const &idlist, + std::vector const &z2event) + : Model(nx_rdata, nxtrue_rdata, nx_solver, nxtrue_solver, ny, nytrue, + nz, nztrue, ne, nJ, nw, ndwdx, ndwdp, ndxdotdw, + std::move(ndJydy), nnz, ubw, lbw, o2mode, p, k, plist, idlist, + z2event) {} + + void fJ(realtype t, realtype cj, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, SUNMatrix J) override; + + /** + * @brief Implementation of fJ at the N_Vector level + * + * This function provides an + * interface to the model specific routines for the solver + * implementation as well as the AmiVector level implementation + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + * @param J Matrix to which the Jacobian will be written + **/ + void fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J); + + /** implementation of fJB at the N_Vector level, this function provides an + *interface to the model specific routines for the solver implementation + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + * @param JB Matrix to which the Jacobian will be written + **/ + void fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, SUNMatrix JB); + + void fJSparse(realtype t, realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, + SUNMatrix J) override; + + /** + * Implementation of fJSparse at the N_Vector level, this function + * provides + * an interface to the model specific routines for the solver implementation + * aswell as the AmiVector level implementation + * @param t timepoint + * @param x Vector with the states + * @param J Matrix to which the Jacobian will be written + */ + void fJSparse(realtype t, N_Vector x, SUNMatrix J); + + /** implementation of fJSparseB at the N_Vector level, this function + * provides an interface to the model specific routines for the solver + * implementation + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + * @param JB Matrix to which the Jacobian will be written + */ + void fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB); + + /** implementation of fJDiag at the N_Vector level, this function provides + *an interface to the model specific routines for the solver implementation + * @param t timepoint + * @param JDiag Vector to which the Jacobian diagonal will be written + * @param x Vector with the states + **/ + void fJDiag(realtype t, N_Vector JDiag, N_Vector x); + + /** + * @brief diagonalized Jacobian (for preconditioning) + * @param t timepoint + * @param JDiag Vector to which the Jacobian diagonal will be written + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @return status flag indicating successful execution + **/ + void fJDiag(realtype t, AmiVector &JDiag, realtype cj, const AmiVector &x, + const AmiVector &dx) override; + + void fJv(realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, const AmiVector &v, AmiVector &nJv, + realtype cj) override; + + /** implementation of fJv at the N_Vector level. + * @param t timepoint + * @param x Vector with the states + * @param v Vector with which the Jacobian is multiplied + * @param Jv Vector to which the Jacobian vector product will be + * written + **/ + void fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x); + + /** + * @brief implementation of fJvB at the N_Vector level + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param vB Vector with which the Jacobian is multiplied + * @param JvB Vector to which the Jacobian vector product will be written + **/ + void fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, N_Vector xB); + + void froot(realtype t, const AmiVector &x, const AmiVector &dx, + gsl::span root) override; + + /** + * @brief implementation of froot at the N_Vector level + * + * This function provides an interface to the model specific routines for + * the solver implementation aswell as the AmiVector level implementation + * @param t timepoint + * @param x Vector with the states + * @param root array with root function values + */ + void froot(realtype t, N_Vector x, gsl::span root); + + void fxdot(realtype t, const AmiVector &x, const AmiVector &dx, + AmiVector &xdot) override; + + /** implementation of fxdot at the N_Vector level, this function provides an + * interface to the model specific routines for the solver implementation + * aswell as the AmiVector level implementation + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + */ + void fxdot(realtype t, N_Vector x, N_Vector xdot); + + /** implementation of fxBdot at the N_Vector level + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + */ + void fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot); + + /** implementation of fqBdot at the N_Vector level + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param qBdot Vector with the adjoint quadrature right hand side + */ + void fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot); - class CVodeSolver; + /** Sensitivity of dx/dt wrt model parameters w + * @param t timepoint + * @param x Vector with the states + * @return status flag indicating successful execution + */ + void fdxdotdw(realtype t, const N_Vector x); + + /** Sensitivity of dx/dt wrt model parameters p + * @param t timepoint + * @param x Vector with the states + * @return status flag indicating successful execution + */ + void fdxdotdp(realtype t, const N_Vector x); + + void fdxdotdp(realtype t, const AmiVector &x, const AmiVector &dx) override; + + void fsxdot(realtype t, const AmiVector &x, const AmiVector &dx, int ip, + const AmiVector &sx, const AmiVector &sdx, + AmiVector &sxdot) override; + + /** + * @brief implementation of fsxdot at the N_Vector level + * @param t timepoint + * @param x Vector with the states + * @param ip parameter index + * @param sx Vector with the state sensitivities + * @param sxdot Vector with the sensitivity right hand side + */ + void fsxdot(realtype t, N_Vector x, int ip, N_Vector sx, N_Vector sxdot); + + std::unique_ptr getSolver() override; + + protected: + /** model specific implementation for fJ + * @param J Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJ(realtype *J, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + const realtype *w, const realtype *dwdx) = 0; + + /** model specific implementation for fJB + * @param JB Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param xB Vector with the adjoint states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJB(realtype *JB, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + const realtype *xB, const realtype *w, + const realtype *dwdx); + + /** model specific implementation for fJSparse + * @param JSparse Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparse(SUNMatrixContent_Sparse JSparse, realtype t, + const realtype *x, const realtype *p, + const realtype *k, const realtype *h, + const realtype *w, const realtype *dwdx); + + /** model specific implementation for fJSparse, data only + * @param JSparse Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparse(realtype *JSparse, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, const realtype *w, + const realtype *dwdx); + + /** + * @brief model specific implementation for fJSparse, column pointers + * @param indexptrs column pointers + **/ + virtual void fJSparse_colptrs(sunindextype *indexptrs); + + /** + * @brief Model specific implementation for fJSparse, row values + * @param indexvals row values + **/ + virtual void fJSparse_rowvals(sunindextype *indexvals); + + /** + * @brief Model specific implementation for fJSparseB + * @param JSparseB Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param xB Vector with the adjoint states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparseB(SUNMatrixContent_Sparse JSparseB, realtype t, + const realtype *x, const realtype *p, + const realtype *k, const realtype *h, + const realtype *xB, const realtype *w, + const realtype *dwdx); + + /** model specific implementation for fJSparseB + * @param JSparseB data array + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param xB Vector with the adjoint states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparseB(realtype *JSparseB, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, const realtype *xB, + const realtype *w, const realtype *dwdx); + + /** + * @brief Model specific implementation for fJSparse, column pointers + * @param indexptrs column pointers + **/ + virtual void fJSparseB_colptrs(sunindextype *indexptrs); + + /** + * @brief Model specific implementation for fJSparse, row values + * @param indexvals row values + **/ + virtual void fJSparseB_rowvals(sunindextype *indexvals); + + /** + * @brief Model specific implementation for fJDiag + * @param JDiag Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJDiag(realtype *JDiag, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + const realtype *w, const realtype *dwdx); /** - * @brief The Model class represents an AMICI ODE model. - * The model does not contain any data, but represents the state - * of the model at a specific time t. The states must not always be - * in sync, but may be updated asynchroneously. + * @brief model specific implementation for froot + * @param root values of the trigger function + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + **/ + virtual void froot(realtype *root, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h); + + /** model specific implementation for fxdot + * @param xdot residual function + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + **/ + virtual void fxdot(realtype *xdot, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + const realtype *w) = 0; + + /** model specific implementation of fdxdotdp, with w chainrule + * @param dxdotdp partial derivative xdot wrt p + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param ip parameter index + * @param w vector with helper variables + * @param dwdp derivative of w wrt p + */ + virtual void fdxdotdp(realtype *dxdotdp, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, int ip, const realtype *w, + const realtype *dwdp); + + /** model specific implementation of fdxdotdp, without w chainrule + * @param dxdotdp partial derivative xdot wrt p + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param ip parameter index + * @param w vector with helper variables + */ + virtual void fdxdotdp(realtype *dxdotdp, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, int ip, const realtype *w); + + /** model specific implementation of fdxdotdw, data part + * @param dxdotdw partial derivative xdot wrt w + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + */ + virtual void fdxdotdw(realtype *dxdotdw, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, const realtype *w); + + /** model specific implementation of fdxdotdw, colptrs part + * @param indexptrs column pointers + */ + virtual void fdxdotdw_colptrs(sunindextype *indexptrs); + + /** model specific implementation of fdxdotdw, colptrs part + * @param indexvals row values */ - class Model_ODE : public Model { - public: - /** default constructor */ - Model_ODE() : Model() {} - - /** constructor with model dimensions - * @param nx_rdata number of state variables - * @param nxtrue_rdata number of state variables of the non-augmented model - * @param nx_solver number of state variables with conservation laws applied - * @param nxtrue_solver number of state variables of the non-augmented model - with conservation laws applied - * @param ny number of observables - * @param nytrue number of observables of the non-augmented model - * @param nz number of event observables - * @param nztrue number of event observables of the non-augmented model - * @param ne number of events - * @param nJ number of objective functions - * @param nw number of repeating elements - * @param ndwdx number of nonzero elements in the x derivative of the - * repeating elements - * @param ndwdp number of nonzero elements in the p derivative of the - * repeating elements - * @param ndxdotdw number of nonzero elements dxdotdw - * @param ndJydy number of nonzero elements dJydy - * @param nnz number of nonzero elements in Jacobian - * @param ubw upper matrix bandwidth in the Jacobian - * @param lbw lower matrix bandwidth in the Jacobian - * @param o2mode second order sensitivity mode - * @param p parameters - * @param k constants - * @param plist indexes wrt to which sensitivities are to be computed - * @param idlist indexes indicating algebraic components (DAE only) - * @param z2event mapping of event outputs to events - */ - Model_ODE(const int nx_rdata, const int nxtrue_rdata, - const int nx_solver, const int nxtrue_solver, const int ny, - const int nytrue, const int nz, const int nztrue, - const int ne, const int nJ, const int nw, const int ndwdx, - const int ndwdp, const int ndxdotdw, std::vector ndJydy, - const int nnz, - const int ubw, const int lbw, const SecondOrderMode o2mode, - std::vector const &p, - std::vector const &k, std::vector const &plist, - std::vector const &idlist, - std::vector const &z2event) - : Model(nx_rdata, nxtrue_rdata, nx_solver, nxtrue_solver, ny, - nytrue, nz, nztrue, ne, nJ, nw, ndwdx, ndwdp, ndxdotdw, - ndJydy, nnz, - ubw, lbw, o2mode, p, k, plist, idlist, z2event) {} - - virtual void fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) override; - void fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J); - - void fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - SUNMatrix JB); - - virtual void fJSparse(realtype t, realtype cj, AmiVector *x, - AmiVector *dx, AmiVector *xdot, - SUNMatrix J) override; - void fJSparse(realtype t, N_Vector x, SUNMatrix J); - - void fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - SUNMatrix JB); - - void fJDiag(realtype t, N_Vector JDiag, N_Vector x); - virtual void fJDiag(realtype t, AmiVector *Jdiag, realtype cj, - AmiVector *x, AmiVector *dx) override; - - virtual void fJv(realtype t, AmiVector *x, AmiVector *dx, - AmiVector *xdot, AmiVector *v, AmiVector *nJv, - realtype cj) override; - void fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x); - - void fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, - N_Vector xB); - - virtual void froot(realtype t, AmiVector *x, AmiVector *dx, realtype *root) override; - - void froot(realtype t, N_Vector x, realtype *root); - - virtual void fxdot(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot) override; - void fxdot(realtype t, N_Vector x, N_Vector xdot); - - void fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot); - - void fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot); - - void fdxdotdw(const realtype t, const N_Vector x); - - void fdxdotdp(const realtype t, const N_Vector x); - virtual void fdxdotdp(realtype t, AmiVector *x, AmiVector *dx) override { - fdxdotdp(t,x->getNVector()); - } - - void fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, - AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) override; - void fsxdot(realtype t, N_Vector x, int ip, N_Vector sx, N_Vector sxdot); - - virtual std::unique_ptr getSolver() override; - protected: - - /** model specific implementation for fJ - * @param J Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJ(realtype *J, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *w, const realtype *dwdx) = 0; - - /** model specific implementation for fJB - * @param JB Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param xB Vector with the adjoint states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJB(realtype *JB, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *xB, const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - } - - /** model specific implementation for fJSparse - * @param JSparse Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparse(SUNMatrixContent_Sparse JSparse, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const realtype *w, const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, data only - * @param JSparse Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparse(realtype *JSparse, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const realtype *w, const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, column pointers - * @param indexptrs column pointers - **/ - virtual void fJSparse_colptrs(sunindextype *indexptrs) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, row values - * @param indexvals row values - **/ - virtual void fJSparse_rowvals(sunindextype *indexvals) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparseB - * @param JSparseB Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param xB Vector with the adjoint states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparseB(SUNMatrixContent_Sparse JSparseB, - const realtype t, const realtype *x, - const realtype *p, const realtype *k, - const realtype *h, const realtype *xB, - const realtype *w, const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparseB - * @param JSparseB data array - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param xB Vector with the adjoint states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparseB(realtype *JSparseB, - const realtype t, const realtype *x, - const realtype *p, const realtype *k, - const realtype *h, const realtype *xB, - const realtype *w, const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, column pointers - * @param indexptrs column pointers - **/ - virtual void fJSparseB_colptrs(sunindextype *indexptrs) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, row values - * @param indexvals row values - **/ - virtual void fJSparseB_rowvals(sunindextype *indexvals) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJDiag - * @param JDiag Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJDiag(realtype *JDiag, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - } - - /** model specific implementation for froot - * @param root values of the trigger function - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - **/ - virtual void froot(realtype *root, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); // not implemented - } - - /** model specific implementation for fxdot - * @param xdot residual function - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - **/ - virtual void fxdot(realtype *xdot, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w) = 0; - - /** model specific implementation of fdxdotdp, with w chainrule - * @param dxdotdp partial derivative xdot wrt p - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param ip parameter index - * @param w vector with helper variables - * @param dwdp derivative of w wrt p - */ - virtual void fdxdotdp(realtype *dxdotdp, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const int ip, const realtype *w, - const realtype *dwdp) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation of fdxdotdp, without w chainrule - * @param dxdotdp partial derivative xdot wrt p - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param ip parameter index - * @param w vector with helper variables - */ - virtual void fdxdotdp(realtype *dxdotdp, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const int ip, const realtype *w) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation of fdxdotdw, data part - * @param dxdotdw partial derivative xdot wrt w - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - */ - virtual void fdxdotdw(realtype *dxdotdw, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const realtype *w) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation of fdxdotdw, colptrs part - * @param indexptrs column pointers - */ - virtual void fdxdotdw_colptrs(sunindextype *indexptrs) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation of fdxdotdw, colptrs part - * @param indexvals row values - */ - virtual void fdxdotdw_rowvals(sunindextype *indexvals) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - }; + virtual void fdxdotdw_rowvals(sunindextype *indexvals); +}; } // namespace amici diff --git a/include/amici/newton_solver.h b/include/amici/newton_solver.h index efc5a1d6bc..c0c1232b31 100644 --- a/include/amici/newton_solver.h +++ b/include/amici/newton_solver.h @@ -48,8 +48,13 @@ class NewtonSolver { * @param rtol relative tolerance * @return solver NewtonSolver according to the specified linsolType */ - static std::unique_ptr getSolver(realtype *t, AmiVector *x, LinearSolver linsolType, Model *model, - ReturnData *rdata, int maxlinsteps, int maxsteps, double atol, double rtol); + static std::unique_ptr getSolver(realtype *t, AmiVector *x, + LinearSolver linsolType, + Model *model, + ReturnData *rdata, + int maxlinsteps, + int maxsteps, + double atol, double rtol); /** * Computes the solution of one Newton iteration @@ -60,14 +65,14 @@ class NewtonSolver { * @param delta containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - void getStep(int ntry, int nnewt, AmiVector *delta); + void getStep(int ntry, int nnewt, AmiVector &delta); /** * Computes steady state sensitivities * * @param sx pointer to state variable sensitivities */ - void computeNewtonSensis(AmiVectorArray *sx); + void computeNewtonSensis(AmiVectorArray &sx); /** * Writes the Jacobian for the Newton iteration and passes it to the linear @@ -85,7 +90,7 @@ class NewtonSolver { * @param rhs containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - virtual void solveLinearSystem(AmiVector *rhs) = 0; + virtual void solveLinearSystem(AmiVector &rhs) = 0; virtual ~NewtonSolver() = default; @@ -108,7 +113,7 @@ class NewtonSolver { ReturnData *rdata; /** right hand side AmiVector */ AmiVector xdot; - /** current state*/ + /** current state */ AmiVector *x; /** current state time derivative (DAE) */ AmiVector dx; @@ -142,7 +147,7 @@ class NewtonSolverDense : public NewtonSolver { * @param rhs containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - void solveLinearSystem(AmiVector *rhs) override; + void solveLinearSystem(AmiVector &rhs) override; /** * Writes the Jacobian for the Newton iteration and passes it to the linear @@ -185,10 +190,10 @@ class NewtonSolverSparse : public NewtonSolver { /** * Solves the linear system for the Newton step * - * @param rhs containing the RHS of the linear system,will be + * @param rhs containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - void solveLinearSystem(AmiVector *rhs) override; + void solveLinearSystem(AmiVector &rhs) override; /** * Writes the Jacobian for the Newton iteration and passes it to the linear @@ -224,7 +229,7 @@ class NewtonSolverIterative : public NewtonSolver { * @param rdata pointer to the return data object */ NewtonSolverIterative(realtype *t, AmiVector *x, Model *model, ReturnData *rdata); - virtual ~NewtonSolverIterative() = default; + ~NewtonSolverIterative() override = default; /** * Solves the linear system for the Newton step by passing it to @@ -233,7 +238,7 @@ class NewtonSolverIterative : public NewtonSolver { * @param rhs containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - void solveLinearSystem(AmiVector *rhs) override; + void solveLinearSystem(AmiVector &rhs) override; /** * Writes the Jacobian for the Newton iteration and passes it to the linear @@ -256,7 +261,7 @@ class NewtonSolverIterative : public NewtonSolver { * @param nnewt integer number of current Newton step * @param ns_delta Newton step */ - void linsolveSPBCG(int ntry, int nnewt, AmiVector *ns_delta); + void linsolveSPBCG(int ntry, int nnewt, AmiVector &ns_delta); private: /** number of tries */ diff --git a/include/amici/rdata.h b/include/amici/rdata.h index 9d77ebeac8..9f24b59c4c 100644 --- a/include/amici/rdata.h +++ b/include/amici/rdata.h @@ -14,12 +14,13 @@ class Solver; namespace boost { namespace serialization { template -void serialize(Archive &ar, amici::ReturnData &u, const unsigned int version); +void serialize(Archive &ar, amici::ReturnData &u, unsigned int version); }} namespace amici { -/** @brief Stores all data to be returned by amici::runAmiciSimulation. +/** + * @brief Stores all data to be returned by amici::runAmiciSimulation. * * NOTE: multidimensional arrays are stored in row-major order * (FORTRAN-style) @@ -29,7 +30,7 @@ class ReturnData { /** * @brief default constructor */ - ReturnData(); + ReturnData() = default; /** * @brief ReturnData @@ -64,11 +65,11 @@ class ReturnData { /** * @brief constructor that uses information from model and solver to * appropriately initialize fields - * @param solver solver - * @param model pointer to model specification object + * @param solver solver instance + * @param model model instance * bool */ - ReturnData(Solver const& solver, const Model *model); + ReturnData(Solver const& solver, const Model &model); ~ReturnData() = default; @@ -82,7 +83,7 @@ class ReturnData { * sensitivities to NaN (typically after integration failure) * @param t time of integration failure */ - void invalidate(const realtype t); + void invalidate(realtype t); /** * @brief Set likelihood and chi2 to NaN @@ -105,7 +106,7 @@ class ReturnData { applyChainRuleFactorToSimulationResults(const Model *model); /** timepoints (dimension: nt) */ - const std::vector ts; + std::vector ts; /** time derivative (dimension: nx) */ std::vector xdot; @@ -256,44 +257,61 @@ class ReturnData { int status = 0; /** total number of model parameters */ - const int np; + int np{0}; + /** number of fixed parameters */ - const int nk; + int nk{0}; + /** number of states */ - const int nx; + int nx{0}; + /** number of states with conservation laws applied */ - const int nx_solver; + int nx_solver{0}; + /** number of states in the unaugmented system */ - const int nxtrue; + int nxtrue{0}; + /** number of observables */ - const int ny; + int ny{0}; + /** number of observables in the unaugmented system */ - const int nytrue; + int nytrue{0}; + /** number of event outputs */ - const int nz; + int nz{0}; + /** number of event outputs in the unaugmented system */ - const int nztrue; + int nztrue{0}; + /** number of events */ - const int ne; + int ne{0}; + /** dimension of the augmented objective function for 2nd order ASA */ - const int nJ; + int nJ{0}; /** number of parameter for which sensitivities were requested */ - const int nplist; + int nplist{0}; + /** maximal number of occuring events (for every event type) */ - const int nmaxevent; + int nmaxevent{0}; + /** number of considered timepoints */ - const int nt; + int nt{0}; + /** maximal number of newton iterations for steady state calculation */ - const int newton_maxsteps; + int newton_maxsteps{0}; + /** scaling of parameterization (lin,log,log10) */ std::vector pscale; + /** flag indicating whether second order sensitivities were requested */ - const SecondOrderMode o2mode; + SecondOrderMode o2mode{SecondOrderMode::none}; + /** sensitivity order */ - const SensitivityOrder sensi; + SensitivityOrder sensi{SensitivityOrder::none}; + /** sensitivity method */ - const SensitivityMethod sensi_meth; + SensitivityMethod sensi_meth{SensitivityMethod::none}; /** * @brief Serialize ReturnData (see boost::serialization::serialize) @@ -302,7 +320,8 @@ class ReturnData { * @param version Version number */ template - friend void boost::serialization::serialize(Archive &ar, ReturnData &r, const unsigned int version); + friend void boost::serialization::serialize(Archive &ar, ReturnData &r, + unsigned int version); }; } // namespace amici diff --git a/include/amici/serialization.h b/include/amici/serialization.h index dfa02abb33..5c0842ab78 100644 --- a/include/amici/serialization.h +++ b/include/amici/serialization.h @@ -75,26 +75,26 @@ void serialize(Archive &ar, amici::CVodeSolver &u, const unsigned int version) { template void serialize(Archive &ar, amici::Model &u, const unsigned int version) { - ar &const_cast(u.nx_rdata); - ar &const_cast(u.nxtrue_rdata); - ar &const_cast(u.nx_solver); - ar &const_cast(u.nxtrue_solver); - ar &const_cast(u.ny); - ar &const_cast(u.nytrue); - ar &const_cast(u.nz); - ar &const_cast(u.nztrue); - ar &const_cast(u.ne); - ar &const_cast(u.nw); - ar &const_cast(u.ndwdx); - ar &const_cast(u.ndwdp); - ar &const_cast(u.ndxdotdw); - ar &const_cast(u.nnz); - ar &const_cast(u.nJ); - ar &const_cast(u.ubw); - ar &const_cast(u.lbw); - ar &const_cast(u.o2mode); - ar &const_cast &>(u.z2event); - ar &const_cast &>(u.idlist); + ar &u.nx_rdata; + ar &u.nxtrue_rdata; + ar &u.nx_solver; + ar &u.nxtrue_solver; + ar &u.ny; + ar &u.nytrue; + ar &u.nz; + ar &u.nztrue; + ar &u.ne; + ar &u.nw; + ar &u.ndwdx; + ar &u.ndwdp; + ar &u.ndxdotdw; + ar &u.nnz; + ar &u.nJ; + ar &u.ubw; + ar &u.lbw; + ar &u.o2mode; + ar &u.z2event; + ar &u.idlist; ar &u.h; ar &u.unscaledParameters; ar &u.originalParameters; @@ -112,28 +112,27 @@ void serialize(Archive &ar, amici::Model &u, const unsigned int version) { template void serialize(Archive &ar, amici::ReturnData &r, const unsigned int version) { - - ar &const_cast(r.np); - ar &const_cast(r.nk); - ar &const_cast(r.nx); - ar &const_cast(r.nx_solver); - ar &const_cast(r.nxtrue); - ar &const_cast(r.ny); - ar &const_cast(r.nytrue); - ar &const_cast(r.nz); - ar &const_cast(r.nztrue); - ar &const_cast(r.ne); - ar &const_cast(r.nJ); - ar &const_cast(r.nplist); - ar &const_cast(r.nmaxevent); - ar &const_cast(r.nt); - ar &const_cast(r.newton_maxsteps); + ar &r.np; + ar &r.nk; + ar &r.nx; + ar &r.nx_solver; + ar &r.nxtrue; + ar &r.ny; + ar &r.nytrue; + ar &r.nz; + ar &r.nztrue; + ar &r.ne; + ar &r.nJ; + ar &r.nplist; + ar &r.nmaxevent; + ar &r.nt; + ar &r.newton_maxsteps; ar &r.pscale; - ar &const_cast(r.o2mode); - ar &const_cast(r.sensi); - ar &const_cast(r.sensi_meth); + ar &r.o2mode; + ar &r.sensi; + ar &r.sensi_meth; - ar &const_cast &>(r.ts); + ar &r.ts; ar &r.xdot; ar &r.J; ar &r.z & r.sigmaz; @@ -252,7 +251,7 @@ std::string serializeToString(T const& data) { ::boost::iostreams::back_insert_device> s(inserter); ::boost::archive::binary_oarchive oar(s); - + oar << data; s.flush(); @@ -280,9 +279,9 @@ std::vector serializeToStdVec(T const& data) { oar << data; s.flush(); - + std::vector buf(serialized.begin(), serialized.end()); - + return buf; } catch(::boost::archive::archive_exception const& e) { throw AmiException("Serialization to StdVec failed: %s", e.what()); @@ -305,7 +304,7 @@ T deserializeFromString(std::string const& serialized) { device); ::boost::archive::binary_iarchive iar(s); T deserialized; - + iar >> deserialized; return deserialized; diff --git a/include/amici/solver.h b/include/amici/solver.h index b5b9b89ac1..0e3fd13177 100644 --- a/include/amici/solver.h +++ b/include/amici/solver.h @@ -1,13 +1,13 @@ #ifndef AMICI_SOLVER_H #define AMICI_SOLVER_H -#include "amici/vector.h" #include "amici/defines.h" -#include "amici/symbolic_functions.h" #include "amici/sundials_linsol_wrapper.h" +#include "amici/symbolic_functions.h" +#include "amici/vector.h" -#include #include +#include namespace amici { @@ -18,20 +18,23 @@ class Model; class Solver; } // namespace amici - // for serialization friend in Solver -namespace boost { namespace serialization { +namespace boost { +namespace serialization { template -void serialize(Archive &ar, amici::Solver &u, const unsigned int version); -}} // namespace boost::serialization - +void serialize(Archive &ar, amici::Solver &u, unsigned int version); +} +} // namespace boost::serialization namespace amici { /** - * The Solver class provides a generic interface to CVode and IDA solvers, + * The Solver class provides a generic interface to CVODES and IDAS solvers, * individual realizations are realized in the CVodeSolver and the IDASolver - * class. + * class. All transient private/protected members (CVODES/IDAS memory, interface + * variables and status flags) are specified as mutable and not included in + * serialization or equality checks. No solver setting parameter should be + * marked mutable. * * NOTE: Any changes in data members here must be propagated to copy ctor, * equality operator, serialization functions in serialization.h, and @@ -53,36 +56,59 @@ class Solver { * @brief Clone this instance * @return The clone */ - virtual Solver* clone() const = 0; + virtual Solver *clone() const = 0; /** - * @brief Initialises the ami memory object and applies specified options - * @param x state vector - * @param dx state derivative vector (DAE only) - * @param sx state sensitivity vector - * @param sdx state derivative sensitivity vector (DAE only) - * @param model pointer to the model object + * @brief runs a forward simulation until the specified timepoint + * + * @param tout next timepooint + * @return status flag */ + int run(realtype tout) const; - void setup(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, - AmiVectorArray *sdx, Model *model); + /** + * @brief makes a single step in the simulation + * + * @param tout next timepooint + * @return status flag + */ + int step(realtype tout) const; /** - * @brief Initialises the AMI memory object for the backwards problem - * @param bwd pointer to backward problem - * @param model pointer to the model object + * @brief runs a backward simulation until the specified timepoint + * + * @param tout next timepooint + * @return status flag + */ + void runB(realtype tout) const; + + /** + * @brief Initialises the ami memory object and applies specified options + * @param t0 initial timepoint + * @param model pointer to the model instance + * @param x0 initial states + * @param dx0 initial derivative states + * @param sx0 initial state sensitivities + * @param sdx0 initial derivative state sensitivities */ - void setupB(BackwardProblem *bwd, Model *model); + void setup(realtype t0, Model *model, const AmiVector &x0, + const AmiVector &dx0, const AmiVectorArray &sx0, + const AmiVectorArray &sdx0) const; /** - * @brief Extracts diagnosis information from solver memory block and - * writes them into the return data instance - * - * @param tret time at which the sensitivities should be computed - * @param yySout vector with sensitivities + * @brief Initialises the AMI memory object for the backwards problem + * @param which index of the backward problem, will be set by this routine + * @param tf final timepoint (initial timepoint for the bwd problem) + * @param model pointer to the model instance + * @param xB0 initial adjoint states + * @param dxB0 initial adjoint derivative states + * @param xQB0 initial adjoint quadratures */ - virtual void getSens(realtype *tret, AmiVectorArray *yySout) const = 0; + + void setupB(int *which, realtype tf, Model *model, + const AmiVector &xB0, const AmiVector &dxB0, + const AmiVector &xQB0) const; /** * @brief Extracts diagnosis information from solver memory block and @@ -91,7 +117,7 @@ class Solver { * @param it time-point index * @param rdata pointer to the return data object */ - void getDiagnosis(const int it, ReturnData *rdata) const; + void getDiagnosis(int it, ReturnData *rdata) const; /** * @brief Extracts diagnosis information from solver memory block and @@ -101,7 +127,7 @@ class Solver { * @param rdata pointer to the return data object * @param which identifier of the backwards problem */ - void getDiagnosisB(const int it, ReturnData *rdata, int which) const; + void getDiagnosisB(int it, ReturnData *rdata, int which) const; /** * getRootInfo extracts information which event occured @@ -112,155 +138,35 @@ class Solver { virtual void getRootInfo(int *rootsfound) const = 0; /** - * @brief Reinitializes the states in the solver after an event occurence + * @brief Calculates consistent initial conditions, assumes initial + * states to be correct (DAE only) * - * @param t0 new timepoint - * @param yy0 new state variables - * @param yp0 new derivative state variables (DAE only) - */ - virtual void reInit(realtype t0, AmiVector *yy0, AmiVector *yp0) = 0; - - /** - * @brief reInitPostProcessF - * @param t - * @param yout - * @param ypout - * @param tnext - */ - virtual void reInitPostProcessF(realtype *t, AmiVector *yout, - AmiVector *ypout, realtype tnext) = 0; - - /** - * @brief reInitPostProcessB - * @param which - * @param t - * @param yBout - * @param ypBout - * @param tnext + * @param tout1 next timepoint to be computed (sets timescale) */ - virtual void reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector *ypBout, realtype tnext) = 0; + virtual void calcIC(realtype tout1) const = 0; /** - * @brief Reinitializes the state sensitivites in the solver after an - * event occurence + * @brief Calculates consistent initial conditions for the backwards + * problem, assumes initial states to be correct (DAE only) * - * @param yS0 new state sensitivity - * @param ypS0 new derivative state sensitivities (DAE only) + * @param which identifier of the backwards problem + * @param tout1 next timepoint to be computed (sets timescale) */ - virtual void sensReInit(AmiVectorArray *yS0, AmiVectorArray *ypS0) = 0; + virtual void calcICB(int which, realtype tout1) const = 0; /** - * @brief Calculates consistent initial conditions, assumes initial - * states to be correct (DAE only) + * @brief Solves the backward problem until a predefined timepoint + * (adjoint only) * - * @param tout1 next timepoint to be computed (sets timescale) - * @param x initial state variables - * @param dx initial derivative state variables (DAE only) - */ - virtual void calcIC(realtype tout1, AmiVector *x, AmiVector *dx) = 0; - - /** - * @brief Calculates consistent initial conditions for the backwards - * problem, assumes initial states to be correct (DAE only) - * - * @param which identifier of the backwards problem - * @param tout1 next timepoint to be computed (sets timescale) - * @param xB states of final solution of the forward problem - * @param dxB derivative states of final solution of the forward - * problem (DAE only) - */ - virtual void calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) = 0; - - /** - * @brief Solves the forward problem until a predefined timepoint - * - * @param tout timepoint until which simulation should be performed - * @param yret states - * @param ypret derivative states (DAE only) - * @param tret pointer to the time variable - * @param itask task identifier, can be CV_NORMAL or CV_ONE_STEP - * @return status flag indicating success of execution - */ - virtual int solve(realtype tout, AmiVector *yret, AmiVector *ypret, - realtype *tret, int itask) = 0; - - /** - * @brief Solves the forward problem until a predefined timepoint - * (adjoint only) - * - * @param tout timepoint until which simulation should be performed - * @param yret states - * @param ypret derivative states (DAE only) - * @param tret pointer to the time variable - * @param itask task identifier, can be CV_NORMAL or CV_ONE_STEP - * @param ncheckPtr pointer to a number that counts the internal - * checkpoints - * @return status flag indicating success of execution - */ - virtual int solveF(realtype tout, AmiVector *yret, AmiVector *ypret, - realtype *tret, int itask, int *ncheckPtr) = 0; - - /** - * @brief Solves the backward problem until a predefined timepoint - * (adjoint only) - * - * @param tBout timepoint until which simulation should be performed - * @param itaskB task identifier, can be CV_NORMAL or CV_ONE_STEP - */ - virtual void solveB(realtype tBout, int itaskB) = 0; - - /** - * @brief Sets a timepoint at which the simulation will be stopped - * - * @param tstop timepoint until which simulation should be performed - */ - virtual void setStopTime(realtype tstop) = 0; - - /** - * @brief Reinitializes the adjoint states after an event occurence - * - * @param which identifier of the backwards problem - * @param tB0 new timepoint - * @param yyB0 new adjoint state variables - * @param ypB0 new adjoint derivative state variables (DAE only) - */ - virtual void reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector *ypB0) = 0; - - /** - * @brief Returns the current adjoint states - * - * @param which identifier of the backwards problem - * @param tret time at which the adjoint states should be computed - * @param yy adjoint state variables - * @param yp adjoint derivative state variables (DAE only) - */ - virtual void getB(int which, realtype *tret, AmiVector *yy, - AmiVector *yp) const = 0; - - /** - * @brief Returns the current adjoint states - * - * @param which identifier of the backwards problem - * @param tret time at which the adjoint states should be computed - * @param qB adjoint quadrature state variables - */ - virtual void getQuadB(int which, realtype *tret, AmiVector *qB) const = 0; - - /** - * @brief Reinitialize the adjoint states after an event occurence - * - * @param which identifier of the backwards problem - * @param yQB0 new adjoint quadrature state variables - */ - virtual void quadReInitB(int which, AmiVector *yQB0) = 0; + * @param tBout timepoint until which simulation should be performed + * @param itaskB task identifier, can be CV_NORMAL or CV_ONE_STEP + */ + virtual void solveB(realtype tBout, int itaskB) const = 0; /** - * @brief Disable rootfinding - */ - virtual void turnOffRootFinding() = 0; + * @brief Disable rootfinding + */ + virtual void turnOffRootFinding() const = 0; /** * @brief Return current sensitivity method @@ -367,7 +273,8 @@ class Solver { void setAbsoluteTolerance(double atol); /** - * @brief Returns the relative tolerances for the forward sensitivity problem + * @brief Returns the relative tolerances for the forward sensitivity + * problem * @return relative tolerances */ double getRelativeToleranceFSA() const; @@ -379,7 +286,8 @@ class Solver { void setRelativeToleranceFSA(double rtol); /** - * @brief Returns the absolute tolerances for the forward sensitivity problem + * @brief Returns the absolute tolerances for the forward sensitivity + * problem * @return absolute tolerances */ double getAbsoluteToleranceFSA() const; @@ -391,7 +299,8 @@ class Solver { void setAbsoluteToleranceFSA(double atol); /** - * @brief Returns the relative tolerances for the adjoint sensitivity problem + * @brief Returns the relative tolerances for the adjoint sensitivity + * problem * @return relative tolerances */ double getRelativeToleranceB() const; @@ -497,25 +406,26 @@ class Solver { * problem * @return maximum number of solver steps */ - int getMaxSteps() const; + long int getMaxSteps() const; /** * @brief sets the maximum number of solver steps for the forward problem * @param maxsteps maximum number of solver steps (non-negative number) */ - void setMaxSteps(int maxsteps); + void setMaxSteps(long int maxsteps); /** * @brief returns the maximum number of solver steps for the backward * problem * @return maximum number of solver steps */ - int getMaxStepsBackwardProblem() const; + long int getMaxStepsBackwardProblem() const; + /** * @brief sets the maximum number of solver steps for the backward problem * @param maxsteps maximum number of solver steps (non-negative number) */ - void setMaxStepsBackwardProblem(int maxsteps); + void setMaxStepsBackwardProblem(long int maxsteps); /** * @brief returns the linear system multistep method @@ -609,6 +519,136 @@ class Solver { */ void setInternalSensitivityMethod(InternalSensitivityMethod ism); + /** + * @brief write solution from forward simulation + * @param t time + * @param x state + * @param dx derivative state + * @param sx state sensitivity + */ + void writeSolution(realtype *t, AmiVector &x, AmiVector &dx, + AmiVectorArray &sx) const; + + /** + * @brief write solution from forward simulation + * @param t time + * @param xB adjoint state + * @param dxB adjoint derivative state + * @param xQB adjoint quadrature + * @param which index of adjoint problem + */ + void writeSolutionB(realtype *t, AmiVector &xB, AmiVector &dxB, + AmiVector &xQB, int which) const; + + /** + * @brief Access state solution at time t + * @param t time + * @return x or interpolated solution dky + */ + const AmiVector &getState(realtype t) const; + + /** + * @brief Access derivative state solution at time t + * @param t time + * @return dx or interpolated solution dky + */ + const AmiVector &getDerivativeState(realtype t) const; + + /** + * @brief Access state sensitivity solution at time t + * @param t time + * @return (interpolated) solution sx + */ + const AmiVectorArray &getStateSensitivity(realtype t) const; + + /** + * @brief Access adjoint solution at time t + * @param which adjoint problem index + * @param t time + * @return (interpolated) solution xB + */ + const AmiVector &getAdjointState(int which, realtype t) const; + + /** + * @brief Access adjoint derivative solution at time t + * @param which adjoint problem index + * @param t time + * @return (interpolated) solution dxB + */ + const AmiVector &getAdjointDerivativeState(int which, + realtype t) const; + + /** + * @brief Access adjoint quadrature solution at time t + * @param which adjoint problem index + * @param t time + * @return (interpolated) solution xQB + */ + const AmiVector &getAdjointQuadrature(int which, realtype t) const; + + /** + * @brief Reinitializes the states in the solver after an event occurence + * + * @param t0 reinitialization timepoint + * @param yy0 inital state variables + * @param yp0 initial derivative state variables (DAE only) + */ + virtual void reInit(realtype t0, const AmiVector &yy0, + const AmiVector &yp0) const = 0; + + /** + * @brief Reinitializes the state sensitivites in the solver after an + * event occurence + * + * @param yyS0 new state sensitivity + * @param ypS0 new derivative state sensitivities (DAE only) + */ + virtual void sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray &ypS0) const = 0; + + /** + * @brief Reinitializes the adjoint states after an event occurence + * + * @param which identifier of the backwards problem + * @param tB0 reinitialization timepoint + * @param yyB0 new adjoint state + * @param ypB0 new adjoint derivative state + */ + virtual void reInitB(int which, realtype tB0, + const AmiVector &yyB0, const AmiVector &ypB0) const = 0; + + /** + * @brief Reinitialize the adjoint states after an event occurence + * + * @param which identifier of the backwards problem + * @param yQB0 new adjoint quadrature state + */ + virtual void quadReInitB(int which, const AmiVector &yQB0) const = 0; + + /** + * @brief current solver timepoint + * @return t + */ + realtype gett() const; + + /** + * @brief number of states with which the solver was initialized + * @return x.getLength() + */ + int nx() const; + + /** + * @brief number of parameters with which the solver was initialized + * @return sx.getLength() + */ + int nplist() const; + + /** + * @brief number of quadratures with which the solver was initialized + * @return xQB.getLength() + */ + int nquad() const; + /** * @brief Serialize Solver (see boost::serialization::serialize) * @param ar Archive to serialize to @@ -616,7 +656,8 @@ class Solver { * @param version Version number */ template - friend void boost::serialization::serialize(Archive &ar, Solver &r, const unsigned int version); + friend void boost::serialization::serialize(Archive &ar, Solver &r, + unsigned int version); /** * @brief Check equality of data members excluding solver memory @@ -624,128 +665,175 @@ class Solver { * @param b * @return */ - friend bool operator ==(const Solver &a, const Solver &b); + friend bool operator==(const Solver &a, const Solver &b); protected: + /** + * @brief Sets a timepoint at which the simulation will be stopped + * + * @param tstop timepoint until which simulation should be performed + */ + virtual void setStopTime(realtype tstop) const = 0; + + /** + * @brief Solves the forward problem until a predefined timepoint + * + * @param tout timepoint until which simulation should be performed + * @param itask task identifier, can be CV_NORMAL or CV_ONE_STEP + * @return status flag indicating success of execution + */ + virtual int solve(realtype tout, int itask) const = 0; + + /** + * @brief Solves the forward problem until a predefined timepoint + * (adjoint only) + * + * @param tout timepoint until which simulation should be performed + * @param itask task identifier, can be CV_NORMAL or CV_ONE_STEP + * @param ncheckPtr pointer to a number that counts the internal + * checkpoints + * @return status flag indicating success of execution + */ + virtual int solveF(realtype tout, int itask, + int *ncheckPtr) const = 0; + + /** + * @brief reInitPostProcessF postprocessing of the solver memory after a + * discontinuity in the forward problem + * @param tnext next timepoint (defines integration direction) + */ + virtual void reInitPostProcessF(realtype tnext) const = 0; + + /** + * @brief reInitPostProcessB postprocessing of the solver memory after a + * discontinuity in the backward problem + * @param tnext next timepoint (defines integration direction) + */ + virtual void reInitPostProcessB(realtype tnext) const = 0; + + /** + * @brief extracts the state sensitivity at the current timepoint from + * solver memory and writes it to the sx member variable + */ + virtual void getSens() const = 0; + + /** + * @brief extracts the adjoint state at the current timepoint from + * solver memory and writes it to the xB member variable + * @param which index of the backwards problem + */ + virtual void getB(int which) const = 0; + + /** + * @brief extracts the adjoint quadrature state at the current timepoint + * from solver memory and writes it to the xQB member variable + * @param which index of the backwards problem + */ + virtual void getQuadB(int which) const = 0; + /** * @brief Initialises the states at the specified initial timepoint * - * @param x initial state variables - * @param dx initial derivative state variables (DAE only) - * @param t initial timepoint + * @param t0 initial timepoint + * @param x0 initial states + * @param dx0 initial derivative states + */ + virtual void init(realtype t0, const AmiVector &x0, + const AmiVector &dx0) const = 0; + + /** + * @brief initialises the forward sensitivities + * @param sx0 initial states semsitivities + * @param sdx0 initial derivative states sensitivities */ - virtual void init(AmiVector *x, AmiVector *dx, realtype t) = 0; + virtual void sensInit1(const AmiVectorArray &sx0, + const AmiVectorArray &sdx0) const = 0; /** * @brief Initialise the adjoint states at the specified final timepoint * * @param which identifier of the backwards problem - * @param xB initial adjoint state variables - * @param dxB initial adjoint derivative state variables (DAE only) - * @param t final timepoint + * @param tf final timepoint + * @param xB0 initial adjoint state + * @param dxB0 initial adjoint derivative state */ - virtual void binit(int which, AmiVector *xB, AmiVector *dxB, realtype t) = 0; + virtual void binit(int which, realtype tf, const AmiVector &xB0, + const AmiVector &dxB0) const = 0; /** * @brief Initialise the quadrature states at the specified final timepoint * * @param which identifier of the backwards problem - * @param qBdot initial adjoint quadrature state variables + * @param xQB0 intial adjoint quadrature state */ - virtual void qbinit(int which, AmiVector *qBdot) = 0; + virtual void qbinit(int which, const AmiVector &xQB0) const = 0; /** * @brief Initialises the rootfinding for events * * @param ne number of different events */ - virtual void rootInit(int ne) = 0; - - /** - * @brief initialises the sensitivities at the specified initial - * timepoint - * - * @param sx initial state sensitivities - * @param sdx initial derivative state sensitivities (DAE only) - * @param nplist number parameter wrt which sensitivities are to be computed - */ - virtual void sensInit1(AmiVectorArray *sx, AmiVectorArray *sdx, int nplist) = 0; + virtual void rootInit(int ne) const = 0; /** * @brief Initalize non-linear solver for sensitivities - * @param x - * @param model + * @param model Model instance */ - void initalizeNonLinearSolverSens(AmiVector *x, Model *model); + void initializeNonLinearSolverSens(const Model *model) const; /** * @brief Set the dense Jacobian function */ - virtual void setDenseJacFn() = 0; + virtual void setDenseJacFn() const = 0; /** * @brief sets the sparse Jacobian function */ - virtual void setSparseJacFn() = 0; + virtual void setSparseJacFn() const = 0; /** * @brief sets the banded Jacobian function */ - virtual void setBandJacFn() = 0; + virtual void setBandJacFn() const = 0; /** * @brief sets the Jacobian vector multiplication function */ - virtual void setJacTimesVecFn() = 0; + virtual void setJacTimesVecFn() const = 0; /** * @brief sets the dense Jacobian function * * @param which identifier of the backwards problem */ - virtual void setDenseJacFnB(int which) = 0; + virtual void setDenseJacFnB(int which) const = 0; /** * @brief sets the sparse Jacobian function * * @param which identifier of the backwards problem */ - virtual void setSparseJacFnB(int which) = 0; + virtual void setSparseJacFnB(int which) const = 0; /** * @brief sets the banded Jacobian function * * @param which identifier of the backwards problem */ - virtual void setBandJacFnB(int which) = 0; + virtual void setBandJacFnB(int which) const = 0; /** * @brief sets the Jacobian vector multiplication function * * @param which identifier of the backwards problem */ - virtual void setJacTimesVecFnB(int which) = 0; - - /** - * @brief Extracts diagnosis information from solver memory block and - * writes them into the return data object for the backward problem - * - * @param error_code error identifier - * @param module name of the module in which the error occured - * @param function name of the function in which the error occured @type - * char - * @param msg error message - * @param eh_data unused input - */ - static void wrapErrHandlerFn(int error_code, const char *module, - const char *function, char *msg, - void *eh_data); + virtual void setJacTimesVecFnB(int which) const = 0; /** * @brief Create specifies solver method and initializes solver memory for * the forward problem */ - virtual void allocateSolver() = 0; + virtual void allocateSolver() const = 0; /** * @brief sets scalar relative and absolute tolerances for the forward @@ -754,7 +842,8 @@ class Solver { * @param rtol relative tolerances * @param atol absolute tolerances */ - virtual void setSStolerances(double rtol, double atol) = 0; + virtual void setSStolerances(double rtol, + double atol) const = 0; /** * @brief activates sets scalar relative and absolute tolerances for the @@ -763,7 +852,8 @@ class Solver { * @param rtol relative tolerances * @param atol array of absolute tolerances for every sensitivy variable */ - virtual void setSensSStolerances(double rtol, double *atol) = 0; + virtual void setSensSStolerances(double rtol, + const double *atol) const = 0; /** * SetSensErrCon specifies whether error control is also enforced for @@ -771,7 +861,7 @@ class Solver { * * @param error_corr activation flag */ - virtual void setSensErrCon(bool error_corr) = 0; + virtual void setSensErrCon(bool error_corr) const = 0; /** * @brief Specifies whether error control is also enforced for the @@ -780,31 +870,31 @@ class Solver { * @param which identifier of the backwards problem * @param flag activation flag */ - virtual void setQuadErrConB(int which, bool flag) = 0; + virtual void setQuadErrConB(int which, bool flag) const = 0; /** * @brief Attaches the error handler function (errMsgIdAndTxt) * to the solver * */ - virtual void setErrHandlerFn() = 0; + virtual void setErrHandlerFn() const = 0; /** * @brief Attaches the user data instance (here this is a Model) to the * forward problem * - * @param model Model instance, + * @param model Model instance */ - virtual void setUserData(Model *model) = 0; + virtual void setUserData(Model *model) const = 0; /** * @brief attaches the user data instance (here this is a Model) to the * backward problem * * @param which identifier of the backwards problem - * @param model Model instance, + * @param model Model instance */ - virtual void setUserDataB(int which, Model *model) = 0; + virtual void setUserDataB(int which, Model *model) const = 0; /** * @brief specifies the maximum number of steps for the forward @@ -812,7 +902,7 @@ class Solver { * * @param mxsteps number of steps */ - virtual void setMaxNumSteps(long int mxsteps) = 0; + virtual void setMaxNumSteps(long int mxsteps) const = 0; /** * @brief specifies the maximum number of steps for the forward @@ -821,7 +911,7 @@ class Solver { * @param which identifier of the backwards problem * @param mxstepsB number of steps */ - virtual void setMaxNumStepsB(int which, long int mxstepsB) = 0; + virtual void setMaxNumStepsB(int which, long int mxstepsB) const = 0; /** * @brief activates stability limit detection for the forward @@ -830,7 +920,7 @@ class Solver { * @param stldet flag for stability limit detection (TRUE or FALSE) * */ - virtual void setStabLimDet(int stldet) = 0; + virtual void setStabLimDet(int stldet) const = 0; /** * @brief activates stability limit detection for the backward @@ -840,21 +930,21 @@ class Solver { * @param stldet flag for stability limit detection (TRUE or FALSE) * */ - virtual void setStabLimDetB(int which, int stldet) = 0; + virtual void setStabLimDetB(int which, int stldet) const = 0; /** * @brief specify algebraic/differential components (DAE only) * * @param model model specification */ - virtual void setId(Model *model) = 0; + virtual void setId(const Model *model) const = 0; /** * @brief deactivates error control for algebraic components (DAE only) * * @param flag deactivation flag */ - virtual void setSuppressAlg(bool flag) = 0; + virtual void setSuppressAlg(bool flag) const = 0; /** * @brief specifies the scaling and indexes for sensitivity @@ -864,7 +954,37 @@ class Solver { * @param pbar parameter scaling constants * @param plist parameter index list */ - virtual void setSensParams(realtype *p, realtype *pbar, int *plist) = 0; + virtual void setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const = 0; + + /** + * @brief interpolates the (derivative of the) solution at the requested + * timepoint + * + * @param t timepoint + * @param k derivative order + */ + virtual void getDky(realtype t, int k) const = 0; + + /** + * @brief interpolates the (derivative of the) solution at the requested + * timepoint + * + * @param t timepoint + * @param k derivative order + * @param which index of backward problem + */ + virtual void getDkyB(realtype t, int k, + int which) const = 0; + + /** + * @brief interpolates the (derivative of the) solution at the requested + * timepoint + * + * @param t timepoint + * @param k derivative order + */ + virtual void getSensDky(realtype t, int k) const = 0; /** * @brief interpolates the (derivative of the) solution at the requested @@ -872,15 +992,16 @@ class Solver { * * @param t timepoint * @param k derivative order - * @param dky interpolated solution + * @param which index of backward problem */ - virtual void getDky(realtype t, int k, AmiVector *dky) const = 0; + virtual void getQuadDkyB(realtype t, int k, + int which) const = 0; /** * @brief initializes the adjoint problem * */ - virtual void adjInit() = 0; + virtual void adjInit() const = 0; /** * @brief Specifies solver method and initializes solver memory for the @@ -888,7 +1009,7 @@ class Solver { * * @param which identifier of the backwards problem */ - virtual void allocateSolverB(int *which) = 0; + virtual void allocateSolverB(int *which) const = 0; /** * @brief sets relative and absolute tolerances for the backward @@ -899,7 +1020,7 @@ class Solver { * @param absTolB absolute tolerances */ virtual void setSStolerancesB(int which, realtype relTolB, - realtype absTolB) = 0; + realtype absTolB) const = 0; /** * @brief sets relative and absolute tolerances for the quadrature @@ -910,7 +1031,7 @@ class Solver { * @param abstolQB absolute tolerances */ virtual void quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) = 0; + realtype abstolQB) const = 0; /** * @brief reports the number of solver steps @@ -919,7 +1040,7 @@ class Solver { * forward or backward problem) * @param numsteps output array */ - virtual void getNumSteps(void *ami_mem, long int *numsteps) const = 0; + virtual void getNumSteps(const void *ami_mem, long int *numsteps) const = 0; /** * @brief reports the number of right hand evaluations @@ -928,7 +1049,8 @@ class Solver { * forward or backward problem) * @param numrhsevals output array */ - virtual void getNumRhsEvals(void *ami_mem, long int *numrhsevals) const = 0; + virtual void getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const = 0; /** * @brief reports the number of local error test failures @@ -937,8 +1059,8 @@ class Solver { * forward or backward problem) * @param numerrtestfails output array */ - virtual void getNumErrTestFails(void *ami_mem, - long int *numerrtestfails) const = 0; + virtual void getNumErrTestFails(const void *ami_mem, + long int *numerrtestfails) const = 0; /** * @brief reports the number of nonlinear convergence failures @@ -948,8 +1070,8 @@ class Solver { * @param numnonlinsolvconvfails output array */ virtual void - getNumNonlinSolvConvFails(void *ami_mem, - long int *numnonlinsolvconvfails) const = 0; + getNumNonlinSolvConvFails(const void *ami_mem, + long int *numnonlinsolvconvfails) const = 0; /** * @brief Reports the order of the integration method during the @@ -959,137 +1081,145 @@ class Solver { * forward or backward problem) * @param order output array */ - virtual void getLastOrder(void *ami_mem, int *order) const = 0; + virtual void getLastOrder(const void *ami_mem, int *order) const = 0; /** * @brief Initializes and sets the linear solver for the forward problem * * @param model pointer to the model object - * @param x */ - void initializeLinearSolver(const Model *model, AmiVector *x); + void initializeLinearSolver(const Model *model) const; /** * @brief Sets the non-linear solver - * @param x */ - void initializeNonLinearSolver(AmiVector *x); + void initializeNonLinearSolver() const; /** * @brief Sets the linear solver for the forward problem */ - virtual void setLinearSolver() = 0; + virtual void setLinearSolver() const = 0; /** * @brief Sets the linear solver for the backward problem - * @param which + * @param which index of the backward problem */ - virtual void setLinearSolverB(int which) = 0; + virtual void setLinearSolverB(int which) const = 0; /** * @brief Set the non-linear solver for the forward problem */ - virtual void setNonLinearSolver() = 0; + virtual void setNonLinearSolver() const = 0; /** * @brief Set the non-linear solver for the backward problem - * @param which + * @param which index of the backward problem */ - virtual void setNonLinearSolverB(int which) = 0; + virtual void setNonLinearSolverB(int which) const = 0; /** * @brief Set the non-linear solver for sensitivities */ - virtual void setNonLinearSolverSens() = 0; + virtual void setNonLinearSolverSens() const = 0; /** * @brief Initializes the linear solver for the backward problem * * @param model pointer to the model object - * @param xB * @param which index of the backward problem */ - void initializeLinearSolverB(const Model *model, AmiVector *xB, - const int which); + void initializeLinearSolverB(const Model *model, int which) const; /** * @brief Initializes the non-linear solver for the backward problem - * @param xB - * @param which + * @param which index of the backward problem */ - void initializeNonLinearSolverB(AmiVector *xB, const int which); + void initializeNonLinearSolverB(int which) const; /** - * @brief Accessor function to the number of sensitivity parameters in the - * model stored in the user data + * Accessor function to the model stored in the user data * - * @return number of sensitivity parameters + * @return user data model */ - virtual int nplist() const = 0; + virtual const Model *getModel() const = 0; + /** - * @brief Accessor function to the number of state variables in the model - * stored in the user data + * @brief checks whether memory for the forward problem has been allocated * - * @return number of state variables + * @return proxy for solverMemory->(cv|ida)_MallocDone */ - virtual int nx() const = 0; + bool getInitDone() const; + /** - * Accessor function to the model stored in the user data + * @brief checks whether memory for forward sensitivities has been allocated * - * @return user data model + * @return proxy for solverMemory->(cv|ida)_SensMallocDone */ - virtual const Model *getModel() const = 0; + bool getSensInitDone() const; /** - * @brief checks whether memory for the forward problem has been allocated + * @brief checks whether memory for forward interpolation has been allocated * - * @return solverMemory->(cv|ida)__MallocDone + * @return proxy for solverMemory->(cv|ida)_adjMallocDone */ - virtual bool getMallocDone() const = 0; + bool getAdjInitDone() const; /** * @brief checks whether memory for the backward problem has been allocated - * - * @return solverMemory->(cv|ida)__adjMallocDone + * @param which adjoint problem index + * @return proxy for solverMemoryB->(cv|ida)_MallocDone + */ + bool getInitDoneB(int which) const; + + /** + * @brief checks whether memory for backward quadratures has been allocated + * @param which adjoint problem index + * @return proxy for solverMemoryB->(cv|ida)_QuadMallocDone */ - virtual bool getAdjMallocDone() const = 0; + bool getQuadInitDoneB(int which) const; /** * @brief attaches a diagonal linear solver to the forward problem */ - virtual void diag() = 0; + virtual void diag() const = 0; /** * @brief attaches a diagonal linear solver to the backward problem * * @param which identifier of the backwards problem */ - virtual void diagB(int which) = 0; + virtual void diagB(int which) const = 0; - -protected: + /** + * @brief resets solverMemory and solverMemoryB + * @param nx new number of state variables + * @param nplist new number of sensitivity parameters + * @param nquad new number of quadratures (only differs from nplist for + * higher order senisitivity computation) + */ + void resetMutableMemory(int nx, int nplist, int nquad) const; /** - * @brief retrieves the solver memory instance for the backward problem + * @brief Retrieves the solver memory instance for the backward problem * * @param which identifier of the backwards problem * @param ami_mem pointer to the forward solver memory instance - * @return ami_memB pointer to the backward solver memory instance + * @return pointer to the backward solver memory instance */ - virtual void *getAdjBmem(void *ami_mem, int which) = 0; + virtual void *getAdjBmem(void *ami_mem, int which) const = 0; /** * @brief updates solver tolerances according to the currently specified * member variables */ - void applyTolerances(); + void applyTolerances() const; /** * @brief updates FSA solver tolerances according to the currently * specified member variables */ - void applyTolerancesFSA(); + void applyTolerancesFSA() const; /** * @brief updates ASA solver tolerances according to the currently @@ -1097,7 +1227,7 @@ class Solver { * * @param which identifier of the backwards problem */ - void applyTolerancesASA(int which); + void applyTolerancesASA(int which) const; /** * @brief updates ASA quadrature solver tolerances according to the @@ -1105,23 +1235,20 @@ class Solver { * * @param which identifier of the backwards problem */ - void applyQuadTolerancesASA(int which); + void applyQuadTolerancesASA(int which) const; /** * @brief updates all senstivivity solver tolerances according to the * currently specified member variables */ - void applySensitivityTolerances(); - + void applySensitivityTolerances() const; /** pointer to solver memory block */ - std::unique_ptr> solverMemory; + mutable std::unique_ptr> solverMemory; /** pointer to solver memory block */ - std::vector>> solverMemoryB; - - /** flag indicating whether the solver was called */ - bool solverWasCalled = false; + mutable std::vector>> + solverMemoryB; /** internal sensitivity method flag used to select the sensitivity solution * method. Only applies for Forward Sensitivities. */ @@ -1142,22 +1269,91 @@ class Solver { InterpolationType interpType = InterpolationType::hermite; /** maximum number of allowed integration steps */ - int maxsteps = 10000; + long int maxsteps = 10000; /** linear solver for the forward problem */ - std::unique_ptr linearSolver; + mutable std::unique_ptr linearSolver; + /** linear solver for the backward problem */ - std::unique_ptr linearSolverB; + mutable std::unique_ptr linearSolverB; /** non-linear solver for the forward problem */ - std::unique_ptr nonLinearSolver; + mutable std::unique_ptr nonLinearSolver; + /** non-linear solver for the backward problem */ - std::unique_ptr nonLinearSolverB; - /** non-linear solver for the sensitivities*/ - std::unique_ptr nonLinearSolverSens; + mutable std::unique_ptr nonLinearSolverB; + + /** non-linear solver for the sensitivities */ + mutable std::unique_ptr nonLinearSolverSens; + + /** flag indicating whether the forward solver has been called */ + mutable bool solverWasCalledF = false; + + /** flag indicating whether the backward solver has been called */ + mutable bool solverWasCalledB = false; + /** + * @brief sets that memory for the forward problem has been allocated + */ + void setInitDone() const; -private: + /** + * @brief sets that memory for forward sensitivities has been allocated + */ + void setSensInitDone() const; + + /** + * @brief sets that memory for forward interpolation has been allocated + */ + void setAdjInitDone() const; + + /** + * @brief sets that memory for the backward problem has been allocated + * @param which adjoint problem index + */ + void setInitDoneB(int which) const; + + /** + * @brief sets that memory for backward quadratures has been allocated + * @param which adjoint problem index + */ + void setQuadInitDoneB(int which) const; + + /** state (dimension: nx_solver) */ + mutable AmiVector x = AmiVector(0); + + /** state interface variable (dimension: nx_solver) */ + mutable AmiVector dky = AmiVector(0); + + /** state derivative dummy (dimension: nx_solver) */ + mutable AmiVector dx = AmiVector(0); + + /** state sensititivities interface variable (dimension: nx_solver x nplist) + */ + mutable AmiVectorArray sx = AmiVectorArray(0, 0); + /** state derivative sensititivities dummy (dimension: nx_solver x nplist) + */ + mutable AmiVectorArray sdx = AmiVectorArray(0, 0); + + /** adjoint state interface variable (dimension: nx_solver) */ + mutable AmiVector xB = AmiVector(0); + + /** adjoint derivative dummy variable (dimension: nx_solver) */ + mutable AmiVector dxB = AmiVector(0); + + /** adjoint quadrature interface variable (dimension: nJ x nplist) */ + mutable AmiVector xQB = AmiVector(0); + + /** integration time of the forward problem */ + mutable realtype t; + + /** flag to force reInitPostProcessF before next call to solve */ + mutable bool forceReInitPostProcessF = false; + + /** flag to force reInitPostProcessB before next call to solveB */ + mutable bool forceReInitPostProcessB = false; + + private: /** method for sensitivity computation */ SensitivityMethod sensi_meth = SensitivityMethod::forward; @@ -1169,11 +1365,11 @@ class Solver { int ordering = static_cast(SUNLinSolKLU::StateOrdering::AMD); /** maximum number of allowed Newton steps for steady state computation */ - int newton_maxsteps = 0; + long int newton_maxsteps = 0; /** maximum number of allowed linear steps per Newton step for steady state * computation */ - int newton_maxlinsteps = 0; + long int newton_maxlinsteps = 0; /** Preequilibration of model via Newton solver? */ bool newton_preeq = false; @@ -1182,50 +1378,83 @@ class Solver { LinearSolver linsol = LinearSolver::KLU; /** absolute tolerances for integration */ - double atol = 1e-16; + realtype atol = 1e-16; /** relative tolerances for integration */ - double rtol = 1e-8; + realtype rtol = 1e-8; /** absolute tolerances for forward sensitivity integration */ - double atol_fsa = NAN; + realtype atol_fsa = NAN; /** relative tolerances for forward sensitivity integration */ - double rtol_fsa = NAN; + realtype rtol_fsa = NAN; /** absolute tolerances for adjoint sensitivity integration */ - double atolB = NAN; + realtype atolB = NAN; /** relative tolerances for adjoint sensitivity integration */ - double rtolB = NAN; + realtype rtolB = NAN; /** absolute tolerances for backward quadratures */ - double quad_atol = 1e-12; + realtype quad_atol = 1e-12; /** relative tolerances for backward quadratures */ - double quad_rtol = 1e-8; + realtype quad_rtol = 1e-8; /** absolute tolerances for steadystate computation */ - double ss_atol = NAN; + realtype ss_atol = NAN; /** relative tolerances for steadystate computation */ - double ss_rtol = NAN; + realtype ss_rtol = NAN; /** absolute tolerances for steadystate computation */ - double ss_atol_sensi = NAN; + realtype ss_atol_sensi = NAN; /** relative tolerances for steadystate computation */ - double ss_rtol_sensi = NAN; + realtype ss_rtol_sensi = NAN; /** maximum number of allowed integration steps for backward problem */ - int maxstepsB = 0; + long int maxstepsB = 0; /** flag indicating whether sensitivities are supposed to be computed */ SensitivityOrder sensi = SensitivityOrder::none; + /** flag indicating whether init was called */ + mutable bool initialized = false; + + /** flag indicating whether sensInit1 was called */ + mutable bool sensInitialized = false; + + /** flag indicating whether adjInit was called */ + mutable bool adjInitialized = false; + + /** vector of flags indicating whether binit was called for respective + which */ + mutable std::vector initializedB{false}; + + /** vector of flags indicating whether qbinit was called for respective + which */ + mutable std::vector initializedQB{false}; + + /** number of checkpoints in the forward problem */ + mutable int ncheckPtr; }; -bool operator ==(const Solver &a, const Solver &b); +bool operator==(const Solver &a, const Solver &b); + +/** + * @brief Extracts diagnosis information from solver memory block and + * writes them into the return data object for the backward problem + * + * @param error_code error identifier + * @param module name of the module in which the error occured + * @param function name of the function in which the error occured @type + * char + * @param msg error message + * @param eh_data unused input + */ +void wrapErrHandlerFn(int error_code, const char *module, + const char *function, char *msg, void *eh_data); } // namespace amici diff --git a/include/amici/solver_cvodes.h b/include/amici/solver_cvodes.h index 63796de340..ce2278c3d8 100644 --- a/include/amici/solver_cvodes.h +++ b/include/amici/solver_cvodes.h @@ -1,26 +1,26 @@ #ifndef AMICI_SOLVER_CVODES_h #define AMICI_SOLVER_CVODES_h -#include "amici/solver.h" #include "amici/defines.h" +#include "amici/solver.h" #include "amici/vector.h" #include namespace amici { - class ExpData; class ReturnData; class Model_ODE; class CVodeSolver; -} +} // namespace amici // for serialization friend in Solver -namespace boost { namespace serialization { +namespace boost { +namespace serialization { template -void serialize(Archive &ar, amici::CVodeSolver &u, const unsigned int version); -}} - +void serialize(Archive &ar, amici::CVodeSolver &u, unsigned int version); +} +} // namespace boost::serialization namespace amici { @@ -34,215 +34,174 @@ class CVodeSolver : public Solver { * @brief Clone this instance * @return The clone */ - virtual Solver* clone() const override; - - void reInitPostProcessF(realtype *t, AmiVector *yout, AmiVector *ypout, - realtype tnext) override; - - void reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector *ypBout, realtype tnext) override; + Solver *clone() const override; - void reInit(realtype t0, AmiVector *yy0, AmiVector *yp0) override; + void reInit(realtype t0, const AmiVector &yy0, + const AmiVector &yp0) const override; - void sensReInit( AmiVectorArray *yS0, AmiVectorArray *ypS0) override; + void sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray &ypS0) const override; - void reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector *ypB0) override; + void reInitB(int which, realtype tB0, + const AmiVector &yyB0, const AmiVector &ypB0) const override; - void quadReInitB(int which, AmiVector *yQB0) override; + void quadReInitB(int which, const AmiVector &yQB0) const override; - int solve(realtype tout, AmiVector *yret, AmiVector *ypret, realtype *tret, - int itask) override; + int solve(realtype tout, int itask) const override; - int solveF(realtype tout, AmiVector *yret, AmiVector *ypret, realtype *tret, - int itask, int *ncheckPtr) override; + int solveF(realtype tout, int itask, + int *ncheckPtr) const override; - void solveB(realtype tBout, int itaskB) override; + void solveB(realtype tBout, int itaskB) const override; - void getB(int which, realtype *tret, AmiVector *yy, AmiVector *yp) const override; + void getDky(realtype t, int k) const override; - void getDky(realtype t, int k, AmiVector *dky) const override; + void getSensDky(realtype t, int k) const override; - void getSens(realtype *tret, AmiVectorArray *yySout) const override; + void getQuadDkyB(realtype t, int k, + int which) const override; - void getQuadB(int which, realtype *tret, AmiVector *qB) const override; + void getDkyB(realtype t, int k, int which) const override; void getRootInfo(int *rootsfound) const override; - void calcIC(realtype tout1, AmiVector *x, AmiVector *dx) override; + void setStopTime(realtype tstop) const override; - void calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) override; - - void setStopTime(realtype tstop) override; - - void turnOffRootFinding() override; - - int nplist() const override; - - int nx() const override; + void turnOffRootFinding() const override; const Model *getModel() const override; - bool getMallocDone() const override; - - bool getAdjMallocDone() const override; - - static int fxdot(realtype t, N_Vector x, N_Vector xdot, void *user_data); - - static int fJSparse(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, - void *user_data, N_Vector tmp1, N_Vector tmp2, - N_Vector tmp3); - - static int fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, - void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); - - using Solver::setLinearSolver; + using Solver::setLinearSolverB; - void setLinearSolver() override; + void setLinearSolver() const override; - void setLinearSolverB(int which) override; + void setLinearSolverB(int which) const override; - void setNonLinearSolver() override; + void setNonLinearSolver() const override; - void setNonLinearSolverSens() override; + void setNonLinearSolverSens() const override; - void setNonLinearSolverB(int which) override; + void setNonLinearSolverB(int which) const override; protected: - - void reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, - realtype tout); - - void allocateSolver() override; - void setSStolerances(double rtol, double atol) override; + void calcIC(realtype tout1) const override; - void setSensSStolerances(double rtol, double *atol) override; + void calcICB(int which, realtype tout1) const override; - void setSensErrCon(bool error_corr) override; + void getB(int which) const override; - void setQuadErrConB(int which, bool flag) override; + void getSens() const override; - void setErrHandlerFn() override; + void getQuadB(int which) const override; - void setUserData(Model *model) override; + void reInitPostProcessF(realtype tnext) const override; - void setUserDataB(int which, Model *model) override; + void reInitPostProcessB(realtype tnext) const override; - void setMaxNumSteps(long int mxsteps) override; + void reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, + realtype tout) const; - void setStabLimDet(int stldet) override; + void allocateSolver() const override; - void setStabLimDetB(int which, int stldet) override; + void setSStolerances(double rtol, double atol) const override; - void setId(Model *model) override; + void setSensSStolerances(double rtol, + const double *atol) const override; - void setSuppressAlg(bool flag) override; - - void resetState(void *cv_mem, N_Vector y0); + void setSensErrCon(bool error_corr) const override; - void setSensParams(realtype *p, realtype *pbar, int *plist) override; + void setQuadErrConB(int which, bool flag) const override; - void adjInit() override; + void setErrHandlerFn() const override; - void allocateSolverB(int *which) override; + void setUserData(Model *model) const override; - void setSStolerancesB(int which, realtype relTolB, - realtype absTolB) override; + void setUserDataB(int which, Model *model) const override; - void quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) override; - - void setMaxNumStepsB(int which, long int mxstepsB) override; + void setMaxNumSteps(long int mxsteps) const override; - void diag() override; + void setStabLimDet(int stldet) const override; - void diagB(int which) override; + void setStabLimDetB(int which, int stldet) const override; - void getNumSteps(void *ami_mem, long int *numsteps) const override; + void setId(const Model *model) const override; - void getNumRhsEvals(void *ami_mem, long int *numrhsevals) const override; + void setSuppressAlg(bool flag) const override; - void getNumErrTestFails(void *ami_mem, - long int *numerrtestfails) const override; + void resetState(void *cv_mem, const_N_Vector y0) const; - void getNumNonlinSolvConvFails(void *ami_mem, - long int *numnonlinsolvconvfails) const override; + void setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const override; - void getLastOrder(void *ami_ami_mem, int *order) const override; + void adjInit() const override; - void *getAdjBmem(void *ami_mem, int which) override; + void allocateSolverB(int *which) const override; - template - friend void boost::serialization::serialize(Archive &ar, CVodeSolver &r, const unsigned int version); + void setSStolerancesB(int which, realtype relTolB, + realtype absTolB) const override; - friend bool operator ==(const CVodeSolver &a, const CVodeSolver &b); + void quadSStolerancesB(int which, realtype reltolQB, + realtype abstolQB) const override; - void init(AmiVector *x, AmiVector *dx, realtype t) override; + void setMaxNumStepsB(int which, long int mxstepsB) const override; - void binit(int which, AmiVector *xB, AmiVector *dxB, realtype t) override; + void diag() const override; - void qbinit(int which, AmiVector *qBdot) override; + void diagB(int which) const override; - void rootInit(int ne) override; + void getNumSteps(const void *ami_mem, long int *numsteps) const override; - void sensInit1(AmiVectorArray *sx, AmiVectorArray *sdx, int nplist) override; + void getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const override; - void setDenseJacFn() override; + void getNumErrTestFails(const void *ami_mem, + long int *numerrtestfails) const override; - void setSparseJacFn() override; + void + getNumNonlinSolvConvFails(const void *ami_mem, + long int *numnonlinsolvconvfails) const override; - void setBandJacFn() override; + void getLastOrder(const void *ami_ami_mem, int *order) const override; - void setJacTimesVecFn() override; + void *getAdjBmem(void *ami_mem, int which) const override; - void setDenseJacFnB(int which) override; + template + friend void boost::serialization::serialize(Archive &ar, CVodeSolver &r, + unsigned int version); - void setSparseJacFnB(int which) override; + friend bool operator==(const CVodeSolver &a, const CVodeSolver &b); - void setBandJacFnB(int which) override; + void init(realtype t0, const AmiVector &x0, const AmiVector &dx0) + const override; - void setJacTimesVecFnB(int which) override; + void sensInit1(const AmiVectorArray &sx0, const AmiVectorArray &sdx0) + const override; - static int fJB(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void binit(int which, realtype tf, const AmiVector &xB0, + const AmiVector &dxB0) const override; - static int fJSparseB(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void qbinit(int which, const AmiVector &xQB0) const override; - static int fJBand(realtype t, N_Vector x, N_Vector xdot, - SUNMatrix J, void *user_data, N_Vector tmp1, - N_Vector tmp2, N_Vector tmp3); + void rootInit(int ne) const override; - static int fJBandB(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void setDenseJacFn() const override; - static int fJDiag(realtype t, N_Vector JDiag, N_Vector x, void *user_data); + void setSparseJacFn() const override; - static int fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, N_Vector xdot, - void *user_data, N_Vector tmp); + void setBandJacFn() const override; - static int fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - void *user_data, N_Vector tmpB); + void setJacTimesVecFn() const override; - static int froot(realtype t, N_Vector x, realtype *root, - void *user_data); + void setDenseJacFnB(int which) const override; - static int fxBdot(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, void *user_data); + void setSparseJacFnB(int which) const override; - static int fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot, - void *user_data); + void setBandJacFnB(int which) const override; - static int fsxdot(int Ns, realtype t, N_Vector x, N_Vector xdot, int ip, - N_Vector sx, N_Vector sxdot, void *user_data, - N_Vector tmp1, N_Vector tmp2); + void setJacTimesVecFnB(int which) const override; }; } // namespace amici diff --git a/include/amici/solver_idas.h b/include/amici/solver_idas.h index c1870bdd32..21648ddd88 100644 --- a/include/amici/solver_idas.h +++ b/include/amici/solver_idas.h @@ -4,7 +4,21 @@ #include "amici/solver.h" #include -#include + +namespace amici { +class ExpData; +class ReturnData; +class Model_DAE; +class IDASolver; +} // namespace amici + +// for serialization friend in Solver +namespace boost { +namespace serialization { +template +void serialize(Archive &ar, amici::IDASolver &u, unsigned int version); +} +} // namespace boost::serialization namespace amici { @@ -17,213 +31,164 @@ class IDASolver : public Solver { * @brief Clone this instance * @return The clone */ - virtual Solver* clone() const override; - - void reInitPostProcessF(realtype *t, AmiVector *yout, AmiVector *ypout, - realtype tnext) override; - - void reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector *ypBout, realtype tnext) override; - - void reInit(realtype t0, AmiVector *yy0, AmiVector *yp0) override; - - void sensReInit(AmiVectorArray *yS0, AmiVectorArray *ypS0) override; + Solver *clone() const override; - void reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector *ypB0) override; + void reInitPostProcessF(realtype tnext) const override; - void quadReInitB(int which, AmiVector *yQB0) override; + void reInitPostProcessB(realtype tnext) const override; - void quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) override; - - int solve(realtype tout, AmiVector *yret, AmiVector *ypret, realtype *tret, - int itask) override; + void reInit(realtype t0, const AmiVector &yy0, + const AmiVector &yp0) const override; - int solveF(realtype tout, AmiVector *yret, AmiVector *ypret, realtype *tret, - int itask, int *ncheckPtr) override; + void sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray &ypS0) const override; - void solveB(realtype tBout, int itaskB) override; + void reInitB(int which, realtype tB0, + const AmiVector &yyB0, const AmiVector &ypB0) const override; - void getRootInfo(int *rootsfound) const override; + void quadReInitB(int which, const AmiVector &yQB0) const override; - void getDky(realtype t, int k, AmiVector *dky) const override; + void quadSStolerancesB(int which, realtype reltolQB, + realtype abstolQB) const override; - void getSens(realtype *tret, AmiVectorArray *yySout) const override; + int solve(realtype tout, int itask) const override; - void getB(int which, realtype *tret, AmiVector *yy, AmiVector *yp) const override; + int solveF(realtype tout, int itask, + int *ncheckPtr) const override; - void getQuadB(int which, realtype *tret, AmiVector *qB) const override; + void solveB(realtype tBout, int itaskB) const override; - void calcIC(realtype tout1, AmiVector *x, AmiVector *dx) override; + void getRootInfo(int *rootsfound) const override; - void calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) override; + void getDky(realtype t, int k) const override; - void setStopTime(realtype tstop) override; + void getSens() const override; - void turnOffRootFinding() override; + void getSensDky(realtype t, int k) const override; - int nplist() const override; + void getB(int which) const override; - int nx() const override; + void getDkyB(realtype t, int k, int which) const override; - const Model *getModel() const override; + void getQuadB(int which) const override; - bool getMallocDone() const override; + void getQuadDkyB(realtype t, int k, int which) const override; - bool getAdjMallocDone() const override; + void calcIC(realtype tout1) const override; - static int fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, - void *user_data); + void calcICB(int which, realtype tout1) const override; - static int fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xdot, SUNMatrix J, void *user_data, N_Vector tmp1, - N_Vector tmp2, N_Vector tmp3); + void setStopTime(realtype tstop) const override; - static int fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xdot, SUNMatrix J, void *user_data, - N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + void turnOffRootFinding() const override; - void setLinearSolver() override; + const Model *getModel() const override; - void setLinearSolverB(int which) override; + void setLinearSolver() const override; - void setNonLinearSolver() override; + void setLinearSolverB(int which) const override; - void setNonLinearSolverSens() override; + void setNonLinearSolver() const override; - void setNonLinearSolverB(int which) override; + void setNonLinearSolverSens() const override; + void setNonLinearSolverB(int which) const override; protected: - void reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, - AmiVector *ypout, realtype tout); + AmiVector *ypout, realtype tout) const; + + void allocateSolver() const override; - void allocateSolver() override; + void setSStolerances(realtype rtol, + realtype atol) const override; - void setSStolerances(double rtol, double atol) override; + void setSensSStolerances(realtype rtol, + const realtype *atol) const override; - void setSensSStolerances(double rtol, double *atol) override; + void setSensErrCon(bool error_corr) const override; - void setSensErrCon(bool error_corr) override; + void setQuadErrConB(int which, bool flag) const override; - void setQuadErrConB(int which, bool flag) override; + void setErrHandlerFn() const override; - void setErrHandlerFn() override; + void setUserData(Model *model) const override; - void setUserData(Model *model) override; + void setUserDataB(int which, Model *model) const override; - void setUserDataB(int which, Model *model) override; + void setMaxNumSteps(long int mxsteps) const override; - void setMaxNumSteps(long int mxsteps) override; + void setStabLimDet(int stldet) const override; - void setStabLimDet(int stldet) override; + void setStabLimDetB(int which, int stldet) const override; - void setStabLimDetB(int which, int stldet) override; + void setId(const Model *model) const override; - void setId(Model *model) override; + void setSuppressAlg(bool flag) const override; - void setSuppressAlg(bool flag) override; - - void resetState(void *ida_mem, N_Vector yy0, N_Vector yp0); + void resetState(void *ida_mem, const_N_Vector yy0, + const_N_Vector yp0) const; - void setSensParams(realtype *p, realtype *pbar, int *plist) override; + void setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const override; - void adjInit() override; + void adjInit() const override; - void allocateSolverB(int *which) override; + void allocateSolverB(int *which) const override; - void setMaxNumStepsB(int which, long int mxstepsB) override; + void setMaxNumStepsB(int which, + long int mxstepsB) const override; void setSStolerancesB(int which, realtype relTolB, - realtype absTolB) override; + realtype absTolB) const override; - void diag() override; + void diag() const override; - void diagB(int which) override; + void diagB(int which) const override; - void getNumSteps(void *ami_mem, long int *numsteps) const override; + void getNumSteps(const void *ami_mem, long int *numsteps) const override; - void getNumRhsEvals(void *ami_mem, long int *numrhsevals) const override; + void getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const override; - void getNumErrTestFails(void *ami_mem, + void getNumErrTestFails(const void *ami_mem, long int *numerrtestfails) const override; - void getNumNonlinSolvConvFails(void *ami_mem, - long int *numnonlinsolvconvfails) const override; - - void getLastOrder(void *ami_mem, int *order) const override; - - void *getAdjBmem(void *ami_mem, int which) override; - - void init(AmiVector *x, AmiVector *dx, realtype t) override; - - void binit(int which, AmiVector *xB, AmiVector *dxB, realtype t) override; - - void qbinit(int which, AmiVector *qBdot) override; - - void rootInit(int ne) override; - - void sensInit1(AmiVectorArray *sx, AmiVectorArray *sdx, int nplist) override; - - void setDenseJacFn() override; - - void setSparseJacFn() override; - - void setBandJacFn() override; + void + getNumNonlinSolvConvFails(const void *ami_mem, + long int *numnonlinsolvconvfails) const override; - void setJacTimesVecFn() override; + void getLastOrder(const void *ami_mem, int *order) const override; - void setDenseJacFnB(int which) override; + void *getAdjBmem(void *ami_mem, int which) const override; - void setSparseJacFnB(int which) override; + void init(realtype t0, const AmiVector &x0, + const AmiVector &dx0) const override; - void setBandJacFnB(int which) override; + void sensInit1(const AmiVectorArray &sx0, const AmiVectorArray &sdx0) const override; - void setJacTimesVecFnB(int which) override; + void binit(int which, realtype tf, + const AmiVector &xB0, const AmiVector &dxB0) const override; - static int fJB(realtype t, realtype cj, N_Vector x, - N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector xBdot, - SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void qbinit(int which, const AmiVector &xQB0) const override; - static int fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, N_Vector xBdot, - SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void rootInit(int ne) const override; - static int fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xdot, SUNMatrix J, void *user_data, - N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + void setDenseJacFn() const override; - static int fJBandB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, N_Vector xBdot, SUNMatrix JB, - void *user_data, N_Vector tmp1B, N_Vector tmp2B, - N_Vector tmp3B); + void setSparseJacFn() const override; - static int fJv(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, - N_Vector v, N_Vector Jv, realtype cj, void *user_data, - N_Vector tmp1, N_Vector tmp2); + void setBandJacFn() const override; - static int fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector xBdot, N_Vector vB, N_Vector JvB, - realtype cj, void *user_data, N_Vector tmpB1, - N_Vector tmpB2); + void setJacTimesVecFn() const override; - static int froot(realtype t, N_Vector x, N_Vector dx, realtype *root, - void *user_data); + void setDenseJacFnB(int which) const override; - static int fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector xBdot, void *user_data); + void setSparseJacFnB(int which) const override; - static int fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector qBdot, - void *user_data); + void setBandJacFnB(int which) const override; - static int fsxdot(int Ns, realtype t, N_Vector x, N_Vector dx, N_Vector xdot, - N_Vector *sx, N_Vector *sdx, N_Vector *sxdot, void *user_data, - N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + void setJacTimesVecFnB(int which) const override; }; } // namespace amici diff --git a/include/amici/steadystateproblem.h b/include/amici/steadystateproblem.h index 9d04388946..a5d58d238f 100644 --- a/include/amici/steadystateproblem.h +++ b/include/amici/steadystateproblem.h @@ -97,33 +97,29 @@ class SteadystateProblem { * @param model pointer to the AMICI model object * @return solver instance */ - std::unique_ptr createSteadystateSimSolver(Solver *solver, - Model *model); + std::unique_ptr createSteadystateSimSolver(const Solver *solver, + Model *model) const; - /** default constructor - * @param t pointer to time variable - * @param x pointer to state variables - * @param sx pointer to state sensitivity variables + /** + * @brief constructor + * @param solver pointer to Solver instance + */ + explicit SteadystateProblem(const Solver *solver); + + /** + * @brief routine that writes solutions of steadystate problem to target + vectors + * @param t final timepoint + * @param x steadystate state + * @param sx steadystate state sensitivity */ - SteadystateProblem(realtype *t, AmiVector *x, AmiVectorArray *sx) : - delta(x->getLength()), - ewt(x->getLength()), - rel_x_newton(x->getLength()), - x_newton(x->getLength()), - x_old(x->getLength()), - dx(x->getLength()), - xdot(x->getLength()), - xdot_old(x->getLength()), - sdx(x->getLength(),sx->getLength()) - { - this->t = t; - this->x = x; - this->sx = sx; - } + void writeSolution(realtype *t, AmiVector &x, AmiVectorArray &sx) const; + private: - realtype *t; - /** newton step? */ + /** time variable for simulation steadystate finding */ + realtype t; + /** newton step */ AmiVector delta; /** error weights */ AmiVector ewt; @@ -132,7 +128,7 @@ class SteadystateProblem { /** container for absolute error calcuation? */ AmiVector x_newton; /** state vector */ - AmiVector *x; + AmiVector x; /** old state vector */ AmiVector x_old; /** differential state vector */ @@ -142,7 +138,7 @@ class SteadystateProblem { /** old time derivative state vector */ AmiVector xdot_old; /** state sensitivities */ - AmiVectorArray *sx; + AmiVectorArray sx; /** state differential sensitivities */ AmiVectorArray sdx; diff --git a/include/amici/sundials_linsol_wrapper.h b/include/amici/sundials_linsol_wrapper.h index e1351d9806..e7933a334c 100644 --- a/include/amici/sundials_linsol_wrapper.h +++ b/include/amici/sundials_linsol_wrapper.h @@ -837,7 +837,7 @@ class SUNNonLinSolFixedPoint : public SUNNonLinSolWrapper { * @param x template for cloning vectors needed within the solver. * @param m number of acceleration vectors to use */ - SUNNonLinSolFixedPoint(const N_Vector x, int m = 0); + SUNNonLinSolFixedPoint(const_N_Vector x, int m = 0); /** * @brief Create fixed-point solver for use with sensitivity analysis @@ -848,7 +848,7 @@ class SUNNonLinSolFixedPoint : public SUNNonLinSolWrapper { * @param x template for cloning vectors needed within the solver. * @param m number of acceleration vectors to use */ - SUNNonLinSolFixedPoint(int count, const N_Vector x, int m = 0); + SUNNonLinSolFixedPoint(int count, const_N_Vector x, int m = 0); /** * @brief Get function to evaluate the fixed point function G(y) = y diff --git a/include/amici/sundials_matrix_wrapper.h b/include/amici/sundials_matrix_wrapper.h index 0d773f0944..f39c510129 100644 --- a/include/amici/sundials_matrix_wrapper.h +++ b/include/amici/sundials_matrix_wrapper.h @@ -5,12 +5,12 @@ #include // SUNMatrix_Dense #include // SUNMatrix_Sparse -#include - #include #include +#include "amici/vector.h" + namespace amici { /** @@ -144,7 +144,7 @@ class SUNMatrixWrapper { * @param c output vector, may already contain values * @param b multiplication vector */ - void multiply(N_Vector c, const N_Vector b) const; + void multiply(N_Vector c, const_N_Vector b) const; /** * @brief Perform matrix vector multiplication c += A*b @@ -169,4 +169,23 @@ class SUNMatrixWrapper { } // namespace amici +namespace gsl { +/** + * @brief Create span from SUNMatrix + * @param nv + * @return + */ +inline span make_span(SUNMatrix m) +{ + switch (SUNMatGetID(m)) { + case SUNMATRIX_DENSE: + return span(SM_DATA_D(m), SM_LDATA_D(m)); + case SUNMATRIX_SPARSE: + return span(SM_DATA_S(m), SM_NNZ_S(m)); + default: + throw amici::AmiException("Unimplemented SUNMatrix type for make_span"); + } +} +} // namespace gsl + #endif // AMICI_SUNDIALS_MATRIX_WRAPPER_H diff --git a/include/amici/vector.h b/include/amici/vector.h index 86deba8cd7..daf8f6e002 100644 --- a/include/amici/vector.h +++ b/include/amici/vector.h @@ -2,321 +2,318 @@ #define AMICI_VECTOR_H #include +#include -#include #include +#include + +#include + namespace amici { +/** Since const N_Vector is not what we want */ +using const_N_Vector = + std::add_const::type *>::type; + /** AmiVector class provides a generic interface to the NVector_Serial struct */ class AmiVector { -public: + public: /** - * Creates an std::vector and attaches the - * data pointer to a newly created N_Vector_Serial. - * Using N_VMake_Serial ensures that the N_Vector - * module does not try to deallocate the data vector - * when calling N_VDestroy_Serial - * @param length number of elements in vector - * @return new AmiVector instance - */ - + * @brief Default constructor + * @return new empty AmiVectorArray instance + */ + AmiVector() = default; + + /** Creates an std::vector and attaches the + * data pointer to a newly created N_Vector_Serial. + * Using N_VMake_Serial ensures that the N_Vector + * module does not try to deallocate the data vector + * when calling N_VDestroy_Serial + * @brief emmpty constructor + * @param length number of elements in vector + * @return new AmiVector instance + */ explicit AmiVector(const long int length) : vec(static_cast(length), 0.0), - nvec(N_VMake_Serial(length,vec.data())) - { - } + nvec(N_VMake_Serial(length, vec.data())) {} - /** constructor from std::vector, copies data from std::vector - * and constructs an nvec that points to the data - * @param rvec vector from which the data will be copied - * @return new AmiVector instance - */ + /** Copies data from std::vector and constructs an nvec that points to the + * data + * @brief constructor from std::vector, + * @param rvec vector from which the data will be copied + * @return new AmiVector instance + */ explicit AmiVector(std::vector rvec) : vec(std::move(rvec)), - nvec(N_VMake_Serial(static_cast(vec.size()), vec.data())) - { - } + nvec(N_VMake_Serial(static_cast(vec.size()), vec.data())) {} - /** copy constructor - * @param vold vector from which the data will be copied - */ - AmiVector(const AmiVector& vold): vec(vold.vec) { - nvec = N_VMake_Serial(static_cast(vold.vec.size()), vec.data()); + /** + * @brief copy constructor + * @param vold vector from which the data will be copied + */ + AmiVector(const AmiVector &vold) : vec(vold.vec) { + nvec = + N_VMake_Serial(static_cast(vold.vec.size()), vec.data()); } - /** copy assignment operator - * @param other right hand side - * @return left hand side - */ - AmiVector& operator=(AmiVector const& other) { - vec = other.vec; - if(nvec) - N_VDestroy_Serial(nvec); - nvec = N_VMake_Serial(static_cast(vec.size()),vec.data()); - return *this; - } + /** + * @brief copy assignment operator + * @param other right hand side + * @return left hand side + */ + AmiVector &operator=(AmiVector const &other); - /** data accessor - * @return pointer to data array - */ - realtype *data() { - return vec.data(); - } + /** + * @brief data accessor + * @return pointer to data array + */ + realtype *data(); - /** const data accessor - * @return const pointer to data array - */ - const realtype *data() const { - return vec.data(); - } + /** + * @brief const data accessor + * @return const pointer to data array + */ + const realtype *data() const; - /** N_Vector accessor - * @return N_Vector - */ - N_Vector getNVector() const { - return nvec; - } + /** + * @brief N_Vector accessor + * @return N_Vector + */ + N_Vector getNVector(); - /** Vector accessor - * @return Vector - */ - std::vector const& getVector() const { - return vec; - } + /** + * @brief N_Vector accessor + * @return N_Vector + */ + const_N_Vector getNVector() const; - /** returns the length of the vector - * @return length - */ - int getLength() const { - return static_cast(vec.size()); - } + /** + * @brief Vector accessor + * @return Vector + */ + std::vector const &getVector(); - /** resets the Vector by filling with zero values - */ - void reset() { - set(0.0); - } + /** + * @brief returns the length of the vector + * @return length + */ + int getLength() const; - /** changes the sign of data elements - */ - void minus() { - for(std::vector::iterator it = vec.begin(); - it != vec.end(); ++it) - *it = -*it; - } + /** + * @brief resets the Vector by filling with zero values + */ + void reset(); - /** sets all data elements to a specific value - * @param val value for data elements - */ - void set(realtype val) { - std::fill(vec.begin(), vec.end(), val); - } + /** + * @brief changes the sign of data elements + */ + void minus(); - /** accessor to data elements of the vector - * @param pos index of element - * @return element - */ - realtype& operator[](int pos) { - return vec.at(static_cast(pos)); - } + /** + * @brief sets all data elements to a specific value + * @param val value for data elements + */ + void set(realtype val); - /** accessor to data elements of the vector - * @param pos index of element - * @return element - */ - realtype& at(int pos) { - return vec.at(static_cast(pos)); - } + /** + * @brief accessor to data elements of the vector + * @param pos index of element + * @return element + */ + realtype &operator[](int pos); + /** + * @brief accessor to data elements of the vector + * @param pos index of element + * @return element + */ + realtype &at(int pos); - /** accessor to data elements of the vector + /** + * @brief accessor to data elements of the vector * @param pos index of element * @return element */ - const realtype& at(int pos) const { - return vec.at(static_cast(pos)); - } + const realtype &at(int pos) const; - ~AmiVector(){ - N_VDestroy_Serial(nvec); - } + /** + * @brief copies data from another AmiVector + * @param other data source + */ + void copy(const AmiVector &other); + + /** + * @brief destructor + */ + ~AmiVector(); -private: + private: /** main data storage */ std::vector vec; - /** N_Vector, will be synchronised such that it points to - * data in vec */ + + /** N_Vector, will be synchronised such that it points to data in vec */ N_Vector nvec = nullptr; -}; + /** + * @brief reconstructs nvec such that data pointer points to vec data array + */ + void synchroniseNVector(); +}; -/** AmiVectorArray class. - provides a generic interface to arrays of NVector_Serial structs -*/ +/** + * @brief AmiVectorArray class. + * + * Provides a generic interface to arrays of NVector_Serial structs + */ class AmiVectorArray { -public: - + public: /** * @brief Default constructor * @return new empty AmiVectorArray instance */ - AmiVectorArray() {} - - /** creates an std::vector and attaches the - * data pointer to a newly created N_VectorArray - * using CloneVectorArrayEmpty ensures that the N_Vector - * module does not try to deallocate the data vector - * when calling N_VDestroyVectorArray_Serial - * @param length_inner length of vectors - * @param length_outer number of vectors - * @return New AmiVectorArray instance - */ - AmiVectorArray(long int length_inner, long int length_outer) - : vec_array(length_outer, AmiVector(length_inner)) - { - nvec_array.resize(length_outer); - for (int idx = 0; idx < length_outer; idx++) { - nvec_array.at(idx) = vec_array.at(idx).getNVector(); - } - } - - /** + AmiVectorArray() = default; + + /** + * Creates an std::vector and attaches the + * data pointer to a newly created N_VectorArray + * using CloneVectorArrayEmpty ensures that the N_Vector + * module does not try to deallocate the data vector + * when calling N_VDestroyVectorArray_Serial + * @brief empty constructor + * @param length_inner length of vectors + * @param length_outer number of vectors + * @return New AmiVectorArray instance + */ + AmiVectorArray(long int length_inner, long int length_outer); + + /** * @brief copy assignment operator * @param other right hand side * @return left hand side */ - AmiVectorArray& operator=(AmiVectorArray const& other) { - vec_array = other.vec_array; - nvec_array.resize(other.getLength()); - for (int idx = 0; idx < other.getLength(); idx++) { - nvec_array.at(idx) = vec_array.at(idx).getNVector(); - } - return *this; - } + AmiVectorArray &operator=(AmiVectorArray const &other); - /** copy constructor - * @param vaold object to copy from - * @return new AmiVectorArray instance - */ - AmiVectorArray(const AmiVectorArray& vaold) : vec_array(vaold.vec_array) { - nvec_array.resize(vaold.getLength()); - for (int idx = 0; idx < vaold.getLength(); idx++) { - nvec_array.at(idx) = vec_array.at(idx).getNVector(); - } - } + /** + * @brief copy constructor + * @param vaold object to copy from + * @return new AmiVectorArray instance + */ + AmiVectorArray(const AmiVectorArray &vaold); - /** accessor to data of AmiVector elements - * @param pos index of AmiVector - * @return pointer to data array - */ - realtype *data(int pos) { - return vec_array.at(pos).data(); - } + /** + * @brief accessor to data of AmiVector elements + * @param pos index of AmiVector + * @return pointer to data array + */ + realtype *data(int pos); - /** const accessor to data of AmiVector elements - * @param pos index of AmiVector - * @return const pointer to data array - */ - const realtype *data(int pos) const { - return vec_array.at(pos).data(); - } + /** + * @brief const accessor to data of AmiVector elements + * @param pos index of AmiVector + * @return const pointer to data array + */ + const realtype *data(int pos) const; - /** accessor to elements of AmiVector elements + /** + * @brief accessor to elements of AmiVector elements * @param ipos inner index in AmiVector * @param jpos outer index in AmiVectorArray * @return element */ - realtype& at(int ipos, int jpos) { - return vec_array.at(jpos).at(ipos); - } + realtype &at(int ipos, int jpos); - /** accessor to elements of AmiVector elements - * @param ipos inner index in AmiVector - * @param jpos outer index in AmiVectorArray - * @return element - */ - const realtype& at(int ipos, int jpos) const { - return vec_array.at(jpos).at(ipos); - } + /** + * @brief const accessor to elements of AmiVector elements + * @param ipos inner index in AmiVector + * @param jpos outer index in AmiVectorArray + * @return element + */ + const realtype &at(int ipos, int jpos) const; - /** accessor to NVectorArray - * @return N_VectorArray - */ - N_Vector *getNVectorArray() { - return nvec_array.data(); - } + /** + * @brief accessor to NVectorArray + * @return N_VectorArray + */ + N_Vector *getNVectorArray(); - /** accessor to NVector element - * @param pos index of corresponding AmiVector - * @return N_Vector - */ - N_Vector getNVector(int pos) { - return nvec_array.at(pos); - } + /** + * @brief accessor to NVector element + * @param pos index of corresponding AmiVector + * @return N_Vector + */ + N_Vector getNVector(int pos); - /** accessor to AmiVector elements - * @param pos index of AmiVector - * @return AmiVector - */ - AmiVector& operator[](int pos) { - return vec_array.at(pos); - } + /** + * @brief const accessor to NVector element + * @param pos index of corresponding AmiVector + * @return N_Vector + */ + const_N_Vector getNVector(int pos) const; - /** const accessor to AmiVector elements - * @param pos index of AmiVector - * @return const AmiVector - */ - const AmiVector& operator[](int pos) const { - return vec_array.at(pos); - } + /** + * @brief accessor to AmiVector elements + * @param pos index of AmiVector + * @return AmiVector + */ + AmiVector &operator[](int pos); - /** length of AmiVectorArray - * @return length - */ - int getLength() const { - return static_cast(vec_array.size()); - } + /** + * @brief const accessor to AmiVector elements + * @param pos index of AmiVector + * @return const AmiVector + */ + const AmiVector &operator[](int pos) const; - /** resets every AmiVector in AmiVectorArray */ - void reset() { - for(auto &v: vec_array) - v.reset(); - } + /** + * @brief length of AmiVectorArray + * @return length + */ + int getLength() const; + + /** + * @brief resets every AmiVector in AmiVectorArray + */ + void reset(); - /** flattens the AmiVectorArray to a vector in row-major format + /** + * @brief flattens the AmiVectorArray to a vector in row-major format * @param vec vector into which the AmiVectorArray will be flattened. Must * have length equal to number of elements. */ - void flatten_to_vector(std::vector& vec) const { - int n_outer = vec_array.size(); - if(n_outer == 0) - return; //nothing to do ... - int n_inner = vec_array.at(0).getLength(); - - if (static_cast(vec.size()) != n_inner * n_outer) { - throw AmiException("Dimension of AmiVectorArray (%ix%i) does not " - "match target vector dimension (%u)", - n_inner, n_outer, vec.size()); - } - - for (int outer = 0; outer < n_outer; ++outer) { - for (int inner = 0; inner < n_inner; ++inner) - vec.at(inner + outer * n_inner) = this->at(inner,outer); - } - } + void flatten_to_vector(std::vector &vec) const; + + /** + * @brief copies data from another AmiVectorArray + * @param other data source + */ + void copy(const AmiVectorArray &other); ~AmiVectorArray() = default; -private: + private: /** main data storage */ std::vector vec_array; - /** N_Vector array, will be synchronised such that it points to - * respective elements in the vec_array - */ + + /** + * N_Vector array, will be synchronised such that it points to + * respective elements in the vec_array + */ std::vector nvec_array; }; -} +} // namespace amici + +namespace gsl { +/** + * @brief Create span from N_Vector + * @param nv + * @return + */ +inline span make_span(N_Vector nv) +{ + return span(N_VGetArrayPointer(nv), N_VGetLength_Serial(nv)); +} +} // namespace gsl #endif /* AMICI_VECTOR_H */ diff --git a/matlab/@amimodel/compileAndLinkModel.m b/matlab/@amimodel/compileAndLinkModel.m index f6aceaf6f8..1e709f9ac7 100644 --- a/matlab/@amimodel/compileAndLinkModel.m +++ b/matlab/@amimodel/compileAndLinkModel.m @@ -179,12 +179,13 @@ function compileAndLinkModel(modelname, modelSourceFolder, coptim, debug, funs, % generate hash for file and append debug string if we have an md5 % file, check this hash against the contained hash cppsrc = {'amici', 'symbolic_functions','spline', ... - 'edata','rdata', ... + 'edata','rdata', 'exception', ... 'interface_matlab', 'misc', ... 'solver', 'solver_cvodes', 'solver_idas', ... 'model', 'model_ode', 'model_dae', 'returndata_matlab', ... 'forwardproblem', 'steadystateproblem', 'backwardproblem', 'newton_solver', ... - 'abstract_model', 'sundials_matrix_wrapper', 'sundials_linsol_wrapper' + 'abstract_model', 'sundials_matrix_wrapper', 'sundials_linsol_wrapper', ... + 'vector' }; % to be safe, recompile everything if headers have changed. otherwise % would need to check the full include hierarchy diff --git a/models/model_calvetti/CMakeLists.txt b/models/model_calvetti/CMakeLists.txt index 23760acd3c..854aec73a2 100644 --- a/models/model_calvetti/CMakeLists.txt +++ b/models/model_calvetti/CMakeLists.txt @@ -24,6 +24,7 @@ foreach(FLAG ${MY_CXX_FLAGS}) endforeach(FLAG) find_package(Amici HINTS ${CMAKE_CURRENT_LIST_DIR}/../../build) +message(STATUS "Found AMICI ${Amici_DIR}") set(MODEL_DIR ${CMAKE_CURRENT_LIST_DIR}) diff --git a/python/amici/gradient_check.py b/python/amici/gradient_check.py index 8418aaa396..1a6f73483a 100644 --- a/python/amici/gradient_check.py +++ b/python/amici/gradient_check.py @@ -108,7 +108,7 @@ def check_close(result, expected, assert_fun, atol, rtol, field, ip=None): index_str = f'at index ip={ip} ' check_type = 'FD check ' print(f'{check_type} failed for {field} {index_str}for ' - f'{close.sum()} indices:') + f'{close.size - close.sum()} indices:') adev = np.abs(result - expected) rdev = np.abs((result - expected)/(expected + atol)) print(f'max(adev): {adev.max()}, max(rdev): {rdev.max()}') diff --git a/src/CMakeLists.template.cmake b/src/CMakeLists.template.cmake index a878674ff8..f0d9b18331 100644 --- a/src/CMakeLists.template.cmake +++ b/src/CMakeLists.template.cmake @@ -24,6 +24,7 @@ foreach(FLAG ${MY_CXX_FLAGS}) endforeach(FLAG) find_package(Amici HINTS ${CMAKE_CURRENT_LIST_DIR}/../../build) +message(STATUS "Found AMICI ${Amici_DIR}") set(MODEL_DIR ${CMAKE_CURRENT_LIST_DIR}) diff --git a/src/abstract_model.cpp b/src/abstract_model.cpp index e5ac53d31d..ba89b0f53a 100644 --- a/src/abstract_model.cpp +++ b/src/abstract_model.cpp @@ -35,7 +35,7 @@ void AbstractModel::fsx0(realtype * /*sx0*/, const realtype /*t*/, const realty __func__); } -void AbstractModel::fdx0(AmiVector *x0, AmiVector *dx0) {} +void AbstractModel::fdx0(AmiVector &x0, AmiVector &dx0) {} void AbstractModel::fstau(realtype * /*stau*/, const realtype /*t*/, const realtype * /*x*/, const realtype * /*p*/, const realtype * /*k*/, @@ -302,8 +302,8 @@ void AbstractModel::fdwdx(realtype *dwdx, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w, const realtype *tcl) {} - -void AbstractModel::fdwdx_colptrs(sunindextype *indexvals) {} + +void AbstractModel::fdwdx_colptrs(sunindextype *indexptrs) {} void AbstractModel::fdwdx_rowvals(sunindextype *indexvals) {} diff --git a/src/amici.cpp b/src/amici.cpp index 319c356835..4a3d290e00 100644 --- a/src/amici.cpp +++ b/src/amici.cpp @@ -32,11 +32,10 @@ static_assert(AMICI_ONE_STEP == CV_ONE_STEP, "AMICI_ONE_STEP != CV_ONE_STEP"); static_assert(std::is_same::value, "Definition of realtype does not match"); + namespace amici { -/** errMsgIdAndTxt is a function pointer for printErrMsgIdAndTxt */ msgIdAndTxtFp errMsgIdAndTxt = &printErrMsgIdAndTxt; -/** warnMsgIdAndTxt is a function pointer for printWarnMsgIdAndTxt */ msgIdAndTxtFp warnMsgIdAndTxt = &printWarnMsgIdAndTxt; @@ -48,7 +47,7 @@ std::unique_ptr runAmiciSimulation(Solver &solver, const ExpData *ed ConditionContext conditionContext(&model, edata); try{ - rdata = std::unique_ptr(new ReturnData(solver,&model)); + rdata = std::unique_ptr(new ReturnData(solver, model)); if (model.nx_solver <= 0) { return rdata; @@ -87,15 +86,6 @@ std::unique_ptr runAmiciSimulation(Solver &solver, const ExpData *ed return rdata; } -/*! - * printErrMsgIdAndTxt prints a specified error message associated to the - * specified identifier - * - * @param[in] identifier error identifier @type char - * @param[in] format string with error message printf-style format - * @param ... arguments to be formatted - * @return void - */ void printErrMsgIdAndTxt(const char *identifier, const char *format, ...) { if(identifier && *identifier != '\0') fprintf(stderr, "[Error] %s: ", identifier); @@ -108,15 +98,6 @@ void printErrMsgIdAndTxt(const char *identifier, const char *format, ...) { fprintf(stderr, "\n"); } -/*! - * printErrMsgIdAndTxt prints a specified warning message associated to the - * specified identifier - * - * @param[in] identifier warning identifier @type char - * @param[in] format string with error message printf-style format - * @param ... arguments to be formatted - * @return void - */ void printWarnMsgIdAndTxt(const char *identifier, const char *format, ...) { if(identifier && *identifier != '\0') printf("[Warning] %s: ", identifier); @@ -155,7 +136,7 @@ std::vector > runAmiciSimulations(const Solver &solv if (failed) { ConditionContext conditionContext(myModel.get(), edatas[i]); results[i] = - std::unique_ptr(new ReturnData(solver, &model)); + std::unique_ptr(new ReturnData(solver, model)); } results[i] = runAmiciSimulation(*mySolver, edatas[i], *myModel); diff --git a/src/backwardproblem.cpp b/src/backwardproblem.cpp index 64ff9b5ea3..10f8d32689 100644 --- a/src/backwardproblem.cpp +++ b/src/backwardproblem.cpp @@ -12,6 +12,10 @@ namespace amici { BackwardProblem::BackwardProblem(const ForwardProblem *fwd) : + model(fwd->model), + rdata(fwd->rdata), + solver(fwd->solver), + t(fwd->getTime()), llhS0(static_cast(fwd->model->nJ * fwd->model->nplist()), 0.0), xB(fwd->model->nx_solver), dxB(fwd->model->nx_solver), @@ -19,33 +23,32 @@ BackwardProblem::BackwardProblem(const ForwardProblem *fwd) : x_disc(fwd->getStatesAtDiscontinuities()), xdot_disc(fwd->getRHSAtDiscontinuities()), xdot_old_disc(fwd->getRHSBeforeDiscontinuities()), - sx(fwd->getStateSensitivity()), + sx0(fwd->getStateSensitivity()), nroots(fwd->getNumberOfRoots()), discs(fwd->getDiscontinuities()), irdiscs(fwd->getDiscontinuities()), + iroot(fwd->getRootCounter()), rootidx(fwd->getRootIndexes()), dJydx(fwd->getDJydx()), - dJzdx(fwd->getDJzdx()) - { - t = fwd->getTime(); - model = fwd->model; - solver = fwd->solver; - rdata = fwd->rdata; - iroot = fwd->getRootCounter(); - } + dJzdx(fwd->getDJzdx()) {} void BackwardProblem::workBackwardProblem() { - if (model->nx_solver <= 0 || solver->getSensitivityOrder() < SensitivityOrder::first || - solver->getSensitivityMethod() != SensitivityMethod::adjoint || model->nplist() == 0) { + if (model->nx_solver <= 0 || + solver->getSensitivityOrder() < SensitivityOrder::first || + solver->getSensitivityMethod() != SensitivityMethod::adjoint || + model->nplist() == 0) { return; } - - solver->setupB(this, model); - - int it = rdata->nt - 2; + + int it = rdata->nt - 1; + model->initializeB(xB, dxB, xQB); + handleDataPointB(it); + solver->setupB(&which, rdata->ts[it], model, xB, dxB, xQB); + + --it; --iroot; while (it >= 0 || iroot >= 0) { @@ -54,9 +57,8 @@ void BackwardProblem::workBackwardProblem() { double tnext = getTnext(discs, iroot, it); if (tnext < t) { - solver->solveB(tnext, AMICI_NORMAL); - solver->getB(which, &t, &xB, &dxB); - solver->getQuadB(which, &t, &xQB); + solver->runB(tnext); + solver->writeSolutionB(&t, xB, dxB, xQB, this->which); solver->getDiagnosisB(it, rdata, this->which); } @@ -75,22 +77,15 @@ void BackwardProblem::workBackwardProblem() { } /* reinit states */ - solver->reInitB(which, t, &xB, &dxB); - solver->quadReInitB(which, &xQB); - solver->calcICB(which, t, &xB, &dxB); - - /* if we have to integrate further we need to postprocess for step size - computation */ - if (t > model->t0()) - solver->reInitPostProcessB(which, &t, &xB, &dxB, model->t0()); + solver->reInitB(which, t, xB, dxB); + solver->quadReInitB(which, xQB); } /* we still need to integrate from first datapoint to tstart */ if (t > model->t0()) { /* solve for backward problems */ - solver->solveB(model->t0(), AMICI_NORMAL); - solver->getQuadB(which, &(t), &xQB); - solver->getB(which, &(t), &xB, &dxB); + solver->runB(model->t0()); + solver->writeSolutionB(&t, xB, dxB, xQB, this->which); solver->getDiagnosisB(0, rdata, this->which); } @@ -105,8 +100,10 @@ void BackwardProblem::handleEventB(const int iroot) { continue; } - model->fdeltaqB(ie, t, &x_disc[iroot],&xB,&xdot_disc[iroot], &xdot_old_disc[iroot]); - model->fdeltaxB(ie, t, &x_disc[iroot],&xB,&xdot_disc[iroot], &xdot_old_disc[iroot]); + model->fdeltaqB(ie, t, x_disc[iroot], xB, xdot_disc[iroot], + xdot_old_disc[iroot]); + model->fdeltaxB(ie, t, x_disc[iroot], xB, xdot_disc[iroot], + xdot_old_disc[iroot]); for (int ix = 0; ix < model->nxtrue_solver; ++ix) { for (int iJ = 0; iJ < model->nJ; ++iJ) { @@ -142,7 +139,6 @@ void BackwardProblem::handleDataPointB(const int it) { } } - realtype BackwardProblem::getTnext(std::vector const& troot, const int iroot, const int it) { if (it < 0 @@ -161,7 +157,7 @@ void BackwardProblem::computeLikelihoodSensitivities() for (int ip = 0; ip < model->nplist(); ++ip) { llhS0[ip] = 0.0; for (int ix = 0; ix < model->nxtrue_solver; ++ix) { - llhS0[ip] += xB[ix] * sx.at(ix,ip); + llhS0[ip] += xB[ix] * sx0.at(ix,ip); } } } else { @@ -169,8 +165,8 @@ void BackwardProblem::computeLikelihoodSensitivities() llhS0[ip + iJ * model->nplist()] = 0.0; for (int ix = 0; ix < model->nxtrue_solver; ++ix) { llhS0[ip + iJ * model->nplist()] += - xB[ix + iJ * model->nxtrue_solver] * sx.at(ix,ip)+ - xB[ix] * sx.at(ix + iJ * model->nxtrue_solver,ip); + xB[ix + iJ * model->nxtrue_solver] * sx0.at(ix,ip)+ + xB[ix] * sx0.at(ix + iJ * model->nxtrue_solver,ip); } } } diff --git a/src/edata.cpp b/src/edata.cpp index e474b8303d..67c772062e 100644 --- a/src/edata.cpp +++ b/src/edata.cpp @@ -11,8 +11,6 @@ namespace amici { -ExpData::ExpData() : nytrue_(0), nztrue_(0), nmaxevent_(0) {} - ExpData::ExpData(int nytrue, int nztrue, int nmaxevent) : nytrue_(nytrue), nztrue_(nztrue), nmaxevent_(nmaxevent) { @@ -344,12 +342,12 @@ void ConditionContext::applyCondition(const ExpData *edata) { if(!edata) return; - + // this needs to go first, otherwise nplist will not have the right // dimension for all other fields that depend on Model::nplist if(!edata->plist.empty()) model->setParameterList(edata->plist); - + // this needs to go second as setParameterScale will reset sx0 if(!edata->pscale.empty()) { if(edata->pscale.size() != (unsigned) model->np()) @@ -357,9 +355,9 @@ void ConditionContext::applyCondition(const ExpData *edata) " match ExpData (%zd).", model->np(), edata->pscale.size()); model->setParameterScale(edata->pscale); - + } - + if(!edata->x0.empty()) { if(edata->x0.size() != (unsigned) model->nx_rdata) throw AmiException("Number of initial conditions (%d) in model does" @@ -367,7 +365,7 @@ void ConditionContext::applyCondition(const ExpData *edata) model->nx_rdata, edata->x0.size()); model->setInitialStates(edata->x0); } - + if(!edata->sx0.empty()) { if(edata->sx0.size() != (unsigned) model->nx_rdata * model->nplist()) throw AmiException("Number of initial conditions sensitivities (%d)" @@ -376,7 +374,7 @@ void ConditionContext::applyCondition(const ExpData *edata) edata->sx0.size()); model->setInitialStateSensitivities(edata->sx0); } - + if(!edata->parameters.empty()) { if(edata->parameters.size() != (unsigned) model->np()) throw AmiException("Number of parameters (%d) in model does not" @@ -384,7 +382,7 @@ void ConditionContext::applyCondition(const ExpData *edata) model->np(), edata->parameters.size()); model->setParameters(edata->parameters); } - + if(!edata->fixedParameters.empty()) { // fixed parameter in model are superseded by those provided in edata if(edata->fixedParameters.size() != (unsigned) model->nk()) @@ -411,7 +409,7 @@ void ConditionContext::restore() model->setParameters(originalParameters); model->setFixedParameters(originalFixedParameters); model->setTimepoints(originalTimepoints); - + } diff --git a/src/exception.cpp b/src/exception.cpp new file mode 100644 index 0000000000..e2ea1e9b1c --- /dev/null +++ b/src/exception.cpp @@ -0,0 +1,55 @@ +#include "amici/exception.h" +#include "amici/misc.h" + +#include +#include +#include + + +namespace amici { + +AmiException::AmiException(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + storeBacktrace(12); +} + +AmiException::AmiException(const amici::AmiException &old) { + snprintf(msg, sizeof(msg), "%s", old.msg); + snprintf(trace, sizeof(trace), "%s", old.trace); +} + +const char *AmiException::what() const noexcept { + return msg; +} + +const char *AmiException::getBacktrace() const { + return trace; +} + +void AmiException::storeBacktrace(const int nMaxFrames) { + snprintf(trace, sizeof(trace), "%s", backtraceString(nMaxFrames).c_str()); +} + +CvodeException::CvodeException(const int error_code, const char *function) : + AmiException("Cvode routine %s failed with error code %i",function,error_code){} + +IDAException::IDAException(const int error_code, const char *function) : + AmiException("IDA routine %s failed with error code %i",function,error_code){} + +IntegrationFailure::IntegrationFailure(int code, realtype t) : + AmiException("AMICI failed to integrate the forward problem"), + error_code(code), time(t) {} + +IntegrationFailureB::IntegrationFailureB(int code, realtype t) : + AmiException("AMICI failed to integrate the backward problem"), + error_code(code), time(t) {} + +NewtonFailure::NewtonFailure(int code, const char *function) : + AmiException("NewtonSolver routine %s failed with error code %i",function,code) { + error_code = code; +} + +} // namespace amici diff --git a/src/forwardproblem.cpp b/src/forwardproblem.cpp index 5196084de7..79f449b910 100644 --- a/src/forwardproblem.cpp +++ b/src/forwardproblem.cpp @@ -52,18 +52,18 @@ ForwardProblem::ForwardProblem(ReturnData *rdata, const ExpData *edata, void ForwardProblem::workForwardProblem() { + bool computeSensitivities = + solver->getSensitivityOrder() >= SensitivityOrder::first && + model->nx_solver > 0; - try { - solver->setup(&x, &dx, &sx, &sdx, model); - } catch (std::exception const& ex) { - throw AmiException("AMICI setup failed:\n(%s)",ex.what()); - } catch (...) { - throw AmiException("AMICI setup failed due to an unknown error"); - } + model->initialize(x, dx, sx, sdx, computeSensitivities); + solver->setup(model->t0(), model, x, dx, sx, sdx); + // update x0 after computing consistence IC, only important for DAEs + x.copy(solver->getState(model->t0())); - model->fx_rdata(&x_rdata, &x); + model->fx_rdata(x_rdata, x); if(solver->getSensitivityOrder() >= SensitivityOrder::first) { - model->fsx_rdata(&sx_rdata, &sx); + model->fsx_rdata(sx_rdata, sx); } if(edata){ @@ -74,11 +74,11 @@ void ForwardProblem::workForwardProblem() { if (solver->getNewtonPreequilibration() || (edata && !edata->fixedParametersPreequilibration.empty())) { handlePreequilibration(); } else { - model->fx_rdata(&x_rdata, &x); - rdata->x0 = std::move(x_rdata.getVector()); + model->fx_rdata(x_rdata, x); + rdata->x0 = x_rdata.getVector(); if (solver->getSensitivityMethod() == SensitivityMethod::forward && solver->getSensitivityOrder() >= SensitivityOrder::first) { - model->fsx_rdata(&sx_rdata, &sx); + model->fsx_rdata(sx_rdata, sx); for (int ix = 0; ix < rdata->nx; ix++) { for (int ip = 0; ip < model->nplist(); ip++) rdata->sx0[ip*rdata->nx + ix] = sx_rdata.at(ix,ip); @@ -86,18 +86,14 @@ void ForwardProblem::workForwardProblem() { } } - int ncheck = 0; /* the number of (internal) checkpoints stored so far */ - /* perform presimulation if necessary */ if (edata && edata->t_presim > 0) - handlePresimulation(&ncheck); + handlePresimulation(); /* loop over timepoints */ for (int it = 0; it < model->nt(); it++) { auto nextTimepoint = model->t(it); - solver->setStopTime(nextTimepoint); - if (nextTimepoint > model->t0()) { if (model->nx_solver == 0) { t = nextTimepoint; @@ -107,19 +103,14 @@ void ForwardProblem::workForwardProblem() { // Solve for nextTimepoint while (t < nextTimepoint) { if (std::isinf(nextTimepoint)) { - SteadystateProblem sstate = SteadystateProblem(&t, &x, &sx); + SteadystateProblem sstate = SteadystateProblem(solver); sstate.workSteadyStateProblem(rdata, solver, model, it); + sstate.writeSolution(&t, x, sx); } else { - int status; - if (solver->getSensitivityMethod() == SensitivityMethod::adjoint && - solver->getSensitivityOrder() >= SensitivityOrder::first) { - status = solver->solveF(nextTimepoint, &x, &dx, - &t, AMICI_NORMAL, &ncheck); - } else { - status = solver->solve(nextTimepoint, &x, &dx, - &t, AMICI_NORMAL); - } - + int status = solver->run(nextTimepoint); + solver->writeSolution(&t, x, dx, sx); + /* sx will be copied from solver on demand if sensitivities + are computed */ if (status == AMICI_ILL_INPUT) { /* clustering of roots => turn off rootfinding */ solver->turnOffRootFinding(); @@ -163,9 +154,10 @@ void ForwardProblem::handlePreequilibration() } // pre-equilibrate - SteadystateProblem sstate = SteadystateProblem(&t, &x, &sx); + SteadystateProblem sstate = SteadystateProblem(solver); sstate.workSteadyStateProblem(rdata, solver, model, -1); + sstate.writeSolution(&t, x, sx); if(overrideFixedParameters) { // Restore @@ -175,41 +167,39 @@ void ForwardProblem::handlePreequilibration() updateAndReinitStatesAndSensitivities(true); } - void ForwardProblem::updateAndReinitStatesAndSensitivities(bool isSteadystate) { if (isSteadystate) { - model->fx_rdata(&x_rdata, &x); - rdata->x_ss = std::move(x_rdata.getVector()); + model->fx_rdata(x_rdata, x); + rdata->x_ss = x_rdata.getVector(); } - model->fx0_fixedParameters(&x); - solver->reInit(t, &x, &dx); - model->fx_rdata(&x_rdata, &x); + model->fx0_fixedParameters(x); + solver->reInit(t, x, dx); + model->fx_rdata(x_rdata, x); - rdata->x0 = std::move(x_rdata.getVector()); + rdata->x0 = x_rdata.getVector(); if (solver->getSensitivityOrder() >= SensitivityOrder::first) { if (isSteadystate) { - model->fsx_rdata(&sx_rdata, &sx); + model->fsx_rdata(sx_rdata, sx); for (int ip = 0; ip < model->nplist(); ip++) std::copy_n(sx_rdata.data(ip), rdata->nx, &rdata->sx_ss.at(ip * rdata->nx)); } - model->fsx0_fixedParameters(&sx, &x); - model->fsx_rdata(&sx_rdata, &sx); + model->fsx0_fixedParameters(sx, x); + model->fsx_rdata(sx_rdata, sx); for (int ip = 0; ip < model->nplist(); ip++) std::copy_n(sx_rdata.data(ip), rdata->nx, &rdata->sx0.at(ip * rdata->nx)); if (solver->getSensitivityMethod() == SensitivityMethod::forward) - solver->sensReInit(&sx, &sdx); + solver->sensReInit(sx, sdx); } } - -void ForwardProblem::handlePresimulation(int *ncheck) +void ForwardProblem::handlePresimulation() { // Are there dedicated condition preequilibration parameters provided? bool overrideFixedParameters = edata && !edata->fixedParametersPresimulation.empty(); @@ -226,14 +216,7 @@ void ForwardProblem::handlePresimulation(int *ncheck) t = model->t0() - edata->t_presim; updateAndReinitStatesAndSensitivities(false); - if (solver->getSensitivityMethod() == SensitivityMethod::adjoint && - solver->getSensitivityOrder() >= SensitivityOrder::first) { - solver->solveF(model->t0(), &x, &dx, - &(t), AMICI_NORMAL, ncheck); - } else { - solver->solve(model->t0(), &x, &dx, - &(t), AMICI_NORMAL); - } + solver->run(model->t0()); if(overrideFixedParameters) { model->setFixedParameters(originalFixedParameters); @@ -245,7 +228,7 @@ void ForwardProblem::handlePresimulation(int *ncheck) void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { /* store heaviside information at event occurence */ - model->froot(t, &x, &dx, rootvals.data()); + model->froot(t, x, dx, rootvals); if (!seflag) { solver->getRootInfo(rootsfound.data()); @@ -261,7 +244,7 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { /* only extract in the first event fired */ if (solver->getSensitivityOrder() >= SensitivityOrder::first && solver->getSensitivityMethod() == SensitivityMethod::forward) { - solver->getSens(&(t), &sx); + sx.copy(solver->getStateSensitivity(t)); } /* only check this in the first event fired, otherwise this will always @@ -281,9 +264,9 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { * x and the old xdot */ if (solver->getSensitivityOrder() >= SensitivityOrder::first) { /* store x and xdot to compute jump in sensitivities */ - x_old = x; + x_old = solver->getState(t); if (solver->getSensitivityMethod() == SensitivityMethod::forward) { - model->fxdot(t, &x, &dx, &xdot); + model->fxdot(t, x, dx, xdot); xdot_old = xdot; dx_old = dx; @@ -295,7 +278,7 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { for (int ie = 0; ie < model->ne; ie++) { if (rootsfound.at(ie) == 1) { /* only consider transitions false -> true */ - model->fstau(t, ie, &x, &sx); + model->fstau(t, ie, x, sx); } } } @@ -322,21 +305,21 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { "occured events exceeded (nmaxevents)*(number of " "events in model definition)!"); /* reinitialise so that we can continue in peace */ - solver->reInit(t, &x, &dx); + solver->reInit(t, x, dx); return; } if (solver->getSensitivityOrder() >= SensitivityOrder::first && solver->getSensitivityMethod() == SensitivityMethod::forward) { /* compute the new xdot */ - model->fxdot(t, &x, &dx, &xdot); + model->fxdot(t, x, dx, xdot); applyEventSensiBolusFSA(); } int secondevent = 0; /* check whether we need to fire a secondary event */ - model->froot(t, &x, &dx, rootvals.data()); + model->froot(t, x, dx, rootvals); for (int ie = 0; ie < model->ne; ie++) { /* the same event should not trigger itself */ if (rootsfound.at(ie) == 0) { @@ -363,24 +346,22 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { /* only reinitialise in the first event fired */ if (!seflag) { - solver->reInit(t, &x, &dx); + solver->reInit(t, x, dx); if (solver->getSensitivityOrder() >= SensitivityOrder::first) { if (solver->getSensitivityMethod() == SensitivityMethod::forward) { - solver->sensReInit(&sx, &sdx); + solver->sensReInit(sx, sdx); } } - solver->reInitPostProcessF(&t, &x, &dx, - model->gett(model->nt() - 1, rdata)); } } void ForwardProblem::storeJacobianAndDerivativeInReturnData() { - model->fxdot(t, &x, &dx, &xdot); + model->fxdot(t, x, dx, xdot); rdata->xdot = xdot.getVector(); - model->fJ(t, 0.0, &x, &dx, &xdot, Jtmp.get()); + model->fJ(t, 0.0, x, dx, xdot, Jtmp.get()); // CVODES uses colmajor, so we need to transform to rowmajor for (int ix = 0; ix < model->nx_solver; ix++) { for (int jx = 0; jx < model->nx_solver; jx++) { @@ -394,7 +375,7 @@ void ForwardProblem::storeJacobianAndDerivativeInReturnData() { void ForwardProblem::getEventOutput() { if (t == model->gett(model->nt() - 1,rdata)) { // call from fillEvent at last timepoint - model->froot(t, &x, &dx, rootvals.data()); + model->froot(t, x, dx, rootvals); } /* EVENT OUTPUT */ @@ -409,11 +390,11 @@ void ForwardProblem::getEventOutput() { } /* get event output */ - model->fz(nroots.at(ie), ie, t, &x, rdata); + model->fz(nroots.at(ie), ie, t, x, rdata); /* if called from fillEvent at last timepoint, then also get the root function value */ if (t == model->gett(model->nt() - 1,rdata)) - model->frz(nroots.at(ie), ie, t, &x, rdata); + model->frz(nroots.at(ie), ie, t, x, rdata); if (edata) { model->fsigmaz(t, ie, nroots.data(), rdata, edata); @@ -453,13 +434,13 @@ void ForwardProblem::prepEventSensis(int ie) { if(model->z2event[iz] - 1 != ie) continue; - model->fdzdp(t, ie, &x); + model->fdzdp(t, ie, x); - model->fdzdx(t, ie, &x); + model->fdzdx(t, ie, x); if (t == model->gett(model->nt() - 1,rdata)) { - model->fdrzdp(t, ie, &x); - model->fdrzdx(t, ie, &x); + model->fdrzdp(t, ie, x); + model->fdrzdx(t, ie, x); } /* extract the value for the standard deviation, if the data value is NaN, use the parameter value. Store this value in the return @@ -490,19 +471,19 @@ void ForwardProblem::getEventSensisFSA(int ie) { if (t == model->t(model->nt() - 1)) { // call from fillEvent at last timepoint model->fsz_tf(nroots.data(),ie, rdata); - model->fsrz(nroots.at(ie),ie,t,&x,&sx,rdata); + model->fsrz(nroots.at(ie), ie, t, x, sx, rdata); } else { - model->fsz(nroots.at(ie),ie,t,&x,&sx,rdata); + model->fsz(nroots.at(ie), ie, t, x, sx, rdata); } if (edata) { - model->fsJz(nroots.at(ie),dJzdx,&sx,rdata); + model->fsJz(nroots.at(ie), dJzdx, sx, rdata); } } void ForwardProblem::handleDataPoint(int it) { - model->fx_rdata(&x_rdata, &x); + model->fx_rdata(x_rdata, x); std::copy_n(x_rdata.data(), rdata->nx, &rdata->x.at(it*rdata->nx)); if (model->t(it) > model->t0()) { @@ -514,7 +495,7 @@ void ForwardProblem::handleDataPoint(int it) { void ForwardProblem::getDataOutput(int it) { - model->fy(rdata->ts[it], it, &x, rdata); + model->fy(rdata->ts[it], it, x, rdata); model->fsigmay(it, rdata, edata); model->fJy(it, rdata, edata); model->fres(it, rdata, edata); @@ -529,8 +510,8 @@ void ForwardProblem::getDataOutput(int it) { void ForwardProblem::prepDataSensis(int it) { - model->fdydx(rdata->ts[it], &x); - model->fdydp(rdata->ts[it], &x); + model->fdydx(rdata->ts[it], x); + model->fdydp(rdata->ts[it], x); if (!edata) return; @@ -544,11 +525,7 @@ void ForwardProblem::prepDataSensis(int it) { void ForwardProblem::getDataSensisFSA(int it) { - if (!std::isinf(model->t(it)) && model->t(it) > model->t0()) { - solver->getSens(&(t), &sx); - } - - model->fsx_rdata(&sx_rdata, &sx); + model->fsx_rdata(sx_rdata, sx); for (int ix = 0; ix < rdata->nx; ix++) { for (int ip = 0; ip < model->nplist(); ip++) { rdata->sx[(it * model->nplist() + ip) * rdata->nx + ix] = @@ -558,9 +535,9 @@ void ForwardProblem::getDataSensisFSA(int it) { model->fdsigmaydp(it, rdata, edata); - model->fsy(it, &sx, rdata); + model->fsy(it, sx, rdata); if (edata) { - model->fsJy(it, dJydx, &sx, rdata); + model->fsJy(it, dJydx, sx, rdata); model->fsres(it, rdata, edata); model->fFIM(it, rdata); } @@ -571,7 +548,7 @@ void ForwardProblem::applyEventBolus() { for (int ie = 0; ie < model->ne; ie++) { if (rootsfound.at(ie) == 1) { /* only consider transitions false -> true */ - model->fdeltax(ie, t, &x, &xdot, &xdot_old); + model->fdeltax(ie, t, x, xdot, xdot_old); amici_daxpy(model->nx_solver, 1.0, model->deltax.data(), 1, x.data(), 1); } @@ -583,7 +560,7 @@ void ForwardProblem::applyEventSensiBolusFSA() { for (int ie = 0; ie < model->ne; ie++) { if (rootsfound.at(ie) == 1) { /* only consider transitions false -> true */ - model->fdeltasx(ie, t, &x_old, &sx, &xdot, &xdot_old); + model->fdeltasx(ie, t, x_old, sx, xdot, xdot_old); for (int ip = 0; ip < model->nplist(); ip++) { amici_daxpy(model->nx_solver, 1.0, &model->deltasx[model->nx_solver * ip], 1, sx.data(ip), 1); diff --git a/src/misc.cpp b/src/misc.cpp index 6b89288b7b..d7a80ccff6 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -4,29 +4,21 @@ #include #include +#include +#if defined(_WIN32) +#define PLATFORM_WINDOWS // Windows +#elif defined(_WIN64) +#define PLATFORM_WINDOWS // Windows +#elif defined(__CYGWIN__) && !defined(_WIN32) +#define PLATFORM_WINDOWS // Windows (Cygwin POSIX under Microsoft Window) +#else +#include +#include // for dladdr +#include // for __cxa_demangle +#endif namespace amici { -int checkFinite(const int N, const realtype *array, const char *fun) { - for (int idx = 0; idx < N; idx++) { - if (isNaN(array[idx])) { - warnMsgIdAndTxt( - "AMICI:NaN", - "AMICI encountered a NaN value at index %i of %i in %s!", idx, - N, fun); - return AMICI_RECOVERABLE_ERROR; - } - if (isInf(array[idx])) { - warnMsgIdAndTxt( - "AMICI:Inf", - "AMICI encountered an Inf value at index %i of %i in %s!", idx, - N, fun); - return AMICI_RECOVERABLE_ERROR; - } - } - return AMICI_SUCCESS; -} - double getUnscaledParameter(double scaledParameter, ParameterScaling scaling) { switch (scaling) { @@ -41,20 +33,19 @@ double getUnscaledParameter(double scaledParameter, ParameterScaling scaling) throw AmiException("Invalid value for ParameterScaling."); } -void unscaleParameters(const double *bufferScaled, const ParameterScaling *pscale, int n, double *bufferUnscaled) +void unscaleParameters(gsl::span bufferScaled, + gsl::span pscale, + gsl::span bufferUnscaled) { - for (int ip = 0; ip < n; ++ip) { + Expects(bufferScaled.size() == pscale.size()); + Expects(bufferScaled.size() == bufferUnscaled.size()); + + for (gsl::span::index_type ip = 0; + ip < bufferScaled.size(); ++ip) { bufferUnscaled[ip] = getUnscaledParameter(bufferScaled[ip], pscale[ip]); } - } -void unscaleParameters(const std::vector &bufferScaled, const std::vector &pscale, std::vector &bufferUnscaled) -{ - if(bufferScaled.size() != pscale.size() || pscale.size() != bufferUnscaled.size()) - throw AmiException("Vector size mismatch in unscaleParameters."); - unscaleParameters(bufferScaled.data(), pscale.data(), bufferScaled.size(), bufferUnscaled.data()); -} double getScaledParameter(double unscaledParameter, ParameterScaling scaling) { @@ -71,19 +62,84 @@ double getScaledParameter(double unscaledParameter, ParameterScaling scaling) } -void scaleParameters(const std::vector &bufferUnscaled, const std::vector &pscale, std::vector &bufferScaled) +void scaleParameters(gsl::span bufferUnscaled, + gsl::span pscale, + gsl::span bufferScaled) { - if(bufferScaled.size() != pscale.size() || pscale.size() != bufferUnscaled.size()) - throw AmiException("Vector size mismatch in scaleParameters."); - for (int ip = 0; ip < (int) bufferUnscaled.size(); ++ip) { + Expects(bufferScaled.size() == pscale.size()); + Expects(bufferScaled.size() == bufferUnscaled.size()); + + for (gsl::span::index_type ip = 0; + ip < bufferUnscaled.size(); ++ip) { bufferScaled[ip] = getScaledParameter(bufferUnscaled[ip], pscale[ip]); } } -int checkFinite(const std::vector &array, const char *fun) +int checkFinite(gsl::span array, const char *fun) { - return checkFinite(array.size(), array.data(), fun); + for (int idx = 0; idx < (int) array.size(); idx++) { + if (isNaN(array[idx])) { + warnMsgIdAndTxt( + "AMICI:NaN", + "AMICI encountered a NaN value at index %i of %i in %s!", idx, + (int) array.size(), fun); + return AMICI_RECOVERABLE_ERROR; + } + if (isInf(array[idx])) { + warnMsgIdAndTxt( + "AMICI:Inf", + "AMICI encountered an Inf value at index %i of %i in %s!", idx, + (int) array.size(), fun); + return AMICI_RECOVERABLE_ERROR; + } + } + return AMICI_SUCCESS; +} + +std::string backtraceString(const int maxFrames) +{ + std::ostringstream trace_buf; + +#ifdef PLATFORM_WINDOWS + trace_buf << "stacktrace not available on windows platforms\n"; +#else + void *callstack[maxFrames]; + char buf[1024]; + int nFrames = backtrace(callstack, maxFrames); + char **symbols = backtrace_symbols(callstack, nFrames); + + // start at 2 to omit AmiException and storeBacktrace + for (int i = 2; i < nFrames; i++) { + // call + Dl_info info; + if (dladdr(callstack[i], &info) && info.dli_sname) { + char *demangled = nullptr; + int status = -1; + if (info.dli_sname[0] == '_') + demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, + &status); + snprintf(buf, sizeof(buf), "%-3d %*p %s + %zd\n", i - 2, + int(2 + sizeof(void *) * 2), callstack[i], + status == 0 ? demangled + : info.dli_sname == nullptr ? symbols[i] + : info.dli_sname, + static_cast((char *)callstack[i] - + (char *)info.dli_saddr)); + free(demangled); + } else { + snprintf(buf, sizeof(buf), "%-3d %*p %s\n", i - 2, + int(2 + sizeof(void *) * 2), callstack[i], + symbols[i]); + } + trace_buf << buf; + } + free(symbols); + + if (nFrames == maxFrames) + trace_buf << "[truncated]\n"; +#endif + return trace_buf.str(); } } // namespace amici diff --git a/src/model.cpp b/src/model.cpp index 64183ea04a..c43e70d73f 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -1,59 +1,132 @@ #include "amici/model.h" #include "amici/amici.h" -#include "amici/misc.h" #include "amici/exception.h" +#include "amici/misc.h" #include "amici/symbolic_functions.h" -#include #include -#include #include +#include +#include +#include #include #include -#include namespace amici { -void Model::fsy(const int it, const AmiVectorArray *sx, ReturnData *rdata) { +Model::Model() : dxdotdp(0, 0), x_pos_tmp(0) {} + +Model::Model(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, + const int nxtrue_solver, const int ny, const int nytrue, + const int nz, const int nztrue, const int ne, const int nJ, + const int nw, const int ndwdx, const int ndwdp, const int ndxdotdw, + std::vector ndJydy, const int nnz, const int ubw, + const int lbw, SecondOrderMode o2mode, + const std::vector &p, std::vector k, + const std::vector &plist, std::vector idlist, + std::vector z2event) + : nx_rdata(nx_rdata), nxtrue_rdata(nxtrue_rdata), nx_solver(nx_solver), + nxtrue_solver(nxtrue_solver), ny(ny), nytrue(nytrue), nz(nz), + nztrue(nztrue), ne(ne), nw(nw), ndwdx(ndwdx), ndwdp(ndwdp), + ndxdotdw(ndxdotdw), ndJydy(std::move(ndJydy)), nnz(nnz), nJ(nJ), ubw(ubw), + lbw(lbw), o2mode(o2mode), z2event(std::move(z2event)), + idlist(std::move(idlist)), sigmay(ny, 0.0), + dsigmaydp(ny * plist.size(), 0.0), sigmaz(nz, 0.0), + dsigmazdp(nz * plist.size(), 0.0), dJydp(nJ * plist.size(), 0.0), + dJzdp(nJ * plist.size(), 0.0), deltax(nx_solver, 0.0), + deltasx(nx_solver * plist.size(), 0.0), deltaxB(nx_solver, 0.0), + deltaqB(nJ * plist.size(), 0.0), dxdotdp(nx_solver, plist.size()), + J(nx_solver, nx_solver, nnz, CSC_MAT), + dxdotdw(nx_solver, nw, ndxdotdw, CSC_MAT), + dwdx(nw, nx_solver, ndwdx, CSC_MAT), M(nx_solver, nx_solver), + my(nytrue, 0.0), mz(nztrue, 0.0), dJydsigma(nJ * nytrue * ny, 0.0), + dJzdz(nJ * nztrue * nz, 0.0), dJzdsigma(nJ * nztrue * nz, 0.0), + dJrzdz(nJ * nztrue * nz, 0.0), dJrzdsigma(nJ * nztrue * nz, 0.0), + dzdx(nz * nx_solver, 0.0), dzdp(nz * plist.size(), 0.0), + drzdx(nz * nx_solver, 0.0), drzdp(nz * plist.size(), 0.0), + dydp(ny * plist.size(), 0.0), dydx(ny * nx_solver, 0.0), w(nw, 0.0), + dwdp(ndwdp, 0.0), stau(plist.size(), 0.0), + sx(nx_solver * plist.size(), 0.0), x_rdata(nx_rdata, 0.0), + sx_rdata(nx_rdata, 0.0), h(ne, 0.0), unscaledParameters(p), + originalParameters(p), fixedParameters(std::move(k)), + total_cl(nx_rdata - nx_solver), stotal_cl((nx_rdata - nx_solver) * np()), + plist_(plist), stateIsNonNegative(nx_solver, false), x_pos_tmp(nx_solver), + pscale(std::vector(p.size(), ParameterScaling::none)) { + + // Can't use derivedClass::wasPythonGenerated() in ctor. + // Guess we are using Python if ndJydy is not empty + if (!this->ndJydy.empty()) { + if (static_cast(nytrue) != this->ndJydy.size()) + throw std::runtime_error( + "Number of elements in ndJydy is not equal " + " nytrue."); + + for (int iytrue = 0; iytrue < nytrue; ++iytrue) + dJydy.emplace_back( + SUNMatrixWrapper(nJ, ny, this->ndJydy[iytrue], CSC_MAT)); + } else { + dJydy_matlab = std::vector(nJ * nytrue * ny, 0.0); + } + + requireSensitivitiesForAllParameters(); +} + +void Model::fdJydy_colptrs(sunindextype * /*indexptrs*/, int /*index*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model::fdJydy_rowvals(sunindextype * /*indexptrs*/, int /*index*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model::fsy(const int it, const AmiVectorArray &sx, ReturnData *rdata) { if (!ny) return; // copy dydp for current time to sy std::copy(dydp.begin(), dydp.end(), &rdata->sy[it * nplist() * ny]); - sx->flatten_to_vector(this->sx); + sx.flatten_to_vector(this->sx); // compute sy = 1.0*dydx*sx + 1.0*sy // dydx A[ny,nx_solver] * sx B[nx_solver,nplist] = sy C[ny,nplist] // M K K N M N // lda ldb ldc - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, ny, nplist(), nx_solver, - 1.0, dydx.data(), ny, this->sx.data(), nx_solver, 1.0, - &rdata->sy[it*nplist()*ny], ny); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, ny, nplist(), nx_solver, 1.0, + dydx.data(), ny, this->sx.data(), nx_solver, 1.0, + &rdata->sy[it * nplist() * ny], ny); - if(alwaysCheckFinite) - checkFinite(nplist()*ny, &rdata->sy[it*nplist()*ny], "sy"); + if (alwaysCheckFinite) + checkFinite(gsl::make_span(&rdata->sy[it * nplist() * ny], + nplist() * ny), "sy"); } void Model::fsz_tf(const int *nroots, const int ie, ReturnData *rdata) { for (int iz = 0; iz < nz; ++iz) if (z2event[iz] - 1 == ie) for (int ip = 0; ip < nplist(); ++ip) - rdata->sz.at((nroots[ie]*nplist()+ip)*nz + iz) = 0.0; + rdata->sz.at((nroots[ie] * nplist() + ip) * nz + iz) = 0.0; } -void Model::fsJy(const int it, const std::vector& dJydx, const AmiVectorArray *sx, ReturnData *rdata) { +void Model::fsJy(const int it, const std::vector &dJydx, + const AmiVectorArray &sx, ReturnData *rdata) { // Compute dJydx*sx for current 'it' // dJydx rdata->nt x nJ x nx_solver // sx rdata->nt x nx_solver x nplist() std::vector multResult(nJ * nplist(), 0); - sx->flatten_to_vector(this->sx); + sx.flatten_to_vector(this->sx); // C := alpha*op(A)*op(B) + beta*C, - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, nJ, - nplist(), nx_solver, 1.0, &dJydx.at(it*nJ*nx_solver), nJ, this->sx.data(), nx_solver, 0.0, - multResult.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nx_solver, 1.0, + &dJydx.at(it * nJ * nx_solver), nJ, this->sx.data(), nx_solver, + 0.0, multResult.data(), nJ); // multResult nJ x nplist() // dJydp nJ x nplist() @@ -64,11 +137,12 @@ void Model::fsJy(const int it, const std::vector& dJydx, const AmiVect for (int iJ = 0; iJ < nJ; ++iJ) { if (iJ == 0) for (int ip = 0; ip < nplist(); ++ip) - rdata->sllh.at(ip) -= multResult.at(ip * nJ) + dJydp.at(ip * nJ); + rdata->sllh.at(ip) -= + multResult.at(ip * nJ) + dJydp.at(ip * nJ); else for (int ip = 0; ip < nplist(); ++ip) rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - multResult.at(iJ + ip * nJ) + dJydp.at(iJ + ip * nJ); + multResult.at(iJ + ip * nJ) + dJydp.at(iJ + ip * nJ); } } @@ -85,32 +159,30 @@ void Model::fdJydp(const int it, ReturnData *rdata, const ExpData *edata) { if (isNaN(my.at(iyt))) continue; - if(wasPythonGenerated()) { + if (wasPythonGenerated()) { // dJydp = 1.0 * dJydp + 1.0 * dJydy * dydp - for(int iplist = 0; iplist < nplist(); ++iplist) { + for (int iplist = 0; iplist < nplist(); ++iplist) { dJydy[iyt].multiply( - gsl::span(&dJydp.at(iplist * nJ), nJ), - gsl::span(&dydp.at(iplist * ny), ny)); + gsl::span(&dJydp.at(iplist * nJ), nJ), + gsl::span(&dydp.at(iplist * ny), ny)); } } else { - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), ny, - 1.0, &dJydy_matlab.at(iyt*nJ*ny), nJ, - dydp.data(), ny, + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), ny, 1.0, + &dJydy_matlab.at(iyt * nJ * ny), nJ, dydp.data(), ny, 1.0, dJydp.data(), nJ); } // dJydp = 1.0 * dJydp + 1.0 * dJydsigma * dsigmaydp - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), ny, - 1.0, &dJydsigma.at(iyt*nJ*ny), nJ, - dsigmaydp.data(), ny, - 1.0, dJydp.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), ny, 1.0, + &dJydsigma.at(iyt * nJ * ny), nJ, dsigmaydp.data(), ny, 1.0, + dJydp.data(), nJ); } if (rdata->sensi_meth != SensitivityMethod::adjoint) return; - if(!ny) + if (!ny) return; for (int iJ = 0; iJ < nJ; iJ++) { @@ -119,13 +191,14 @@ void Model::fdJydp(const int it, ReturnData *rdata, const ExpData *edata) { rdata->sllh.at(ip) -= dJydp[ip * nJ]; } else { rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - dJydp[iJ + ip * nJ]; + dJydp[iJ + ip * nJ]; } } } } -void Model::fdJydx(std::vector& dJydx, const int it, const ExpData *edata) { +void Model::fdJydx(std::vector &dJydx, const int it, + const ExpData *edata) { // dJydy: nJ, ny x nytrue // dydx : ny x nx_solver @@ -140,26 +213,28 @@ void Model::fdJydx(std::vector& dJydx, const int it, const ExpData *ed // M K K N M N // lda ldb ldc - if(wasPythonGenerated()) { - for(int ix = 0; ix < nx_solver; ++ix) { + if (wasPythonGenerated()) { + for (int ix = 0; ix < nx_solver; ++ix) { dJydy[iyt].multiply( - gsl::span(&dJydx.at(it * nx_solver * nJ - + ix * nJ), nJ), - gsl::span(&dydx.at(ix * ny), ny)); + gsl::span( + &dJydx.at(it * nx_solver * nJ + ix * nJ), nJ), + gsl::span(&dydx.at(ix * ny), ny)); } } else { - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nx_solver, ny, 1.0, &dJydy_matlab.at(iyt*ny*nJ), nJ, dydx.data(), ny, 1.0, - &dJydx.at(it*nx_solver*nJ), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nx_solver, ny, 1.0, + &dJydy_matlab.at(iyt * ny * nJ), nJ, dydx.data(), ny, + 1.0, &dJydx.at(it * nx_solver * nJ), nJ); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJydx, "dJydx"); } } -void Model::fsJz(const int nroots, const std::vector& dJzdx, const AmiVectorArray *sx, ReturnData *rdata) { +void Model::fsJz(const int nroots, const std::vector &dJzdx, + const AmiVectorArray &sx, ReturnData *rdata) { // sJz nJ x nplist() // dJzdp nJ x nplist() // dJzdx nmaxevent x nJ x nx_solver @@ -170,22 +245,24 @@ void Model::fsJz(const int nroots, const std::vector& dJzdx, const Ami // sx rdata->nt x nx_solver x nplist() std::vector multResult(nJ * nplist(), 0); - sx->flatten_to_vector(this->sx); + sx.flatten_to_vector(this->sx); // C := alpha*op(A)*op(B) + beta*C, - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, nJ, - nplist(), nx_solver, 1.0, &dJzdx.at(nroots*nx_solver*nJ), nJ, this->sx.data(), nx_solver, 1.0, - multResult.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nx_solver, 1.0, + &dJzdx.at(nroots * nx_solver * nJ), nJ, this->sx.data(), + nx_solver, 1.0, multResult.data(), nJ); // sJy += multResult + dJydp for (int iJ = 0; iJ < nJ; ++iJ) { if (iJ == 0) for (int ip = 0; ip < nplist(); ++ip) - rdata->sllh.at(ip) -= multResult.at(ip * nJ) + dJzdp.at(ip * nJ); + rdata->sllh.at(ip) -= + multResult.at(ip * nJ) + dJzdp.at(ip * nJ); else for (int ip = 0; ip < nplist(); ++ip) rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - multResult.at(iJ + ip * nJ) + dJzdp.at(iJ + ip * nJ); + multResult.at(iJ + ip * nJ) + dJzdp.at(iJ + ip * nJ); } } @@ -196,40 +273,44 @@ void Model::fdJzdp(const int nroots, realtype t, const ExpData *edata, // dzdp nz x nplist() // dJzdp nJ x nplist() - getmz(nroots,edata); - std::fill(dJzdp.begin(),dJzdp.end(),0.0); + getmz(nroots, edata); + std::fill(dJzdp.begin(), dJzdp.end(), 0.0); for (int izt = 0; izt < nztrue; ++izt) { if (isNaN(mz.at(izt))) continue; if (t < rdata->ts.at(rdata->ts.size() - 1)) { // with z - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), nz, 1.0, &dJzdz.at(izt*nz*nJ), nJ, dzdp.data(), nz, - 1.0, dJzdp.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJzdz.at(izt * nz * nJ), nJ, dzdp.data(), nz, 1.0, + dJzdp.data(), nJ); } else { // with rz amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, - &dJrzdsigma.at(izt*nz*nJ), nJ, dsigmazdp.data(), nz, 1.0, - dJzdp.data(), nJ); - - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), nz, 1.0, &dJrzdz.at(izt*nz*nJ), nJ, dzdp.data(), nz, + &dJrzdsigma.at(izt * nz * nJ), nJ, dsigmazdp.data(), nz, 1.0, dJzdp.data(), nJ); + + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJrzdz.at(izt * nz * nJ), nJ, dzdp.data(), nz, 1.0, + dJzdp.data(), nJ); } - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), nz, 1.0, &dJzdsigma.at(izt*nz*nJ), nJ, - dsigmazdp.data(), nz, 1.0, dJzdp.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJzdsigma.at(izt * nz * nJ), nJ, dsigmazdp.data(), nz, 1.0, + dJzdp.data(), nJ); } } -void Model::fdJzdx(std::vector *dJzdx, const int nroots, realtype t, const ExpData *edata, const ReturnData *rdata) { +void Model::fdJzdx(std::vector *dJzdx, const int nroots, realtype t, + const ExpData *edata, const ReturnData *rdata) { // dJzdz nJ x nz x nztrue // dzdx nz x nx_solver // dJzdx nJ x nx_solver x nmaxevent - getmz(nroots,edata); + getmz(nroots, edata); for (int izt = 0; izt < nztrue; ++izt) { if (isNaN(mz.at(izt))) continue; @@ -237,35 +318,40 @@ void Model::fdJzdx(std::vector *dJzdx, const int nroots, realtype t, c if (t < rdata->ts.at(rdata->ts.size() - 1)) { // z amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, &dJzdz.at(izt*nz*nJ), nJ, - dzdx.data(), nz, 1.0, &dJzdx->at(nroots*nx_solver*nJ), nJ); + BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, + &dJzdz.at(izt * nz * nJ), nJ, dzdx.data(), nz, 1.0, + &dJzdx->at(nroots * nx_solver * nJ), nJ); } else { // rz amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, &dJrzdz.at(izt*nz*nJ), nJ, - drzdx.data(), nz, 1.0, &dJzdx->at(nroots*nx_solver*nJ), nJ); + BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, + &dJrzdz.at(izt * nz * nJ), nJ, drzdx.data(), nz, 1.0, + &dJzdx->at(nroots * nx_solver * nJ), nJ); } } } -void Model::initialize(AmiVector *x, AmiVector *dx, - AmiVectorArray *sx, AmiVectorArray * /*sdx*/, - bool computeSensitivities) { +void Model::initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx, + AmiVectorArray & /*sdx*/, bool computeSensitivities) { initializeStates(x); - if(computeSensitivities) + if (computeSensitivities) initializeStateSensitivities(sx, x); fdx0(x, dx); - if(computeSensitivities) + if (computeSensitivities) fsdx0(); - if(ne) - initHeaviside(x,dx); - + if (ne) + initHeaviside(x, dx); } +void Model::initializeB(AmiVector &xB, AmiVector &dxB, AmiVector &xQB) { + xB.reset(); + dxB.reset(); + xQB.reset(); +} -void Model::initializeStates(AmiVector *x) { +void Model::initializeStates(AmiVector &x) { if (x0data.empty()) { fx0(x); } else { @@ -273,12 +359,12 @@ void Model::initializeStates(AmiVector *x) { ftotal_cl(total_cl.data(), x0data.data()); fx_solver(x0_solver.data(), x0data.data()); for (int ix = 0; ix < nx_solver; ix++) { - (*x)[ix] = (realtype) x0_solver.at(ix); + x[ix] = (realtype)x0_solver.at(ix); } } } -void Model::initializeStateSensitivities(AmiVectorArray *sx, AmiVector *x) { +void Model::initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x) { if (sx0data.empty()) { fsx0(sx, x); } else { @@ -287,61 +373,48 @@ void Model::initializeStateSensitivities(AmiVectorArray *sx, AmiVector *x) { for (int ip = 0; ip < nplist(); ip++) { if (ncl() > 0) stcl = &stotal_cl.at(plist(ip) * ncl()); - fstotal_cl(stcl, &sx0data.at(ip*nx_rdata), plist(ip)); - fsx_solver(sx0_solver_slice.data(), &sx0data.at(ip*nx_rdata)); + fstotal_cl(stcl, &sx0data.at(ip * nx_rdata), plist(ip)); + fsx_solver(sx0_solver_slice.data(), &sx0data.at(ip * nx_rdata)); for (int ix = 0; ix < nx_solver; ix++) { - sx->at(ix,ip) = (realtype) sx0_solver_slice.at(ix); + sx.at(ix, ip) = (realtype)sx0_solver_slice.at(ix); } } } } -void Model::initHeaviside(AmiVector *x, AmiVector *dx) { - std::vector rootvals(ne,0.0); - froot(tstart, x, dx, rootvals.data()); +void Model::initHeaviside(AmiVector &x, AmiVector &dx) { + std::vector rootvals(ne, 0.0); + froot(tstart, x, dx, rootvals); for (int ie = 0; ie < ne; ie++) { if (rootvals.at(ie) < 0) { h.at(ie) = 0.0; } else if (rootvals.at(ie) == 0) { - throw AmiException("Simulation started in an event. This could lead to " - "unexpected results, aborting simulation! Please " - "specify an earlier simulation start via " - "options.t0"); + throw AmiException( + "Simulation started in an event. This could lead to " + "unexpected results, aborting simulation! Please " + "specify an earlier simulation start via " + "options.t0"); } else { h.at(ie) = 1.0; } } } -int Model::nplist() const { - return plist_.size(); -} +int Model::nplist() const { return plist_.size(); } -int Model::np() const { - return originalParameters.size(); -} +int Model::np() const { return originalParameters.size(); } -int Model::nk() const { - return fixedParameters.size(); -} +int Model::nk() const { return fixedParameters.size(); } -int Model::ncl() const {return nx_rdata-nx_solver;} +int Model::ncl() const { return nx_rdata - nx_solver; } -const double *Model::k() const{ - return fixedParameters.data(); -} +const double *Model::k() const { return fixedParameters.data(); } -int Model::nMaxEvent() const { - return nmaxevent; -} +int Model::nMaxEvent() const { return nmaxevent; } -void Model::setNMaxEvent(int nmaxevent) { - this->nmaxevent = nmaxevent; -} +void Model::setNMaxEvent(int nmaxevent) { this->nmaxevent = nmaxevent; } -int Model::nt() const { - return ts.size(); -} +int Model::nt() const { return ts.size(); } const std::vector &Model::getParameterScale() const { return pscale; @@ -353,15 +426,16 @@ void Model::setParameterScale(ParameterScaling pscale) { sx0data.clear(); } -void Model::setParameterScale(std::vector const& pscaleVec) { - if(pscale.size() != this->originalParameters.size()) - throw AmiException("Dimension mismatch. Size of parameter scaling does not match number of model parameters."); +void Model::setParameterScale(std::vector const &pscaleVec) { + if (pscaleVec.size() != this->originalParameters.size()) + throw AmiException("Dimension mismatch. Size of parameter scaling does " + "not match number of model parameters."); this->pscale = pscaleVec; scaleParameters(unscaledParameters, this->pscale, originalParameters); sx0data.clear(); } -std::vector const& Model::getParameters() const { +std::vector const &Model::getParameters() const { return originalParameters; } @@ -374,13 +448,16 @@ std::vector const& Model::getParameters() const { * @param id_name string indicating whether name or id was specified * @return value of the selected parameter */ -realtype getValueById(std::vector const& ids, std::vector const& values, - std::string const& id, const char* variable_name, const char* id_name) { +realtype getValueById(std::vector const &ids, + std::vector const &values, + std::string const &id, const char *variable_name, + const char *id_name) { auto it = std::find(ids.begin(), ids.end(), id); - if(it != ids.end()) + if (it != ids.end()) return values.at(it - ids.begin()); - throw AmiException("Could not find %s with specified %s", variable_name, id_name); + throw AmiException("Could not find %s with specified %s", variable_name, + id_name); } /** @@ -392,13 +469,16 @@ realtype getValueById(std::vector const& ids, std::vector * @param variable_name string indicating what variable we are lookin at * @param id_name string indicating whether name or id was specified */ -void setValueById(std::vector const& ids, std::vector &values, realtype value, - std::string const& id, const char* variable_name, const char* id_name) { +void setValueById(std::vector const &ids, + std::vector &values, realtype value, + std::string const &id, const char *variable_name, + const char *id_name) { auto it = std::find(ids.begin(), ids.end(), id); - if(it != ids.end()) + if (it != ids.end()) values.at(it - ids.begin()) = value; else - throw AmiException("Could not find %s with specified %s", variable_name, id_name); + throw AmiException("Could not find %s with specified %s", variable_name, + id_name); } /** @@ -411,124 +491,126 @@ void setValueById(std::vector const& ids, std::vector &va * @param id_name string indicating whether name or id was specified * @return number of matched names/ids */ -int setValueByIdRegex(std::vector const& ids, std::vector &values, realtype value, - std::string const& regex, const char* variable_name, const char* id_name) { +int setValueByIdRegex(std::vector const &ids, + std::vector &values, realtype value, + std::string const ®ex, const char *variable_name, + const char *id_name) { try { - std::regex pattern (regex); + std::regex pattern(regex); int n_found = 0; - for(const auto &id: ids) { - if(std::regex_match(id, pattern)) { + for (const auto &id : ids) { + if (std::regex_match(id, pattern)) { values.at(&id - &ids[0]) = value; ++n_found; } } - if(n_found == 0) - throw AmiException("Could not find %s with specified %s", variable_name, id_name); + if (n_found == 0) + throw AmiException("Could not find %s with specified %s", + variable_name, id_name); return n_found; - } catch (std::regex_error const& e) { - throw AmiException("Specified regex pattern could not be compiled: %s", e.what()); + } catch (std::regex_error const &e) { + throw AmiException("Specified regex pattern could not be compiled: %s", + e.what()); } } -realtype Model::getParameterById(std::string const& par_id) const { - if(!hasParameterIds()) - throw AmiException("Could not access parameters by id as they are not set"); - return getValueById(getParameterIds(), originalParameters, par_id, "parameters", "id"); +realtype Model::getParameterById(std::string const &par_id) const { + if (!hasParameterIds()) + throw AmiException( + "Could not access parameters by id as they are not set"); + return getValueById(getParameterIds(), originalParameters, par_id, + "parameters", "id"); } -realtype Model::getParameterByName(std::string const& par_name) const { - if(!hasParameterNames()) - throw AmiException("Could not access parameters by name as they are not set"); - return getValueById(getParameterNames(), - originalParameters, - par_name, - "parameters", - "name"); +realtype Model::getParameterByName(std::string const &par_name) const { + if (!hasParameterNames()) + throw AmiException( + "Could not access parameters by name as they are not set"); + return getValueById(getParameterNames(), originalParameters, par_name, + "parameters", "name"); } void Model::setParameters(const std::vector &p) { - if(p.size() != (unsigned) np()) - throw AmiException("Dimension mismatch. Size of parameters does not match number of model parameters."); + if (p.size() != (unsigned)np()) + throw AmiException("Dimension mismatch. Size of parameters does not " + "match number of model parameters."); this->originalParameters = p; this->unscaledParameters.resize(originalParameters.size()); unscaleParameters(originalParameters, pscale, unscaledParameters); } -void Model::setParameterById(std::string const& par_id, realtype value) { - if(!hasParameterIds()) - throw AmiException("Could not access parameters by id as they are not set"); +void Model::setParameterById(std::string const &par_id, realtype value) { + if (!hasParameterIds()) + throw AmiException( + "Could not access parameters by id as they are not set"); - setValueById(getParameterIds(), - originalParameters, - value, - par_id, - "parameter", - "id"); + setValueById(getParameterIds(), originalParameters, value, par_id, + "parameter", "id"); unscaleParameters(originalParameters, pscale, unscaledParameters); } -int Model::setParametersByIdRegex(std::string const& par_id_regex, realtype value) { - if(!hasParameterIds()) - throw AmiException("Could not access parameters by id as they are not set"); - int n_found = setValueByIdRegex(getParameterIds(), - originalParameters, - value, - par_id_regex, - "parameter", - "id"); +int Model::setParametersByIdRegex(std::string const &par_id_regex, + realtype value) { + if (!hasParameterIds()) + throw AmiException( + "Could not access parameters by id as they are not set"); + int n_found = setValueByIdRegex(getParameterIds(), originalParameters, + value, par_id_regex, "parameter", "id"); unscaleParameters(originalParameters, pscale, unscaledParameters); return n_found; } -void Model::setParameterByName(std::string const& par_name, realtype value) { - if(!hasParameterNames()) - throw AmiException("Could not access parameters by name as they are not set"); +void Model::setParameterByName(std::string const &par_name, realtype value) { + if (!hasParameterNames()) + throw AmiException( + "Could not access parameters by name as they are not set"); - setValueById(getParameterNames(), - originalParameters, - value, - par_name, - "parameter", - "name"); + setValueById(getParameterNames(), originalParameters, value, par_name, + "parameter", "name"); unscaleParameters(originalParameters, pscale, unscaledParameters); } -int Model::setParametersByNameRegex(std::string const& par_name_regex, realtype value) { - if(!hasParameterNames()) - throw AmiException("Could not access parameters by name as they are not set"); +int Model::setParametersByNameRegex(std::string const &par_name_regex, + realtype value) { + if (!hasParameterNames()) + throw AmiException( + "Could not access parameters by name as they are not set"); - int n_found = setValueByIdRegex(getParameterNames(), - originalParameters, - value, - par_name_regex, - "parameter", - "name"); + int n_found = setValueByIdRegex(getParameterNames(), originalParameters, + value, par_name_regex, "parameter", "name"); unscaleParameters(originalParameters, pscale, unscaledParameters); return n_found; } -bool Model::hasStateIds() const { return nx_rdata == 0 || !getStateIds().empty(); } +bool Model::hasStateIds() const { + return nx_rdata == 0 || !getStateIds().empty(); +} std::vector Model::getStateIds() const { return std::vector(); } -bool Model::hasFixedParameterIds() const { return nk() == 0 || !getFixedParameterIds().empty(); } +bool Model::hasFixedParameterIds() const { + return nk() == 0 || !getFixedParameterIds().empty(); +} std::vector Model::getFixedParameterIds() const { return std::vector(); } -bool Model::hasObservableIds() const { return ny == 0 || !getObservableIds().empty(); } +bool Model::hasObservableIds() const { + return ny == 0 || !getObservableIds().empty(); +} std::vector Model::getObservableIds() const { return std::vector(); } -void Model::setSteadyStateSensitivityMode(const SteadyStateSensitivityMode mode) { +void Model::setSteadyStateSensitivityMode( + const SteadyStateSensitivityMode mode) { steadyStateSensitivityMode = mode; } @@ -538,10 +620,11 @@ SteadyStateSensitivityMode Model::getSteadyStateSensitivityMode() const { void Model::setReinitializeFixedParameterInitialStates(bool flag) { if (flag && !isFixedParameterStateReinitializationAllowed()) - throw AmiException("State reinitialization cannot be enabled for this model" - "as this feature was disabled at compile time. Most likely," - " this was because some initial states depending on " - "fixedParameters also depended on parameters"); + throw AmiException( + "State reinitialization cannot be enabled for this model" + "as this feature was disabled at compile time. Most likely," + " this was because some initial states depending on " + "fixedParameters also depended on parameters"); reinitializeFixedParameterInitialStates = flag; } @@ -549,23 +632,25 @@ bool Model::getReinitializeFixedParameterInitialStates() const { return reinitializeFixedParameterInitialStates; } -void Model::fx_rdata(realtype *x_rdata, const realtype *x_solver, const realtype * /*tcl*/) { +void Model::fx_rdata(realtype *x_rdata, const realtype *x_solver, + const realtype * /*tcl*/) { if (nx_solver != nx_rdata) throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own fx_rdata"); + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own fx_rdata"); std::copy_n(x_solver, nx_solver, x_rdata); } -void Model::fsx_rdata(realtype *sx_rdata, const realtype *sx_solver, const realtype *stcl, const int /*ip*/) { +void Model::fsx_rdata(realtype *sx_rdata, const realtype *sx_solver, + const realtype *stcl, const int /*ip*/) { fx_rdata(sx_rdata, sx_solver, stcl); } void Model::fx_solver(realtype *x_solver, const realtype *x_rdata) { if (nx_solver != nx_rdata) throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own fx_solver"); + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own fx_solver"); std::copy_n(x_rdata, nx_rdata, x_solver); } @@ -579,11 +664,12 @@ void Model::fsx_solver(realtype *sx_solver, const realtype *sx_rdata) { void Model::ftotal_cl(realtype * /*total_cl*/, const realtype * /*x_rdata*/) { if (nx_solver != nx_rdata) throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own ftotal_cl"); + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own ftotal_cl"); } -void Model::fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, const int /*ip*/) { +void Model::fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, + const int /*ip*/) { /* for the moment we do not need an implementation of fstotal_cl as * we can simply reuse ftotal_cl and replace states by their * sensitivities */ @@ -598,85 +684,71 @@ const std::vector &Model::getFixedParameters() const { return fixedParameters; } -realtype Model::getFixedParameterById(std::string const& par_id) const { - if(!hasFixedParameterIds()) - throw AmiException("Could not access fixed parameters by id as they are not set"); +realtype Model::getFixedParameterById(std::string const &par_id) const { + if (!hasFixedParameterIds()) + throw AmiException( + "Could not access fixed parameters by id as they are not set"); - return getValueById(getFixedParameterIds(), - fixedParameters, - par_id, - "fixedParameters", - "id"); + return getValueById(getFixedParameterIds(), fixedParameters, par_id, + "fixedParameters", "id"); } -realtype Model::getFixedParameterByName(std::string const& par_name) const { - if(!hasFixedParameterNames()) - throw AmiException("Could not access fixed parameters by name as they are not set"); +realtype Model::getFixedParameterByName(std::string const &par_name) const { + if (!hasFixedParameterNames()) + throw AmiException( + "Could not access fixed parameters by name as they are not set"); - return getValueById(getFixedParameterNames(), - fixedParameters, - par_name, - "fixedParameters", - "name"); + return getValueById(getFixedParameterNames(), fixedParameters, par_name, + "fixedParameters", "name"); } void Model::setFixedParameters(const std::vector &k) { - if(k.size() != (unsigned) nk()) - throw AmiException("Dimension mismatch. Size of fixedParameters does not match number of fixed model parameters."); + if (k.size() != (unsigned)nk()) + throw AmiException("Dimension mismatch. Size of fixedParameters does " + "not match number of fixed model parameters."); this->fixedParameters = k; } -void Model::setFixedParameterById(std::string const& par_id, realtype value) { - if(!hasFixedParameterIds()) - throw AmiException("Could not access fixed parameters by id as they are not set"); +void Model::setFixedParameterById(std::string const &par_id, realtype value) { + if (!hasFixedParameterIds()) + throw AmiException( + "Could not access fixed parameters by id as they are not set"); - setValueById(getFixedParameterIds(), - fixedParameters, - value, - par_id, - "fixedParameters", - "id"); + setValueById(getFixedParameterIds(), fixedParameters, value, par_id, + "fixedParameters", "id"); } -int Model::setFixedParametersByIdRegex(std::string const& par_id_regex, realtype value) { - if(!hasFixedParameterIds()) - throw AmiException("Could not access fixed parameters by id as they are not set"); +int Model::setFixedParametersByIdRegex(std::string const &par_id_regex, + realtype value) { + if (!hasFixedParameterIds()) + throw AmiException( + "Could not access fixed parameters by id as they are not set"); - return setValueByIdRegex(getFixedParameterIds(), - fixedParameters, - value, - par_id_regex, - "fixedParameters", - "id"); + return setValueByIdRegex(getFixedParameterIds(), fixedParameters, value, + par_id_regex, "fixedParameters", "id"); } -void Model::setFixedParameterByName(std::string const& par_name, realtype value) { - if(!hasFixedParameterNames()) - throw AmiException("Could not access fixed parameters by name as they are not set"); +void Model::setFixedParameterByName(std::string const &par_name, + realtype value) { + if (!hasFixedParameterNames()) + throw AmiException( + "Could not access fixed parameters by name as they are not set"); - setValueById(getFixedParameterNames(), - fixedParameters, - value, - par_name, - "fixedParameters", - "name"); + setValueById(getFixedParameterNames(), fixedParameters, value, par_name, + "fixedParameters", "name"); } -int Model::setFixedParametersByNameRegex(std::string const& par_name_regex, realtype value) { - if(!hasFixedParameterNames()) - throw AmiException("Could not access fixed parameters by name as they are not set"); +int Model::setFixedParametersByNameRegex(std::string const &par_name_regex, + realtype value) { + if (!hasFixedParameterNames()) + throw AmiException( + "Could not access fixed parameters by name as they are not set"); - return setValueByIdRegex(getFixedParameterIds(), - fixedParameters, - value, - par_name_regex, - "fixedParameters", - "name"); + return setValueByIdRegex(getFixedParameterIds(), fixedParameters, value, + par_name_regex, "fixedParameters", "name"); } -std::vector const& Model::getTimepoints() const { - return ts; -} +std::vector const &Model::getTimepoints() const { return ts; } void Model::setTimepoints(const std::vector &ts) { if (!std::is_sorted(ts.begin(), ts.end())) @@ -686,12 +758,12 @@ void Model::setTimepoints(const std::vector &ts) { this->ts = ts; } -std::vector const& Model::getStateIsNonNegative() const { +std::vector const &Model::getStateIsNonNegative() const { return stateIsNonNegative; } -void Model::setStateIsNonNegative(std::vector const& nonNegative) { - if(nx_solver != nx_rdata) { +void Model::setStateIsNonNegative(std::vector const &nonNegative) { + if (nx_solver != nx_rdata) { throw AmiException("Nonnegative states are not supported whith" " conservation laws enabled"); } @@ -700,29 +772,24 @@ void Model::setStateIsNonNegative(std::vector const& nonNegative) { "not agree with number of state variables (%d)", stateIsNonNegative.size(), nx_rdata); } - stateIsNonNegative=nonNegative; - anyStateNonNegative=std::any_of(stateIsNonNegative.begin(), - stateIsNonNegative.end(), - [](bool x) { return x; }); + stateIsNonNegative = nonNegative; + anyStateNonNegative = + std::any_of(stateIsNonNegative.begin(), stateIsNonNegative.end(), + [](bool x) { return x; }); } -void Model::setAllStatesNonNegative() -{ +void Model::setAllStatesNonNegative() { setStateIsNonNegative(std::vector(nx_solver, true)); } -double Model::t(int idx) const { - return ts.at(idx); -} +double Model::t(int idx) const { return ts.at(idx); } -const std::vector &Model::getParameterList() const { - return plist_; -} +const std::vector &Model::getParameterList() const { return plist_; } void Model::setParameterList(const std::vector &plist) { int np = this->np(); // cannot capture 'this' in lambda expression - if(std::any_of(plist.begin(), plist.end(), - [&np](int idx){return idx < 0 || idx >= np;})) { + if (std::any_of(plist.begin(), plist.end(), + [&np](int idx) { return idx < 0 || idx >= np; })) { throw AmiException("Indices in plist must be in [0..np]"); } this->plist_ = plist; @@ -730,9 +797,7 @@ void Model::setParameterList(const std::vector &plist) { initializeVectors(); } -std::vector const& Model::getInitialStates() const { - return x0data; -} +std::vector const &Model::getInitialStates() const { return x0data; } void Model::setInitialStates(const std::vector &x0) { if (x0.size() != (unsigned)nx_rdata && !x0.empty()) @@ -768,21 +833,20 @@ void Model::setInitialStateSensitivities(const std::vector &sx0) { // revert chainrule switch (pscale.at(plist(ip))) { - case ParameterScaling::log10: - chainrulefactor = unscaledParameters.at(plist(ip)) - * log(10); - break; - case ParameterScaling::ln: - chainrulefactor = unscaledParameters.at(plist(ip)); - break; - case ParameterScaling::none: - chainrulefactor = 1.0; - break; + case ParameterScaling::log10: + chainrulefactor = unscaledParameters.at(plist(ip)) * log(10); + break; + case ParameterScaling::ln: + chainrulefactor = unscaledParameters.at(plist(ip)); + break; + case ParameterScaling::none: + chainrulefactor = 1.0; + break; } - for(int ix=0; ix < nx_rdata; ++ix) { - sx0_rdata.at(ip*nx_rdata + ix) = - sx0.at(ip*nx_rdata + ix) / chainrulefactor; + for (int ix = 0; ix < nx_rdata; ++ix) { + sx0_rdata.at(ip * nx_rdata + ix) = + sx0.at(ip * nx_rdata + ix) / chainrulefactor; } } setUnscaledInitialStateSensitivities(sx0_rdata); @@ -803,152 +867,53 @@ void Model::setUnscaledInitialStateSensitivities( sx0data = sx0; } -double Model::t0() const{ - return tstart; -} - -void Model::setT0(double t0) { - tstart = t0; -} - -int Model::plist(int pos) const{ - return plist_.at(pos); -} +double Model::t0() const { return tstart; } -bool Model::hasParameterNames() const { return np() == 0 || !getParameterNames().empty(); } +void Model::setT0(double t0) { tstart = t0; } -std::vector Model::getParameterNames() const { return std::vector(); } +int Model::plist(int pos) const { return plist_.at(pos); } -bool Model::hasStateNames() const { return nx_rdata == 0 || !getStateNames().empty(); } +bool Model::hasParameterNames() const { + return np() == 0 || !getParameterNames().empty(); +} -std::vector Model::getStateNames() const { return std::vector(); } +std::vector Model::getParameterNames() const { + return std::vector(); +} -bool Model::hasFixedParameterNames() const { return nk() == 0 || !getFixedParameterNames().empty(); } +bool Model::hasStateNames() const { + return nx_rdata == 0 || !getStateNames().empty(); +} -std::vector Model::getFixedParameterNames() const { return std::vector(); } +std::vector Model::getStateNames() const { + return std::vector(); +} -bool Model::hasObservableNames() const { return ny == 0 || !getObservableNames().empty(); } +bool Model::hasFixedParameterNames() const { + return nk() == 0 || !getFixedParameterNames().empty(); +} -std::vector Model::getObservableNames() const { return std::vector(); } +std::vector Model::getFixedParameterNames() const { + return std::vector(); +} -bool Model::hasParameterIds() const { return np() == 0 || !getParameterIds().empty(); } +bool Model::hasObservableNames() const { + return ny == 0 || !getObservableNames().empty(); +} -std::vector Model::getParameterIds() const { +std::vector Model::getObservableNames() const { return std::vector(); } +bool Model::hasParameterIds() const { + return np() == 0 || !getParameterIds().empty(); +} -Model::Model() - : nx_rdata(0), nxtrue_rdata(0), nx_solver(0), nxtrue_solver(0), ny(0), - nytrue(0), nz(0), nztrue(0), ne(0), nw(0), ndwdx(0), ndwdp(0), ndxdotdw(0), nnz(0), - nJ(0), ubw(0), lbw(0), o2mode(SecondOrderMode::none), dxdotdp(0,0), - x_pos_tmp(0) {} - -Model::Model(const int nx_rdata, - const int nxtrue_rdata, - const int nx_solver, - const int nxtrue_solver, - const int ny, - const int nytrue, - const int nz, - const int nztrue, - const int ne, - const int nJ, - const int nw, - const int ndwdx, - const int ndwdp, - const int ndxdotdw, - std::vector ndJydy, - const int nnz, - const int ubw, - const int lbw, - SecondOrderMode o2mode, - const std::vector& p, - std::vector k, - const std::vector& plist, - std::vector idlist, - std::vector z2event) - : nx_rdata(nx_rdata), nxtrue_rdata(nxtrue_rdata), - nx_solver(nx_solver), nxtrue_solver(nxtrue_solver), - ny(ny), nytrue(nytrue), - nz(nz), nztrue(nztrue), - ne(ne), - nw(nw), - ndwdx(ndwdx), - ndwdp(ndwdp), - ndxdotdw(ndxdotdw), - ndJydy(std::move(ndJydy)), - nnz(nnz), - nJ(nJ), - ubw(ubw), - lbw(lbw), - o2mode(o2mode), - z2event(std::move(z2event)), - idlist(std::move(idlist)), - sigmay(ny, 0.0), - dsigmaydp(ny*plist.size(), 0.0), - sigmaz(nz, 0.0), - dsigmazdp(nz*plist.size(), 0.0), - dJydp(nJ*plist.size(), 0.0), - dJzdp(nJ*plist.size(), 0.0), - deltax(nx_solver, 0.0), - deltasx(nx_solver*plist.size(), 0.0), - deltaxB(nx_solver, 0.0), - deltaqB(nJ*plist.size(), 0.0), - dxdotdp(nx_solver, plist.size()), - J(nx_solver, nx_solver, nnz, CSC_MAT), - dxdotdw(nx_solver, nw, ndxdotdw, CSC_MAT), - dwdx(nw, nx_solver, ndwdx, CSC_MAT), - M(nx_solver, nx_solver), - my(nytrue, 0.0), - mz(nztrue, 0.0), - dJydsigma(nJ*nytrue*ny, 0.0), - dJzdz(nJ*nztrue*nz, 0.0), - dJzdsigma(nJ*nztrue*nz, 0.0), - dJrzdz(nJ*nztrue*nz, 0.0), - dJrzdsigma(nJ*nztrue*nz, 0.0), - dzdx(nz*nx_solver, 0.0), - dzdp(nz*plist.size(), 0.0), - drzdx(nz*nx_solver, 0.0), - drzdp(nz*plist.size(), 0.0), - dydp(ny*plist.size(), 0.0), - dydx(ny*nx_solver,0.0), - w(nw, 0.0), - dwdp(ndwdp, 0.0), - stau(plist.size(), 0.0), - sx(nx_solver*plist.size(), 0.0), - x_rdata(nx_rdata, 0.0), - sx_rdata(nx_rdata, 0.0), - h(ne,0.0), - unscaledParameters(p), - originalParameters(p), - fixedParameters(std::move(k)), - total_cl(nx_rdata-nx_solver), - stotal_cl((nx_rdata-nx_solver) * np()), - plist_(plist), - stateIsNonNegative(nx_solver, false), - x_pos_tmp(nx_solver), - pscale(std::vector(p.size(), ParameterScaling::none)) -{ - // Can't use derivedClass::wasPythonGenerated() in ctor. - // Guess we are using Python if ndJydy is not empty - if(!this->ndJydy.empty()) { - if(static_cast(nytrue) != this->ndJydy.size()) - throw std::runtime_error("Number of elements in ndJydy is not equal " - " nytrue."); - - for(int iytrue = 0; iytrue < nytrue; ++iytrue) - dJydy.push_back(SUNMatrixWrapper(nJ, ny, this->ndJydy[iytrue], - CSC_MAT)); - } else { - dJydy_matlab = std::vector(nJ*nytrue*ny, 0.0); - } - - requireSensitivitiesForAllParameters(); +std::vector Model::getParameterIds() const { + return std::vector(); } -void Model::initializeVectors() -{ +void Model::initializeVectors() { dsigmaydp.resize(ny * nplist(), 0.0); dsigmazdp.resize(nz * nplist(), 0.0); dJydp.resize(nJ * nplist(), 0.0); @@ -964,248 +929,268 @@ void Model::initializeVectors() sx0data.clear(); } -void Model::fx_rdata(AmiVector *x_rdata, const AmiVector *x) { - fx_rdata(x_rdata->data(), x->data(), total_cl.data()); - if(alwaysCheckFinite) - checkFinite(x_rdata->getLength(), x_rdata->data(), "x_rdata"); +void Model::fx_rdata(AmiVector &x_rdata, const AmiVector &x) { + fx_rdata(x_rdata.data(), x.data(), total_cl.data()); + if (alwaysCheckFinite) + checkFinite(x_rdata.getVector(), "x_rdata"); } -void Model::fx0(AmiVector *x) { +void Model::fx0(AmiVector &x) { std::fill(x_rdata.begin(), x_rdata.end(), 0.0); /* this function also computes initial total abundances */ fx0(x_rdata.data(), tstart, unscaledParameters.data(), fixedParameters.data()); - fx_solver(x->data(), x_rdata.data()); + fx_solver(x.data(), x_rdata.data()); ftotal_cl(total_cl.data(), x_rdata.data()); - if(alwaysCheckFinite) { - checkFinite(x_rdata.size(), x_rdata.data(), "x0 x_rdata"); - checkFinite(x->getLength(), x->data(), "x0 x"); + if (alwaysCheckFinite) { + checkFinite(x_rdata, "x0 x_rdata"); + checkFinite(x.getVector(), "x0 x"); } } -void Model::fx0_fixedParameters(AmiVector *x) { +void Model::fx0_fixedParameters(AmiVector &x) { if (!getReinitializeFixedParameterInitialStates()) return; /* we transform to the unreduced states x_rdata and then apply x0_fixedparameters to (i) enable updates to states that were removed from conservation laws and (ii) be able to correctly compute total abundances after updating the state variables */ - fx_rdata(x_rdata.data(), x->data(), total_cl.data()); + fx_rdata(x_rdata.data(), x.data(), total_cl.data()); fx0_fixedParameters(x_rdata.data(), tstart, unscaledParameters.data(), fixedParameters.data()); - fx_solver(x->data(), x_rdata.data()); + fx_solver(x.data(), x_rdata.data()); /* update total abundances */ ftotal_cl(total_cl.data(), x_rdata.data()); } - - -void Model::fsx_rdata(AmiVectorArray *sx_full, const AmiVectorArray *sx) { +void Model::fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx) { realtype *stcl = nullptr; for (int ip = 0; ip < nplist(); ip++) { if (ncl() > 0) stcl = &stotal_cl.at(plist(ip) * ncl()); - fsx_rdata(sx_full->data(ip), sx->data(ip), stcl, ip); + fsx_rdata(sx_rdata.data(ip), sx.data(ip), stcl, ip); } } -void Model::fsx0(AmiVectorArray *sx, const AmiVector *x) { +void Model::fsx0(AmiVectorArray &sx, const AmiVector &x) { /* this function also computes initial total abundance sensitivities */ realtype *stcl = nullptr; for (int ip = 0; ip < nplist(); ip++) { if (ncl() > 0) stcl = &stotal_cl.at(plist(ip) * ncl()); std::fill(sx_rdata.begin(), sx_rdata.end(), 0.0); - fsx0(sx_rdata.data(), tstart, x->data(), unscaledParameters.data(), + fsx0(sx_rdata.data(), tstart, x.data(), unscaledParameters.data(), fixedParameters.data(), plist(ip)); - fsx_solver(sx->data(ip), sx_rdata.data()); + fsx_solver(sx.data(ip), sx_rdata.data()); fstotal_cl(stcl, sx_rdata.data(), plist(ip)); } } -void Model::fsx0_fixedParameters(AmiVectorArray *sx, const AmiVector *x) { +void Model::fsx0_fixedParameters(AmiVectorArray &sx, const AmiVector &x) { if (!getReinitializeFixedParameterInitialStates()) return; realtype *stcl = nullptr; for (int ip = 0; ip < nplist(); ip++) { if (ncl() > 0) stcl = &stotal_cl.at(plist(ip) * ncl()); - fsx_rdata(sx_rdata.data(), sx->data(ip), stcl, plist(ip)); - fsx0_fixedParameters(sx_rdata.data(), tstart, x->data(), + fsx_rdata(sx_rdata.data(), sx.data(ip), stcl, plist(ip)); + fsx0_fixedParameters(sx_rdata.data(), tstart, x.data(), unscaledParameters.data(), fixedParameters.data(), plist(ip)); - fsx_solver(sx->data(ip), sx_rdata.data()); + fsx_solver(sx.data(ip), sx_rdata.data()); fstotal_cl(stcl, sx_rdata.data(), plist(ip)); } } void Model::fsdx0() {} -void Model::fstau(const realtype t, const int ie, const AmiVector *x, const AmiVectorArray *sx) { - std::fill(stau.begin(),stau.end(),0.0); - for(int ip = 0; ip < nplist(); ip++){ - fstau(&stau.at(ip),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),sx->data(ip),plist(ip),ie); +void Model::fstau(const realtype t, const int ie, const AmiVector &x, + const AmiVectorArray &sx) { + std::fill(stau.begin(), stau.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { + fstau(&stau.at(ip), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), sx.data(ip), plist(ip), ie); } } -void Model::fy(const realtype t, const int it, const AmiVector *x, ReturnData *rdata) { +void Model::fy(const realtype t, const int it, const AmiVector &x, + ReturnData *rdata) { if (!ny) return; - fw(t,x->data()); - fy(&rdata->y.at(it*ny),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),w.data()); + fw(t, x.data()); + fy(&rdata->y.at(it * ny), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data()); - if(alwaysCheckFinite) { - amici::checkFinite(ny, &rdata->y.at(it*ny), "y"); + if (alwaysCheckFinite) { + amici::checkFinite(gsl::make_span(&rdata->y.at(it * ny), ny), "y"); } } -void Model::fdydp(const realtype t, const AmiVector *x) { +void Model::fdydp(const realtype t, const AmiVector &x) { if (!ny) return; - std::fill(dydp.begin(),dydp.end(),0.0); - fw(t, x->data()); - fdwdp(t, x->data()); + std::fill(dydp.begin(), dydp.end(), 0.0); + fw(t, x.data()); + fdwdp(t, x.data()); // if dwdp is not dense, fdydp will expect the full sparse array realtype *dwdp_tmp = dwdp.data(); - for(int ip = 0; ip < nplist(); ip++){ + for (int ip = 0; ip < nplist(); ip++) { // get dydp slice (ny) for current time and parameter if (wasPythonGenerated() && nw) dwdp_tmp = &dwdp.at(nw * ip); - fdydp(&dydp.at(ip*ny), - t, - x->data(), - unscaledParameters.data(), - fixedParameters.data(), - h.data(), - plist(ip), - w.data(), - dwdp_tmp); + fdydp(&dydp.at(ip * ny), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip), w.data(), dwdp_tmp); } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dydp, "dydp"); } } -void Model::fdydx(const realtype t, const AmiVector *x) { +void Model::fdydx(const realtype t, const AmiVector &x) { if (!ny) return; - std::fill(dydx.begin(),dydx.end(),0.0); - fw(t,x->data()); - fdwdx(t,x->data()); - fdydx(dydx.data(),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),w.data(),dwdx.data()); + std::fill(dydx.begin(), dydx.end(), 0.0); + fw(t, x.data()); + fdwdx(t, x.data()); + fdydx(dydx.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data(), dwdx.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dydx, "dydx"); } } -void Model::fz(const int nroots, const int ie, const realtype t, const AmiVector *x, ReturnData *rdata) { - fz(&rdata->z.at(nroots*nz),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data()); +void Model::fz(const int nroots, const int ie, const realtype t, + const AmiVector &x, ReturnData *rdata) { + fz(&rdata->z.at(nroots * nz), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); } -void Model::fsz(const int nroots, const int ie, const realtype t, const AmiVector *x, const AmiVectorArray *sx, ReturnData *rdata) { - for(int ip = 0; ip < nplist(); ip++ ){ - fsz(&rdata->sz.at((nroots*nplist()+ip)*nz),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),sx->data(ip),plist(ip)); +void Model::fsz(const int nroots, const int ie, const realtype t, + const AmiVector &x, const AmiVectorArray &sx, + ReturnData *rdata) { + for (int ip = 0; ip < nplist(); ip++) { + fsz(&rdata->sz.at((nroots * nplist() + ip) * nz), ie, t, x.data(), + unscaledParameters.data(), fixedParameters.data(), h.data(), + sx.data(ip), plist(ip)); } } -void Model::frz(const int nroots, const int ie, const realtype t, const AmiVector *x, ReturnData *rdata) { - frz(&rdata->rz.at(nroots*nz),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data()); +void Model::frz(const int nroots, const int ie, const realtype t, + const AmiVector &x, ReturnData *rdata) { + frz(&rdata->rz.at(nroots * nz), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); } -void Model::fsrz(const int nroots, const int ie, const realtype t, const AmiVector *x, const AmiVectorArray *sx, ReturnData *rdata) { - for(int ip = 0; ip < nplist(); ip++ ){ - fsrz(&rdata->srz.at((nroots*nplist()+ip)*nz),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),sx->data(ip),plist(ip)); +void Model::fsrz(const int nroots, const int ie, const realtype t, + const AmiVector &x, const AmiVectorArray &sx, + ReturnData *rdata) { + for (int ip = 0; ip < nplist(); ip++) { + fsrz(&rdata->srz.at((nroots * nplist() + ip) * nz), ie, t, x.data(), + unscaledParameters.data(), fixedParameters.data(), h.data(), + sx.data(ip), plist(ip)); } } -void Model::fdzdp(const realtype t, const int ie, const AmiVector *x) { - std::fill(dzdp.begin(),dzdp.end(),0.0); - for(int ip = 0; ip < nplist(); ip++){ - fdzdp(dzdp.data(),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),plist(ip)); +void Model::fdzdp(const realtype t, const int ie, const AmiVector &x) { + std::fill(dzdp.begin(), dzdp.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { + fdzdp(dzdp.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip)); } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dzdp, "dzdp"); } } -void Model::fdzdx(const realtype t, const int ie, const AmiVector *x) { - std::fill(dzdx.begin(),dzdx.end(),0.0); - fdzdx(dzdx.data(),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data()); +void Model::fdzdx(const realtype t, const int ie, const AmiVector &x) { + std::fill(dzdx.begin(), dzdx.end(), 0.0); + fdzdx(dzdx.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dzdx, "dzdx"); } } -void Model::fdrzdp(const realtype t, const int ie, const AmiVector *x) { - std::fill(drzdp.begin(),drzdp.end(),0.0); - for(int ip = 0; ip < nplist(); ip++){ - fdrzdp(drzdp.data(),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),plist(ip)); +void Model::fdrzdp(const realtype t, const int ie, const AmiVector &x) { + std::fill(drzdp.begin(), drzdp.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { + fdrzdp(drzdp.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip)); } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(drzdp, "drzdp"); } } +void Model::fdrzdx(const realtype t, const int ie, const AmiVector &x) { + std::fill(drzdx.begin(), drzdx.end(), 0.0); + fdrzdx(drzdx.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); -void Model::fdrzdx(const realtype t, const int ie, const AmiVector *x) { - std::fill(drzdx.begin(),drzdx.end(),0.0); - fdrzdx(drzdx.data(),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data()); - - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(drzdx, "drzdx"); } } -void Model::fdeltax(const int ie, const realtype t, const AmiVector *x, - const AmiVector *xdot, const AmiVector *xdot_old) { - std::fill(deltax.begin(),deltax.end(),0.0); - fdeltax(deltax.data(),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),ie,xdot->data(),xdot_old->data()); +void Model::fdeltax(const int ie, const realtype t, const AmiVector &x, + const AmiVector &xdot, const AmiVector &xdot_old) { + std::fill(deltax.begin(), deltax.end(), 0.0); + fdeltax(deltax.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), ie, xdot.data(), xdot_old.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(deltax, "deltax"); } } -void Model::fdeltasx(const int ie, const realtype t, const AmiVector *x, const AmiVectorArray *sx, - const AmiVector *xdot, const AmiVector *xdot_old) { - fw(t,x->data()); - std::fill(deltasx.begin(),deltasx.end(),0.0); - for(int ip = 0; ip < nplist(); ip++) - fdeltasx(&deltasx.at(nx_solver*ip),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),w.data(), - plist(ip),ie,xdot->data(),xdot_old->data(),sx->data(ip),&stau.at(ip)); +void Model::fdeltasx(const int ie, const realtype t, const AmiVector &x, + const AmiVectorArray &sx, const AmiVector &xdot, + const AmiVector &xdot_old) { + fw(t, x.data()); + std::fill(deltasx.begin(), deltasx.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) + fdeltasx(&deltasx.at(nx_solver * ip), t, x.data(), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data(), plist(ip), ie, xdot.data(), xdot_old.data(), + sx.data(ip), &stau.at(ip)); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(deltasx, "deltasx"); } } -void Model::fdeltaxB(const int ie, const realtype t, const AmiVector *x, const AmiVector *xB, - const AmiVector *xdot, const AmiVector *xdot_old) { - std::fill(deltaxB.begin(),deltaxB.end(),0.0); - fdeltaxB(deltaxB.data(),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),ie,xdot->data(),xdot_old->data(),xB->data()); +void Model::fdeltaxB(const int ie, const realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, + const AmiVector &xdot_old) { + std::fill(deltaxB.begin(), deltaxB.end(), 0.0); + fdeltaxB(deltaxB.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), ie, xdot.data(), xdot_old.data(), + xB.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(deltaxB, "deltaxB"); } } -void Model::fdeltaqB(const int ie, const realtype t, const AmiVector *x, const AmiVector *xB, - const AmiVector *xdot, const AmiVector *xdot_old) { - std::fill(deltaqB.begin(),deltaqB.end(),0.0); - for(int ip = 0; ip < nplist(); ip++) - fdeltaqB(deltaqB.data(),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(), - plist(ip),ie,xdot->data(),xdot_old->data(),xB->data()); +void Model::fdeltaqB(const int ie, const realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, + const AmiVector &xdot_old) { + std::fill(deltaqB.begin(), deltaqB.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) + fdeltaqB(deltaqB.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip), ie, xdot.data(), + xdot_old.data(), xB.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(deltaqB, "deltaqB"); } } @@ -1214,11 +1199,12 @@ void Model::fsigmay(const int it, ReturnData *rdata, const ExpData *edata) { if (!ny) return; - std::fill(sigmay.begin(),sigmay.end(),0.0); + std::fill(sigmay.begin(), sigmay.end(), 0.0); - fsigmay(sigmay.data(),rdata->ts.at(it), unscaledParameters.data(),fixedParameters.data()); + fsigmay(sigmay.data(), rdata->ts.at(it), unscaledParameters.data(), + fixedParameters.data()); - if(edata){ + if (edata) { auto sigmay_edata = edata->getObservedDataStdDevPtr(it); /* extract the value for the standard deviation from ExpData, * if the data value is NaN, use the parameter value */ @@ -1229,8 +1215,8 @@ void Model::fsigmay(const int it, ReturnData *rdata, const ExpData *edata) { } } - for(int i = 0; i < nytrue; ++i) { - if(edata && !std::isnan(edata->getObservedData()[it * nytrue + i])) + for (int i = 0; i < nytrue; ++i) { + if (edata && !std::isnan(edata->getObservedData()[it * nytrue + i])) checkSigmaPositivity(sigmay[i], "sigmay"); } std::copy_n(sigmay.data(), nytrue, &rdata->sigmay[it * rdata->ny]); @@ -1242,16 +1228,15 @@ void Model::fdsigmaydp(const int it, ReturnData *rdata, const ExpData *edata) { std::fill(dsigmaydp.begin(), dsigmaydp.end(), 0.0); - for(int ip = 0; ip < nplist(); ip++) + for (int ip = 0; ip < nplist(); ip++) // get dsigmaydp slice (ny) for current timepoint and parameter - fdsigmaydp(&dsigmaydp.at(ip*ny), - rdata->ts.at(it), - unscaledParameters.data(), - fixedParameters.data(), - plist(ip)); - - // sigmas in edata override model-sigma -> for those sigmas, set dsigmaydp to zero - if(edata){ + fdsigmaydp(&dsigmaydp.at(ip * ny), rdata->ts.at(it), + unscaledParameters.data(), fixedParameters.data(), + plist(ip)); + + // sigmas in edata override model-sigma -> for those sigmas, set dsigmaydp + // to zero + if (edata) { for (int iy = 0; iy < nytrue; iy++) { if (!edata->isSetObservedDataStdDev(it, iy)) continue; @@ -1262,9 +1247,10 @@ void Model::fdsigmaydp(const int it, ReturnData *rdata, const ExpData *edata) { } // copy dsigmaydp slice for current timepoint - std::copy(dsigmaydp.begin(), dsigmaydp.end(), &rdata->ssigmay[it * nplist() * ny]); + std::copy(dsigmaydp.begin(), dsigmaydp.end(), + &rdata->ssigmay[it * nplist() * ny]); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dsigmaydp, "dsigmaydp"); } } @@ -1287,71 +1273,78 @@ void Model::fsigmaz(const realtype t, const int ie, const int *nroots, } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(sigmaz, "sigmaz"); } } -void Model::fdsigmazdp(const realtype t, const int ie, const int *nroots, ReturnData *rdata, const ExpData *edata) { - std::fill(dsigmazdp.begin(),dsigmazdp.end(),0.0); - for(int ip = 0; ip < nplist(); ip++) { +void Model::fdsigmazdp(const realtype t, const int ie, const int *nroots, + ReturnData *rdata, const ExpData *edata) { + std::fill(dsigmazdp.begin(), dsigmazdp.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { // get dsigmazdp slice (nz) for current event and parameter - fdsigmazdp(&dsigmazdp.at(ip*nz), - t, - unscaledParameters.data(), - fixedParameters.data(), - plist(ip)); + fdsigmazdp(&dsigmazdp.at(ip * nz), t, unscaledParameters.data(), + fixedParameters.data(), plist(ip)); } - // sigmas in edata override model-sigma -> for those sigmas, set dsigmazdp to zero - if(edata) { + // sigmas in edata override model-sigma -> for those sigmas, set dsigmazdp + // to zero + if (edata) { for (int iz = 0; iz < nztrue; iz++) { - if (z2event.at(iz) - 1 == ie && !edata->isSetObservedEventsStdDev(nroots[ie],iz)) { - for(int ip = 0; ip < nplist(); ip++) + if (z2event.at(iz) - 1 == ie && + !edata->isSetObservedEventsStdDev(nroots[ie], iz)) { + for (int ip = 0; ip < nplist(); ip++) dsigmazdp.at(iz + nz * ip) = 0; } } } // copy dsigmazdp slice for current event - std::copy(dsigmazdp.begin(), dsigmazdp.end(), &rdata->ssigmaz[nroots[ie] * nplist() * nz]); + std::copy(dsigmazdp.begin(), dsigmazdp.end(), + &rdata->ssigmaz[nroots[ie] * nplist() * nz]); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dsigmazdp, "dsigmazdp"); } } void Model::fJy(const int it, ReturnData *rdata, const ExpData *edata) { - std::vector nllh(nJ,0.0); - getmy(it,edata); - for(int iytrue = 0; iytrue < nytrue; iytrue++){ - if(!isNaN(my.at(iytrue))){ - std::fill(nllh.begin(),nllh.end(),0.0); - fJy(nllh.data(),iytrue, unscaledParameters.data(),fixedParameters.data(),gety(it,rdata),sigmay.data(),my.data()); + std::vector nllh(nJ, 0.0); + getmy(it, edata); + for (int iytrue = 0; iytrue < nytrue; iytrue++) { + if (!isNaN(my.at(iytrue))) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJy(nllh.data(), iytrue, unscaledParameters.data(), + fixedParameters.data(), gety(it, rdata), sigmay.data(), + my.data()); rdata->llh -= nllh.at(0); } } } void Model::fJz(const int nroots, ReturnData *rdata, const ExpData *edata) { - std::vector nllh(nJ,0.0); - getmz(nroots,edata); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - std::fill(nllh.begin(),nllh.end(),0.0); - fJz(nllh.data(),iztrue, unscaledParameters.data(),fixedParameters.data(),getz(nroots,rdata),sigmaz.data(),mz.data()); + std::vector nllh(nJ, 0.0); + getmz(nroots, edata); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJz(nllh.data(), iztrue, unscaledParameters.data(), + fixedParameters.data(), getz(nroots, rdata), sigmaz.data(), + mz.data()); rdata->llh -= nllh.at(0); } } } -void Model::fJrz(const int nroots, ReturnData *rdata, const ExpData * /*edata*/) { - std::vector nllh(nJ,0.0); - getrz(nroots,rdata); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - std::fill(nllh.begin(),nllh.end(),0.0); - fJrz(nllh.data(),iztrue, unscaledParameters.data(),fixedParameters.data(),getrz(nroots,rdata),sigmaz.data()); +void Model::fJrz(const int nroots, ReturnData *rdata, + const ExpData * /*edata*/) { + std::vector nllh(nJ, 0.0); + getrz(nroots, rdata); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJrz(nllh.data(), iztrue, unscaledParameters.data(), + fixedParameters.data(), getrz(nroots, rdata), sigmaz.data()); rdata->llh -= nllh.at(0); } } @@ -1362,44 +1355,37 @@ void Model::fdJydy(const int it, const ReturnData *rdata, // load measurements to my getmy(it, edata); - if(wasPythonGenerated()) { - for(int iytrue = 0; iytrue < nytrue; iytrue++) { + if (wasPythonGenerated()) { + for (int iytrue = 0; iytrue < nytrue; iytrue++) { dJydy[iytrue].zero(); fdJydy_colptrs(dJydy[iytrue].indexptrs(), iytrue); fdJydy_rowvals(dJydy[iytrue].indexvals(), iytrue); - if(isNaN(my.at(iytrue))) { + if (isNaN(my.at(iytrue))) { continue; } // get dJydy slice (ny) for current timepoint and observable - fdJydy(dJydy[iytrue].data(), - iytrue, - unscaledParameters.data(), - fixedParameters.data(), - gety(it,rdata), - sigmay.data(), + fdJydy(dJydy[iytrue].data(), iytrue, unscaledParameters.data(), + fixedParameters.data(), gety(it, rdata), sigmay.data(), my.data()); - if(alwaysCheckFinite) { - amici::checkFinite(ndJydy[iytrue], dJydy[iytrue].data(), "dJydy"); + if (alwaysCheckFinite) { + amici::checkFinite(gsl::make_span(dJydy[iytrue].get()), + "dJydy"); } } } else { std::fill(dJydy_matlab.begin(), dJydy_matlab.end(), 0.0); - for(int iytrue = 0; iytrue < nytrue; iytrue++) { - if(isNaN(my.at(iytrue))) { + for (int iytrue = 0; iytrue < nytrue; iytrue++) { + if (isNaN(my.at(iytrue))) { continue; } - fdJydy(&dJydy_matlab.at(iytrue*ny*nJ), - iytrue, - unscaledParameters.data(), - fixedParameters.data(), - gety(it,rdata), - sigmay.data(), - my.data()); + fdJydy(&dJydy_matlab.at(iytrue * ny * nJ), iytrue, + unscaledParameters.data(), fixedParameters.data(), + gety(it, rdata), sigmay.data(), my.data()); } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { // get dJydy slice (ny) for current timepoint and observable amici::checkFinite(dJydy_matlab, "dJydy"); } @@ -1410,90 +1396,95 @@ void Model::fdJydsigma(const int it, const ReturnData *rdata, const ExpData *edata) { // load measurements to my getmy(it, edata); - std::fill(dJydsigma.begin(),dJydsigma.end(),0.0); + std::fill(dJydsigma.begin(), dJydsigma.end(), 0.0); - for(int iytrue = 0; iytrue < nytrue; iytrue++){ - if(!isNaN(my.at(iytrue))){ + for (int iytrue = 0; iytrue < nytrue; iytrue++) { + if (!isNaN(my.at(iytrue))) { // get dJydsigma slice (ny) for current timepoint and observable - fdJydsigma(&dJydsigma.at(iytrue*ny*nJ), - iytrue, - unscaledParameters.data(), - fixedParameters.data(), - gety(it,rdata), - sigmay.data(), - my.data()); + fdJydsigma(&dJydsigma.at(iytrue * ny * nJ), iytrue, + unscaledParameters.data(), fixedParameters.data(), + gety(it, rdata), sigmay.data(), my.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJydsigma, "dJydsigma"); } } void Model::fdJzdz(const int nroots, const ReturnData *rdata, const ExpData *edata) { - getmz(nroots,edata); - std::fill(dJzdz.begin(),dJzdz.end(),0.0); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - fdJzdz(&dJzdz.at(iztrue*nz*nJ),iztrue, unscaledParameters.data(),fixedParameters.data(),getz(nroots,rdata),sigmaz.data(),mz.data()); + getmz(nroots, edata); + std::fill(dJzdz.begin(), dJzdz.end(), 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + fdJzdz(&dJzdz.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), + getz(nroots, rdata), sigmaz.data(), mz.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJzdz, "dJzdz"); } } void Model::fdJzdsigma(const int nroots, const ReturnData *rdata, const ExpData *edata) { - getmz(nroots,edata); - std::fill(dJzdsigma.begin(),dJzdsigma.end(),0.0); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - fdJzdsigma(&dJzdsigma.at(iztrue*nz*nJ),iztrue, unscaledParameters.data(),fixedParameters.data(),getz(nroots,rdata),sigmaz.data(),mz.data()); + getmz(nroots, edata); + std::fill(dJzdsigma.begin(), dJzdsigma.end(), 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + fdJzdsigma(&dJzdsigma.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), + getz(nroots, rdata), sigmaz.data(), mz.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJzdsigma, "dJzdsigma"); } } void Model::fdJrzdz(const int nroots, const ReturnData *rdata, const ExpData *edata) { - getmz(nroots,edata); - std::fill(dJrzdz.begin(),dJrzdz.end(),0.0); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - fdJrzdz(&dJrzdz.at(iztrue*nz*nJ),iztrue, unscaledParameters.data(),fixedParameters.data(),getrz(nroots,rdata),sigmaz.data()); + getmz(nroots, edata); + std::fill(dJrzdz.begin(), dJrzdz.end(), 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + fdJrzdz(&dJrzdz.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), + getrz(nroots, rdata), sigmaz.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJrzdz, "dJrzdz"); } } -void Model::fdJrzdsigma(const int nroots,const ReturnData *rdata, +void Model::fdJrzdsigma(const int nroots, const ReturnData *rdata, const ExpData * /*edata*/) { - std::fill(dJrzdsigma.begin(),dJrzdsigma.end(),0.0); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - fdJrzdsigma(&dJrzdsigma.at(iztrue*nz*nJ),iztrue, unscaledParameters.data(),fixedParameters.data(),getrz(nroots,rdata),sigmaz.data()); + std::fill(dJrzdsigma.begin(), dJrzdsigma.end(), 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + fdJrzdsigma(&dJrzdsigma.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), + getrz(nroots, rdata), sigmaz.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJrzdsigma, "dJrzdsigma"); } } void Model::fw(const realtype t, const realtype *x) { - std::fill(w.begin(),w.end(),0.0); - fw(w.data(), t, x, unscaledParameters.data(), fixedParameters.data(), h.data(), total_cl.data()); + std::fill(w.begin(), w.end(), 0.0); + fw(w.data(), t, x, unscaledParameters.data(), fixedParameters.data(), + h.data(), total_cl.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(w, "w"); } } @@ -1528,15 +1519,15 @@ void Model::fdwdp(const realtype t, const realtype *x) { } void Model::fdwdx(const realtype t, const realtype *x) { - fw(t,x); + fw(t, x); dwdx.reset(); fdwdx(dwdx.data(), t, x, unscaledParameters.data(), fixedParameters.data(), h.data(), w.data(), total_cl.data()); fdwdx_colptrs(dwdx.indexptrs()); fdwdx_rowvals(dwdx.indexptrs()); - if(alwaysCheckFinite) { - amici::checkFinite(ndwdx, dwdx.data(), "dwdx"); + if (alwaysCheckFinite) { + amici::checkFinite(gsl::make_span(dwdx.get()), "dwdx"); } } @@ -1550,7 +1541,8 @@ void Model::fres(const int it, ReturnData *rdata, const ExpData *edata) { int iyt = iy + it * rdata->ny; if (!edata->isSetObservedData(it, iy)) continue; - rdata->res.at(iyt_true) = (rdata->y.at(iyt) - observedData[iy])/rdata->sigmay.at(iyt); + rdata->res.at(iyt_true) = + (rdata->y.at(iyt) - observedData[iy]) / rdata->sigmay.at(iyt); } } @@ -1571,11 +1563,12 @@ void Model::fsres(const int it, ReturnData *rdata, const ExpData *edata) { for (int iy = 0; iy < nytrue; ++iy) { int iyt_true = iy + it * edata->nytrue(); int iyt = iy + it * rdata->ny; - if (!edata->isSetObservedData(it,iy)) + if (!edata->isSetObservedData(it, iy)) continue; for (int ip = 0; ip < nplist(); ++ip) { rdata->sres.at(iyt_true * nplist() + ip) = - rdata->sy.at(iy + rdata->ny*(ip + it*nplist()))/rdata->sigmay.at(iyt); + rdata->sy.at(iy + rdata->ny * (ip + it * nplist())) / + rdata->sigmay.at(iyt); } } } @@ -1589,14 +1582,14 @@ void Model::fFIM(const int it, ReturnData *rdata) { for (int ip = 0; ip < nplist(); ++ip) { for (int jp = 0; jp < nplist(); ++jp) { rdata->FIM.at(ip + nplist() * jp) += - rdata->sres.at(iyt_true * nplist() + ip) - * rdata->sres.at(iyt_true * nplist() + jp); + rdata->sres.at(iyt_true * nplist() + ip) * + rdata->sres.at(iyt_true * nplist() + jp); } } } } -void Model::updateHeaviside(const std::vector& rootsfound) { +void Model::updateHeaviside(const std::vector &rootsfound) { for (int ie = 0; ie < ne; ie++) { h.at(ie) += rootsfound.at(ie); } @@ -1608,9 +1601,8 @@ void Model::updateHeavisideB(const int *rootsfound) { } } - -void Model::getmy(const int it, const ExpData *edata){ - if(edata) { +void Model::getmy(const int it, const ExpData *edata) { + if (edata) { std::copy_n(edata->getObservedDataPtr(it), nytrue, my.begin()); } else { std::fill(my.begin(), my.end(), getNaN()); @@ -1618,7 +1610,7 @@ void Model::getmy(const int it, const ExpData *edata){ } const realtype *Model::gety(const int it, const ReturnData *rdata) const { - return &rdata->y.at(it*ny); + return &rdata->y.at(it * ny); } realtype Model::gett(const int it, const ReturnData *rdata) const { @@ -1626,7 +1618,7 @@ realtype Model::gett(const int it, const ReturnData *rdata) const { } void Model::getmz(const int nroots, const ExpData *edata) { - if(edata){ + if (edata) { std::copy_n(edata->getObservedEventsPtr(nroots), nztrue, mz.begin()); } else { std::fill(mz.begin(), mz.end(), getNaN()); @@ -1634,41 +1626,37 @@ void Model::getmz(const int nroots, const ExpData *edata) { } const realtype *Model::getz(const int nroots, const ReturnData *rdata) const { - return(&rdata->z.at(nroots*nz)); + return (&rdata->z.at(nroots * nz)); } const realtype *Model::getrz(const int nroots, const ReturnData *rdata) const { - return(&rdata->rz.at(nroots*nz)); + return (&rdata->rz.at(nroots * nz)); } -const realtype *Model::getsz(const int nroots, const int ip, const ReturnData *rdata) const { - return(&rdata->sz.at((nroots*nplist()+ip)*nz)); +const realtype *Model::getsz(const int nroots, const int ip, + const ReturnData *rdata) const { + return (&rdata->sz.at((nroots * nplist() + ip) * nz)); } -const realtype *Model::getsrz(const int nroots, const int ip, const ReturnData *rdata) const { - return(&rdata->srz.at((nroots*nplist()+ip)*nz)); +const realtype *Model::getsrz(const int nroots, const int ip, + const ReturnData *rdata) const { + return (&rdata->srz.at((nroots * nplist() + ip) * nz)); } -void Model::setAlwaysCheckFinite(bool alwaysCheck) -{ +void Model::setAlwaysCheckFinite(bool alwaysCheck) { alwaysCheckFinite = alwaysCheck; } -bool Model::getAlwaysCheckFinite() const -{ - return alwaysCheckFinite; -} +bool Model::getAlwaysCheckFinite() const { return alwaysCheckFinite; } -int Model::checkFinite(const int N, const realtype *array, - const char *fun) const { - auto result = amici::checkFinite(N, array, fun); +int Model::checkFinite(gsl::span array, const char *fun) const { + auto result = amici::checkFinite(array, fun); if (result != AMICI_SUCCESS) { - amici::checkFinite(ts.size(), ts.data(), "ts"); - amici::checkFinite(fixedParameters.size(), fixedParameters.data(), "k"); - amici::checkFinite(unscaledParameters.size(), unscaledParameters.data(), - "p"); - amici::checkFinite(w.size(), w.data(), "w"); + amici::checkFinite(ts, "ts"); + amici::checkFinite(fixedParameters, "k"); + amici::checkFinite(unscaledParameters, "p"); + amici::checkFinite(w, "w"); } return result; @@ -1680,49 +1668,36 @@ void Model::requireSensitivitiesForAllParameters() { initializeVectors(); } -bool operator ==(const Model &a, const Model &b) -{ +bool operator==(const Model &a, const Model &b) { if (typeid(a) != typeid(b)) return false; - return (a.nx_rdata == b.nx_rdata) - && (a.nxtrue_rdata == b.nxtrue_rdata) - && (a.nx_solver == b.nx_solver) - && (a.nxtrue_solver == b.nxtrue_solver) - && (a.ny == b.ny) - && (a.nytrue == b.nytrue) - && (a.nz == b.nz) - && (a.nztrue == b.nztrue) - && (a.ne == b.ne) - && (a.nw == b.nw) - && (a.ndwdx == b.ndwdx) - && (a.ndwdp == b.ndwdp) - && (a.ndxdotdw == b.ndxdotdw) - && (a.nnz == b.nnz) - && (a.nJ == b.nJ) - && (a.ubw == b.ubw) - && (a.lbw == b.lbw) - && (a.o2mode == b.o2mode) - && (a.z2event == b.z2event) - && (a.idlist == b.idlist) - && (a.h == b.h) - && (a.unscaledParameters == b.unscaledParameters) - && (a.originalParameters == b.originalParameters) - && (a.fixedParameters == b.fixedParameters) - && (a.plist_ == b.plist_) - && (a.x0data == b.x0data) - && (a.sx0data == b.sx0data) - && (a.ts == b.ts) - && (a.nmaxevent == b.nmaxevent) - && (a.pscale == b.pscale) - && (a.stateIsNonNegative == b.stateIsNonNegative) - && (a.tstart == b.tstart); -} - -N_Vector Model::computeX_pos(N_Vector x) { - if (anyStateNonNegative){ + return (a.nx_rdata == b.nx_rdata) && (a.nxtrue_rdata == b.nxtrue_rdata) && + (a.nx_solver == b.nx_solver) && + (a.nxtrue_solver == b.nxtrue_solver) && (a.ny == b.ny) && + (a.nytrue == b.nytrue) && (a.nz == b.nz) && (a.nztrue == b.nztrue) && + (a.ne == b.ne) && (a.nw == b.nw) && (a.ndwdx == b.ndwdx) && + (a.ndwdp == b.ndwdp) && (a.ndxdotdw == b.ndxdotdw) && + (a.nnz == b.nnz) && (a.nJ == b.nJ) && (a.ubw == b.ubw) && + (a.lbw == b.lbw) && (a.o2mode == b.o2mode) && + (a.z2event == b.z2event) && (a.idlist == b.idlist) && (a.h == b.h) && + (a.unscaledParameters == b.unscaledParameters) && + (a.originalParameters == b.originalParameters) && + (a.fixedParameters == b.fixedParameters) && (a.plist_ == b.plist_) && + (a.x0data == b.x0data) && (a.sx0data == b.sx0data) && + (a.ts == b.ts) && (a.nmaxevent == b.nmaxevent) && + (a.pscale == b.pscale) && + (a.stateIsNonNegative == b.stateIsNonNegative) && + (a.tstart == b.tstart); +} + +N_Vector Model::computeX_pos(const_N_Vector x) { + if (anyStateNonNegative) { for (int ix = 0; ix < x_pos_tmp.getLength(); ++ix) { - x_pos_tmp.at(ix) = (stateIsNonNegative.at(ix) && NV_Ith_S(x, ix) < 0) ? 0 : NV_Ith_S(x, ix); + x_pos_tmp.at(ix) = + (stateIsNonNegative.at(ix) && NV_Ith_S(x, ix) < 0) + ? 0 + : NV_Ith_S(x, ix); } return x_pos_tmp.getNVector(); } diff --git a/src/model_dae.cpp b/src/model_dae.cpp index 6d2546e21c..339a802b77 100644 --- a/src/model_dae.cpp +++ b/src/model_dae.cpp @@ -1,295 +1,253 @@ -#include "amici/solver_idas.h" #include "amici/model_dae.h" +#include "amici/solver_idas.h" namespace amici { - void Model_DAE::fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) { - fJ(t,cj, x->getNVector(), dx->getNVector(), xdot->getNVector(), J); - } +void Model_DAE::fJ(const realtype t, const realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, SUNMatrix J) { + fJ(t, cj, x.getNVector(), dx.getNVector(), xdot.getNVector(), J); +} - /** Jacobian of xdot with respect to states x - * @param t timepoint - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - * @param J Matrix to which the Jacobian will be written +void Model_DAE::fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector /*xdot*/, SUNMatrix J) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(J); + fJ(SM_DATA_D(J), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(dx), w.data(), + dwdx.data()); +} - **/ - void Model_DAE::fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector /*xdot*/, SUNMatrix J) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(J); - fJ(SM_DATA_D(J), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), h.data(), cj, - N_VGetArrayPointer(dx), w.data(), dwdx.data()); - } +void Model_DAE::fJSparse(const realtype t, const realtype cj, + const AmiVector &x, const AmiVector &dx, + const AmiVector & /*xdot*/, SUNMatrix J) { + fJSparse(t, cj, x.getNVector(), dx.getNVector(), J); +} - void Model_DAE::fJSparse(realtype t, realtype cj, AmiVector *x, - AmiVector *dx, AmiVector * /*xdot*/, SUNMatrix J) { - fJSparse(t, cj, x->getNVector(), dx->getNVector(), J); - } +void Model_DAE::fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, + SUNMatrix J) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(J); + fJSparse(static_cast(SM_CONTENT_S(J)), t, + N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(dx), + w.data(), dwdx.data()); +} + +void Model_DAE::fJSparseB(SUNMatrixContent_Sparse /*JSparseB*/, + const realtype /*t*/, const realtype * /*x*/, + const double * /*p*/, const double * /*k*/, + const realtype * /*h*/, const realtype /*cj*/, + const realtype * /*xB*/, const realtype * /*dx*/, + const realtype * /*dxB*/, const realtype * /*w*/, + const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); +} - /** J in sparse form (for sparse solvers from the SuiteSparse Package) - * @param t timepoint - * @param cj scalar in Jacobian (inverse stepsize) - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param J Matrix to which the Jacobian will be written - */ - void Model_DAE::fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, - SUNMatrix J) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(J); - fJSparse(static_cast(SM_CONTENT_S(J)), t, - N_VGetArrayPointer(x_pos), unscaledParameters.data(), - fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(dx), - w.data(), dwdx.data()); - } +void Model_DAE::fJv(const realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector & /*xdot*/, const AmiVector &v, + AmiVector &Jv, const realtype cj) { + fJv(t, x.getNVector(), dx.getNVector(), v.getNVector(), Jv.getNVector(), + cj); +} - void Model_DAE::fJv(realtype t, AmiVector *x, AmiVector *dx, AmiVector * /*xdot*/, - AmiVector *v, AmiVector *Jv, realtype cj){ - fJv(t,x->getNVector(),dx->getNVector(),v->getNVector(), - Jv->getNVector(),cj); - } +void Model_DAE::fJv(realtype t, N_Vector x, N_Vector dx, N_Vector v, + N_Vector Jv, realtype cj) { + N_VConst(0.0, Jv); + fJSparse(t, cj, x, dx, J.get()); + J.multiply(Jv, v); +} - /** Matrix vector product of J with a vector v (for iterative solvers) - * @param t timepoint @type realtype - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param v Vector with which the Jacobian is multiplied - * @param Jv Vector to which the Jacobian vector product will be - *written - **/ - void Model_DAE::fJv(realtype t, N_Vector x, N_Vector dx, N_Vector v, N_Vector Jv, - realtype cj) { - N_VConst(0.0, Jv); - fJSparse(t, cj, x, dx, J.get()); - J.multiply(Jv, v); - } +void Model_DAE::froot(const realtype t, const AmiVector &x, const AmiVector &dx, + gsl::span root) { + froot(t, x.getNVector(), dx.getNVector(), root); +} - void Model_DAE::froot(realtype t, AmiVector *x, AmiVector *dx, realtype *root){ - froot(t,x->getNVector(),dx->getNVector(),root); - } +void Model_DAE::froot(realtype t, N_Vector x, N_Vector dx, + gsl::span root) { + std::fill(root.begin(), root.end(), 0.0); + auto x_pos = computeX_pos(x); + froot(root.data(), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), N_VGetArrayPointer(dx)); +} - /** Event trigger function for events - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param root array with root function values - */ - void Model_DAE::froot(realtype t, N_Vector x, N_Vector dx, realtype *root) { - memset(root, 0,sizeof(realtype)*ne); - auto x_pos = computeX_pos(x); - froot(root,t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - N_VGetArrayPointer(dx)); - } +void Model_DAE::fxdot(const realtype t, const AmiVector &x, const AmiVector &dx, + AmiVector &xdot) { + fxdot(t, x.getNVector(), dx.getNVector(), xdot.getNVector()); +} - void Model_DAE::fxdot(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot){ - fxdot(t,x->getNVector(),dx->getNVector(),xdot->getNVector()); - } +void Model_DAE::fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot) { + auto x_pos = computeX_pos(x); + fw(t, N_VGetArrayPointer(x)); + N_VConst(0.0, xdot); + fxdot(N_VGetArrayPointer(xdot), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + N_VGetArrayPointer(dx), w.data()); +} - /** residual function of the DAE - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - */ - void Model_DAE::fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot) { - auto x_pos = computeX_pos(x); - fw(t,N_VGetArrayPointer(x)); - N_VConst(0.0,xdot); - fxdot(N_VGetArrayPointer(xdot),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - N_VGetArrayPointer(dx),w.data()); - } +void Model_DAE::fJDiag(const realtype t, AmiVector &JDiag, + const realtype /*cj*/, const AmiVector &x, + const AmiVector &dx) { + auto x_pos = computeX_pos(x.getNVector()); + fdwdx(t, N_VGetArrayPointer(x_pos)); + JDiag.set(0.0); + fJDiag(JDiag.data(), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), 0.0, + dx.data(), w.data(), dwdx.data()); + if (!checkFinite(JDiag.getVector(), "Jacobian")) + throw AmiException("Evaluation of fJDiag failed!"); +} - /** diagonalized Jacobian (for preconditioning) - * @param t timepoint - * @param JDiag Vector to which the Jacobian diagonal will be written - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @return status flag indicating successful execution - **/ - void Model_DAE::fJDiag(realtype t, AmiVector *JDiag, realtype /*cj*/, AmiVector *x, - AmiVector *dx) { - auto x_pos = computeX_pos(x->getNVector()); - fdwdx(t,N_VGetArrayPointer(x_pos)); - JDiag->set(0.0); - fJDiag(JDiag->data(),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - 0.0,dx->data(),w.data(),dwdx.data()); - if(!checkFinite(nx_solver,JDiag->data(),"Jacobian")) - throw AmiException("Evaluation of fJDiag failed!"); +void Model_DAE::fdxdotdp(const realtype t, const N_Vector x, + const N_Vector dx) { + auto x_pos = computeX_pos(x); + fdwdp(t, N_VGetArrayPointer(x_pos)); + for (int ip = 0; ip < nplist(); ip++) { + N_VConst(0.0, dxdotdp.getNVector(ip)); + fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + plist_[ip], N_VGetArrayPointer(dx), w.data(), dwdp.data()); } +} - /** Sensitivity of dx/dt wrt model parameters p - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @return status flag indicating successful execution - */ - void Model_DAE::fdxdotdp(const realtype t, const N_Vector x, const N_Vector dx) { - auto x_pos = computeX_pos(x); - fdwdp(t,N_VGetArrayPointer(x_pos)); - for(int ip = 0; ip < nplist(); ip++){ - N_VConst(0.0, dxdotdp.getNVector(ip)); - fdxdotdp(dxdotdp.data(ip),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - plist_[ip],N_VGetArrayPointer(dx),w.data(),dwdp.data()); - } - - - } +void Model_DAE::fM(realtype t, const N_Vector x) { + SUNMatZero(M.get()); + auto x_pos = computeX_pos(x); + fM(M.data(), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data()); +} - /** - * @brief Mass matrix for DAE systems - * @param t timepoint - * @param x Vector with the states - */ - void Model_DAE::fM(realtype t, const N_Vector x) { - SUNMatZero(M.get()); - auto x_pos = computeX_pos(x); - fM(M.data(),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data()); - } +std::unique_ptr Model_DAE::getSolver() { + return std::unique_ptr(new amici::IDASolver()); +} - std::unique_ptr Model_DAE::getSolver() { - return std::unique_ptr(new amici::IDASolver()); - } +void Model_DAE::fJB(realtype * /*JB*/, const realtype /*t*/, + const realtype * /*x*/, const double * /*p*/, + const double * /*k*/, const realtype * /*h*/, + const realtype /*cj*/, const realtype * /*xB*/, + const realtype * /*dx*/, const realtype * /*dxB*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** Jacobian of xBdot with respect to adjoint state xB - * @param t timepoint - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param JB Matrix to which the Jacobian will be written - **/ - void Model_DAE::fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, SUNMatrix JB) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(JB); - fJB(SM_DATA_D(JB), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), h.data(), cj, - N_VGetArrayPointer(xB), N_VGetArrayPointer(dx), - N_VGetArrayPointer(dxB), w.data(), dwdx.data()); - } +void Model_DAE::fJDiag(realtype * /*JDiag*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype /*cj*/, const realtype * /*dx*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** JB in sparse form (for sparse solvers from the SuiteSparse Package) - * @param t timepoint - * @param cj scalar in Jacobian - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param JB Matrix to which the Jacobian will be written - */ - void Model_DAE::fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, SUNMatrix JB) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(JB); - fJSparseB(static_cast(SM_CONTENT_S(JB)), t, - N_VGetArrayPointer(x_pos), unscaledParameters.data(), - fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(xB), - N_VGetArrayPointer(dx), N_VGetArrayPointer(dxB), w.data(), - dwdx.data()); - } +void Model_DAE::fJvB(realtype * /*JvB*/, const realtype /*t*/, const realtype * /*x*/, + const double * /*p*/, const double * /*k*/, + const realtype * /*h*/, const realtype /*cj*/, + const realtype * /*xB*/, const realtype * /*dx*/, + const realtype * /*dxB*/, const realtype * /*vB*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); // not implemented +} - /** Matrix vector product of JB with a vector v (for iterative solvers) - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param vB Vector with which the Jacobian is multiplied - * @param JvB Vector to which the Jacobian vector product will be - *written - * @param cj scalar in Jacobian (inverse stepsize) - **/ - void Model_DAE::fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector vB, N_Vector JvB, realtype cj) { - N_VConst(0.0, JvB); - fJSparseB(t, cj, x, dx, xB, dxB, J.get()); - J.multiply(JvB, vB); - } +void Model_DAE::froot(realtype * /*root*/, const realtype /*t*/, + const realtype * /*x*/, const double * /*p*/, const double * /*k*/, + const realtype * /*h*/, const realtype * /*dx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); // not implemented +} - /** Right hand side of differential equation for adjoint state xB - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param xBdot Vector with the adjoint right hand side - */ - void Model_DAE::fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector xBdot) { - N_VConst(0.0, xBdot); - fJSparseB(t, 1.0, x, dx, xB, dxB, J.get()); - fM(t, x); - J.multiply(xBdot, xB); - } +void Model_DAE::fdxdotdp(realtype * /*dxdotdp*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const int /*ip*/, const realtype * /*dx*/, + const realtype * /*w*/, const realtype * /*dwdp*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** Right hand side of integral equation for quadrature states qB - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param qBdot Vector with the adjoint quadrature right hand side - */ - void Model_DAE::fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector qBdot) { - N_VConst(0.0, qBdot); - fdxdotdp(t, x, dx); - for (int ip = 0; ip < nplist(); ip++) { +void Model_DAE::fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, SUNMatrix JB) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(JB); + fJB(SM_DATA_D(JB), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(xB), + N_VGetArrayPointer(dx), N_VGetArrayPointer(dxB), w.data(), dwdx.data()); +} + +void Model_DAE::fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, SUNMatrix JB) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(JB); + fJSparseB(static_cast(SM_CONTENT_S(JB)), t, + N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(xB), + N_VGetArrayPointer(dx), N_VGetArrayPointer(dxB), w.data(), + dwdx.data()); +} + +void Model_DAE::fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector vB, N_Vector JvB, realtype cj) { + N_VConst(0.0, JvB); + fJSparseB(t, cj, x, dx, xB, dxB, J.get()); + J.multiply(JvB, vB); +} + +void Model_DAE::fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector xBdot) { + N_VConst(0.0, xBdot); + fJSparseB(t, 1.0, x, dx, xB, dxB, J.get()); + fM(t, x); + J.multiply(xBdot, xB); +} + +void Model_DAE::fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector /*dxB*/, N_Vector qBdot) { + N_VConst(0.0, qBdot); + fdxdotdp(t, x, dx); + for (int ip = 0; ip < nplist(); ip++) { + for (int ix = 0; ix < nxtrue_solver; ix++) + NV_Ith_S(qBdot, ip * nJ) -= NV_Ith_S(xB, ix) * dxdotdp.at(ix, ip); + // second order part + for (int iJ = 1; iJ < nJ; iJ++) for (int ix = 0; ix < nxtrue_solver; ix++) - NV_Ith_S(qBdot, ip * nJ) -= - NV_Ith_S(xB, ix) * dxdotdp.at(ix, ip); - // second order part - for (int iJ = 1; iJ < nJ; iJ++) - for (int ix = 0; ix < nxtrue_solver; ix++) - NV_Ith_S(qBdot, ip * nJ + iJ) -= - NV_Ith_S(xB, ix) * - dxdotdp.at(ix + iJ * nxtrue_solver, ip) + - NV_Ith_S(xB, ix + iJ * nxtrue_solver) * - dxdotdp.at(ix, ip); - } + NV_Ith_S(qBdot, ip * nJ + iJ) -= + NV_Ith_S(xB, ix) * dxdotdp.at(ix + iJ * nxtrue_solver, ip) + + NV_Ith_S(xB, ix + iJ * nxtrue_solver) * dxdotdp.at(ix, ip); } +} - void Model_DAE::fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, - AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) { - fsxdot(t,x->getNVector(),dx->getNVector(), ip, - sx->getNVector(),sdx->getNVector(), - sxdot->getNVector()); - } +void Model_DAE::fsxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, const int ip, const AmiVector &sx, + const AmiVector &sdx, AmiVector &sxdot) { + fsxdot(t, x.getNVector(), dx.getNVector(), ip, sx.getNVector(), + sdx.getNVector(), sxdot.getNVector()); +} - /** Right hand side of differential equation for state sensitivities sx - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param ip parameter index - * @param sx Vector with the state sensitivities - * @param sdx Vector with the derivative state sensitivities - * @param sxdot Vector with the sensitivity right hand side - */ - void Model_DAE::fsxdot(realtype t, N_Vector x, N_Vector dx, int ip, - N_Vector sx, N_Vector sdx, N_Vector sxdot) { - if(ip == 0) { - // we only need to call this for the first parameter index will be - // the same for all remaining - fM(t, x); - fdxdotdp(t, x, dx); - fJSparse(t, 0.0, x, dx, J.get()); - } - N_VScale(1.0, dxdotdp.getNVector(ip), sxdot); - J.multiply(sxdot, sx); - N_VScale(-1.0, sdx, sdx); - M.multiply(sxdot, sdx); - N_VScale(-1.0, sdx, sdx); +void Model_DAE::fsxdot(realtype t, N_Vector x, N_Vector dx, int ip, N_Vector sx, + N_Vector sdx, N_Vector sxdot) { + if (ip == 0) { + // we only need to call this for the first parameter index will be + // the same for all remaining + fM(t, x); + fdxdotdp(t, x, dx); + fJSparse(t, 0.0, x, dx, J.get()); } + N_VScale(1.0, dxdotdp.getNVector(ip), sxdot); + J.multiply(sxdot, sx); + N_VScale(-1.0, sdx, sdx); + M.multiply(sxdot, sdx); + N_VScale(-1.0, sdx, sdx); } + +} // namespace amici diff --git a/src/model_ode.cpp b/src/model_ode.cpp index 9f9f93c72e..4284c801d3 100644 --- a/src/model_ode.cpp +++ b/src/model_ode.cpp @@ -1,314 +1,350 @@ -#include "amici/solver_cvodes.h" #include "amici/model_ode.h" +#include "amici/solver_cvodes.h" namespace amici { - void Model_ODE::fJ(realtype t, realtype /*cj*/, AmiVector *x, AmiVector * /*dx*/, - AmiVector *xdot, SUNMatrix J) { - fJ(t, x->getNVector(), xdot->getNVector(), J); +void Model_ODE::fJ(const realtype t, const realtype /*cj*/, const AmiVector &x, + const AmiVector & /*dx*/, const AmiVector &xdot, + SUNMatrix J) { + fJ(t, x.getNVector(), xdot.getNVector(), J); +} - } +void Model_ODE::fJ(realtype t, N_Vector x, N_Vector /*xdot*/, SUNMatrix J) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(J); + fJ(SM_DATA_D(J), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data(), dwdx.data()); +} - /** implementation of fJ at the N_Vector level, this function provides an - *interface to the model specific routines for the solver implementation - *aswell as the AmiVector level implementation - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - * @param J Matrix to which the Jacobian will be written - **/ - void Model_ODE::fJ(realtype t, N_Vector x, N_Vector /*xdot*/, SUNMatrix J) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(J); - fJ(SM_DATA_D(J), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), h.data(), - w.data(), dwdx.data()); - } +void Model_ODE::fJSparse(const realtype t, const realtype /*cj*/, + const AmiVector &x, const AmiVector & /*dx*/, + const AmiVector & /*xdot*/, SUNMatrix J) { + fJSparse(t, x.getNVector(), J); +} - void Model_ODE::fJSparse(realtype t, realtype /*cj*/, AmiVector *x, - AmiVector * /*dx*/, AmiVector * /*xdot*/, SUNMatrix J) { - fJSparse(t, x->getNVector(), J); +void Model_ODE::fJSparse(realtype t, N_Vector x, SUNMatrix J) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(J); + if (wasPythonGenerated()) { + fJSparse(SM_DATA_S(J), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data(), dwdx.data()); + fJSparse_colptrs(SM_INDEXPTRS_S(J)); + fJSparse_rowvals(SM_INDEXVALS_S(J)); + } else { + fJSparse(static_cast(SM_CONTENT_S(J)), t, + N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data(), dwdx.data()); } +} + +void Model_ODE::fJv(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/, const AmiVector & /*xdot*/, + const AmiVector &v, AmiVector &Jv, const realtype /*cj*/) { + fJv(v.getNVector(), Jv.getNVector(), t, x.getNVector()); +} + +void Model_ODE::fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x) { + N_VConst(0.0, Jv); + fJSparse(t, x, J.get()); + J.multiply(Jv, v); +} + +void Model_ODE::froot(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/, gsl::span root) { + froot(t, x.getNVector(), root); +} + +void Model_ODE::froot(realtype t, N_Vector x, gsl::span root) { + auto x_pos = computeX_pos(x); + std::fill(root.begin(), root.end(), 0.0); + froot(root.data(), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data()); +} + +void Model_ODE::fxdot(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/, AmiVector &xdot) { + fxdot(t, x.getNVector(), xdot.getNVector()); +} + +void Model_ODE::fxdot(realtype t, N_Vector x, N_Vector xdot) { + auto x_pos = computeX_pos(x); + fw(t, N_VGetArrayPointer(x_pos)); + N_VConst(0.0, xdot); + fxdot(N_VGetArrayPointer(xdot), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data()); +} + +void Model_ODE::fJDiag(const realtype t, AmiVector &JDiag, + const realtype /*cj*/, const AmiVector &x, + const AmiVector & /*dx*/) { + fJDiag(t, JDiag.getNVector(), x.getNVector()); + if (checkFinite(JDiag.getVector(), "Jacobian") != AMICI_SUCCESS) + throw AmiException("Evaluation of fJDiag failed!"); +} + +void Model_ODE::fdxdotdw(const realtype t, const N_Vector x) { + dxdotdw.reset(); + auto x_pos = computeX_pos(x); + fdxdotdw(dxdotdw.data(), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data()); + fdxdotdw_colptrs(dxdotdw.indexptrs()); + fdxdotdw_rowvals(dxdotdw.indexvals()); +} - /** implementation of fJSparse at the N_Vector level, this function provides - * an interface to the model specific routines for the solver implementation - * aswell as the AmiVector level implementation - * @param t timepoint - * @param x Vector with the states - * @param J Matrix to which the Jacobian will be written - */ - void Model_ODE::fJSparse(realtype t, N_Vector x, SUNMatrix J) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(J); - if (wasPythonGenerated()) { - fJSparse(SM_DATA_S(J), t, N_VGetArrayPointer(x_pos), +void Model_ODE::fdxdotdp(const realtype t, const N_Vector x) { + auto x_pos = computeX_pos(x); + fdwdp(t, N_VGetArrayPointer(x)); + if (wasPythonGenerated()) { + // python generated + fdxdotdw(t, x); + for (int ip = 0; ip < nplist(); ip++) { + N_VConst(0.0, dxdotdp.getNVector(ip)); + fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), + h.data(), plist_[ip], w.data()); + if (nw > 0) + dxdotdw.multiply( + gsl::span(dxdotdp.data(ip), nx_solver), + gsl::span(&dwdp.at(nw * ip), nw)); + } + } else { + // matlab generated + for (int ip = 0; ip < nplist(); ip++) { + N_VConst(0.0, dxdotdp.getNVector(ip)); + fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), fixedParameters.data(), - h.data(), w.data(), dwdx.data()); - fJSparse_colptrs(SM_INDEXPTRS_S(J)); - fJSparse_rowvals(SM_INDEXVALS_S(J)); - } else { - fJSparse(static_cast(SM_CONTENT_S(J)), t, - N_VGetArrayPointer(x_pos), unscaledParameters.data(), - fixedParameters.data(), h.data(), w.data(), dwdx.data()); + h.data(), plist_[ip], w.data(), dwdp.data()); } } +} - void Model_ODE::fJv(realtype t, AmiVector *x, AmiVector * /*dx*/, - AmiVector * /*xdot*/, AmiVector *v, AmiVector *Jv, - realtype /*cj*/) { - fJv(v->getNVector(), Jv->getNVector(), t, x->getNVector()); - } +void Model_ODE::fdxdotdp(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/) { + fdxdotdp(t, x.getNVector()); +} - /** implementation of fJv at the N_Vector level. - * @param t timepoint - * @param x Vector with the states - * @param v Vector with which the Jacobian is multiplied - * @param Jv Vector to which the Jacobian vector product will be - * written - **/ - void Model_ODE::fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x) { - N_VConst(0.0, Jv); - fJSparse(t, x, J.get()); - J.multiply(Jv, v); - } +std::unique_ptr Model_ODE::getSolver() { + return std::unique_ptr(new amici::CVodeSolver()); +} - void Model_ODE::froot(realtype t, AmiVector *x, AmiVector * /*dx*/, realtype *root){ - froot(t,x->getNVector(),root); - } +void Model_ODE::fJB(realtype * /*JB*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*xB*/, const realtype * /*w*/, + const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** implementation of froot at the N_Vector level, this function provides an interface - * to the model specific routines for the solver implementation aswell as the AmiVector - * level implementation - * @param t timepoint - * @param x Vector with the states - * @param root array with root function values - */ - void Model_ODE::froot(realtype t, N_Vector x, realtype *root) { - auto x_pos = computeX_pos(x); - memset(root,0,sizeof(realtype)*ne); - froot(root,t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data()); - } +void Model_ODE::fJSparse(SUNMatrixContent_Sparse /*JSparse*/, + const realtype /*t*/, const realtype * /*x*/, + const realtype * /*p*/, const realtype * /*k*/, + const realtype * /*h*/, const realtype * /*w*/, + const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - void Model_ODE::fxdot(realtype t, AmiVector *x, AmiVector * /*dx*/, AmiVector *xdot) { - fxdot(t,x->getNVector(),xdot->getNVector()); - } +void Model_ODE::fJSparse(realtype * /*JSparse*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** implementation of fxdot at the N_Vector level, this function provides an interface - * to the model specific routines for the solver implementation aswell as the AmiVector - * level implementation - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - */ - void Model_ODE::fxdot(realtype t, N_Vector x, N_Vector xdot) { - auto x_pos = computeX_pos(x); - fw(t,N_VGetArrayPointer(x_pos)); - N_VConst(0.0,xdot); - fxdot(N_VGetArrayPointer(xdot),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - w.data()); - } +void Model_ODE::fJSparse_colptrs(sunindextype * /*indexptrs*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** diagonalized Jacobian (for preconditioning) - * @param t timepoint - * @param JDiag Vector to which the Jacobian diagonal will be written - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @return status flag indicating successful execution - **/ - void Model_ODE::fJDiag(realtype t, AmiVector *JDiag, realtype /*cj*/, AmiVector *x, - AmiVector * /*dx*/) { - fJDiag(t, JDiag->getNVector(), x->getNVector()); - if(checkFinite(nx_solver,JDiag->data(),"Jacobian") != AMICI_SUCCESS) - throw AmiException("Evaluation of fJDiag failed!"); - } +void Model_ODE::fJSparse_rowvals(sunindextype * /*indexvals*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** Sensitivity of dx/dt wrt model parameters w - * @param t timepoint - * @param x Vector with the states - * @return status flag indicating successful execution - */ - void Model_ODE::fdxdotdw(const realtype t, const N_Vector x) { - dxdotdw.reset(); - auto x_pos = computeX_pos(x); - fdxdotdw(dxdotdw.data(), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), - h.data(), w.data()); - fdxdotdw_colptrs(dxdotdw.indexptrs()); - fdxdotdw_rowvals(dxdotdw.indexvals()); - } +void Model_ODE::fJSparseB(SUNMatrixContent_Sparse /*JSparseB*/, + const realtype /*t*/, const realtype * /*x*/, + const realtype * /*p*/, const realtype * /*k*/, + const realtype * /*h*/, const realtype * /*xB*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** Sensitivity of dx/dt wrt model parameters p - * @param t timepoint - * @param x Vector with the states - * @return status flag indicating successful execution - */ - void Model_ODE::fdxdotdp(const realtype t, const N_Vector x) { - auto x_pos = computeX_pos(x); - fdwdp(t, N_VGetArrayPointer(x)); - if (wasPythonGenerated()) { - // python generated - fdxdotdw(t, x); - for (int ip = 0; ip < nplist(); ip++) { - N_VConst(0.0, dxdotdp.getNVector(ip)); - fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), - h.data(), plist_[ip], w.data()); - if (nw > 0) - dxdotdw.multiply(gsl::span(dxdotdp.data(ip), nx_solver), - gsl::span(&dwdp.at(nw * ip), nw)); - } - } else { - // matlab generated - for (int ip = 0; ip < nplist(); ip++) { - N_VConst(0.0, dxdotdp.getNVector(ip)); - fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), - h.data(), plist_[ip], w.data(), dwdp.data()); - } - } - } +void Model_ODE::fJSparseB(realtype * /*JSparseB*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*xB*/, const realtype * /*w*/, + const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - std::unique_ptr Model_ODE::getSolver() { - return std::unique_ptr(new amici::CVodeSolver()); - } +void Model_ODE::fJSparseB_colptrs(sunindextype * /*indexptrs*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** implementation of fJB at the N_Vector level, this function provides an - *interface to the model specific routines for the solver implementation - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - * @param JB Matrix to which the Jacobian will be written - **/ - void Model_ODE::fJB(realtype t, N_Vector x, N_Vector xB, N_Vector /*xBdot*/, - SUNMatrix JB) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(JB); - fJB(SM_DATA_D(JB), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), h.data(), - N_VGetArrayPointer(xB), w.data(), dwdx.data()); - } +void Model_ODE::fJSparseB_rowvals(sunindextype * /*indexvals*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** implementation of fJSparseB at the N_Vector level, this function - * provides an interface to the model specific routines for the solver - * implementation - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - * @param JB Matrix to which the Jacobian will be written - */ - void Model_ODE::fJSparseB(realtype t, N_Vector x, N_Vector xB, - N_Vector /*xBdot*/, SUNMatrix JB) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(JB); - if (wasPythonGenerated()) { - fJSparseB(SM_DATA_S(JB), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), - h.data(), N_VGetArrayPointer(xB), w.data(), dwdx.data()); - fJSparseB_colptrs(SM_INDEXPTRS_S(JB)); - fJSparseB_rowvals(SM_INDEXVALS_S(JB)); - } else { - fJSparseB(static_cast(SM_CONTENT_S(JB)), t, - N_VGetArrayPointer(x_pos), unscaledParameters.data(), - fixedParameters.data(), h.data(), N_VGetArrayPointer(xB), - w.data(), dwdx.data()); - } - } +void Model_ODE::fJDiag(realtype * /*JDiag*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** implementation of fJDiag at the N_Vector level, this function provides an interface - * to the model specific routines for the solver implementation - * @param t timepoint - * @param JDiag Vector to which the Jacobian diagonal will be written - * @param x Vector with the states - **/ - void Model_ODE::fJDiag(realtype t, N_Vector JDiag, N_Vector x) { - auto x_pos = computeX_pos(x); - fdwdx(t,N_VGetArrayPointer(x_pos)); - N_VConst(0.0,JDiag); - fJDiag(N_VGetArrayPointer(JDiag),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - w.data(),dwdx.data()); - } +void Model_ODE::froot(realtype * /*root*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); // not implemented +} - /** implementation of fJvB at the N_Vector level - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param vB Vector with which the Jacobian is multiplied - * @param JvB Vector to which the Jacobian vector product will be - *written - **/ - void Model_ODE::fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, - N_Vector xB) { - N_VConst(0.0, JvB); - fJSparseB(t, x, xB, nullptr, J.get()); - J.multiply(JvB, vB); - } +void Model_ODE::fdxdotdp(realtype * /*dxdotdp*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const int /*ip*/, const realtype * /*w*/, + const realtype * /*dwdp*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model_ODE::fdxdotdp(realtype * /*dxdotdp*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const int /*ip*/, const realtype * /*w*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** implementation of fxBdot at the N_Vector level - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - */ - void Model_ODE::fxBdot(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot) { - N_VConst(0.0, xBdot); - fJSparseB(t, x, xB, nullptr, J.get()); - J.multiply(xBdot, xB); +void Model_ODE::fdxdotdw(realtype * /*dxdotdw*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*w*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model_ODE::fdxdotdw_colptrs(sunindextype * /*indexptrs*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model_ODE::fdxdotdw_rowvals(sunindextype * /*indexvals*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model_ODE::fJB(realtype t, N_Vector x, N_Vector xB, N_Vector /*xBdot*/, + SUNMatrix JB) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(JB); + fJB(SM_DATA_D(JB), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), N_VGetArrayPointer(xB), w.data(), + dwdx.data()); +} + +void Model_ODE::fJSparseB(realtype t, N_Vector x, N_Vector xB, + N_Vector /*xBdot*/, SUNMatrix JB) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(JB); + if (wasPythonGenerated()) { + fJSparseB(SM_DATA_S(JB), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + N_VGetArrayPointer(xB), w.data(), dwdx.data()); + fJSparseB_colptrs(SM_INDEXPTRS_S(JB)); + fJSparseB_rowvals(SM_INDEXVALS_S(JB)); + } else { + fJSparseB(static_cast(SM_CONTENT_S(JB)), t, + N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), N_VGetArrayPointer(xB), + w.data(), dwdx.data()); } +} - /** implementation of fqBdot at the N_Vector level - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param qBdot Vector with the adjoint quadrature right hand side - */ - void Model_ODE::fqBdot(realtype t, N_Vector x, N_Vector xB, - N_Vector qBdot) { - N_VConst(0.0, qBdot); - fdxdotdp(t, x); - for (int ip = 0; ip < nplist(); ip++) { +void Model_ODE::fJDiag(realtype t, N_Vector JDiag, N_Vector x) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + N_VConst(0.0, JDiag); + fJDiag(N_VGetArrayPointer(JDiag), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data(), dwdx.data()); +} + +void Model_ODE::fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, + N_Vector xB) { + N_VConst(0.0, JvB); + fJSparseB(t, x, xB, nullptr, J.get()); + J.multiply(JvB, vB); +} + +void Model_ODE::fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot) { + N_VConst(0.0, xBdot); + fJSparseB(t, x, xB, nullptr, J.get()); + J.multiply(xBdot, xB); +} + +void Model_ODE::fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot) { + N_VConst(0.0, qBdot); + fdxdotdp(t, x); + for (int ip = 0; ip < nplist(); ip++) { + for (int ix = 0; ix < nxtrue_solver; ix++) + NV_Ith_S(qBdot, ip * nJ) -= NV_Ith_S(xB, ix) * dxdotdp.at(ix, ip); + // second order part + for (int iJ = 1; iJ < nJ; iJ++) for (int ix = 0; ix < nxtrue_solver; ix++) - NV_Ith_S(qBdot, ip * nJ) -= - NV_Ith_S(xB, ix) * dxdotdp.at(ix, ip); - // second order part - for (int iJ = 1; iJ < nJ; iJ++) - for (int ix = 0; ix < nxtrue_solver; ix++) - NV_Ith_S(qBdot, ip * nJ + iJ) -= - NV_Ith_S(xB, ix) * - dxdotdp.at(ix + iJ * nxtrue_solver, ip) + - NV_Ith_S(xB, ix + iJ * nxtrue_solver) * - dxdotdp.at(ix, ip); - } + NV_Ith_S(qBdot, ip * nJ + iJ) -= + NV_Ith_S(xB, ix) * dxdotdp.at(ix + iJ * nxtrue_solver, ip) + + NV_Ith_S(xB, ix + iJ * nxtrue_solver) * dxdotdp.at(ix, ip); } +} - void Model_ODE::fsxdot(realtype t, AmiVector *x, AmiVector * /*dx*/, int ip, - AmiVector *sx, AmiVector * /*sdx*/, AmiVector *sxdot) { - fsxdot(t,x->getNVector(), ip, sx->getNVector(), sxdot->getNVector()); - } +void Model_ODE::fsxdot(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/, const int ip, + const AmiVector &sx, const AmiVector & /*sdx*/, + AmiVector &sxdot) { + fsxdot(t, x.getNVector(), ip, sx.getNVector(), sxdot.getNVector()); +} - /** implementation of fsxdot at the N_Vector level - * @param t timepoint - * @param x Vector with the states - * @param ip parameter index - * @param sx Vector with the state sensitivities - * @param sxdot Vector with the sensitivity right hand side - */ - void Model_ODE::fsxdot(realtype t, N_Vector x, int ip, N_Vector sx, - N_Vector sxdot) { - if (ip == 0) { - // we only need to call this for the first parameter index will be - // the same for all remaining - fdxdotdp(t, x); - fJSparse(t, x, J.get()); - } - N_VScale(1.0, dxdotdp.getNVector(ip), sxdot); - J.multiply(sxdot, sx); +void Model_ODE::fsxdot(realtype t, N_Vector x, int ip, N_Vector sx, + N_Vector sxdot) { + if (ip == 0) { + // we only need to call this for the first parameter index will be + // the same for all remaining + fdxdotdp(t, x); + fJSparse(t, x, J.get()); } + N_VScale(1.0, dxdotdp.getNVector(ip), sxdot); + J.multiply(sxdot, sx); } + +} // namespace amici diff --git a/src/newton_solver.cpp b/src/newton_solver.cpp index fa62a0252f..c651ea0094 100644 --- a/src/newton_solver.cpp +++ b/src/newton_solver.cpp @@ -16,8 +16,9 @@ namespace amici { -NewtonSolver::NewtonSolver(realtype *t, AmiVector *x, Model *model, ReturnData *rdata) - : model(model), rdata(rdata), xdot(x->getLength()), dx(x->getLength()) +NewtonSolver::NewtonSolver(realtype *t, AmiVector *x, Model *model, + ReturnData *rdata) + : model(model), rdata(rdata), xdot(model->nx_solver), dx(model->nx_solver) { this->t = t; this->x = x; @@ -81,33 +82,34 @@ std::unique_ptr NewtonSolver::getSolver( /* ------------------------------------------------------------------------- */ -void NewtonSolver::getStep(int ntry, int nnewt, AmiVector *delta) { - +void NewtonSolver::getStep(int ntry, int nnewt, AmiVector &delta) { this->prepareLinearSystem(ntry, nnewt); - delta->minus(); + delta.minus(); this->solveLinearSystem(delta); } /* ------------------------------------------------------------------------- */ -void NewtonSolver::computeNewtonSensis(AmiVectorArray *sx) { +void NewtonSolver::computeNewtonSensis(AmiVectorArray &sx) { prepareLinearSystem(0, -1); - model->fdxdotdp(*t, x, &dx); + model->fdxdotdp(*t, *x, dx); for (int ip = 0; ip < model->nplist(); ip++) { for (int ix = 0; ix < model->nx_solver; ix++) { - sx->at(ix,ip) = -model->dxdotdp.at(ix, ip); + sx.at(ix,ip) = -model->dxdotdp.at(ix, ip); } - solveLinearSystem(&((*sx)[ip])); + solveLinearSystem(sx[ip]); } } /* ------------------------------------------------------------------------- */ /* - Dense linear solver --------------------------------------------------- */ /* ------------------------------------------------------------------------- */ -NewtonSolverDense::NewtonSolverDense(realtype *t, AmiVector *x, Model *model, ReturnData *rdata) +/* Derived class for dense linear solver */ +NewtonSolverDense::NewtonSolverDense(realtype *t, AmiVector *x, Model *model, + ReturnData *rdata) : NewtonSolver(t, x, model, rdata), Jtmp(model->nx_solver, model->nx_solver), linsol(SUNLinSol_Dense(x->getNVector(), Jtmp.get())) @@ -120,7 +122,7 @@ NewtonSolverDense::NewtonSolverDense(realtype *t, AmiVector *x, Model *model, Re /* ------------------------------------------------------------------------- */ void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { - model->fJ(*t, 0.0, x, &dx, &xdot, Jtmp.get()); + model->fJ(*t, 0.0, *x, dx, xdot, Jtmp.get()); int status = SUNLinSolSetup_Dense(linsol, Jtmp.get()); if(status != AMICI_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_Dense"); @@ -128,9 +130,9 @@ void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { /* ------------------------------------------------------------------------- */ -void NewtonSolverDense::solveLinearSystem(AmiVector *rhs) { +void NewtonSolverDense::solveLinearSystem(AmiVector &rhs) { int status = SUNLinSolSolve_Dense(linsol, Jtmp.get(), - rhs->getNVector(), rhs->getNVector(), + rhs.getNVector(), rhs.getNVector(), 0.0); // last argument is tolerance and does not have any influence on result @@ -150,7 +152,8 @@ NewtonSolverDense::~NewtonSolverDense() { /* ------------------------------------------------------------------------- */ /* Derived class for sparse linear solver */ -NewtonSolverSparse::NewtonSolverSparse(realtype *t, AmiVector *x, Model *model, ReturnData *rdata) +NewtonSolverSparse::NewtonSolverSparse(realtype *t, AmiVector *x, Model *model, + ReturnData *rdata) : NewtonSolver(t, x, model, rdata), Jtmp(model->nx_solver, model->nx_solver, model->nnz, CSC_MAT), linsol(SUNKLU(x->getNVector(), Jtmp.get())) @@ -164,7 +167,7 @@ NewtonSolverSparse::NewtonSolverSparse(realtype *t, AmiVector *x, Model *model, void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { /* Get sparse Jacobian */ - model->fJSparse(*t, 0.0, x, &dx, &xdot, Jtmp.get()); + model->fJSparse(*t, 0.0, *x, dx, xdot, Jtmp.get()); int status = SUNLinSolSetup_KLU(linsol, Jtmp.get()); if(status != AMICI_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_KLU"); @@ -172,11 +175,10 @@ void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { /* ------------------------------------------------------------------------- */ -void NewtonSolverSparse::solveLinearSystem(AmiVector *rhs) { +void NewtonSolverSparse::solveLinearSystem(AmiVector &rhs) { /* Pass pointer to the linear solver */ int status = SUNLinSolSolve_KLU(linsol, Jtmp.get(), - rhs->getNVector(), rhs->getNVector(), - 0.0); + rhs.getNVector(), rhs.getNVector(), 0.0); // last argument is tolerance and does not have any influence on result if(status != AMICI_SUCCESS) @@ -194,10 +196,13 @@ NewtonSolverSparse::~NewtonSolverSparse() { /* - Iterative linear solver------------------------------------------------ */ /* ------------------------------------------------------------------------- */ -NewtonSolverIterative::NewtonSolverIterative(realtype *t, AmiVector *x, Model *model, ReturnData *rdata) - : NewtonSolver(t, x, model, rdata), ns_p(model->nx_solver), ns_h(model->nx_solver), - ns_t(model->nx_solver), ns_s(model->nx_solver), ns_r(model->nx_solver), ns_rt(model->nx_solver), ns_v(model->nx_solver), - ns_Jv(model->nx_solver), ns_tmp(model->nx_solver), ns_Jdiag(model->nx_solver) +NewtonSolverIterative::NewtonSolverIterative(realtype *t, AmiVector *x, + Model *model, ReturnData *rdata) + : NewtonSolver(t, x, model, rdata), ns_p(model->nx_solver), + ns_h(model->nx_solver), ns_t(model->nx_solver), ns_s(model->nx_solver), + ns_r(model->nx_solver), ns_rt(model->nx_solver), ns_v(model->nx_solver), + ns_Jv(model->nx_solver), ns_tmp(model->nx_solver), + ns_Jdiag(model->nx_solver) { } @@ -213,18 +218,19 @@ void NewtonSolverIterative::prepareLinearSystem(int ntry, int nnewt) { /* ------------------------------------------------------------------------- */ -void NewtonSolverIterative::solveLinearSystem(AmiVector *rhs) { +void NewtonSolverIterative::solveLinearSystem(AmiVector &rhs) { linsolveSPBCG(newton_try, i_newton, rhs); - rhs->minus(); + rhs.minus(); } -void NewtonSolverIterative::linsolveSPBCG(int ntry, int nnewt, AmiVector *ns_delta) { - xdot = *ns_delta; +void NewtonSolverIterative::linsolveSPBCG(int ntry, int nnewt, + AmiVector &ns_delta) { + xdot = ns_delta; xdot.minus(); // Get the diagonal of the Jacobian for preconditioning - model->fJDiag(*t, &ns_Jdiag, 0.0, x, &dx); + model->fJDiag(*t, ns_Jdiag, 0.0, *x, dx); // Ensure positivity of entries in ns_Jdiag ns_p.set(1.0); @@ -236,14 +242,14 @@ void NewtonSolverIterative::linsolveSPBCG(int ntry, int nnewt, AmiVector *ns_del // Initialize for linear solve ns_p.reset(); ns_v.reset(); - ns_delta->reset(); + ns_delta.reset(); ns_tmp.reset(); double rho = 1.0; double omega = 1.0; double alpha = 1.0; // can be set to 0 at the moment - model->fJv(*t, x, &dx, &xdot, ns_delta, &ns_Jv, 0.0); + model->fJv(*t, *x, dx, xdot, ns_delta, ns_Jv, 0.0); // ns_r = xdot - ns_Jv; N_VLinearSum(-1.0, ns_Jv.getNVector(), 1.0, xdot.getNVector(), ns_r.getNVector()); @@ -263,26 +269,28 @@ void NewtonSolverIterative::linsolveSPBCG(int ntry, int nnewt, AmiVector *ns_del N_VLinearSum(1.0, ns_r.getNVector(), beta, ns_p.getNVector(), ns_p.getNVector()); // ns_v = J * ns_p - model->fJv(*t, x, &dx, &xdot, &ns_p, &ns_v, 0.0); + model->fJv(*t, *x, dx, xdot, ns_p, ns_v, 0.0); N_VDiv(ns_v.getNVector(), ns_Jdiag.getNVector(), ns_v.getNVector()); // Compute factor alpha = rho / N_VDotProd(ns_rt.getNVector(), ns_v.getNVector()); // ns_h = ns_delta + alpha * ns_p; - N_VLinearSum(1.0, ns_delta->getNVector(), alpha, ns_p.getNVector(), ns_h.getNVector()); + N_VLinearSum(1.0, ns_delta.getNVector(), alpha, ns_p.getNVector(), + ns_h.getNVector()); // ns_s = ns_r - alpha * ns_v; N_VLinearSum(1.0, ns_r.getNVector(), -alpha, ns_v.getNVector(), ns_s.getNVector()); // ns_t = J * ns_s - model->fJv(*t, x, &dx, &xdot, &ns_s, &ns_t, 0.0); + model->fJv(*t, *x, dx, xdot, ns_s, ns_t, 0.0); N_VDiv(ns_t.getNVector(), ns_Jdiag.getNVector(), ns_t.getNVector()); // Compute factor omega = N_VDotProd(ns_t.getNVector(), ns_s.getNVector()) / N_VDotProd(ns_t.getNVector(), ns_t.getNVector()); // ns_delta = ns_h + omega * ns_s; - N_VLinearSum(1.0, ns_h.getNVector(), omega, ns_s.getNVector(), ns_delta->getNVector()); + N_VLinearSum(1.0, ns_h.getNVector(), omega, ns_s.getNVector(), + ns_delta.getNVector()); // ns_r = ns_s - omega * ns_t; N_VLinearSum(1.0, ns_s.getNVector(), -omega, ns_t.getNVector(), ns_r.getNVector()); diff --git a/src/rdata.cpp b/src/rdata.cpp index 7352444096..dc3992984a 100644 --- a/src/rdata.cpp +++ b/src/rdata.cpp @@ -10,19 +10,13 @@ namespace amici { -ReturnData::ReturnData() - : np(0), nk(0), nx(0), nx_solver(0), nxtrue(0), ny(0), nytrue(0), nz(0), nztrue(0), ne(0), - nJ(0), nplist(0), nmaxevent(0), nt(0), newton_maxsteps(0), - pscale(std::vector(0, ParameterScaling::none)), o2mode(SecondOrderMode::none), - sensi(SensitivityOrder::none), sensi_meth(SensitivityMethod::none) {} - -ReturnData::ReturnData(Solver const& solver, const Model *model) - : ReturnData(model->getTimepoints(), model->np(), model->nk(), - model->nx_rdata, model->nx_solver, model->nxtrue_rdata, - model->ny, model->nytrue, model->nz, model->nztrue, model->ne, model->nJ, - model->nplist(), model->nMaxEvent(), model->nt(), - solver.getNewtonMaxSteps(), model->getParameterScale(), - model->o2mode, solver.getSensitivityOrder(), +ReturnData::ReturnData(Solver const& solver, const Model &model) + : ReturnData(model.getTimepoints(), model.np(), model.nk(), + model.nx_rdata, model.nx_solver, model.nxtrue_rdata, + model.ny, model.nytrue, model.nz, model.nztrue, model.ne, model.nJ, + model.nplist(), model.nMaxEvent(), model.nt(), + solver.getNewtonMaxSteps(), model.getParameterScale(), + model.o2mode, solver.getSensitivityOrder(), static_cast(solver.getSensitivityMethod())) { } diff --git a/src/solver.cpp b/src/solver.cpp index 72c927c95a..59f073e4cf 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -1,10 +1,9 @@ #include "amici/solver.h" + #include "amici/exception.h" -#include "amici/forwardproblem.h" -#include "amici/backwardproblem.h" +#include "amici/misc.h" #include "amici/model.h" #include "amici/rdata.h" -#include "amici/misc.h" #include #include @@ -14,9 +13,9 @@ namespace amici { extern msgIdAndTxtFp warnMsgIdAndTxt; - -Solver::Solver(const Solver &other) : Solver() -{ +Solver::Solver(const Solver &other) : Solver() { + t = nan(""); + ncheckPtr = 0; sensi = other.sensi; atol = other.atol; rtol = other.rtol; @@ -45,18 +44,47 @@ Solver::Solver(const Solver &other) : Solver() ordering = other.ordering; } -void Solver::setup(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, AmiVectorArray *sdx, Model *model) { - bool computeSensitivities = sensi >= SensitivityOrder::first - && model->nx_solver > 0; - model->initialize(x, dx, sx, sdx, computeSensitivities); +int Solver::run(const realtype tout) const { + setStopTime(tout); + int status; + if (getAdjInitDone()) { + status = solveF(tout, AMICI_NORMAL, &ncheckPtr); + } else { + status = solve(tout, AMICI_NORMAL); + } + return status; +} + +int Solver::step(const realtype tout) const { + int status; + if (getAdjInitDone()) { + status = solveF(tout, AMICI_ONE_STEP, &ncheckPtr); + } else { + status = solve(tout, AMICI_ONE_STEP); + } + return status; +} + +void Solver::runB(const realtype tout) const { + solveB(tout, AMICI_NORMAL); + t = tout; +} - /* Create solver memory object */ +void Solver::setup(const realtype t0, Model *model, const AmiVector &x0, + const AmiVector &dx0, const AmiVectorArray &sx0, + const AmiVectorArray &sdx0) const { + if (nx() != model->nx_solver || nplist() != model->nplist() || + nquad() != model->nJ * model->nplist()) { + resetMutableMemory(model->nx_solver, model->nplist(), + model->nJ * model->nplist()); + } + /* Create solver memory object if necessary */ allocateSolver(); if (!solverMemory) throw AmiException("Failed to allocated solver memory!"); /* Initialize CVodes/IDAs solver*/ - init(x, dx, model->t0()); + init(t0, x0, dx0); applyTolerances(); @@ -71,19 +99,18 @@ void Solver::setup(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, AmiVectorArr rootInit(model->ne); - initializeLinearSolver(model, x); - initializeNonLinearSolver(x); + initializeLinearSolver(model); + initializeNonLinearSolver(); - if (computeSensitivities) { + if (sensi >= SensitivityOrder::first && model->nx_solver > 0) { auto plist = model->getParameterList(); - if (sensi_meth == SensitivityMethod::forward && !plist.empty()) { /* Set sensitivity analysis optional inputs */ auto par = model->getUnscaledParameters(); /* Activate sensitivity calculations */ - sensInit1(sx, sdx, plist.size()); - initalizeNonLinearSolverSens(x, model); + sensInit1(sx0, sdx0); + initializeNonLinearSolverSens(model); setSensParams(par.data(), nullptr, plist.data()); applyTolerancesFSA(); @@ -96,88 +123,45 @@ void Solver::setup(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, AmiVectorArr setId(model); setSuppressAlg(true); /* calculate consistent DAE initial conditions (no effect for ODE) */ - if(model->nt()>1) - calcIC(model->t(1), x, dx); + if (model->nt() > 1) + calcIC(model->t(1)); } -void Solver::setupB(BackwardProblem *bwd, Model *model) { +void Solver::setupB(int *which, const realtype tf, Model *model, + const AmiVector &xB0, const AmiVector &dxB0, + const AmiVector &xQB0) const { if (!solverMemory) - throw AmiException("Solver for the forward problem must be setup first"); - - /* write initial conditions */ - std::vector dJydx = bwd->getdJydx(); - AmiVector *xB = bwd->getxBptr(); - xB->reset(); - for (int ix = 0; ix < model->nxtrue_solver; ++ix) - for (int iJ = 0; iJ < model->nJ; ++iJ) - xB->at(ix + iJ * model->nxtrue_solver) += - dJydx.at(iJ + ( ix + (model->nt() - 1) * model->nx_solver ) * model->nJ); - bwd->getdxBptr()->reset(); - bwd->getxQBptr()->reset(); + throw AmiException( + "Solver for the forward problem must be setup first"); /* allocate memory for the backward problem */ - allocateSolverB(bwd->getwhichptr()); + allocateSolverB(which); /* initialise states */ - binit(bwd->getwhich(), bwd->getxBptr(), bwd->getdxBptr(), bwd->gett()); + binit(*which, tf, xB0, dxB0); /* Attach user data */ - setUserDataB(bwd->getwhich(), model); + setUserDataB(*which, model); /* Number of maximal internal steps */ - setMaxNumStepsB(bwd->getwhich(), (maxstepsB == 0) ? maxsteps * 100 : maxstepsB); + setMaxNumStepsB(*which, (maxstepsB == 0) ? maxsteps * 100 : maxstepsB); - initializeLinearSolverB(model, xB, bwd->getwhich()); - initializeNonLinearSolverB(xB, bwd->getwhich()); + initializeLinearSolverB(model, *which); + initializeNonLinearSolverB(*which); /* Initialise quadrature calculation */ - qbinit(bwd->getwhich(), bwd->getxQBptr()); + qbinit(*which, xQB0); - applyTolerancesASA(bwd->getwhich()); - applyQuadTolerancesASA(bwd->getwhich()); + applyTolerancesASA(*which); + applyQuadTolerancesASA(*which); - setStabLimDetB(bwd->getwhich(), stldet); -} - -void Solver::wrapErrHandlerFn(int error_code, const char *module, - const char *function, char *msg, void * /*eh_data*/) { - char buffer[250]; - char buffid[250]; - sprintf(buffer, "AMICI ERROR: in module %s in function %s : %s ", module, - function, msg); - switch (error_code) { - case 99: - sprintf(buffid, "AMICI:mex:%s:%s:WARNING", module, function); - break; - - case -1: - sprintf(buffid, "AMICI:mex:%s:%s:TOO_MUCH_WORK", module, function); - break; - - case -2: - sprintf(buffid, "AMICI:mex:%s:%s:TOO_MUCH_ACC", module, function); - break; - - case -3: - sprintf(buffid, "AMICI:mex:%s:%s:ERR_FAILURE", module, function); - break; - - case -4: - sprintf(buffid, "AMICI:mex:%s:%s:CONV_FAILURE", module, function); - break; - - default: - sprintf(buffid, "AMICI:mex:%s:%s:OTHER", module, function); - break; - } - - warnMsgIdAndTxt(buffid, buffer); + setStabLimDetB(*which, stldet); } void Solver::getDiagnosis(const int it, ReturnData *rdata) const { long int number; - if(solverWasCalled && solverMemory) { + if (solverWasCalledF && solverMemory) { getNumSteps(solverMemory.get(), &number); rdata->numsteps[it] = number; @@ -194,10 +178,11 @@ void Solver::getDiagnosis(const int it, ReturnData *rdata) const { } } -void Solver::getDiagnosisB(const int it, ReturnData *rdata, int which) const { +void Solver::getDiagnosisB(const int it, ReturnData *rdata, + const int which) const { long int number; - if(solverWasCalled && solverMemoryB.at(which)) { + if (solverWasCalledB && solverMemoryB.at(which)) { getNumSteps(solverMemoryB.at(which).get(), &number); rdata->numstepsB[it] = number; @@ -212,19 +197,20 @@ void Solver::getDiagnosisB(const int it, ReturnData *rdata, int which) const { } } -void Solver::initializeLinearSolver(const Model *model, AmiVector *x) { +void Solver::initializeLinearSolver(const Model *model) const { switch (linsol) { - /* DIRECT SOLVERS */ + /* DIRECT SOLVERS */ case LinearSolver::dense: - linearSolver = std::make_unique(*x); + linearSolver = std::make_unique(x); setLinearSolver(); setDenseJacFn(); break; case LinearSolver::band: - linearSolver = std::make_unique(*x, model->ubw, model->lbw); + linearSolver = + std::make_unique(x, model->ubw, model->lbw); setLinearSolver(); setBandJacFn(); break; @@ -243,19 +229,19 @@ void Solver::initializeLinearSolver(const Model *model, AmiVector *x) { /* ITERATIVE SOLVERS */ case LinearSolver::SPGMR: - linearSolver = std::make_unique(*x); + linearSolver = std::make_unique(x); setLinearSolver(); setJacTimesVecFn(); break; case LinearSolver::SPBCG: - linearSolver = std::make_unique(*x); + linearSolver = std::make_unique(x); setLinearSolver(); setJacTimesVecFn(); break; case LinearSolver::SPTFQMR: - linearSolver = std::make_unique(*x); + linearSolver = std::make_unique(x); setLinearSolver(); setJacTimesVecFn(); break; @@ -264,8 +250,8 @@ void Solver::initializeLinearSolver(const Model *model, AmiVector *x) { case LinearSolver::KLU: linearSolver = std::make_unique( - *x, model->nnz, CSC_MAT, - static_cast(getStateOrdering())); + x, model->nnz, CSC_MAT, + static_cast(getStateOrdering())); setLinearSolver(); setSparseJacFn(); break; @@ -274,45 +260,49 @@ void Solver::initializeLinearSolver(const Model *model, AmiVector *x) { case LinearSolver::SuperLUMT: // TODO state ordering linearSolver = std::make_unique( - *x, model->nnz, CSC_MAT, - static_cast(getStateOrdering())); + *x, model->nnz, CSC_MAT, + static_cast(getStateOrdering())); setLinearSolver(); setSparseJacFn(); break; #endif default: - throw AmiException("Invalid choice of solver: %d", static_cast(linsol)); + throw AmiException("Invalid choice of solver: %d", + static_cast(linsol)); } } -void Solver::initializeNonLinearSolver(AmiVector *x) -{ - switch(iter) { +void Solver::initializeNonLinearSolver() const { + switch (iter) { case NonlinearSolverIteration::newton: - nonLinearSolver = std::make_unique(x->getNVector()); + nonLinearSolver = std::make_unique(x.getNVector()); break; case NonlinearSolverIteration::fixedpoint: - nonLinearSolver = std::make_unique(x->getNVector()); + nonLinearSolver = + std::make_unique(x.getNVector()); break; default: - throw AmiException("Invalid non-linear solver specified (%d).", static_cast(iter)); + throw AmiException("Invalid non-linear solver specified (%d).", + static_cast(iter)); } setNonLinearSolver(); } -void Solver::initializeLinearSolverB(const Model *model, AmiVector *xB, const int which) { +void Solver::initializeLinearSolverB(const Model *model, + const int which) const { switch (linsol) { /* DIRECT SOLVERS */ case LinearSolver::dense: - linearSolverB = std::make_unique(*xB); + linearSolverB = std::make_unique(xB); setLinearSolverB(which); setDenseJacFnB(which); break; case LinearSolver::band: - linearSolverB = std::make_unique(*xB, model->ubw, model->lbw); + linearSolverB = + std::make_unique(xB, model->ubw, model->lbw); setLinearSolverB(which); setBandJacFnB(which); break; @@ -331,19 +321,19 @@ void Solver::initializeLinearSolverB(const Model *model, AmiVector *xB, const in /* ITERATIVE SOLVERS */ case LinearSolver::SPGMR: - linearSolverB = std::make_unique(*xB); + linearSolverB = std::make_unique(xB); setLinearSolverB(which); setJacTimesVecFnB(which); break; case LinearSolver::SPBCG: - linearSolverB = std::make_unique(*xB); + linearSolverB = std::make_unique(xB); setLinearSolverB(which); setJacTimesVecFnB(which); break; case LinearSolver::SPTFQMR: - linearSolverB = std::make_unique(*xB); + linearSolverB = std::make_unique(xB); setLinearSolverB(which); setJacTimesVecFnB(which); break; @@ -352,102 +342,101 @@ void Solver::initializeLinearSolverB(const Model *model, AmiVector *xB, const in case LinearSolver::KLU: linearSolverB = std::make_unique( - *xB, model->nnz, CSC_MAT, - static_cast(getStateOrdering())); + xB, model->nnz, CSC_MAT, + static_cast(getStateOrdering())); setLinearSolverB(which); setSparseJacFnB(which); break; #ifdef SUNDIALS_SUPERLUMT case LinearSolver::SuperLUMT: linearSolverB = std::make_unique( - *xB, model->nnz, CSC_MAT, - static_cast(getStateOrdering())); + *xB, model->nnz, CSC_MAT, + static_cast(getStateOrdering())); setLinearSolverB(which); setSparseJacFnB(which); break; #endif default: - throw AmiException("Invalid choice of solver: %d", static_cast(linsol)); + throw AmiException("Invalid choice of solver: %d", + static_cast(linsol)); } } -void Solver::initializeNonLinearSolverB(AmiVector *xB, const int which) -{ - switch(iter) { +void Solver::initializeNonLinearSolverB(const int which) const { + switch (iter) { case NonlinearSolverIteration::newton: - nonLinearSolverB = std::make_unique(xB->getNVector()); + nonLinearSolverB = + std::make_unique(xB.getNVector()); break; case NonlinearSolverIteration::fixedpoint: - nonLinearSolverB = std::make_unique(xB->getNVector()); + nonLinearSolverB = + std::make_unique(xB.getNVector()); break; default: - throw AmiException("Invalid non-linear solver specified (%d).", static_cast(iter)); + throw AmiException("Invalid non-linear solver specified (%d).", + static_cast(iter)); } setNonLinearSolverB(which); } -bool operator ==(const Solver &a, const Solver &b) -{ +bool operator==(const Solver &a, const Solver &b) { if (typeid(a) != typeid(b)) - return false; - - return (a.interpType == b.interpType) - && (a.lmm == b.lmm) - && (a.iter == b.iter) - && (a.stldet == b.stldet) - && (a.ordering == b.ordering) - && (a.newton_maxsteps == b.newton_maxsteps) - && (a.newton_maxlinsteps == b.newton_maxlinsteps) - && (a.newton_preeq == b.newton_preeq) - && (a.ism == b.ism) - && (a.linsol == b.linsol) - && (a.atol == b.atol) - && (a.rtol == b.rtol) - && (a.maxsteps == b.maxsteps) - && (a.maxstepsB == b.maxstepsB) - && (a.quad_atol == b.quad_atol) - && (a.quad_rtol == b.quad_rtol) - && (a.getAbsoluteToleranceSteadyState() == - b.getAbsoluteToleranceSteadyState()) - && (a.getRelativeToleranceSteadyState() == - b.getRelativeToleranceSteadyState()) - && (a.getAbsoluteToleranceSteadyStateSensi() == - b.getAbsoluteToleranceSteadyStateSensi()) - && (a.getRelativeToleranceSteadyStateSensi() == - b.getRelativeToleranceSteadyStateSensi()) - && (a.rtol_fsa == b.rtol_fsa || (isNaN(a.rtol_fsa) && isNaN(b.rtol_fsa))) - && (a.atol_fsa == b.atol_fsa || (isNaN(a.atol_fsa) && isNaN(b.atol_fsa))) - && (a.rtolB == b.rtolB || (isNaN(a.rtolB) && isNaN(b.rtolB))) - && (a.atolB == b.atolB || (isNaN(a.atolB) && isNaN(b.atolB))) - && (a.sensi == b.sensi) - && (a.sensi_meth == b.sensi_meth); -} - -void Solver::applyTolerances() { - if (!getMallocDone()) - throw AmiException(("Solver instance was not yet set up, the tolerances cannot be applied yet!")); + return false; + + return (a.interpType == b.interpType) && (a.lmm == b.lmm) && + (a.iter == b.iter) && (a.stldet == b.stldet) && + (a.ordering == b.ordering) && + (a.newton_maxsteps == b.newton_maxsteps) && + (a.newton_maxlinsteps == b.newton_maxlinsteps) && + (a.newton_preeq == b.newton_preeq) && (a.ism == b.ism) && + (a.linsol == b.linsol) && (a.atol == b.atol) && (a.rtol == b.rtol) && + (a.maxsteps == b.maxsteps) && (a.maxstepsB == b.maxstepsB) && + (a.quad_atol == b.quad_atol) && (a.quad_rtol == b.quad_rtol) && + (a.getAbsoluteToleranceSteadyState() == + b.getAbsoluteToleranceSteadyState()) && + (a.getRelativeToleranceSteadyState() == + b.getRelativeToleranceSteadyState()) && + (a.getAbsoluteToleranceSteadyStateSensi() == + b.getAbsoluteToleranceSteadyStateSensi()) && + (a.getRelativeToleranceSteadyStateSensi() == + b.getRelativeToleranceSteadyStateSensi()) && + (a.rtol_fsa == b.rtol_fsa || + (isNaN(a.rtol_fsa) && isNaN(b.rtol_fsa))) && + (a.atol_fsa == b.atol_fsa || + (isNaN(a.atol_fsa) && isNaN(b.atol_fsa))) && + (a.rtolB == b.rtolB || (isNaN(a.rtolB) && isNaN(b.rtolB))) && + (a.atolB == b.atolB || (isNaN(a.atolB) && isNaN(b.atolB))) && + (a.sensi == b.sensi) && (a.sensi_meth == b.sensi_meth); +} + +void Solver::applyTolerances() const { + if (!getInitDone()) + throw AmiException(("Solver instance was not yet set up, the " + "tolerances cannot be applied yet!")); setSStolerances(this->rtol, this->atol); } -void Solver::applyTolerancesFSA() { - if (!getMallocDone()) - throw AmiException(("Solver instance was not yet set up, the tolerances cannot be applied yet!")); +void Solver::applyTolerancesFSA() const { + if (!getInitDone()) + throw AmiException(("Solver instance was not yet set up, the " + "tolerances cannot be applied yet!")); if (sensi < SensitivityOrder::first) return; - if(nplist()) { + if (nplist()) { std::vector atols(nplist(), getAbsoluteToleranceFSA()); setSensSStolerances(getRelativeToleranceFSA(), atols.data()); setSensErrCon(true); } } -void Solver::applyTolerancesASA(int which) { - if (!getAdjMallocDone()) - throw AmiException(("Adjoint solver instance was not yet set up, the tolerances cannot be applied yet!")); +void Solver::applyTolerancesASA(const int which) const { + if (!getAdjInitDone()) + throw AmiException(("Adjoint solver instance was not yet set up, the " + "tolerances cannot be applied yet!")); if (sensi < SensitivityOrder::first) return; @@ -456,292 +445,276 @@ void Solver::applyTolerancesASA(int which) { setSStolerancesB(which, getRelativeToleranceB(), getAbsoluteToleranceB()); } -void Solver::applyQuadTolerancesASA(int which) { - if (!getAdjMallocDone()) - throw AmiException(("Adjoint solver instance was not yet set up, the tolerances cannot be applied yet!")); +void Solver::applyQuadTolerancesASA(const int which) const { + if (!getAdjInitDone()) + throw AmiException(("Adjoint solver instance was not yet set up, the " + "tolerances cannot be applied yet!")); if (sensi < SensitivityOrder::first) return; - double quad_rtol = isNaN(this->quad_rtol) ? rtol : this->quad_rtol; - double quad_atol = isNaN(this->quad_atol) ? atol : this->quad_atol; + realtype quad_rtol = isNaN(this->quad_rtol) ? rtol : this->quad_rtol; + realtype quad_atol = isNaN(this->quad_atol) ? atol : this->quad_atol; /* Enable Quadrature Error Control */ - setQuadErrConB(which, - !std::isinf(quad_atol) && !std::isinf(quad_rtol)); + setQuadErrConB(which, !std::isinf(quad_atol) && !std::isinf(quad_rtol)); quadSStolerancesB(which, quad_rtol, quad_atol); } -void Solver::applySensitivityTolerances() { +void Solver::applySensitivityTolerances() const { if (sensi < SensitivityOrder::first) return; if (sensi_meth == SensitivityMethod::forward) applyTolerancesFSA(); - else if (sensi_meth == SensitivityMethod::adjoint && getAdjMallocDone()) { - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) + else if (sensi_meth == SensitivityMethod::adjoint && getAdjInitDone()) { + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) applyTolerancesASA(iMem); } } -SensitivityMethod Solver::getSensitivityMethod() const{ - return sensi_meth; -} +SensitivityMethod Solver::getSensitivityMethod() const { return sensi_meth; } -void Solver::setSensitivityMethod(SensitivityMethod sensi_meth) { +void Solver::setSensitivityMethod(const SensitivityMethod sensi_meth) { + if (sensi_meth != this->sensi_meth) + resetMutableMemory(nx(), nplist(), nquad()); this->sensi_meth = sensi_meth; } -int Solver::getNewtonMaxSteps() const { - return newton_maxsteps; -} +int Solver::getNewtonMaxSteps() const { return newton_maxsteps; } -void Solver::setNewtonMaxSteps(int newton_maxsteps) { - if(newton_maxsteps < 0) +void Solver::setNewtonMaxSteps(const int newton_maxsteps) { + if (newton_maxsteps < 0) throw AmiException("newton_maxsteps must be a non-negative number"); this->newton_maxsteps = newton_maxsteps; } -bool Solver::getNewtonPreequilibration() const { - return newton_preeq; -} +bool Solver::getNewtonPreequilibration() const { return newton_preeq; } -void Solver::setNewtonPreequilibration(bool newton_preeq) { +void Solver::setNewtonPreequilibration(const bool newton_preeq) { this->newton_preeq = newton_preeq; } -int Solver::getNewtonMaxLinearSteps() const { - return newton_maxlinsteps; -} +int Solver::getNewtonMaxLinearSteps() const { return newton_maxlinsteps; } -void Solver::setNewtonMaxLinearSteps(int newton_maxlinsteps) { - if(newton_maxlinsteps < 0) +void Solver::setNewtonMaxLinearSteps(const int newton_maxlinsteps) { + if (newton_maxlinsteps < 0) throw AmiException("newton_maxlinsteps must be a non-negative number"); this->newton_maxlinsteps = newton_maxlinsteps; } -SensitivityOrder Solver::getSensitivityOrder() const { - return sensi; -} +SensitivityOrder Solver::getSensitivityOrder() const { return sensi; } -void Solver::setSensitivityOrder(SensitivityOrder sensi) { +void Solver::setSensitivityOrder(const SensitivityOrder sensi) { + if (this->sensi != sensi) + resetMutableMemory(nx(), nplist(), nquad()); this->sensi = sensi; - if(getMallocDone()) + if (getInitDone()) applySensitivityTolerances(); } double Solver::getRelativeTolerance() const { - return rtol; + return static_cast(rtol); } -void Solver::setRelativeTolerance(double rtol) { - if(rtol < 0) +void Solver::setRelativeTolerance(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - this->rtol = rtol; + this->rtol = static_cast(rtol); - if(getMallocDone()) { + if (getInitDone()) { applyTolerances(); applySensitivityTolerances(); } } double Solver::getAbsoluteTolerance() const { - return atol; + return static_cast(atol); } void Solver::setAbsoluteTolerance(double atol) { - if(atol < 0) + if (atol < 0) throw AmiException("atol must be a non-negative number"); - this->atol = atol; + this->atol = static_cast(atol); - if(getMallocDone()) { + if (getInitDone()) { applyTolerances(); applySensitivityTolerances(); } } double Solver::getRelativeToleranceFSA() const { - return isNaN(rtol_fsa) ? rtol : rtol_fsa; + return static_cast(isNaN(rtol_fsa) ? rtol : rtol_fsa); } -void Solver::setRelativeToleranceFSA(double rtol) { - if(rtol < 0) +void Solver::setRelativeToleranceFSA(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - rtol_fsa = rtol; + rtol_fsa = static_cast(rtol); - if(getMallocDone()) { + if (getInitDone()) { applySensitivityTolerances(); } } double Solver::getAbsoluteToleranceFSA() const { - return isNaN(atol_fsa) ? atol : atol_fsa; + return static_cast(isNaN(atol_fsa) ? atol : atol_fsa); } -void Solver::setAbsoluteToleranceFSA(double atol) { - if(atol < 0) +void Solver::setAbsoluteToleranceFSA(const double atol) { + if (atol < 0) throw AmiException("atol must be a non-negative number"); - atol_fsa = atol; + atol_fsa = static_cast(atol); - if(getMallocDone()) { + if (getInitDone()) { applySensitivityTolerances(); } } -double Solver::getRelativeToleranceB() const -{ - return isNaN(rtolB) ? rtol : rtolB; +double Solver::getRelativeToleranceB() const { + return static_cast(isNaN(rtolB) ? rtol : rtolB); } -void Solver::setRelativeToleranceB(double rtol) -{ - if(rtol < 0) +void Solver::setRelativeToleranceB(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - rtolB = rtol; + rtolB = static_cast(rtol); - if(getMallocDone()) { + if (getInitDone()) { applySensitivityTolerances(); } } -double Solver::getAbsoluteToleranceB() const -{ - return isNaN(atolB) ? atol : atolB; +double Solver::getAbsoluteToleranceB() const { + return static_cast(isNaN(atolB) ? atol : atolB); } -void Solver::setAbsoluteToleranceB(double atol) -{ - if(atol < 0) +void Solver::setAbsoluteToleranceB(const double atol) { + if (atol < 0) throw AmiException("atol must be a non-negative number"); - atolB = atol; + atolB = static_cast(atol); - if(getMallocDone()) { + if (getInitDone()) { applySensitivityTolerances(); } } double Solver::getRelativeToleranceQuadratures() const { - return quad_rtol; + return static_cast(quad_rtol); } -void Solver::setRelativeToleranceQuadratures(double rtol) { - if(rtol < 0) +void Solver::setRelativeToleranceQuadratures(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - this->quad_rtol = rtol; + this->quad_rtol = static_cast(rtol); if (sensi_meth != SensitivityMethod::adjoint) return; - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) - if(solverMemoryB.at(iMem)) + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) + if (solverMemoryB.at(iMem)) applyQuadTolerancesASA(iMem); } double Solver::getAbsoluteToleranceQuadratures() const { - return quad_atol; + return static_cast(quad_atol); } -void Solver::setAbsoluteToleranceQuadratures(double atol) { +void Solver::setAbsoluteToleranceQuadratures(const double atol) { if (atol < 0) throw AmiException("atol must be a non-negative number"); - this->quad_atol = atol; + this->quad_atol = static_cast(atol); if (sensi_meth != SensitivityMethod::adjoint) return; - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) - if(solverMemoryB.at(iMem)) + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) + if (solverMemoryB.at(iMem)) applyTolerancesASA(iMem); } double Solver::getRelativeToleranceSteadyState() const { - return isNaN(ss_rtol) ? rtol : ss_rtol; + return static_cast(isNaN(ss_rtol) ? rtol : ss_rtol); } -void Solver::setRelativeToleranceSteadyState(double rtol) { - if(rtol < 0) +void Solver::setRelativeToleranceSteadyState(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - this->ss_rtol = rtol; + this->ss_rtol = static_cast(rtol); } double Solver::getAbsoluteToleranceSteadyState() const { - return isNaN(ss_atol) ? atol : ss_atol; + return static_cast(isNaN(ss_atol) ? atol : ss_atol); } -void Solver::setAbsoluteToleranceSteadyState(double atol) { +void Solver::setAbsoluteToleranceSteadyState(const double atol) { if (atol < 0) throw AmiException("atol must be a non-negative number"); - this->ss_atol = atol; + this->ss_atol = static_cast(atol); } double Solver::getRelativeToleranceSteadyStateSensi() const { - return isNaN(ss_rtol_sensi) ? rtol : ss_rtol_sensi; + return static_cast(isNaN(ss_rtol_sensi) ? rtol : ss_rtol_sensi); } -void Solver::setRelativeToleranceSteadyStateSensi(double rtol) { - if(rtol < 0) +void Solver::setRelativeToleranceSteadyStateSensi(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - this->ss_rtol_sensi = rtol; + this->ss_rtol_sensi = static_cast(rtol); } double Solver::getAbsoluteToleranceSteadyStateSensi() const { - return isNaN(ss_atol_sensi) ? atol : ss_atol_sensi; + return static_cast(isNaN(ss_atol_sensi) ? atol : ss_atol_sensi); } -void Solver::setAbsoluteToleranceSteadyStateSensi(double atol) { +void Solver::setAbsoluteToleranceSteadyStateSensi(const double atol) { if (atol < 0) throw AmiException("atol must be a non-negative number"); - this->ss_atol_sensi = atol; + this->ss_atol_sensi = static_cast(atol); } -int Solver::getMaxSteps() const { - return maxsteps; -} +long int Solver::getMaxSteps() const { return maxsteps; } -void Solver::setMaxSteps(int maxsteps) { +void Solver::setMaxSteps(const long int maxsteps) { if (maxsteps < 0) throw AmiException("maxsteps must be a non-negative number"); this->maxsteps = maxsteps; - if(solverMemory) - setMaxNumSteps(this->maxsteps); + if (getAdjInitDone()) + resetMutableMemory(nx(), nplist(), nquad()); } -int Solver::getMaxStepsBackwardProblem() const { - return maxstepsB; -} +long int Solver::getMaxStepsBackwardProblem() const { return maxstepsB; } -void Solver::setMaxStepsBackwardProblem(int maxsteps) { +void Solver::setMaxStepsBackwardProblem(const long int maxsteps) { if (maxsteps < 0) throw AmiException("maxsteps must be a non-negative number"); this->maxstepsB = maxsteps; - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) - if(solverMemoryB.at(iMem)) + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) + if (solverMemoryB.at(iMem)) setMaxNumStepsB(iMem, this->maxstepsB); } -LinearMultistepMethod Solver::getLinearMultistepMethod() const { - return lmm; -} +LinearMultistepMethod Solver::getLinearMultistepMethod() const { return lmm; } -void Solver::setLinearMultistepMethod(LinearMultistepMethod lmm) { - if(solverMemory) - throw AmiException("Solver instance was already set up, the linear system multistep method can no longer be changed!"); +void Solver::setLinearMultistepMethod(const LinearMultistepMethod lmm) { + if (solverMemory) + resetMutableMemory(nx(), nplist(), nquad()); this->lmm = lmm; } @@ -749,68 +722,63 @@ NonlinearSolverIteration Solver::getNonlinearSolverIteration() const { return iter; } -void Solver::setNonlinearSolverIteration(NonlinearSolverIteration iter) { - if(solverMemory) - throw AmiException("Solver instance was already set up, the nonlinear system solution method can no longer be changed!"); +void Solver::setNonlinearSolverIteration(const NonlinearSolverIteration iter) { + if (solverMemory) + resetMutableMemory(nx(), nplist(), nquad()); this->iter = iter; } -InterpolationType Solver::getInterpolationType() const { - return interpType; -} +InterpolationType Solver::getInterpolationType() const { return interpType; } -void Solver::setInterpolationType(InterpolationType interpType) { - if(!solverMemoryB.empty()) - throw AmiException("Adjoint solver object was already set up, the interpolation type can no longer be changed!"); +void Solver::setInterpolationType(const InterpolationType interpType) { + if (!solverMemoryB.empty()) + resetMutableMemory(nx(), nplist(), nquad()); this->interpType = interpType; } -int Solver::getStateOrdering() const { - return ordering; -} +int Solver::getStateOrdering() const { return ordering; } void Solver::setStateOrdering(int ordering) { this->ordering = ordering; if (solverMemory && linsol == LinearSolver::KLU) { - auto klu = dynamic_cast(linearSolver.get()); + auto klu = dynamic_cast(linearSolver.get()); klu->setOrdering(static_cast(ordering)); - klu = dynamic_cast(linearSolverB.get()); + klu = dynamic_cast(linearSolverB.get()); klu->setOrdering(static_cast(ordering)); } #ifdef SUNDIALS_SUPERLUMT if (solverMemory && linsol == LinearSolver::SuperLUMT) { - auto klu = dynamic_cast(linearSolver.get()); - klu->setOrdering(static_cast(ordering)); - klu = dynamic_cast(linearSolverB.get()); - klu->setOrdering(static_cast(ordering)); + auto klu = dynamic_cast(linearSolver.get()); + klu->setOrdering( + static_cast(ordering)); + klu = dynamic_cast(linearSolverB.get()); + klu->setOrdering( + static_cast(ordering)); } #endif } -int Solver::getStabilityLimitFlag() const { - return stldet; -} +int Solver::getStabilityLimitFlag() const { return stldet; } -void Solver::setStabilityLimitFlag(int stldet) { +void Solver::setStabilityLimitFlag(const int stldet) { if (stldet != TRUE && stldet != FALSE) - throw AmiException("Invalid stldet flag, valid values are %i or %i",TRUE,FALSE); + throw AmiException("Invalid stldet flag, valid values are %i or %i", + TRUE, FALSE); this->stldet = stldet; if (solverMemory) { setStabLimDet(stldet); - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) - if(solverMemoryB.at(iMem)) - setStabLimDetB(iMem,stldet); + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) + if (solverMemoryB.at(iMem)) + setStabLimDetB(iMem, stldet); } } -LinearSolver Solver::getLinearSolver() const { - return linsol; -} +LinearSolver Solver::getLinearSolver() const { return linsol; } void Solver::setLinearSolver(LinearSolver linsol) { - if(solverMemory) - throw AmiException("Solver object was already set up, the linear solver can no longer be changed!"); + if (solverMemory) + resetMutableMemory(nx(), nplist(), nquad()); this->linsol = linsol; /*if(solverMemory) initializeLinearSolver(getModel());*/ @@ -820,47 +788,253 @@ InternalSensitivityMethod Solver::getInternalSensitivityMethod() const { return ism; } -void Solver::setInternalSensitivityMethod(InternalSensitivityMethod ism) { - if(solverMemory) - throw AmiException("Solver object was already set up, the sensitivity method can no longer be changed!"); +void Solver::setInternalSensitivityMethod(const InternalSensitivityMethod ism) { + if (solverMemory) + resetMutableMemory(nx(), nplist(), nquad()); this->ism = ism; } -void Solver::initalizeNonLinearSolverSens(AmiVector *x, Model *model) -{ - switch(iter) { +void Solver::initializeNonLinearSolverSens(const Model *model) const { + switch (iter) { case NonlinearSolverIteration::newton: - switch(ism) { + switch (ism) { case InternalSensitivityMethod::staggered: case InternalSensitivityMethod::simultaneous: - nonLinearSolverSens = std::make_unique(1 + model->nplist(), x->getNVector()); + nonLinearSolverSens = std::make_unique( + 1 + model->nplist(), x.getNVector()); break; case InternalSensitivityMethod::staggered1: - nonLinearSolverSens = std::make_unique(x->getNVector()); + nonLinearSolverSens = + std::make_unique(x.getNVector()); break; default: - throw AmiException("Unsupported internal sensitivity method selected: %d", ism); + throw AmiException( + "Unsupported internal sensitivity method selected: %d", ism); } break; case NonlinearSolverIteration::fixedpoint: - switch(ism) { + switch (ism) { case InternalSensitivityMethod::staggered: case InternalSensitivityMethod::simultaneous: - nonLinearSolverSens = std::make_unique(1 + model->nplist(), x->getNVector()); + nonLinearSolverSens = std::make_unique( + 1 + model->nplist(), x.getNVector()); break; case InternalSensitivityMethod::staggered1: - nonLinearSolverSens = std::make_unique(x->getNVector()); + nonLinearSolverSens = + std::make_unique(x.getNVector()); break; default: - throw AmiException("Unsupported internal sensitivity method selected: %d", ism); - + throw AmiException( + "Unsupported internal sensitivity method selected: %d", ism); } break; default: - throw AmiException("Invalid non-linear solver specified (%d).", static_cast(iter)); + throw AmiException("Invalid non-linear solver specified (%d).", + static_cast(iter)); } setNonLinearSolverSens(); } +int Solver::nplist() const { return sx.getLength(); } + +int Solver::nx() const { return x.getLength(); } + +int Solver::nquad() const { return xQB.getLength(); } + +bool Solver::getInitDone() const { return initialized; }; + +bool Solver::getSensInitDone() const { return sensInitialized; } + +bool Solver::getAdjInitDone() const { return adjInitialized; } + +bool Solver::getInitDoneB(const int which) const { + return static_cast(initializedB.size()) > which && + initializedB.at(which); +} + +bool Solver::getQuadInitDoneB(const int which) const { + return static_cast(initializedQB.size()) > which && + initializedQB.at(which); +} + +void Solver::setInitDone() const { initialized = true; }; + +void Solver::setSensInitDone() const { sensInitialized = true; } + +void Solver::setAdjInitDone() const { adjInitialized = true; } + +void Solver::setInitDoneB(const int which) const { + if (which >= static_cast(initializedB.size())) + initializedB.resize(which + 1, false); + initializedB.at(which) = true; +} + +void Solver::setQuadInitDoneB(const int which) const { + if (which >= static_cast(initializedQB.size())) + initializedQB.resize(which + 1, false); + initializedQB.at(which) = true; +} + +void Solver::resetMutableMemory(const int nx, const int nplist, + const int nquad) const { + solverMemory = nullptr; + initialized = false; + adjInitialized = false; + sensInitialized = false; + solverWasCalledF = false; + solverWasCalledB = false; + + x = AmiVector(nx); + dx = AmiVector(nx); + sx = AmiVectorArray(nx, nplist); + sdx = AmiVectorArray(nx, nplist); + + xB = AmiVector(nx); + dxB = AmiVector(nx); + xQB = AmiVector(nquad); + + solverMemoryB.clear(); + initializedB.clear(); + initializedQB.clear(); +} + +void Solver::writeSolution(realtype *t, AmiVector &x, AmiVector &dx, + AmiVectorArray &sx) const { + *t = gett(); + x.copy(getState(*t)); + dx.copy(getDerivativeState(*t)); + if (sensInitialized) { + sx.copy(getStateSensitivity(*t)); + } +} + +void Solver::writeSolutionB(realtype *t, AmiVector &xB, AmiVector &dxB, + AmiVector &xQB, const int which) const { + *t = gett(); + xB.copy(getAdjointState(which, *t)); + dxB.copy(getAdjointDerivativeState(which, *t)); + xQB.copy(getAdjointQuadrature(which, *t)); +} + +const AmiVector &Solver::getState(const realtype t) const { + if (t == this->t) + return x; + + if (solverWasCalledF) + getDky(t, 0); + + return dky; +} + +const AmiVector &Solver::getDerivativeState(const realtype t) const { + if (t == this->t) + return dx; + + if (solverWasCalledF) + getDky(t, 1); + + return dky; +} + +const AmiVectorArray &Solver::getStateSensitivity(const realtype t) const { + if (sensInitialized) { + if (solverWasCalledF) { + if (t == this->t) { + getSens(); + } else { + getSensDky(t, 0); + } + } + } else { + sx.reset(); + } + return sx; +} + +const AmiVector &Solver::getAdjointState(const int which, + const realtype t) const { + if (adjInitialized) { + if (solverWasCalledB) { + if (t == this->t) { + getB(which); + return xB; + } + getDkyB(t, 0, which); + } + } else { + dky.reset(); + } + return dky; +} + +const AmiVector &Solver::getAdjointDerivativeState(const int which, + const realtype t) const { + if (adjInitialized) { + if (solverWasCalledB) { + if (t == this->t) { + getB(which); + return dxB; + } + getDkyB(t, 1, which); + } + } else { + dky.reset(); + } + return dky; +} + +const AmiVector &Solver::getAdjointQuadrature(const int which, + const realtype t) const { + if (adjInitialized) { + if (solverWasCalledB) { + if (t == this->t) { + getQuadB(which); + return xQB; + } + getQuadDkyB(t, 0, which); + } + } else { + xQB.reset(); + } + return xQB; +} + +realtype Solver::gett() const { return t; } + +void wrapErrHandlerFn(int error_code, const char *module, + const char *function, char *msg, void * /*eh_data*/) { + char buffer[250]; + char buffid[250]; + sprintf(buffer, "AMICI ERROR: in module %s in function %s : %s ", module, + function, msg); + switch (error_code) { + case 99: + sprintf(buffid, "AMICI:mex:%s:%s:WARNING", module, function); + break; + + case -1: + sprintf(buffid, "AMICI:mex:%s:%s:TOO_MUCH_WORK", module, function); + break; + + case -2: + sprintf(buffid, "AMICI:mex:%s:%s:TOO_MUCH_ACC", module, function); + break; + + case -3: + sprintf(buffid, "AMICI:mex:%s:%s:ERR_FAILURE", module, function); + break; + + case -4: + sprintf(buffid, "AMICI:mex:%s:%s:CONV_FAILURE", module, function); + break; + + default: + sprintf(buffid, "AMICI:mex:%s:%s:OTHER", module, function); + break; + } + + warnMsgIdAndTxt(buffid, buffer); +} + } // namespace amici diff --git a/src/solver_cvodes.cpp b/src/solver_cvodes.cpp index b017b0f1d5..4267cee5d3 100644 --- a/src/solver_cvodes.cpp +++ b/src/solver_cvodes.cpp @@ -1,33 +1,28 @@ -#include "amici/misc.h" -#include "amici/model_ode.h" #include "amici/solver_cvodes.h" + #include "amici/exception.h" +#include "amici/misc.h" +#include "amici/model_ode.h" +#include "amici/sundials_linsol_wrapper.h" #include -#include #include - -#include "amici/sundials_linsol_wrapper.h" - +#include #include #include #include #include -#define ZERO RCONST(0.0) -#define ONE RCONST(1.0) -#define FOUR RCONST(4.0) - -/** - * @ brief extract information from a property of a matlab class (matrix) - * @ param OPTION name of the property - */ +#define ZERO RCONST(0.0) +#define ONE RCONST(1.0) +#define FOUR RCONST(4.0) namespace amici { // Ensure AMICI options are in sync with Sundials options -static_assert((int)InternalSensitivityMethod::simultaneous == CV_SIMULTANEOUS, ""); +static_assert((int)InternalSensitivityMethod::simultaneous == CV_SIMULTANEOUS, + ""); static_assert((int)InternalSensitivityMethod::staggered == CV_STAGGERED, ""); static_assert((int)InternalSensitivityMethod::staggered1 == CV_STAGGERED1, ""); @@ -40,222 +35,321 @@ static_assert((int)LinearMultistepMethod::BDF == CV_BDF, ""); static_assert(AMICI_ROOT_RETURN == CV_ROOT_RETURN, ""); -void CVodeSolver::init(AmiVector *x, AmiVector * /*dx*/, realtype t) { - int status = CVodeInit(solverMemory.get(), fxdot, t, x->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeInit"); -} +/* + * The following static members are callback function to CVODES. + * Their signatures must not be changes. + */ +static int fxdot(realtype t, N_Vector x, N_Vector xdot, void *user_data); -void CVodeSolver::binit(int which, AmiVector *xB, AmiVector * /*dxB*/, realtype t) { - int status = CVodeInitB(solverMemory.get(), which, fxBdot, t, xB->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeInitB"); -} +static int fJSparse(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector tmp1, N_Vector tmp2, + N_Vector tmp3); -void CVodeSolver::qbinit(int which, AmiVector *qBdot) { - int status = CVodeQuadInitB(solverMemory.get(), which, fqBdot, qBdot->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeQuadInitB"); -} +static int fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); -void CVodeSolver::rootInit(int ne) { - int status = CVodeRootInit(solverMemory.get(), ne, froot); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeRootInit"); +static int fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector tmp1B, + N_Vector tmp2B, N_Vector tmp3B); + +static int fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector tmp1B, + N_Vector tmp2B, N_Vector tmp3B); + +static int fJBand(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector tmp1, N_Vector tmp2, + N_Vector tmp3); + +static int fJBandB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector tmp1B, + N_Vector tmp2B, N_Vector tmp3B); + +static int fJDiag(realtype t, N_Vector JDiag, N_Vector x, void *user_data) +__attribute__((unused)); + +static int fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, + N_Vector xdot, void *user_data, N_Vector tmp); + +static int fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, + N_Vector xB, N_Vector xBdot, void *user_data, + N_Vector tmpB); + +static int froot(realtype t, N_Vector x, realtype *root, void *user_data); + +static int fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + void *user_data); + +static int fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot, + void *user_data); + +static int fsxdot(int Ns, realtype t, N_Vector x, N_Vector xdot, int ip, + N_Vector sx, N_Vector sxdot, void *user_data, + N_Vector tmp1, N_Vector tmp2); + + +/* Function implementations */ + +void CVodeSolver::init(const realtype t0, const AmiVector &x0, + const AmiVector & /*dx0*/) const { + solverWasCalledF = false; + t = t0; + x.copy(x0); + int status; + if (getInitDone()) { + status = CVodeReInit(solverMemory.get(), t0, x.getNVector()); + } else { + status = CVodeInit(solverMemory.get(), fxdot, t0, x.getNVector()); + setInitDone(); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeInit"); +} + +void CVodeSolver::sensInit1(const AmiVectorArray &sx0, + const AmiVectorArray & /*sdx0*/) const { + int status; + sx.copy(sx0); + if (getSensInitDone()) { + status = CVodeSensReInit(solverMemory.get(), + static_cast(getSensitivityMethod()), + sx.getNVectorArray()); + } else { + status = CVodeSensInit1(solverMemory.get(), nplist(), + static_cast(getSensitivityMethod()), + fsxdot, sx.getNVectorArray()); + setSensInitDone(); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSensInit1"); +} + +void CVodeSolver::binit(const int which, const realtype tf, + const AmiVector &xB0, + const AmiVector & /*dxB0*/) const { + solverWasCalledB = false; + xB.copy(xB0); + int status; + if (getInitDoneB(which)) { + status = CVodeReInitB(solverMemory.get(), which, tf, xB.getNVector()); + } else { + status = + CVodeInitB(solverMemory.get(), which, fxBdot, tf, xB.getNVector()); + setInitDoneB(which); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeInitB"); +} + +void CVodeSolver::qbinit(const int which, const AmiVector &xQB0) const { + xQB.copy(xQB0); + int status; + if (getQuadInitDoneB(which)) { + status = CVodeQuadReInitB(solverMemory.get(), which, xQB.getNVector()); + } else { + status = + CVodeQuadInitB(solverMemory.get(), which, fqBdot, xQB.getNVector()); + setQuadInitDoneB(which); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeQuadInitB"); } -void CVodeSolver::sensInit1(AmiVectorArray *sx, AmiVectorArray * /*sdx*/, int nplist) { - int status = CVodeSensInit1(solverMemory.get(), nplist, static_cast(getSensitivityMethod()), fsxdot, - sx->getNVectorArray()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSensInit1"); +void CVodeSolver::rootInit(int ne) const { + int status = CVodeRootInit(solverMemory.get(), ne, froot); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeRootInit"); } -void CVodeSolver::setDenseJacFn() { +void CVodeSolver::setDenseJacFn() const { int status = CVodeSetJacFn(solverMemory.get(), fJ); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFn"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFn"); } -void CVodeSolver::setSparseJacFn() { +void CVodeSolver::setSparseJacFn() const { int status = CVodeSetJacFn(solverMemory.get(), fJSparse); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFn"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFn"); } -void CVodeSolver::setBandJacFn() { +void CVodeSolver::setBandJacFn() const { int status = CVodeSetJacFn(solverMemory.get(), fJBand); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFn"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFn"); } -void CVodeSolver::setJacTimesVecFn() { +void CVodeSolver::setJacTimesVecFn() const { int status = CVodeSetJacTimes(solverMemory.get(), nullptr, fJv); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacTimes"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacTimes"); } -void CVodeSolver::setDenseJacFnB(int which) { +void CVodeSolver::setDenseJacFnB(int which) const { int status = CVodeSetJacFnB(solverMemory.get(), which, fJB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFnB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFnB"); } -void CVodeSolver::setSparseJacFnB(int which) { +void CVodeSolver::setSparseJacFnB(int which) const { int status = CVodeSetJacFnB(solverMemory.get(), which, fJSparseB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFnB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFnB"); } -void CVodeSolver::setBandJacFnB(int which) { +void CVodeSolver::setBandJacFnB(int which) const { int status = CVodeSetJacFnB(solverMemory.get(), which, fJBandB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFnB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFnB"); } -void CVodeSolver::setJacTimesVecFnB(int which) { +void CVodeSolver::setJacTimesVecFnB(int which) const { int status = CVodeSetJacTimesB(solverMemory.get(), which, nullptr, fJvB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacTimesB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacTimesB"); } -Solver *CVodeSolver::clone() const { - return new CVodeSolver(*this); -} +Solver *CVodeSolver::clone() const { return new CVodeSolver(*this); } -void CVodeSolver::allocateSolver() { - solverMemory = std::unique_ptr> - (CVodeCreate(static_cast(lmm)), - [](void *ptr) { CVodeFree(&ptr); }); +void CVodeSolver::allocateSolver() const { + if (!solverMemory) + solverMemory = std::unique_ptr>( + CVodeCreate(static_cast(lmm)), + [](void *ptr) { CVodeFree(&ptr); }); } -void CVodeSolver::setSStolerances(double rtol, double atol) { +void CVodeSolver::setSStolerances(const double rtol, const double atol) const { int status = CVodeSStolerances(solverMemory.get(), rtol, atol); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSStolerances"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSStolerances"); } -void CVodeSolver::setSensSStolerances(double rtol, double *atol) { - int status = CVodeSensSStolerances(solverMemory.get(), rtol, atol); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSensEEtolerances"); +void CVodeSolver::setSensSStolerances(const double rtol, + const double *atol) const { + int status = CVodeSensSStolerances(solverMemory.get(), rtol, + const_cast(atol)); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSensEEtolerances"); } -void CVodeSolver::setSensErrCon(bool error_corr) { +void CVodeSolver::setSensErrCon(const bool error_corr) const { int status = CVodeSetSensErrCon(solverMemory.get(), error_corr); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetSensErrCon"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetSensErrCon"); } -void CVodeSolver::setQuadErrConB(int which, bool flag) { +void CVodeSolver::setQuadErrConB(const int which, const bool flag) const { int status = CVodeSetQuadErrConB(solverMemory.get(), which, flag); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetQuadErrConB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetQuadErrConB"); } void CVodeSolver::getRootInfo(int *rootsfound) const { int status = CVodeGetRootInfo(solverMemory.get(), rootsfound); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetRootInfo"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetRootInfo"); } -void CVodeSolver::setLinearSolver() -{ - int status = CVodeSetLinearSolver(solverMemory.get(), linearSolver->get(), linearSolver->getMatrix()); - if(status != CV_SUCCESS) - throw CvodeException(status,"setLinearSolver"); +void CVodeSolver::setLinearSolver() const { + int status = CVodeSetLinearSolver(solverMemory.get(), linearSolver->get(), + linearSolver->getMatrix()); + if (status != CV_SUCCESS) + throw CvodeException(status, "setLinearSolver"); } -void CVodeSolver::setLinearSolverB(int which) -{ - int status = CVodeSetLinearSolverB(solverMemory.get(), which, linearSolverB->get(), linearSolverB->getMatrix()); - if(status != CV_SUCCESS) - throw CvodeException(status,"setLinearSolverB"); +void CVodeSolver::setLinearSolverB(int which) const { + int status = + CVodeSetLinearSolverB(solverMemory.get(), which, linearSolverB->get(), + linearSolverB->getMatrix()); + if (status != CV_SUCCESS) + throw CvodeException(status, "setLinearSolverB"); } -void CVodeSolver::setNonLinearSolver() -{ - int status = CVodeSetNonlinearSolver(solverMemory.get(), nonLinearSolver->get()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetNonlinearSolver"); +void CVodeSolver::setNonLinearSolver() const { + int status = + CVodeSetNonlinearSolver(solverMemory.get(), nonLinearSolver->get()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetNonlinearSolver"); } -void CVodeSolver::setNonLinearSolverSens() -{ - if(getSensitivityOrder() < SensitivityOrder::first) +void CVodeSolver::setNonLinearSolverSens() const { + if (getSensitivityOrder() < SensitivityOrder::first) return; - if(getSensitivityMethod() != SensitivityMethod::forward) + if (getSensitivityMethod() != SensitivityMethod::forward) return; int status = CV_SUCCESS; switch (ism) { case InternalSensitivityMethod::staggered: - status = CVodeSetNonlinearSolverSensStg(solverMemory.get(), nonLinearSolverSens->get()); + status = CVodeSetNonlinearSolverSensStg(solverMemory.get(), + nonLinearSolverSens->get()); break; case InternalSensitivityMethod::simultaneous: - status = CVodeSetNonlinearSolverSensSim(solverMemory.get(), nonLinearSolverSens->get()); + status = CVodeSetNonlinearSolverSensSim(solverMemory.get(), + nonLinearSolverSens->get()); break; case InternalSensitivityMethod::staggered1: - status = CVodeSetNonlinearSolverSensStg1(solverMemory.get(), nonLinearSolverSens->get()); + status = CVodeSetNonlinearSolverSensStg1(solverMemory.get(), + nonLinearSolverSens->get()); break; default: - throw AmiException("Unsupported internal sensitivity method selected: %d", ism); + throw AmiException( + "Unsupported internal sensitivity method selected: %d", ism); } - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSolver::setNonLinearSolverSens"); - + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSolver::setNonLinearSolverSens"); } -void CVodeSolver::setNonLinearSolverB(int which) -{ - int status = CVodeSetNonlinearSolverB(solverMemory.get(), which, nonLinearSolverB->get()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetNonlinearSolverB"); +void CVodeSolver::setNonLinearSolverB(const int which) const { + int status = CVodeSetNonlinearSolverB(solverMemory.get(), which, + nonLinearSolverB->get()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetNonlinearSolverB"); } -void CVodeSolver::setErrHandlerFn() { - int status = CVodeSetErrHandlerFn(solverMemory.get(), wrapErrHandlerFn, nullptr); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetErrHandlerFn"); +void CVodeSolver::setErrHandlerFn() const { + int status = + CVodeSetErrHandlerFn(solverMemory.get(), wrapErrHandlerFn, nullptr); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetErrHandlerFn"); } -void CVodeSolver::setUserData(Model *model) { +void CVodeSolver::setUserData(Model *model) const { int status = CVodeSetUserData(solverMemory.get(), model); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetUserData"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetUserData"); } -void CVodeSolver::setUserDataB(int which, Model *model) { +void CVodeSolver::setUserDataB(const int which, Model *model) const { int status = CVodeSetUserDataB(solverMemory.get(), which, model); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetUserDataB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetUserDataB"); } -void CVodeSolver::setMaxNumSteps(long mxsteps) { +void CVodeSolver::setMaxNumSteps(const long mxsteps) const { int status = CVodeSetMaxNumSteps(solverMemory.get(), mxsteps); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetMaxNumSteps"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetMaxNumSteps"); } -void CVodeSolver::setStabLimDet(int stldet) { +void CVodeSolver::setStabLimDet(const int stldet) const { int status = CVodeSetStabLimDet(solverMemory.get(), stldet); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetStabLimDet"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetStabLimDet"); } -void CVodeSolver::setStabLimDetB(int which, int stldet) { +void CVodeSolver::setStabLimDetB(const int which, const int stldet) const { int status = CVodeSetStabLimDetB(solverMemory.get(), which, stldet); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetStabLimDetB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetStabLimDetB"); } -void CVodeSolver::setId(Model *model) { } +void CVodeSolver::setId(const Model *model) const {} -void CVodeSolver::setSuppressAlg(bool flag) { } +void CVodeSolver::setSuppressAlg(const bool flag) const {} -void CVodeSolver::resetState(void *ami_mem, N_Vector y0) { +void CVodeSolver::resetState(void *ami_mem, const_N_Vector y0) const { auto cv_mem = static_cast(ami_mem); /* here we force the order in the next step to zero, and update the @@ -266,60 +360,70 @@ void CVodeSolver::resetState(void *ami_mem, N_Vector y0) { /* Set step parameters */ /* current order */ - cv_mem->cv_q = 1; + cv_mem->cv_q = 1; /* L = q + 1 */ - cv_mem->cv_L = 2; + cv_mem->cv_L = 2; /* number of steps to wait before updating in q */ - cv_mem->cv_qwait = cv_mem->cv_L; + cv_mem->cv_qwait = cv_mem->cv_L; /* last successful q value used */ - cv_mem->cv_qu = 0; + cv_mem->cv_qu = 0; /* last successful h value used */ - cv_mem->cv_hu = ZERO; + cv_mem->cv_hu = ZERO; /* tolerance scale factor */ - cv_mem->cv_tolsf = ONE; + cv_mem->cv_tolsf = ONE; /* Initialize other integrator optional outputs */ /* actual initial stepsize */ - cv_mem->cv_h0u = ZERO; + cv_mem->cv_h0u = ZERO; /* step size to be used on the next step */ - cv_mem->cv_next_h = ZERO; + cv_mem->cv_next_h = ZERO; /* order to be used on the next step */ - cv_mem->cv_next_q = 0; + cv_mem->cv_next_q = 0; /* write updated state to Nordsieck history array */ N_VScale(ONE, y0, cv_mem->cv_zn[0]); } - -void CVodeSolver::reInitPostProcessF(realtype *t, AmiVector *yout, - AmiVector * /*ypout*/, realtype tnext) { - reInitPostProcess(solverMemory.get(), t, yout, tnext); +void CVodeSolver::reInitPostProcessF(const realtype tnext) const { + reInitPostProcess(solverMemory.get(), &t, &x, tnext); + forceReInitPostProcessF = false; } -void CVodeSolver::reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector * /*ypBout*/, realtype tnext) { - reInitPostProcess(CVodeGetAdjCVodeBmem(solverMemory.get(), which), - t, yBout, tnext); +void CVodeSolver::reInitPostProcessB(const realtype tnext) const { + realtype tBret; + auto cv_mem = static_cast(solverMemory.get()); + auto ca_mem = cv_mem->cv_adj_mem; + auto cvB_mem = ca_mem->cvB_mem; + // loop over all backward problems + while (cvB_mem != nullptr) { + // store current backward problem in ca_mem to make it accessible in + // adjoint rhs wrapper functions + ca_mem->ca_bckpbCrt = cvB_mem; + reInitPostProcess(static_cast(cvB_mem->cv_mem), &tBret, &xB, + tnext); + cvB_mem->cv_tout = tBret; + cvB_mem = cvB_mem->cv_next; + } + forceReInitPostProcessB = false; } -void CVodeSolver::reInitPostProcess(void *ami_mem, realtype *t, - AmiVector *yout, realtype tout) { +void CVodeSolver::reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, + const realtype tout) const { auto cv_mem = static_cast(ami_mem); auto nst_tmp = cv_mem->cv_nst; cv_mem->cv_nst = 0; auto status = CVodeSetStopTime(cv_mem, tout); - if(status != CV_SUCCESS) + if (status != CV_SUCCESS) throw CvodeException(status, "CVodeSetStopTime"); - status = CVode(ami_mem, tout, yout->getNVector(), - t, CV_ONE_STEP); + status = CVode(ami_mem, tout, yout->getNVector(), t, CV_ONE_STEP); - if(status != CV_SUCCESS) + if (status != CV_SUCCESS) throw CvodeException(status, "reInitPostProcess"); - cv_mem->cv_nst = nst_tmp+1; + cv_mem->cv_nst = nst_tmp + 1; if (cv_mem->cv_adjMallocDone == SUNTRUE) { /* add new step to history array, this is copied from CVodeF */ auto ca_mem = cv_mem->cv_adj_mem; @@ -346,238 +450,271 @@ void CVodeSolver::reInitPostProcess(void *ami_mem, realtype *t, } } -void CVodeSolver::reInit(realtype t0, AmiVector *yy0, AmiVector * /*yp0*/) { +void CVodeSolver::reInit(const realtype t0, const AmiVector &yy0, + const AmiVector & /*yp0*/) const { auto cv_mem = static_cast(solverMemory.get()); - /* set time */ cv_mem->cv_tn = t0; - resetState(cv_mem, yy0->getNVector()); + if (solverWasCalledF) + forceReInitPostProcessF = true; + x.copy(yy0); + resetState(cv_mem, x.getNVector()); } -void CVodeSolver::sensReInit(AmiVectorArray *yS0, AmiVectorArray * /*ypS0*/) { +void CVodeSolver::sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray & /*ypS0*/) const { auto cv_mem = static_cast(solverMemory.get()); /* Initialize znS[0] in the history array */ - - for (int is=0; iscv_Ns; is++) + for (int is = 0; is < nplist(); is++) cv_mem->cv_cvals[is] = ONE; - - int status = N_VScaleVectorArray(cv_mem->cv_Ns, cv_mem->cv_cvals, - yS0->getNVectorArray(), cv_mem->cv_znS[0]); - if(status != CV_SUCCESS) - throw CvodeException(CV_VECTOROP_ERR,"CVodeSensReInit"); + if (solverWasCalledF) + forceReInitPostProcessF = true; + sx.copy(yyS0); + int status = N_VScaleVectorArray(nplist(), cv_mem->cv_cvals, + sx.getNVectorArray(), cv_mem->cv_znS[0]); + if (status != CV_SUCCESS) + throw CvodeException(CV_VECTOROP_ERR, "CVodeSensReInit"); +} + +void CVodeSolver::reInitB(const int which, const realtype tB0, + const AmiVector &yyB0, + const AmiVector & /*ypB0*/) const { + auto cv_memB = + static_cast(CVodeGetAdjCVodeBmem(solverMemory.get(), which)); + if (solverWasCalledB) + forceReInitPostProcessB = true; + cv_memB->cv_tn = tB0; + xB.copy(yyB0); + resetState(cv_memB, xB.getNVector()); } -void CVodeSolver::setSensParams(realtype *p, realtype *pbar, int *plist) { - int status = CVodeSetSensParams(solverMemory.get(), p, pbar, plist); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetSensParams"); +void CVodeSolver::quadReInitB(int which, const AmiVector &yQB0) const { + auto cv_memB = + static_cast(CVodeGetAdjCVodeBmem(solverMemory.get(), which)); + if (solverWasCalledB) + forceReInitPostProcessB = true; + xQB.copy(yQB0); + N_VScale(ONE, xQB.getNVector(), cv_memB->cv_znQ[0]); } -void CVodeSolver::getDky(realtype t, int k, AmiVector *dky) const { - int status = CVodeGetDky(solverMemory.get(), t, k, dky->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetDky"); +void CVodeSolver::setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const { + int status = CVodeSetSensParams( + solverMemory.get(), const_cast(p), + const_cast(pbar), const_cast(plist)); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetSensParams"); } -void CVodeSolver::getSens(realtype *tret, AmiVectorArray *yySout) const { - int status = CVodeGetSens(solverMemory.get(), tret, yySout->getNVectorArray()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetSens"); +void CVodeSolver::getDky(realtype t, int k) const { + int status = CVodeGetDky(solverMemory.get(), t, k, dky.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetDky"); } -void CVodeSolver::adjInit() { - int status = CVodeAdjInit(solverMemory.get(), static_cast(maxsteps), static_cast(interpType)); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeAdjInit"); +void CVodeSolver::getSens() const { + realtype tDummy = 0; + int status = + CVodeGetSens(solverMemory.get(), &tDummy, sx.getNVectorArray()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetSens"); } -void CVodeSolver::allocateSolverB(int *which) { - int status = CVodeCreateB(solverMemory.get(), static_cast(lmm), which); - - if (*which + 1 > static_cast(solverMemoryB.size())) - solverMemoryB.resize(*which + 1); - solverMemoryB.at(*which) = std::unique_ptr> - (getAdjBmem(solverMemory.get(), *which), [](void *ptr){}); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeCreateB"); +void CVodeSolver::getSensDky(const realtype t, const int k) const { + int status = + CVodeGetSensDky(solverMemory.get(), t, k, sx.getNVectorArray()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetSens"); } -void CVodeSolver::reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector * /*ypB0*/) { - auto cv_memB = static_cast(CVodeGetAdjCVodeBmem(solverMemory.get(), which)); - cv_memB->cv_tn = tB0; - resetState(cv_memB, yyB0->getNVector()); +void CVodeSolver::getDkyB(const realtype t, const int k, + const int which) const { + int status = CVodeGetDky(CVodeGetAdjCVodeBmem(solverMemory.get(), which), t, + k, dky.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetDkyB"); } -void CVodeSolver::setSStolerancesB(int which, realtype relTolB, - realtype absTolB) { - int status = CVodeSStolerancesB(solverMemory.get(), which, relTolB, absTolB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSStolerancesB"); +void CVodeSolver::getQuadB(int which) const { + realtype tDummy = 0; + int status = + CVodeGetQuadB(solverMemory.get(), which, &tDummy, xQB.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetQuadB"); } -void CVodeSolver::quadReInitB(int which, AmiVector *yQB0) { - auto cv_memB = static_cast(CVodeGetAdjCVodeBmem(solverMemory.get(), which)); - N_VScale(ONE, yQB0->getNVector(), cv_memB->cv_znQ[0]); +void CVodeSolver::getQuadDkyB(const realtype t, const int k, int which) const { + int status = + CVodeGetQuadDky(CVodeGetAdjCVodeBmem(solverMemory.get(), which), t, k, + xQB.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetQuadDkyB"); } -void CVodeSolver::quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) { - int status = CVodeQuadSStolerancesB(solverMemory.get(), which, reltolQB, abstolQB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeQuadSStolerancesB"); -} - -void CVodeSolver::getQuadB(int which, realtype *tret, AmiVector *qB) const { - int status = CVodeGetQuadB(solverMemory.get(), which, tret, qB->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetQuadB"); +void CVodeSolver::adjInit() const { + int status; + if (getAdjInitDone()) { + status = CVodeAdjReInit(solverMemory.get()); + } else { + status = CVodeAdjInit(solverMemory.get(), static_cast(maxsteps), + static_cast(interpType)); + setAdjInitDone(); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeAdjInit"); } - -int CVodeSolver::solve(realtype tout, AmiVector *yret, AmiVector * /*ypret*/, - realtype *tret, int itask) { - int status = CVode(solverMemory.get(), tout, yret->getNVector(), tret, itask); - if(status<0) { - throw IntegrationFailure(status,*tret); +void CVodeSolver::allocateSolverB(int *which) const { + if (!solverMemoryB.empty()) { + *which = 0; + return; } - - solverWasCalled = true; + int status = CVodeCreateB(solverMemory.get(), static_cast(lmm), which); + if (*which + 1 > static_cast(solverMemoryB.size())) + solverMemoryB.resize(*which + 1); + solverMemoryB.at(*which) = + std::unique_ptr>( + getAdjBmem(solverMemory.get(), *which), [](void *ptr) {}); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeCreateB"); +} + +void CVodeSolver::setSStolerancesB(const int which, const realtype relTolB, + const realtype absTolB) const { + int status = + CVodeSStolerancesB(solverMemory.get(), which, relTolB, absTolB); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSStolerancesB"); +} + +void CVodeSolver::quadSStolerancesB(const int which, const realtype reltolQB, + const realtype abstolQB) const { + int status = + CVodeQuadSStolerancesB(solverMemory.get(), which, reltolQB, abstolQB); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeQuadSStolerancesB"); +} + +void CVodeSolver::getB(const int which) const { + realtype tDummy = 0; + int status = CVodeGetB(solverMemory.get(), which, &tDummy, xB.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetB"); +} + +int CVodeSolver::solve(const realtype tout, const int itask) const { + if (forceReInitPostProcessF) + reInitPostProcessF(tout); + int status = CVode(solverMemory.get(), tout, x.getNVector(), &t, itask); + if (status < 0) // status > 0 is okay and is used for e.g. root return + throw IntegrationFailure(status, t); + solverWasCalledF = true; return status; } -int CVodeSolver::solveF(realtype tout, AmiVector *yret, AmiVector * /*ypret*/, - realtype *tret, int itask, int *ncheckPtr) { - int status = CVodeF(solverMemory.get(), tout, yret->getNVector(), tret, itask, ncheckPtr); - if(status<0) { - throw IntegrationFailure(status,*tret); - } - - solverWasCalled = true; +int CVodeSolver::solveF(const realtype tout, const int itask, + int *ncheckPtr) const { + if (forceReInitPostProcessF) + reInitPostProcessF(tout); + int status = + CVodeF(solverMemory.get(), tout, x.getNVector(), &t, itask, ncheckPtr); + if (status < 0) // status > 0 is okay and is used for e.g. root return + throw IntegrationFailure(status, t); + solverWasCalledF = true; return status; } -void CVodeSolver::solveB(realtype tBout, int itaskB) { +void CVodeSolver::solveB(const realtype tBout, const int itaskB) const { + if (forceReInitPostProcessB) + reInitPostProcessB(tBout); int status = CVodeB(solverMemory.get(), tBout, itaskB); - if(status != CV_SUCCESS) - throw IntegrationFailureB(status,tBout); + if (status != CV_SUCCESS) + throw IntegrationFailureB(status, tBout); + solverWasCalledB = true; } -void CVodeSolver::setMaxNumStepsB(int which, long mxstepsB) { +void CVodeSolver::setMaxNumStepsB(const int which, const long mxstepsB) const { int status = CVodeSetMaxNumStepsB(solverMemory.get(), which, mxstepsB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetMaxNumStepsB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetMaxNumStepsB"); } -void CVodeSolver::diag() -{ +void CVodeSolver::diag() const { int status = CVDiag(solverMemory.get()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVDiag"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVDiag"); } -void CVodeSolver::diagB(int which) -{ +void CVodeSolver::diagB(const int which) const { int status = CVDiagB(solverMemory.get(), which); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVDiagB"); - + if (status != CV_SUCCESS) + throw CvodeException(status, "CVDiagB"); } -void CVodeSolver::getB(int which, realtype *tret, AmiVector *yy, AmiVector * /*yp*/) const { - int status = CVodeGetB(solverMemory.get(), which, tret, yy->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetB"); +void CVodeSolver::getNumSteps(const void *ami_mem, long int *numsteps) const { + int status = CVodeGetNumSteps(const_cast(ami_mem), numsteps); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetNumSteps"); } - -void CVodeSolver::getNumSteps(void *ami_mem, long *numsteps) const { - int status = CVodeGetNumSteps(ami_mem, numsteps); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetNumSteps"); +void CVodeSolver::getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const { + int status = CVodeGetNumRhsEvals(const_cast(ami_mem), numrhsevals); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetNumRhsEvals"); } -void CVodeSolver::getNumRhsEvals(void *ami_mem, long *numrhsevals) const { - int status = CVodeGetNumRhsEvals(ami_mem, numrhsevals); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetNumRhsEvals"); +void CVodeSolver::getNumErrTestFails(const void *ami_mem, + long int *numerrtestfails) const { + int status = + CVodeGetNumErrTestFails(const_cast(ami_mem), numerrtestfails); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetNumErrTestFails"); } -void CVodeSolver::getNumErrTestFails(void *ami_mem, long *numerrtestfails) const { - int status = CVodeGetNumErrTestFails(ami_mem, numerrtestfails); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetNumErrTestFails"); +void CVodeSolver::getNumNonlinSolvConvFails( + const void *ami_mem, long int *numnonlinsolvconvfails) const { + int status = CVodeGetNumNonlinSolvConvFails(const_cast(ami_mem), + numnonlinsolvconvfails); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetNumNonlinSolvConvFails"); } -void CVodeSolver::getNumNonlinSolvConvFails(void *ami_mem, - long *numnonlinsolvconvfails) const { - int status = CVodeGetNumNonlinSolvConvFails(ami_mem, numnonlinsolvconvfails); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetNumNonlinSolvConvFails"); +void CVodeSolver::getLastOrder(const void *ami_mem, int *order) const { + int status = CVodeGetLastOrder(const_cast(ami_mem), order); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetLastOrder"); } -void CVodeSolver::getLastOrder(void *ami_mem, int *order) const { - int status = CVodeGetLastOrder(ami_mem, order); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetLastOrder"); -} - -void *CVodeSolver::getAdjBmem(void *ami_mem, int which) { +void *CVodeSolver::getAdjBmem(void *ami_mem, int which) const { return CVodeGetAdjCVodeBmem(ami_mem, which); } -void CVodeSolver::calcIC(realtype tout1, AmiVector *x, AmiVector *dx) { }; +void CVodeSolver::calcIC(const realtype tout1) const {}; -void CVodeSolver::calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) {}; +void CVodeSolver::calcICB(const int which, const realtype tout1) const {}; -void CVodeSolver::setStopTime(realtype tstop) { +void CVodeSolver::setStopTime(const realtype tstop) const { int status = CVodeSetStopTime(solverMemory.get(), tstop); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetStopTime"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetStopTime"); } -void CVodeSolver::turnOffRootFinding() { +void CVodeSolver::turnOffRootFinding() const { int status = CVodeRootInit(solverMemory.get(), 0, nullptr); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeRootInit"); -} - -int CVodeSolver::nplist() const { - if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto cv_mem = static_cast(solverMemory.get()); - return cv_mem->cv_Ns; -} - -int CVodeSolver::nx() const { - if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto cv_mem = static_cast(solverMemory.get()); - return NV_LENGTH_S(cv_mem->cv_zn[0]); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeRootInit"); } const Model *CVodeSolver::getModel() const { if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); + throw AmiException("Solver has not been allocated, information is not " + "available"); auto cv_mem = static_cast(solverMemory.get()); return static_cast(cv_mem->cv_user_data); } -bool CVodeSolver::getMallocDone() const { - if (!solverMemory) - return false; - auto cv_mem = static_cast(solverMemory.get()); - return cv_mem->cv_MallocDone; -} - -bool CVodeSolver::getAdjMallocDone() const { - if (!solverMemory) - return false; - auto cv_mem = static_cast(solverMemory.get()); - return cv_mem->cv_adjMallocDone; -} - -/** Jacobian of xdot with respect to states x +/** + * @brief Jacobian of xdot with respect to states x * @param N number of state variables * @param t timepoint * @param x Vector with the states @@ -589,15 +726,16 @@ bool CVodeSolver::getAdjMallocDone() const { * @param tmp3 temporary storage vector * @return status flag indicating successful execution **/ -int CVodeSolver::fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, - void *user_data, N_Vector /*tmp1*/, N_Vector /*tmp2*/, - N_Vector /*tmp3*/) { +int fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector /*tmp1*/, N_Vector /*tmp2*/, + N_Vector /*tmp3*/) { auto model = static_cast(user_data); model->fJ(t, x, xdot, J); - return model->checkFinite(SM_ROWS_D(J), SM_DATA_D(J), "Jacobian"); + return model->checkFinite(gsl::make_span(J), "Jacobian"); } -/** Jacobian of xBdot with respect to adjoint state xB +/** + * @brief Jacobian of xBdot with respect to adjoint state xB * @param NeqBdot number of adjoint state variables * @param t timepoint * @param x Vector with the states @@ -610,15 +748,16 @@ int CVodeSolver::fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, * @param tmp3B temporary storage vector * @return status flag indicating successful execution **/ -int CVodeSolver::fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, - N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { +int fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, + N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { auto model = static_cast(user_data); model->fJB(t, x, xB, xBdot, JB); - return model->checkFinite(SM_ROWS_D(JB), SM_DATA_D(JB), "Jacobian"); + return model->checkFinite(gsl::make_span(JB), "Jacobian"); } -/** J in sparse form (for sparse solvers from the SuiteSparse Package) +/** + * @brief J in sparse form (for sparse solvers from the SuiteSparse Package) * @param t timepoint * @param x Vector with the states * @param xdot Vector with the right hand side @@ -629,15 +768,16 @@ int CVodeSolver::fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, * @param tmp3 temporary storage vector * @return status flag indicating successful execution */ -int CVodeSolver::fJSparse(realtype t, N_Vector x, N_Vector /*xdot*/, SUNMatrix J, - void *user_data, N_Vector /*tmp1*/, N_Vector /*tmp2*/, - N_Vector /*tmp3*/) { +int fJSparse(realtype t, N_Vector x, N_Vector /*xdot*/, + SUNMatrix J, void *user_data, N_Vector /*tmp1*/, + N_Vector /*tmp2*/, N_Vector /*tmp3*/) { auto model = static_cast(user_data); model->fJSparse(t, x, J); - return model->checkFinite(SM_NNZ_S(J), SM_DATA_S(J), "Jacobian"); + return model->checkFinite(gsl::make_span(J), "Jacobian"); } -/** JB in sparse form (for sparse solvers from the SuiteSparse Package) +/** + * @brief JB in sparse form (for sparse solvers from the SuiteSparse Package) * @param t timepoint * @param x Vector with the states * @param xB Vector with the adjoint states @@ -649,15 +789,16 @@ int CVodeSolver::fJSparse(realtype t, N_Vector x, N_Vector /*xdot*/, SUNMatrix * @param tmp3B temporary storage vector * @return status flag indicating successful execution */ -int CVodeSolver::fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, - N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { +int fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, + N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { auto model = static_cast(user_data); model->fJSparseB(t, x, xB, xBdot, JB); - return model->checkFinite(SM_NNZ_S(JB), SM_DATA_S(JB), "Jacobian"); + return model->checkFinite(gsl::make_span(JB), "Jacobian"); } -/** J in banded form (for banded solvers) +/** + * @brief J in banded form (for banded solvers) * @param N number of states * @param mupper upper matrix bandwidth * @param mlower lower matrix bandwidth @@ -671,13 +812,13 @@ int CVodeSolver::fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, * @param tmp3 temporary storage vector * @return status flag indicating successful execution */ -int CVodeSolver::fJBand(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, - void *user_data, N_Vector tmp1, N_Vector tmp2, - N_Vector tmp3) { +int fJBand(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) { return fJ(t, x, xdot, J, user_data, tmp1, tmp2, tmp3); } -/** JB in banded form (for banded solvers) +/** + * @brief JB in banded form (for banded solvers) * @param NeqBdot number of states * @param mupper upper matrix bandwidth * @param mlower lower matrix bandwidth @@ -692,155 +833,161 @@ int CVodeSolver::fJBand(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, * @param tmp3B temporary storage vector * @return status flag indicating successful execution */ -int CVodeSolver::fJBandB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, +int fJBandB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, N_Vector tmp2B, N_Vector tmp3B) { return fJB(t, x, xB, xBdot, JB, user_data, tmp1B, tmp2B, tmp3B); } -/** diagonalized Jacobian (for preconditioning) +/** + * @brief Diagonalized Jacobian (for preconditioning) * @param t timepoint * @param JDiag Vector to which the Jacobian diagonal will be written * @param x Vector with the states * @param user_data object with user input @type Model_ODE **/ -int CVodeSolver::fJDiag(realtype t, N_Vector JDiag, N_Vector x, - void *user_data) { +int fJDiag(realtype t, N_Vector JDiag, N_Vector x, void *user_data) { auto model = static_cast(user_data); model->fJDiag(t, JDiag, x); - return model->checkFinite(model->nx_solver, N_VGetArrayPointer(JDiag), - "Jacobian"); -} - - /** Matrix vector product of J with a vector v (for iterative solvers) - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - * @param v Vector with which the Jacobian is multiplied - * @param Jv Vector to which the Jacobian vector product will be - *written - * @param user_data object with user input @type Model_ODE - * @param tmp temporary storage vector - * @return status flag indicating successful execution - **/ - int CVodeSolver::fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, N_Vector /*xdot*/, - void *user_data, N_Vector /*tmp*/) { - auto model = static_cast(user_data); - model->fJv(v,Jv,t,x); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(Jv),"Jacobian"); - } + return model->checkFinite(gsl::make_span(JDiag), "Jacobian"); +} - /** Matrix vector product of JB with a vector v (for iterative solvers) - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - * @param vB Vector with which the Jacobian is multiplied - * @param JvB Vector to which the Jacobian vector product will be - *written - * @param user_data object with user input @type Model_ODE - * @param tmpB temporary storage vector - * @return status flag indicating successful execution - **/ - int CVodeSolver::fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, N_Vector xB, N_Vector /*xBdot*/, - void *user_data, N_Vector /*tmpB*/) { - auto model = static_cast(user_data); - model->fJvB(vB, JvB, t, x, xB); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(JvB),"Jacobian"); - } +/** + * @brief Matrix vector product of J with a vector v (for iterative solvers) + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + * @param v Vector with which the Jacobian is multiplied + * @param Jv Vector to which the Jacobian vector product will be + *written + * @param user_data object with user input @type Model_ODE + * @param tmp temporary storage vector + * @return status flag indicating successful execution + **/ +int fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, + N_Vector /*xdot*/, void *user_data, N_Vector /*tmp*/) { + auto model = static_cast(user_data); + model->fJv(v, Jv, t, x); + return model->checkFinite(gsl::make_span(Jv), "Jacobian"); +} - /** Event trigger function for events - * @param t timepoint - * @param x Vector with the states - * @param root array with root function values - * @param user_data object with user input @type Model_ODE - * @return status flag indicating successful execution - */ - int CVodeSolver::froot(realtype t, N_Vector x, realtype *root, - void *user_data) { - auto model = static_cast(user_data); - model->froot(t, x, root); - return model->checkFinite(model->ne,root,"root function"); - } +/** + * @brief Matrix vector product of JB with a vector v (for iterative solvers) + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + * @param vB Vector with which the Jacobian is multiplied + * @param JvB Vector to which the Jacobian vector product will be + *written + * @param user_data object with user input @type Model_ODE + * @param tmpB temporary storage vector + * @return status flag indicating successful execution + **/ +int fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, + N_Vector xB, N_Vector /*xBdot*/, void *user_data, + N_Vector /*tmpB*/) { + auto model = static_cast(user_data); + model->fJvB(vB, JvB, t, x, xB); + return model->checkFinite(gsl::make_span(JvB), "Jacobian"); +} - /** residual function of the ODE - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - * @param user_data object with user input @type Model_ODE - * @return status flag indicating successful execution - */ - int CVodeSolver::fxdot(realtype t, N_Vector x, N_Vector xdot, - void *user_data) { - auto model = static_cast(user_data); - - if (t > 1e200 && !amici::checkFinite(model->nx_solver, - N_VGetArrayPointer(x), "fxdot")) - return AMICI_UNRECOVERABLE_ERROR; - /* when t is large (typically ~1e300), CVODES may pass all NaN x - to fxdot from which we typically cannot recover. To save time - on normal execution, we do not always want to check finiteness - of x, but only do so when t is large and we expect problems. */ - - model->fxdot(t, x, xdot); - return model->checkFinite(model->nx_solver, N_VGetArrayPointer(xdot), - "fxdot"); - } +/** + * @brief Event trigger function for events + * @param t timepoint + * @param x Vector with the states + * @param root array with root function values + * @param user_data object with user input @type Model_ODE + * @return status flag indicating successful execution + */ +int froot(realtype t, N_Vector x, realtype *root, + void *user_data) { + auto model = static_cast(user_data); + model->froot(t, x, gsl::make_span(root, model->ne)); + return model->checkFinite(gsl::make_span(root, model->ne), + "root function"); +} - /** Right hand side of differential equation for adjoint state xB - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - * @param user_data object with user input @type Model_ODE - * @return status flag indicating successful execution - */ - int CVodeSolver::fxBdot(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, void *user_data) { - auto model = static_cast(user_data); - model->fxBdot(t, x, xB, xBdot); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(xBdot),"fxBdot"); - } +/** + * @brief residual function of the ODE + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + * @param user_data object with user input @type Model_ODE + * @return status flag indicating successful execution + */ +int fxdot(realtype t, N_Vector x, N_Vector xdot, void *user_data) { + auto model = static_cast(user_data); - /** Right hand side of integral equation for quadrature states qB - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param qBdot Vector with the adjoint quadrature right hand side - * @param user_data pointer to temp data object - * @return status flag indicating successful execution - */ - int CVodeSolver::fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot, - void *user_data) { - auto model = static_cast(user_data); - model->fqBdot(t, x, xB, qBdot); - return model->checkFinite(model->nplist()*model->nJ,N_VGetArrayPointer(qBdot),"qBdot"); + if (t > 1e200 && !amici::checkFinite(gsl::make_span(x), "fxdot")) { + /* when t is large (typically ~1e300), CVODES may pass all NaN x + to fxdot from which we typically cannot recover. To save time + on normal execution, we do not always want to check finiteness + of x, but only do so when t is large and we expect problems. */ + return AMICI_UNRECOVERABLE_ERROR; } - /** Right hand side of differential equation for state sensitivities sx - * @param Ns number of parameters - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - * @param ip parameter index - * @param sx Vector with the state sensitivities - * @param sxdot Vector with the sensitivity right hand side - * @param user_data object with user input @type Model_ODE - * @param tmp1 temporary storage vector - * @param tmp2 temporary storage vector - * @param tmp3 temporary storage vector - * @return status flag indicating successful execution - */ - int CVodeSolver::fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector /*xdot*/, int ip, - N_Vector sx, N_Vector sxdot, void *user_data, - N_Vector /*tmp1*/, N_Vector /*tmp2*/) { - auto model = static_cast(user_data); - model->fsxdot(t, x, ip, sx, sxdot); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(sxdot),"sxdot"); - } + model->fxdot(t, x, xdot); + return model->checkFinite(gsl::make_span(xdot), "fxdot"); +} + +/** + * @brief Right hand side of differential equation for adjoint state xB + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + * @param user_data object with user input @type Model_ODE + * @return status flag indicating successful execution + */ +int fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + void *user_data) { + auto model = static_cast(user_data); + model->fxBdot(t, x, xB, xBdot); + return model->checkFinite(gsl::make_span(xBdot), "fxBdot"); +} + +/** + * @brief Right hand side of integral equation for quadrature states qB + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param qBdot Vector with the adjoint quadrature right hand side + * @param user_data pointer to temp data object + * @return status flag indicating successful execution + */ +int fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot, + void *user_data) { + auto model = static_cast(user_data); + model->fqBdot(t, x, xB, qBdot); + return model->checkFinite(gsl::make_span(qBdot), "qBdot"); +} + +/** + * @brief Right hand side of differential equation for state sensitivities sx + * @param Ns number of parameters + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + * @param ip parameter index + * @param sx Vector with the state sensitivities + * @param sxdot Vector with the sensitivity right hand side + * @param user_data object with user input @type Model_ODE + * @param tmp1 temporary storage vector + * @param tmp2 temporary storage vector + * @param tmp3 temporary storage vector + * @return status flag indicating successful execution + */ +int fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector /*xdot*/, + int ip, N_Vector sx, N_Vector sxdot, void *user_data, + N_Vector /*tmp1*/, N_Vector /*tmp2*/) { + auto model = static_cast(user_data); + model->fsxdot(t, x, ip, sx, sxdot); + return model->checkFinite(gsl::make_span(sxdot), "sxdot"); +} + +bool operator==(const CVodeSolver &a, const CVodeSolver &b) { + return static_cast(a) == static_cast(b); +} - bool operator ==(const CVodeSolver &a, const CVodeSolver &b) - { - return static_cast(a) == static_cast(b); - } } // namespace amici diff --git a/src/solver_idas.cpp b/src/solver_idas.cpp index 5ba9b4b0d1..670b22805b 100644 --- a/src/solver_idas.cpp +++ b/src/solver_idas.cpp @@ -1,462 +1,717 @@ -#include "amici/misc.h" -#include "amici/model_dae.h" #include "amici/solver_idas.h" + #include "amici/exception.h" +#include "amici/misc.h" +#include "amici/model_dae.h" +#include "amici/sundials_linsol_wrapper.h" #include #include -#include "amici/sundials_linsol_wrapper.h" - #include #include #include #include -#define ONE RCONST(1.0) +#define ONE RCONST(1.0) namespace amici { -void IDASolver::init(AmiVector *x, AmiVector *dx, realtype t) { - int status = IDAInit(solverMemory.get(), fxdot, t, x->getNVector(), dx->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAInit"); +/* + * The following static members are callback function to CVODES. + * Their signatures must not be changes. + */ + +static int fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, + void *user_data); + +static int fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xdot, SUNMatrix J, void *user_data, N_Vector tmp1, + N_Vector tmp2, N_Vector tmp3); + +static int fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xdot, SUNMatrix J, void *user_data, + N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + +static int fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector xBdot, SUNMatrix JB, + void *user_data, N_Vector tmp1B, N_Vector tmp2B, + N_Vector tmp3B); + +static int fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector tmp1B, + N_Vector tmp2B, N_Vector tmp3B); + +static int fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xdot, SUNMatrix J, void *user_data, + N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + +static int fJBandB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector xBdot, SUNMatrix JB, + void *user_data, N_Vector tmp1B, N_Vector tmp2B, + N_Vector tmp3B); + +static int fJv(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, + N_Vector v, N_Vector Jv, realtype cj, void *user_data, + N_Vector tmp1, N_Vector tmp2); + +static int fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector xBdot, N_Vector vB, N_Vector JvB, + realtype cj, void *user_data, N_Vector tmpB1, + N_Vector tmpB2); + +static int froot(realtype t, N_Vector x, N_Vector dx, realtype *root, + void *user_data); + +static int fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector xBdot, void *user_data); + +static int fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector qBdot, void *user_data); + +static int fsxdot(int Ns, realtype t, N_Vector x, N_Vector dx, + N_Vector xdot, N_Vector *sx, N_Vector *sdx, + N_Vector *sxdot, void *user_data, N_Vector tmp1, + N_Vector tmp2, N_Vector tmp3); + + +/* Function implementations */ + +void IDASolver::init(const realtype t0, const AmiVector &x0, + const AmiVector &dx0) const { + int status; + solverWasCalledF = false; + t = t0; + x.copy(x0); + dx.copy(dx0); + if (getInitDone()) { + status = + IDAReInit(solverMemory.get(), t, x.getNVector(), dx.getNVector()); + } else { + status = IDAInit(solverMemory.get(), fxdot, t, x.getNVector(), + dx.getNVector()); + setInitDone(); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAInit"); } -void IDASolver::binit(int which, AmiVector *xB, AmiVector *dxB, realtype t) { - int status = IDAInitB(solverMemory.get(), which, fxBdot, t, xB->getNVector(), dxB->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAInitB"); + +void IDASolver::sensInit1(const AmiVectorArray &sx0, + const AmiVectorArray &sdx0) const { + int status; + sx.copy(sx0); + sdx.copy(sdx0); + if (getSensInitDone()) { + status = IDASensReInit(solverMemory.get(), + static_cast(getSensitivityMethod()), + sx.getNVectorArray(), sdx.getNVectorArray()); + } else { + status = IDASensInit(solverMemory.get(), nplist(), + static_cast(getSensitivityMethod()), fsxdot, + sx.getNVectorArray(), sdx.getNVectorArray()); + setSensInitDone(); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASensInit"); } -void IDASolver::qbinit(int which, AmiVector *qBdot) { - int status = IDAQuadInitB(solverMemory.get(), which, fqBdot, qBdot->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAQuadInitB"); + +void IDASolver::binit(const int which, const realtype tf, const AmiVector &xB0, + const AmiVector &dxB0) const { + int status; + xB.copy(xB0); + dxB.copy(dxB0); + if (getInitDoneB(which)) + status = IDAReInitB(solverMemory.get(), which, tf, xB.getNVector(), + dxB.getNVector()); + else { + + status = IDAInitB(solverMemory.get(), which, fxBdot, tf, + xB.getNVector(), dxB.getNVector()); + setInitDoneB(which); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAInitB"); } -void IDASolver::rootInit(int ne) { - int status = IDARootInit(solverMemory.get(), ne, froot); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDARootInit"); + +void IDASolver::qbinit(const int which, const AmiVector &xQB0) const { + int status; + xQB.copy(xQB0); + if (getQuadInitDoneB(which)) + status = IDAQuadReInitB(solverMemory.get(), which, xQB.getNVector()); + else { + status = + IDAQuadInitB(solverMemory.get(), which, fqBdot, xQB.getNVector()); + setQuadInitDoneB(which); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAQuadInitB"); } -void IDASolver::sensInit1(AmiVectorArray *sx, AmiVectorArray *sdx, int nplist) { - int status = IDASensInit(solverMemory.get(), nplist, static_cast(getSensitivityMethod()), fsxdot, - sx->getNVectorArray(),sdx->getNVectorArray()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASensInit"); + +void IDASolver::rootInit(int ne) const { + int status = IDARootInit(solverMemory.get(), ne, froot); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDARootInit"); } -void IDASolver::setDenseJacFn() { + +void IDASolver::setDenseJacFn() const { int status = IDASetJacFn(solverMemory.get(), fJ); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDADlsSetDenseJacFn"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDADlsSetDenseJacFn"); } -void IDASolver::setSparseJacFn() { +void IDASolver::setSparseJacFn() const { int status = IDASetJacFn(solverMemory.get(), fJSparse); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASlsSetSparseJacFn"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASlsSetSparseJacFn"); } -void IDASolver::setBandJacFn() { + +void IDASolver::setBandJacFn() const { int status = IDASetJacFn(solverMemory.get(), fJBand); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDADlsSetBandJacFn"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDADlsSetBandJacFn"); } -void IDASolver::setJacTimesVecFn() { +void IDASolver::setJacTimesVecFn() const { int status = IDASetJacTimes(solverMemory.get(), nullptr, fJv); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASpilsSetJacTimesVecFn"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASpilsSetJacTimesVecFn"); } -void IDASolver::setDenseJacFnB(int which) { + +void IDASolver::setDenseJacFnB(const int which) const { int status = IDASetJacFnB(solverMemory.get(), which, fJB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDADlsSetDenseJacFnB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDADlsSetDenseJacFnB"); } -void IDASolver::setSparseJacFnB(int which) { + +void IDASolver::setSparseJacFnB(const int which) const { int status = IDASetJacFnB(solverMemory.get(), which, fJSparseB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASlsSetSparseJacFnB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASlsSetSparseJacFnB"); } -void IDASolver::setBandJacFnB(int which) { + +void IDASolver::setBandJacFnB(const int which) const { int status = IDASetJacFnB(solverMemory.get(), which, fJBandB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDADlsSetBandJacFnB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDADlsSetBandJacFnB"); } -void IDASolver::setJacTimesVecFnB(int which) { + +void IDASolver::setJacTimesVecFnB(const int which) const { int status = IDASetJacTimesB(solverMemory.get(), which, nullptr, fJvB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASpilsSetJacTimesVecFnB"); -} -Solver *IDASolver::clone() const { - return new IDASolver(*this); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASpilsSetJacTimesVecFnB"); } -void IDASolver::allocateSolver() { - solverMemory = std::unique_ptr>(IDACreate(), - [](void *ptr) { IDAFree(&ptr); }); +Solver *IDASolver::clone() const { return new IDASolver(*this); } + +void IDASolver::allocateSolver() const { + if (!solverMemory) + solverMemory = std::unique_ptr>( + IDACreate(), [](void *ptr) { IDAFree(&ptr); }); } -void IDASolver::setSStolerances(double rtol, double atol) { +void IDASolver::setSStolerances(const realtype rtol, + const realtype atol) const { int status = IDASStolerances(solverMemory.get(), rtol, atol); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASStolerances"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASStolerances"); } -void IDASolver::setSensSStolerances(double rtol, double *atol) { - int status = IDASensSStolerances(solverMemory.get(), rtol, atol); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASensEEtolerances"); +void IDASolver::setSensSStolerances(const realtype rtol, + const realtype *atol) const { + int status = IDASensSStolerances(solverMemory.get(), rtol, + const_cast(atol)); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASensEEtolerances"); } -void IDASolver::setSensErrCon(bool error_corr) { +void IDASolver::setSensErrCon(const bool error_corr) const { int status = IDASetSensErrCon(solverMemory.get(), error_corr); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetSensErrCon"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetSensErrCon"); } -void IDASolver::setQuadErrConB(int which, bool flag) { + +void IDASolver::setQuadErrConB(const int which, const bool flag) const { int status = IDASetQuadErrConB(solverMemory.get(), which, flag); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetQuadErrConB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetQuadErrConB"); } + void IDASolver::getRootInfo(int *rootsfound) const { int status = IDAGetRootInfo(solverMemory.get(), rootsfound); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetRootInfo"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetRootInfo"); } -void IDASolver::setErrHandlerFn() { - int status = IDASetErrHandlerFn(solverMemory.get(), wrapErrHandlerFn, nullptr); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetErrHandlerFn"); + +void IDASolver::setErrHandlerFn() const { + int status = + IDASetErrHandlerFn(solverMemory.get(), wrapErrHandlerFn, nullptr); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetErrHandlerFn"); } -void IDASolver::setUserData(Model *model) { + +void IDASolver::setUserData(Model *model) const { int status = IDASetUserData(solverMemory.get(), model); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetUserData"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetUserData"); } -void IDASolver::setUserDataB(int which, Model *model) { + +void IDASolver::setUserDataB(int which, Model *model) const { int status = IDASetUserDataB(solverMemory.get(), which, model); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetUserDataB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetUserDataB"); } -void IDASolver::setMaxNumSteps(long mxsteps) { + +void IDASolver::setMaxNumSteps(const long int mxsteps) const { int status = IDASetMaxNumSteps(solverMemory.get(), mxsteps); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetMaxNumSteps"); -} -void IDASolver::setStabLimDet(int stldet) { + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetMaxNumSteps"); } -void IDASolver::setStabLimDetB(int which, int stldet) { -} +void IDASolver::setStabLimDet(const int stldet) const {} -void IDASolver::setId(Model *model) { +void IDASolver::setStabLimDetB(const int which, const int stldet) const {} - N_Vector id = N_VMake_Serial(model->nx_solver,const_cast(model->idlist.data())); +void IDASolver::setId(const Model *model) const { + + N_Vector id = N_VMake_Serial(model->nx_solver, + const_cast(model->idlist.data())); int status = IDASetId(solverMemory.get(), id); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetMaxNumSteps"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetMaxNumSteps"); N_VDestroy_Serial(id); } -void IDASolver::setSuppressAlg(bool flag) { +void IDASolver::setSuppressAlg(const bool flag) const { int status = IDASetSuppressAlg(solverMemory.get(), flag); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetSuppressAlg"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetSuppressAlg"); } - -void IDASolver::resetState(void *ami_mem, N_Vector yy0, N_Vector yp0) { - + +void IDASolver::resetState(void *ami_mem, const_N_Vector yy0, + const_N_Vector yp0) const { + auto ida_mem = static_cast(ami_mem); /* here we force the order in the next step to zero, and update the phi arrays, this is largely copied from IDAReInit with explanations from idas_impl.h */ - + /* Initialize the phi array */ - + N_VScale(ONE, yy0, ida_mem->ida_phi[0]); N_VScale(ONE, yp0, ida_mem->ida_phi[1]); - + /* Set step parameters */ - + /* current order */ ida_mem->ida_kk = 0; } -void IDASolver::reInitPostProcessF(realtype *t, AmiVector *yout, - AmiVector *ypout, realtype tnext) { - reInitPostProcess(solverMemory.get(), t, yout, ypout, tnext); +void IDASolver::reInitPostProcessF(const realtype tnext) const { + reInitPostProcess(solverMemory.get(), &t, &x, &dx, tnext); } -void IDASolver::reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector *ypBout, realtype tnext) { - reInitPostProcess(IDAGetAdjIDABmem(solverMemory.get(), which), - t, yBout, ypBout, tnext); +void IDASolver::reInitPostProcessB(const realtype tnext) const { + realtype tBret; + auto ida_mem = static_cast(solverMemory.get()); + auto idaadj_mem = ida_mem->ida_adj_mem; + auto idaB_mem = idaadj_mem->IDAB_mem; + // loop over all backward problems + while (idaB_mem != nullptr) { + // store current backward problem in ca_mem to make it accessible in + // adjoint rhs wrapper functions + idaadj_mem->ia_bckpbCrt = idaB_mem; + reInitPostProcess(static_cast(idaB_mem->IDA_mem), &tBret, &xB, + &dxB, tnext); + // idaB_mem->ida_tout = tBret; + idaB_mem = idaB_mem->ida_next; + } + forceReInitPostProcessB = false; } void IDASolver::reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, AmiVector *ypout, - realtype tout) { + realtype tout) const { auto ida_mem = static_cast(ami_mem); auto nst_tmp = ida_mem->ida_nst; ida_mem->ida_nst = 0; - + auto status = IDASetStopTime(ida_mem, tout); if(status != IDA_SUCCESS) throw IDAException(status, "CVodeSetStopTime"); - + status = IDASolve(ami_mem, tout, t, yout->getNVector(), ypout->getNVector(), IDA_ONE_STEP); - + if(status != IDA_SUCCESS) throw IDAException(status, "reInitPostProcess"); - + ida_mem->ida_nst = nst_tmp+1; if (ida_mem->ida_adjMallocDone == SUNTRUE) { /* add new step to history array, this is copied from CVodeF */ auto ia_mem = ida_mem->ida_adj_mem; auto dt_mem = ia_mem->dt_mem; - + if (ida_mem->ida_nst % ia_mem->ia_nsteps == 0) { /* currently not implemented, we should never get here as we limit cv_mem->cv_nst < ca_mem->ca_nsteps, keeping this for future regression */ throw IDAException(AMICI_ERROR, "reInitPostProcess"); } - + /* Load next point in dt_mem */ dt_mem[ida_mem->ida_nst % ia_mem->ia_nsteps]->t = *t; ia_mem->ia_storePnt(ida_mem, dt_mem[ida_mem->ida_nst % ia_mem->ia_nsteps]); - + /* Set t1 field of the current ckeck point structure for the case in which there will be no future check points */ ia_mem->ck_mem->ck_t1 = *t; - + /* tfinal is now set to *tret */ ia_mem->ia_tfinal = *t; } } -void IDASolver::reInit(realtype t0, AmiVector *yy0, AmiVector *yp0) { +void IDASolver::reInit(const realtype t0, const AmiVector &yy0, + const AmiVector &yp0) const { + auto ida_mem = static_cast(solverMemory.get()); - /* set time */ ida_mem->ida_tn = t0; - resetState(ida_mem, yy0->getNVector(), yp0->getNVector()); + if (solverWasCalledF) + forceReInitPostProcessF = true; + x.copy(yy0); + dx.copy(yp0); + resetState(ida_mem, x.getNVector(), xB.getNVector()); } -void IDASolver::sensReInit(AmiVectorArray *yS0, AmiVectorArray *ypS0) { + +void IDASolver::sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray &ypS0) const { auto ida_mem = static_cast(solverMemory.get()); /* Initialize znS[0] in the history array */ - - for (int is=0; isida_Ns; is++) + for (int is = 0; is < nplist(); is++) ida_mem->ida_cvals[is] = ONE; - - auto status = N_VScaleVectorArray(ida_mem->ida_Ns, ida_mem->ida_cvals, - yS0->getNVectorArray(), - ida_mem->ida_phiS[0]); - if(status != IDA_SUCCESS) - throw IDAException(IDA_VECTOROP_ERR,"IDASensReInit"); - - status = N_VScaleVectorArray(ida_mem->ida_Ns, ida_mem->ida_cvals, - ypS0->getNVectorArray(), - ida_mem->ida_phiS[1]); - if(status != IDA_SUCCESS) - throw IDAException(IDA_VECTOROP_ERR,"IDASensReInit"); + if (solverWasCalledF) + forceReInitPostProcessF = true; + sx.copy(yyS0); + sdx.copy(ypS0); + auto status = + N_VScaleVectorArray(nplist(), ida_mem->ida_cvals, sx.getNVectorArray(), + ida_mem->ida_phiS[0]); + if (status != IDA_SUCCESS) + throw IDAException(IDA_VECTOROP_ERR, "IDASensReInit"); + status = N_VScaleVectorArray(nplist(), ida_mem->ida_cvals, + sdx.getNVectorArray(), ida_mem->ida_phiS[1]); + if (status != IDA_SUCCESS) + throw IDAException(IDA_VECTOROP_ERR, "IDASensReInit"); } -void IDASolver::setSensParams(realtype *p, realtype *pbar, int *plist) { - int status = IDASetSensParams(solverMemory.get(), p, pbar, plist); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetSensParams"); + +void IDASolver::reInitB(const int which, const realtype tB0, + const AmiVector &yyB0, const AmiVector &ypB0) const { + + auto ida_memB = + static_cast(IDAGetAdjIDABmem(solverMemory.get(), which)); + if (solverWasCalledB) + forceReInitPostProcessB = true; + ida_memB->ida_tn = tB0; + xB.copy(yyB0); + dxB.copy(ypB0); + resetState(ida_memB, xB.getNVector(), dxB.getNVector()); } -void IDASolver::getDky(realtype t, int k, AmiVector *dky) const { - int status = IDAGetDky(solverMemory.get(), t, k, dky->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetDky"); + +void IDASolver::quadReInitB(const int which, const AmiVector &yQB0) const { + auto ida_memB = + static_cast(IDAGetAdjIDABmem(solverMemory.get(), which)); + if (solverWasCalledB) + forceReInitPostProcessB = true; + xQB.copy(yQB0); + N_VScale(ONE, xQB.getNVector(), ida_memB->ida_phiQ[0]); } -void IDASolver::getSens(realtype *tret, AmiVectorArray *yySout) const { - int status = IDAGetSens(solverMemory.get(), tret, yySout->getNVectorArray()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetSens"); + +void IDASolver::setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const { + int status = IDASetSensParams(solverMemory.get(), const_cast(p), + const_cast(pbar), + const_cast(plist)); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetSensParams"); } -void IDASolver::adjInit() { - int status = IDAAdjInit(solverMemory.get(), static_cast(maxsteps), static_cast(interpType)); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAAdjInit"); +void IDASolver::getDky(const realtype t, const int k) const { + int status = IDAGetDky(solverMemory.get(), t, k, dky.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetDky"); +} + +void IDASolver::getSens() const { + realtype tDummy = 0; + int status = IDAGetSens(solverMemory.get(), &tDummy, sx.getNVectorArray()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetSens"); +} + +void IDASolver::getSensDky(const realtype t, const int k) const { + int status = IDAGetSensDky(solverMemory.get(), t, k, sx.getNVectorArray()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetSens"); +} + +void IDASolver::getB(const int which) const { + realtype tDummy = 0; + int status = IDAGetB(solverMemory.get(), which, &tDummy, xB.getNVector(), + dxB.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetB"); +} + +void IDASolver::getDkyB(const realtype t, int k, const int which) const { + int status = IDAGetDky(IDAGetAdjIDABmem(solverMemory.get(), which), t, k, + dky.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetB"); +} +void IDASolver::getQuadB(int which) const { + realtype tDummy = 0; + int status = + IDAGetQuadB(solverMemory.get(), which, &tDummy, xQB.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetQuadB"); +} + +void IDASolver::getQuadDkyB(const realtype t, int k, const int which) const { + int status = IDAGetQuadDky(IDAGetAdjIDABmem(solverMemory.get(), which), t, + k, xQB.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetB"); +} + +void IDASolver::adjInit() const { + int status; + if (getAdjInitDone()) { + status = IDAAdjReInit(solverMemory.get()); + } else { + status = IDAAdjInit(solverMemory.get(), static_cast(maxsteps), + static_cast(interpType)); + setAdjInitDone(); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAAdjInit"); } -void IDASolver::allocateSolverB(int *which) { + +void IDASolver::allocateSolverB(int *which) const { + if (!solverMemoryB.empty()) { + *which = 0; + return; + } int status = IDACreateB(solverMemory.get(), which); if (*which + 1 > static_cast(solverMemoryB.size())) solverMemoryB.resize(*which + 1); - solverMemoryB.at(*which) = std::unique_ptr> - (getAdjBmem(solverMemory.get(), *which), [](void *ptr){}); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDACreateB"); -} -void IDASolver::reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector *ypB0) { - auto ida_memB = static_cast(IDAGetAdjIDABmem(solverMemory.get(), which)); - ida_memB->ida_tn = tB0; - resetState(ida_memB, yyB0->getNVector(), ypB0->getNVector()); + solverMemoryB.at(*which) = + std::unique_ptr>( + getAdjBmem(solverMemory.get(), *which), [](void *ptr) {}); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDACreateB"); } -void IDASolver::setSStolerancesB(int which, realtype relTolB, realtype absTolB) { + +void IDASolver::setSStolerancesB(const int which, const realtype relTolB, + const realtype absTolB) const { int status = IDASStolerancesB(solverMemory.get(), which, relTolB, absTolB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASStolerancesB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASStolerancesB"); } -void IDASolver::quadReInitB(int which, AmiVector *yQB0) { - auto ida_memB = static_cast(IDAGetAdjIDABmem(solverMemory.get(), which)); - N_VScale(ONE, yQB0->getNVector(), ida_memB->ida_phiQ[0]); -} -void IDASolver::quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) { - int status = IDAQuadSStolerancesB(solverMemory.get(), which, reltolQB, abstolQB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAQuadSStolerancesB"); + +void IDASolver::quadSStolerancesB(const int which, const realtype reltolQB, + const realtype abstolQB) const { + int status = + IDAQuadSStolerancesB(solverMemory.get(), which, reltolQB, abstolQB); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAQuadSStolerancesB"); } -int IDASolver::solve(realtype tout, AmiVector *yret, AmiVector *ypret, - realtype *tret, int itask) { - int status = IDASolve(solverMemory.get(), tout, tret, yret->getNVector(), ypret->getNVector(), itask); - if(status<0) { - throw IntegrationFailure(status,*tret); - } - solverWasCalled = true; +int IDASolver::solve(const realtype tout, const int itask) const { + if (forceReInitPostProcessF) + reInitPostProcessF(tout); + int status = IDASolve(solverMemory.get(), tout, &t, x.getNVector(), + dx.getNVector(), itask); + solverWasCalledF = true; + if (status < 0) // status > 0 is okay and is used for e.g. root return + throw IntegrationFailure(status, t); return status; } -int IDASolver::solveF(realtype tout, AmiVector *yret, AmiVector *ypret, - realtype *tret, int itask, int *ncheckPtr) { - int status = IDASolveF(solverMemory.get(), tout, tret, yret->getNVector(), ypret->getNVector(), itask, ncheckPtr); - if(status<0) { - throw IntegrationFailure(status,*tret); - } - solverWasCalled = true; +int IDASolver::solveF(const realtype tout, const int itask, + int *ncheckPtr) const { + if (forceReInitPostProcessF) + reInitPostProcessF(tout); + int status = IDASolveF(solverMemory.get(), tout, &t, x.getNVector(), + xB.getNVector(), itask, ncheckPtr); + solverWasCalledF = true; + if (status < 0) // status > 0 is okay and is used for e.g. root return + throw IntegrationFailure(status, t); return status; } -void IDASolver::solveB(realtype tBout, int itaskB) { + +void IDASolver::solveB(const realtype tBout, const int itaskB) const { + if (forceReInitPostProcessB) + reInitPostProcessB(tBout); int status = IDASolveB(solverMemory.get(), tBout, itaskB); - if(status != IDA_SUCCESS) - throw IntegrationFailure(status,tBout); + solverWasCalledB = true; + if (status != IDA_SUCCESS) + throw IntegrationFailure(status, tBout); } -void IDASolver::setMaxNumStepsB(int which, long mxstepsB) { + +void IDASolver::setMaxNumStepsB(const int which, + const long int mxstepsB) const { int status = IDASetMaxNumStepsB(solverMemory.get(), which, mxstepsB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetMaxNumStepsB"); -} -void IDASolver::getB(int which, realtype *tret, AmiVector *yy, AmiVector *yp) const { - int status = IDAGetB(solverMemory.get(), which, tret, yy->getNVector(), yp->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetB"); -} -void IDASolver::getQuadB(int which, realtype *tret, AmiVector *qB) const { - int status = IDAGetQuadB(solverMemory.get(), which, tret, qB->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetQuadB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetMaxNumStepsB"); } -void IDASolver::diag() { +void IDASolver::diag() const { throw AmiException("Diag Solver was not implemented for DAEs"); } -void IDASolver::diagB(int /*which*/) { +void IDASolver::diagB(const int /*which*/) const { throw AmiException("Diag Solver was not implemented for DAEs"); } -void IDASolver::getNumSteps(void *ami_mem, long *numsteps) const { - int status = IDAGetNumSteps(ami_mem, numsteps); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetNumSteps"); +void IDASolver::getNumSteps(const void *ami_mem, long int *numsteps) const { + int status = IDAGetNumSteps(const_cast(ami_mem), numsteps); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetNumSteps"); } -void IDASolver::getNumRhsEvals(void *ami_mem, long *numrhsevals) const { - int status = IDAGetNumResEvals(ami_mem, numrhsevals); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetNumResEvals"); + +void IDASolver::getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const { + int status = IDAGetNumResEvals(const_cast(ami_mem), numrhsevals); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetNumResEvals"); } -void IDASolver::getNumErrTestFails(void *ami_mem, long *numerrtestfails) const { - int status = IDAGetNumErrTestFails(ami_mem, numerrtestfails); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetNumErrTestFails"); + +void IDASolver::getNumErrTestFails(const void *ami_mem, + long int *numerrtestfails) const { + int status = + IDAGetNumErrTestFails(const_cast(ami_mem), numerrtestfails); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetNumErrTestFails"); } -void IDASolver::getNumNonlinSolvConvFails(void *ami_mem, - long *numnonlinsolvconvfails) const { - int status = IDAGetNumNonlinSolvConvFails(ami_mem, numnonlinsolvconvfails); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetNumNonlinSolvConvFails"); + +void IDASolver::getNumNonlinSolvConvFails( + const void *ami_mem, long int *numnonlinsolvconvfails) const { + int status = IDAGetNumNonlinSolvConvFails(const_cast(ami_mem), + numnonlinsolvconvfails); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetNumNonlinSolvConvFails"); } -void IDASolver::getLastOrder(void *ami_mem, int *order) const { - int status = IDAGetLastOrder(ami_mem, order); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetLastOrder"); + +void IDASolver::getLastOrder(const void *ami_mem, int *order) const { + int status = IDAGetLastOrder(const_cast(ami_mem), order); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetLastOrder"); } -void *IDASolver::getAdjBmem(void *ami_mem, int which) { + +void *IDASolver::getAdjBmem(void *ami_mem, int which) const { return IDAGetAdjIDABmem(ami_mem, which); } -void IDASolver::calcIC(realtype tout1, AmiVector *x, AmiVector *dx) { +void IDASolver::calcIC(realtype tout1) const { int status = IDACalcIC(solverMemory.get(), IDA_YA_YDP_INIT, tout1); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDACalcIC"); - status = IDAGetConsistentIC(solverMemory.get(), x->getNVector(), dx->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDACalcIC"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDACalcIC"); + status = + IDAGetConsistentIC(solverMemory.get(), x.getNVector(), dx.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDACalcIC"); } -void IDASolver::calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) { - int status = IDACalcICB(solverMemory.get(), which, tout1, xB->getNVector(), dxB->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDACalcICB"); +void IDASolver::calcICB(const int which, const realtype tout1) const { + int status = IDACalcICB(solverMemory.get(), which, tout1, xB.getNVector(), + dxB.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDACalcICB"); } -void IDASolver::setStopTime(realtype tstop) { +void IDASolver::setStopTime(const realtype tstop) const { int status = IDASetStopTime(solverMemory.get(), tstop); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetStopTime"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetStopTime"); } -void IDASolver::turnOffRootFinding() { +void IDASolver::turnOffRootFinding() const { int status = IDARootInit(solverMemory.get(), 0, nullptr); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDARootInit"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDARootInit"); } -int IDASolver::nplist() const { +const Model *IDASolver::getModel() const { if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto IDA_mem = (IDAMem) solverMemory.get(); - return IDA_mem->ida_Ns; + throw AmiException( + "Solver has not been allocated, information is not available"); + auto ida_mem = static_cast(solverMemory.get()); + return static_cast(ida_mem->ida_user_data); } -int IDASolver::nx() const { - if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto IDA_mem = (IDAMem) solverMemory.get(); - return NV_LENGTH_S(IDA_mem->ida_yy0); +void IDASolver::setLinearSolver() const { + int status = IDASetLinearSolver(solverMemory.get(), linearSolver->get(), + linearSolver->getMatrix()); + if (status != IDA_SUCCESS) + throw IDAException(status, "setLinearSolver"); } -const Model *IDASolver::getModel() const { - if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto ida_mem = (IDAMem) solverMemory.get(); - return static_cast(ida_mem->ida_user_data); +void IDASolver::setLinearSolverB(const int which) const { + int status = + IDASetLinearSolverB(solverMemoryB[which].get(), which, + linearSolverB->get(), linearSolverB->getMatrix()); + if (status != IDA_SUCCESS) + throw IDAException(status, "setLinearSolverB"); } -bool IDASolver::getMallocDone() const { - if (!solverMemory) - return false; - auto ida_mem = (IDAMem) solverMemory.get(); - return ida_mem->ida_MallocDone; +void IDASolver::setNonLinearSolver() const { + int status = + IDASetNonlinearSolver(solverMemory.get(), nonLinearSolver->get()); + if (status != IDA_SUCCESS) + throw CvodeException(status, "CVodeSetNonlinearSolver"); } -bool IDASolver::getAdjMallocDone() const { - if (!solverMemory) - return false; - auto ida_mem = (IDAMem) solverMemory.get(); - return ida_mem->ida_adjMallocDone; +void IDASolver::setNonLinearSolverSens() const { + if (getSensitivityOrder() < SensitivityOrder::first) + return; + if (getSensitivityMethod() != SensitivityMethod::forward) + return; + + int status = IDA_SUCCESS; + + switch (ism) { + case InternalSensitivityMethod::staggered: + status = IDASetNonlinearSolverSensStg(solverMemory.get(), + nonLinearSolverSens->get()); + break; + case InternalSensitivityMethod::simultaneous: + status = IDASetNonlinearSolverSensSim(solverMemory.get(), + nonLinearSolverSens->get()); + break; + case InternalSensitivityMethod::staggered1: + default: + throw AmiException( + "Unsupported internal sensitivity method selected: %d", ism); + } + + if (status != IDA_SUCCESS) + throw CvodeException(status, "CVodeSolver::setNonLinearSolverSens"); +} + +void IDASolver::setNonLinearSolverB(int which) const { + int status = IDASetNonlinearSolverB(solverMemory.get(), which, + nonLinearSolverB->get()); + if (status != IDA_SUCCESS) + throw CvodeException(status, "CVodeSetNonlinearSolverB"); } -/** Jacobian of xdot with respect to states x +/** + * @brief Jacobian of xdot with respect to states x * @param N number of state variables * @param t timepoint * @param cj scaling factor, inverse of the step size @@ -470,15 +725,17 @@ bool IDASolver::getAdjMallocDone() const { * @param tmp3 temporary storage vector * @return status flag indicating successful execution **/ -int IDASolver::fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xdot, SUNMatrix J, void *user_data, N_Vector /*tmp1*/, - N_Vector /*tmp2*/, N_Vector /*tmp3*/) { +int fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xdot, SUNMatrix J, void *user_data, + N_Vector /*tmp1*/, N_Vector /*tmp2*/, N_Vector /*tmp3*/) { + auto model = static_cast(user_data); model->fJ(t, cj, x, dx, xdot, J); - return model->checkFinite(SM_ROWS_D(J), SM_DATA_D(J), "Jacobian"); + return model->checkFinite(gsl::make_span(J), "Jacobian"); } -/** Jacobian of xBdot with respect to adjoint state xB +/** + * @brief Jacobian of xBdot with respect to adjoint state xB * @param NeqBdot number of adjoint state variables * @param t timepoint * @param cj scaling factor, inverse of the step size @@ -494,16 +751,18 @@ int IDASolver::fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, * @param tmp3B temporary storage vector * @return status flag indicating successful execution **/ -int IDASolver::fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, SUNMatrix JB, - void *user_data, N_Vector /*tmp1B*/, N_Vector /*tmp2B*/, - N_Vector /*tmp3B*/) { +int fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, SUNMatrix JB, + void *user_data, N_Vector /*tmp1B*/, N_Vector /*tmp2B*/, + N_Vector /*tmp3B*/) { + auto model = static_cast(user_data); model->fJB(t, cj, x, dx, xB, dxB, JB); - return model->checkFinite(SM_ROWS_D(JB), SM_DATA_D(JB), "Jacobian"); + return model->checkFinite(gsl::make_span(JB), "Jacobian"); } -/** J in sparse form (for sparse solvers from the SuiteSparse Package) +/** + * @brief J in sparse form (for sparse solvers from the SuiteSparse Package) * @param t timepoint * @param cj scalar in Jacobian (inverse stepsize) * @param x Vector with the states @@ -516,70 +775,17 @@ int IDASolver::fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, * @param tmp3 temporary storage vector * @return status flag indicating successful execution */ -int IDASolver::fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector /*xdot*/, SUNMatrix J, void *user_data, - N_Vector /*tmp1*/, N_Vector /*tmp2*/, N_Vector /*tmp3*/) { +int fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector /*xdot*/, SUNMatrix J, void *user_data, + N_Vector /*tmp1*/, N_Vector /*tmp2*/, + N_Vector /*tmp3*/) { auto model = static_cast(user_data); model->fJSparse(t, cj, x, dx, J); - return model->checkFinite(SM_NNZ_S(J), SM_DATA_S(J), "Jacobian"); -} - -void IDASolver::setLinearSolver() -{ - int status = IDASetLinearSolver(solverMemory.get(), linearSolver->get(), linearSolver->getMatrix()); - if(status != IDA_SUCCESS) - throw IDAException(status, "setLinearSolver"); - -} - -void IDASolver::setLinearSolverB(int which) -{ - int status = IDASetLinearSolverB(solverMemoryB[which].get(), which, linearSolverB->get(), linearSolverB->getMatrix()); - if(status != IDA_SUCCESS) - throw IDAException(status, "setLinearSolverB"); - -} - -void IDASolver::setNonLinearSolver() -{ - int status = IDASetNonlinearSolver(solverMemory.get(), nonLinearSolver->get()); - if(status != IDA_SUCCESS) - throw CvodeException(status,"CVodeSetNonlinearSolver"); -} - -void IDASolver::setNonLinearSolverSens() -{ - if(getSensitivityOrder() < SensitivityOrder::first) - return; - if(getSensitivityMethod() != SensitivityMethod::forward) - return; - - int status = IDA_SUCCESS; - - switch (ism) { - case InternalSensitivityMethod::staggered: - status = IDASetNonlinearSolverSensStg(solverMemory.get(), nonLinearSolverSens->get()); - break; - case InternalSensitivityMethod::simultaneous: - status = IDASetNonlinearSolverSensSim(solverMemory.get(), nonLinearSolverSens->get()); - break; - case InternalSensitivityMethod::staggered1: - default: - throw AmiException("Unsupported internal sensitivity method selected: %d", ism); - } - - if(status != IDA_SUCCESS) - throw CvodeException(status,"CVodeSolver::setNonLinearSolverSens"); + return model->checkFinite(gsl::make_span(J), "Jacobian"); } -void IDASolver::setNonLinearSolverB(int which) -{ - int status = IDASetNonlinearSolverB(solverMemory.get(), which, nonLinearSolverB->get()); - if(status != IDA_SUCCESS) - throw CvodeException(status,"CVodeSetNonlinearSolverB"); -} - -/** JB in sparse form (for sparse solvers from the SuiteSparse Package) +/** + * @brief JB in sparse form (for sparse solvers from the SuiteSparse Package) * @param t timepoint * @param cj scalar in Jacobian * @param x Vector with the states @@ -594,16 +800,17 @@ void IDASolver::setNonLinearSolverB(int which) * @param tmp3B temporary storage vector * @return status flag indicating successful execution */ -int IDASolver::fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, - SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, - N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { +int fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, + SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, + N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { auto model = static_cast(user_data); model->fJSparseB(t, cj, x, dx, xB, dxB, JB); - return model->checkFinite(SM_NNZ_S(JB), SM_DATA_S(JB), "Jacobian"); + return model->checkFinite(gsl::make_span(JB), "Jacobian"); } -/** J in banded form (for banded solvers) +/** + * @brief J in banded form (for banded solvers) * @param N number of states * @param mupper upper matrix bandwidth * @param mlower lower matrix bandwidth @@ -619,13 +826,14 @@ int IDASolver::fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, * @param tmp3 temporary storage vector * @return status flag indicating successful execution */ -int IDASolver::fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, +int fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xdot, SUNMatrix J, void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) { return fJ(t, cj, x, dx, xdot, J, user_data, tmp1, tmp2, tmp3); } -/** JB in banded form (for banded solvers) +/** + * @brief JB in banded form (for banded solvers) * @param NeqBdot number of states * @param mupper upper matrix bandwidth * @param mlower lower matrix bandwidth @@ -643,157 +851,175 @@ int IDASolver::fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, * @param tmp3B temporary storage vector * @return status flag indicating successful execution */ -int IDASolver::fJBandB(realtype t, realtype cj, N_Vector x, N_Vector dx, +int fJBandB(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, N_Vector tmp2B, N_Vector tmp3B) { return fJB(t, cj, x, dx, xB, dxB, xBdot, JB, user_data, tmp1B, tmp2B, tmp3B); } - /** Matrix vector product of J with a vector v (for iterative solvers) - * @param t timepoint @type realtype - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - * @param v Vector with which the Jacobian is multiplied - * @param Jv Vector to which the Jacobian vector product will be - *written - * @param user_data object with user input @type Model_DAE - * @param tmp1 temporary storage vector - * @param tmp2 temporary storage vector - * @return status flag indicating successful execution - **/ - int IDASolver::fJv(realtype t, N_Vector x, N_Vector dx, N_Vector /*xdot*/, N_Vector v, N_Vector Jv, - realtype cj, void *user_data, N_Vector /*tmp1*/, N_Vector /*tmp2*/) { - auto model = static_cast(user_data); - model->fJv(t, x, dx, v, Jv, cj); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(Jv),"Jacobian"); - } - /** Matrix vector product of JB with a vector v (for iterative solvers) - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param xBdot Vector with the adjoint right hand side - * @param vB Vector with which the Jacobian is multiplied - * @param JvB Vector to which the Jacobian vector product will be - *written - * @param cj scalar in Jacobian (inverse stepsize) - * @param user_data object with user input @type Model_DAE - * @param tmpB1 temporary storage vector - * @param tmpB2 temporary storage vector - * @return status flag indicating successful execution - **/ - int IDASolver::fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, - N_Vector vB, N_Vector JvB, realtype cj, void *user_data, - N_Vector /*tmpB1*/, N_Vector /*tmpB2*/) { - auto model = static_cast(user_data); - model->fJvB(t, x, dx, xB, dxB, vB, JvB, cj); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(JvB),"Jacobian"); - } +/** + * @brief Matrix vector product of J with a vector v (for iterative solvers) + * @param t timepoint @type realtype + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + * @param v Vector with which the Jacobian is multiplied + * @param Jv Vector to which the Jacobian vector product will be written + * @param user_data object with user input @type Model_DAE + * @param tmp1 temporary storage vector + * @param tmp2 temporary storage vector + * @return status flag indicating successful execution + **/ +int fJv(realtype t, N_Vector x, N_Vector dx, N_Vector /*xdot*/, + N_Vector v, N_Vector Jv, realtype cj, void *user_data, + N_Vector /*tmp1*/, N_Vector /*tmp2*/) { - /** Event trigger function for events - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param root array with root function values - * @param user_data object with user input @type Model_DAE - * @return status flag indicating successful execution - */ - int IDASolver::froot(realtype t, N_Vector x, N_Vector dx, realtype *root, + auto model = static_cast(user_data); + model->fJv(t, x, dx, v, Jv, cj); + return model->checkFinite(gsl::make_span(Jv), "Jacobian"); +} + +/** + * @brief Matrix vector product of JB with a vector v (for iterative solvers) + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param xBdot Vector with the adjoint right hand side + * @param vB Vector with which the Jacobian is multiplied + * @param JvB Vector to which the Jacobian vector product will be written + * @param cj scalar in Jacobian (inverse stepsize) + * @param user_data object with user input @type Model_DAE + * @param tmpB1 temporary storage vector + * @param tmpB2 temporary storage vector + * @return status flag indicating successful execution + **/ +int fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector /*xBdot*/, N_Vector vB, N_Vector JvB, + realtype cj, void *user_data, N_Vector /*tmpB1*/, + N_Vector /*tmpB2*/) { + + auto model = static_cast(user_data); + model->fJvB(t, x, dx, xB, dxB, vB, JvB, cj); + return model->checkFinite(gsl::make_span(JvB), "Jacobian"); +} + +/** + * @brief Event trigger function for events + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param root array with root function values + * @param user_data object with user input @type Model_DAE + * @return status flag indicating successful execution + */ +int froot(realtype t, N_Vector x, N_Vector dx, realtype *root, void *user_data) { - auto model = static_cast(user_data); - model->froot(t,x,dx,root); - return model->checkFinite(model->ne,root,"root function"); - } + auto model = static_cast(user_data); + model->froot(t, x, dx, gsl::make_span(root, model->ne)); + return model->checkFinite(gsl::make_span(root, model->ne), + "root function"); +} - /** residual function of the DAE - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - * @param user_data object with user input @type Model_DAE - * @return status flag indicating successful execution - */ - int IDASolver::fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, - void *user_data) { - auto model = static_cast(user_data); +/** + * @brief Residual function of the DAE + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + * @param user_data object with user input @type Model_DAE + * @return status flag indicating successful execution + */ +int fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, + void *user_data) { + auto model = static_cast(user_data); - if (t > 1e200 && !amici::checkFinite(model->nx_solver, - N_VGetArrayPointer(x), "fxdot")) - return AMICI_UNRECOVERABLE_ERROR; + if (t > 1e200 && !amici::checkFinite(gsl::make_span(x), "fxdot")) { /* when t is large (typically ~1e300), CVODES may pass all NaN x to fxdot from which we typically cannot recover. To save time on normal execution, we do not always want to check finiteness of x, but only do so when t is large and we expect problems. */ - - model->fxdot(t, x, dx, xdot); - return model->checkFinite(model->nx_solver, N_VGetArrayPointer(xdot), - "fxdot"); + return AMICI_UNRECOVERABLE_ERROR; } - /** Right hand side of differential equation for adjoint state xB - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param xBdot Vector with the adjoint right hand side - * @param user_data object with user input @type Model_DAE - * @return status flag indicating successful execution - */ - int IDASolver::fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + model->fxdot(t, x, dx, xdot); + return model->checkFinite(gsl::make_span(xdot), "fxdot"); +} + +/** + * @brief Right hand side of differential equation for adjoint state xB + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param xBdot Vector with the adjoint right hand side + * @param user_data object with user input @type Model_DAE + * @return status flag indicating successful execution + */ +int fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector xBdot, void *user_data) { - auto model = static_cast(user_data); - model->fxBdot(t, x, dx, xB, dxB, xBdot); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(xBdot),"xBdot"); - } - /** Right hand side of integral equation for quadrature states qB - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param qBdot Vector with the adjoint quadrature right hand side - * @param user_data pointer to temp data object @type Model_DAE - * @return status flag indicating successful execution - */ - int IDASolver::fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector qBdot, - void *user_data) { - auto model = static_cast(user_data); - model->fqBdot(t, x, dx, xB, dxB, qBdot); - return model->checkFinite(model->nJ*model->nplist(),N_VGetArrayPointer(qBdot),"qBdot"); - } + auto model = static_cast(user_data); + model->fxBdot(t, x, dx, xB, dxB, xBdot); + return model->checkFinite(gsl::make_span(xBdot), "xBdot"); +} - /** Right hand side of differential equation for state sensitivities sx - * @param Ns number of parameters - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - * @param sx Vector with the state sensitivities - * @param sdx Vector with the derivative state sensitivities - * @param sxdot Vector with the sensitivity right hand side - * @param user_data object with user input @type Model_DAE - * @param tmp1 temporary storage vector - * @param tmp2 temporary storage vector - * @param tmp3 temporary storage vector - * @return status flag indicating successful execution - */ - int IDASolver::fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector dx, N_Vector /*xdot*/, - N_Vector *sx, N_Vector *sdx, N_Vector *sxdot, void *user_data, - N_Vector /*tmp1*/, N_Vector /*tmp2*/, N_Vector /*tmp3*/) { - auto model = static_cast(user_data); - for(int ip = 0; ip < model->nplist(); ip++){ - model->fsxdot(t, x, dx, ip, sx[ip], sdx[ip], sxdot[ip]); - if(model->checkFinite(model->nx_solver,N_VGetArrayPointer(sxdot[ip]),"sxdot") != AMICI_SUCCESS) - return AMICI_RECOVERABLE_ERROR; - } - return AMICI_SUCCESS; +/** + * @brief Right hand side of integral equation for quadrature states qB + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param qBdot Vector with the adjoint quadrature right hand side + * @param user_data pointer to temp data object @type Model_DAE + * @return status flag indicating successful execution + */ +int fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector qBdot, void *user_data) { + + auto model = static_cast(user_data); + model->fqBdot(t, x, dx, xB, dxB, qBdot); + return model->checkFinite(gsl::make_span(qBdot), "qBdot"); + +} + +/** + * @brief Right hand side of differential equation for state sensitivities sx + * @param Ns number of parameters + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + * @param sx Vector with the state sensitivities + * @param sdx Vector with the derivative state sensitivities + * @param sxdot Vector with the sensitivity right hand side + * @param user_data object with user input @type Model_DAE + * @param tmp1 temporary storage vector + * @param tmp2 temporary storage vector + * @param tmp3 temporary storage vector + * @return status flag indicating successful execution + */ +int fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector dx, + N_Vector /*xdot*/, N_Vector *sx, N_Vector *sdx, + N_Vector *sxdot, void *user_data, N_Vector /*tmp1*/, + N_Vector /*tmp2*/, N_Vector /*tmp3*/) { + + auto model = static_cast(user_data); + + for (int ip = 0; ip < model->nplist(); ip++) { + model->fsxdot(t, x, dx, ip, sx[ip], sdx[ip], sxdot[ip]); + if (model->checkFinite(gsl::make_span(sxdot[ip]), "sxdot") + != AMICI_SUCCESS) + return AMICI_RECOVERABLE_ERROR; } + return AMICI_SUCCESS; +} + } // namespace amici diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index ae006e60d2..335d26f0e5 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -16,6 +16,14 @@ #include namespace amici { + +SteadystateProblem::SteadystateProblem(const Solver *solver): + t(solver->gett()), delta(solver->nx()), ewt(solver->nx()), + rel_x_newton(solver->nx()), x_newton(solver->nx()), + x(solver->getState(solver->gett())), x_old(solver->nx()), + dx(solver->nx()), xdot(solver->nx()), xdot_old(solver->nx()), + sx(solver->getStateSensitivity(solver->gett())), + sdx(solver->nx(), solver->nplist()) {} void SteadystateProblem::workSteadyStateProblem(ReturnData *rdata, Solver *solver, Model *model, @@ -38,7 +46,7 @@ void SteadystateProblem::workSteadyStateProblem(ReturnData *rdata, starttime = clock(); auto newtonSolver = NewtonSolver::getSolver( - t, x, solver->getLinearSolver(), model, rdata, + &t, &x, solver->getLinearSolver(), model, rdata, solver->getNewtonMaxLinearSteps(), solver->getNewtonMaxSteps(), solver->getAbsoluteTolerance(), solver->getRelativeTolerance()); @@ -51,9 +59,9 @@ void SteadystateProblem::workSteadyStateProblem(ReturnData *rdata, try { /* Newton solver did not work, so try a simulation */ if (it < 1) /* No previous time point computed, set t = t0 */ - *t = model->t0(); + t = model->t0(); else /* Carry on simulating from last point */ - *t = model->t(it - 1); + t = model->t(it - 1); if (it < 0) { /* Preequilibration? -> Create a new CVode object for sim */ auto newtonSimSolver = @@ -111,16 +119,18 @@ bool SteadystateProblem::checkConvergence( const Solver *solver, Model *model ) { - model->fxdot(*t, x, &dx, &xdot); - wrms = getWrmsNorm(*x, xdot, solver->getAbsoluteToleranceSteadyState(), solver->getRelativeToleranceSteadyState()); + model->fxdot(t, x, dx, xdot); + wrms = getWrmsNorm(x, xdot, solver->getAbsoluteToleranceSteadyState(), solver->getRelativeToleranceSteadyState()); bool converged = wrms < RCONST(1.0); if (solver->getSensitivityOrder()>SensitivityOrder::none && solver->getSensitivityMethod() == SensitivityMethod::forward) { for (int ip = 0; ip < model->nplist(); ++ip) { if (converged) { - solver->getSens(t, sx); - model->fsxdot(*t, x, &dx, ip, &(*sx)[ip], &dx, &xdot); - wrms = getWrmsNorm(*x, xdot, solver->getAbsoluteToleranceSteadyStateSensi(), solver->getRelativeToleranceSteadyStateSensi()); + sx = solver->getStateSensitivity(t); + model->fsxdot(t, x, dx, ip, sx[ip], dx, xdot); + wrms = getWrmsNorm(x, xdot, + solver->getAbsoluteToleranceSteadyStateSensi(), + solver->getRelativeToleranceSteadyStateSensi()); converged = wrms < RCONST(1.0); } } @@ -143,12 +153,12 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, /* initialize output of linear solver for Newton step */ delta.reset(); - model->fxdot(*t, x, &dx, &xdot); + model->fxdot(t, x, dx,xdot); /* Check for relative error, but make sure not to divide by 0! Ensure positivity of the state */ - x_newton = *x; - x_old = *x; + x_newton = x; + x_old = x; xdot_old = xdot; //rdata->newton_numsteps[newton_try - 1] = 0.0; @@ -162,7 +172,7 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, delta = xdot; newtonSolver->getStep(steadystate_try == NewtonStatus::newt ? 1 : 2, - i_newtonstep, &delta); + i_newtonstep, delta); } catch (NewtonFailure const &ex) { rdata->newton_numsteps.at(steadystate_try == NewtonStatus::newt ? 0 @@ -178,17 +188,18 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, } /* Try a full, undamped Newton step */ - N_VLinearSum(1.0, x_old.getNVector(), gamma, delta.getNVector(), x->getNVector()); + N_VLinearSum(1.0, x_old.getNVector(), gamma, delta.getNVector(), + x.getNVector()); /* Compute new xdot and residuals */ - model->fxdot(*t, x, &dx, &xdot); + model->fxdot(t, x, dx, xdot); realtype wrms_tmp = getWrmsNorm(x_newton, xdot, newtonSolver->atol, newtonSolver->rtol); if (wrms_tmp < wrms) { /* If new residuals are smaller than old ones, update state */ wrms = wrms_tmp; - x_old = *x; + x_old = x; xdot_old = xdot; /* New linear solve due to new state */ compNewStep = TRUE; @@ -198,8 +209,8 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, if (converged) { /* Ensure positivity of the found state */ for (ix = 0; ix < model->nx_solver; ix++) { - if ((*x)[ix] < 0.0) { - (*x)[ix] = 0.0; + if (x[ix] < 0.0) { + x[ix] = 0.0; converged = FALSE; } } @@ -228,9 +239,10 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, /* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */ -void SteadystateProblem::writeNewtonOutput(ReturnData *rdata,const Model *model, - NewtonStatus newton_status, - double run_time, int it) +void SteadystateProblem::writeNewtonOutput(ReturnData *rdata, + const Model *model, + const NewtonStatus newton_status, + const double run_time, const int it) { /* Get cpu time for Newton solve in seconds */ @@ -238,14 +250,14 @@ void SteadystateProblem::writeNewtonOutput(ReturnData *rdata,const Model *model, rdata->newton_status = static_cast(newton_status); rdata->wrms_steadystate = wrms; if (newton_status == NewtonStatus::newt_sim) { - rdata->t_steadystate = *t; + rdata->t_steadystate = t; } /* Steady state was found: set t to t0 if preeq, otherwise to inf */ if (it == AMICI_PREEQUILIBRATE) { - *t = model->t0(); + t = model->t0(); } else { - *t = INFINITY; + t = INFINITY; } } @@ -253,7 +265,8 @@ void SteadystateProblem::writeNewtonOutput(ReturnData *rdata,const Model *model, /* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */ -void SteadystateProblem::getSteadystateSimulation(ReturnData *rdata, Solver *solver, +void SteadystateProblem::getSteadystateSimulation(ReturnData *rdata, + Solver *solver, Model *model) { /* Loop over steps and check for convergence */ @@ -267,7 +280,8 @@ void SteadystateProblem::getSteadystateSimulation(ReturnData *rdata, Solver *sol multiplication with 10 ensures nonzero difference and should ensure stable computation value is not important for AMICI_ONE_STEP mode, only direction w.r.t. current t */ - solver->solve(std::max(*t,1.0) * 10, x, &dx, t, AMICI_ONE_STEP); + solver->step(std::max(t, 1.0) * 10); + solver->writeSolution(&t, x, dx, sx); /* Check for convergence */ converged = checkConvergence(solver, model); @@ -282,11 +296,11 @@ void SteadystateProblem::getSteadystateSimulation(ReturnData *rdata, Solver *sol rdata->newton_numsteps.at(static_cast(NewtonStatus::newt_sim) - 1) = steps_newton; if (solver->getSensitivityOrder()>SensitivityOrder::none) - solver->getSens(t, sx); + sx = solver->getStateSensitivity(t); } std::unique_ptr SteadystateProblem::createSteadystateSimSolver( - Solver *solver, Model *model) + const Solver *solver, Model *model) const { /* Create new CVode solver object */ @@ -307,9 +321,16 @@ std::unique_ptr SteadystateProblem::createSteadystateSimSolver( newton_solver->setSensitivityMethod(SensitivityMethod::none); // use x and sx as dummies for dx and sdx (they wont get touched in a CVodeSolver) - newton_solver->setup(x,x,sx,sx,model); + newton_solver->setup(model->t0(), model, x, x, sx, sx); return newton_solver; } + +void SteadystateProblem::writeSolution(realtype *t, AmiVector &x, + AmiVectorArray &sx) const { + *t = this->t; + x.copy(this->x); + sx.copy(this->sx); +} } // namespace amici diff --git a/src/sundials_linsol_wrapper.cpp b/src/sundials_linsol_wrapper.cpp index ea0b3ac8a2..84ace7e0a9 100644 --- a/src/sundials_linsol_wrapper.cpp +++ b/src/sundials_linsol_wrapper.cpp @@ -356,11 +356,11 @@ int SUNNonLinSolNewton::getSysFn(SUNNonlinSolSysFn *SysFn) const { return SUNNonlinSolGetSysFn_Newton(solver, SysFn); } -SUNNonLinSolFixedPoint::SUNNonLinSolFixedPoint(const N_Vector x, int m) +SUNNonLinSolFixedPoint::SUNNonLinSolFixedPoint(const_N_Vector x, int m) : SUNNonLinSolWrapper(SUNNonlinSol_FixedPoint(x, m)) { } -SUNNonLinSolFixedPoint::SUNNonLinSolFixedPoint(int count, const N_Vector x, int m) +SUNNonLinSolFixedPoint::SUNNonLinSolFixedPoint(int count, const_N_Vector x, int m) : SUNNonLinSolWrapper(SUNNonlinSol_FixedPointSens(count, x, m)) { } diff --git a/src/sundials_matrix_wrapper.cpp b/src/sundials_matrix_wrapper.cpp index 1f3667c754..6f5f7cb0e7 100644 --- a/src/sundials_matrix_wrapper.cpp +++ b/src/sundials_matrix_wrapper.cpp @@ -88,6 +88,8 @@ SUNMatrixWrapper::SUNMatrixWrapper(SUNMatrixWrapper &&other) noexcept { } SUNMatrixWrapper &SUNMatrixWrapper::operator=(const SUNMatrixWrapper &other) { + if(&other == this) + return *this; return *this = SUNMatrixWrapper(other); } @@ -151,8 +153,7 @@ sunindextype *SUNMatrixWrapper::indexptrs() const { int SUNMatrixWrapper::sparsetype() const { if (SUNMatGetID(matrix) == SUNMATRIX_SPARSE) return SM_SPARSETYPE_S(matrix); - else - throw std::domain_error("Function only available for sparse matrices"); + throw std::domain_error("Function only available for sparse matrices"); } void SUNMatrixWrapper::reset() { @@ -160,7 +161,7 @@ void SUNMatrixWrapper::reset() { SUNMatZero(matrix); } -void SUNMatrixWrapper::multiply(N_Vector c, const N_Vector b) const { +void SUNMatrixWrapper::multiply(N_Vector c, const_N_Vector b) const { multiply(gsl::make_span(NV_DATA_S(c), NV_LENGTH_S(c)), gsl::make_span(NV_DATA_S(b), NV_LENGTH_S(b))); } diff --git a/src/vector.cpp b/src/vector.cpp new file mode 100644 index 0000000000..6462219db6 --- /dev/null +++ b/src/vector.cpp @@ -0,0 +1,149 @@ +#include "amici/vector.h" + +namespace amici { + +AmiVector &AmiVector::operator=(AmiVector const &other) { + vec = other.vec; + synchroniseNVector(); + return *this; +} + +realtype *AmiVector::data() { return vec.data(); } + +const realtype *AmiVector::data() const { return vec.data(); } + +N_Vector AmiVector::getNVector() { return nvec; } + +const_N_Vector AmiVector::getNVector() const { return nvec; } + +std::vector const &AmiVector::getVector() { return vec; } + +int AmiVector::getLength() const { return static_cast(vec.size()); } + +void AmiVector::reset() { set(0.0); } + +void AmiVector::minus() { + for (auto & it : vec) + it = -it; +} + +void AmiVector::set(realtype val) { std::fill(vec.begin(), vec.end(), val); } + +realtype &AmiVector::operator[](int pos) { + return vec.at(static_cast(pos)); +} + +realtype &AmiVector::at(int pos) { + return vec.at(static_cast(pos)); +} + +const realtype &AmiVector::at(int pos) const { + return vec.at(static_cast(pos)); +} + +void AmiVector::copy(const AmiVector &other) { + if(getLength() != other.getLength()) + throw AmiException("Dimension of AmiVector (%i) does not " + "match input dimension (%i)", + getLength(), other.getLength()); + std::copy(other.vec.begin(), other.vec.end(), vec.begin()); + synchroniseNVector(); +} + +void AmiVector::synchroniseNVector() { + if (nvec) + N_VDestroy_Serial(nvec); + nvec = N_VMake_Serial(static_cast(vec.size()), vec.data()); +} + +AmiVector::~AmiVector() { N_VDestroy_Serial(nvec); } + +AmiVectorArray::AmiVectorArray(long int length_inner, long int length_outer) + : vec_array(length_outer, AmiVector(length_inner)) { + nvec_array.resize(length_outer); + for (int idx = 0; idx < length_outer; idx++) { + nvec_array.at(idx) = vec_array.at(idx).getNVector(); + } +} + +AmiVectorArray &AmiVectorArray::operator=(AmiVectorArray const &other) { + vec_array = other.vec_array; + nvec_array.resize(other.getLength()); + for (int idx = 0; idx < other.getLength(); idx++) { + nvec_array.at(idx) = vec_array.at(idx).getNVector(); + } + return *this; +} + +AmiVectorArray::AmiVectorArray(const AmiVectorArray &vaold) + : vec_array(vaold.vec_array) { + nvec_array.resize(vaold.getLength()); + for (int idx = 0; idx < vaold.getLength(); idx++) { + nvec_array.at(idx) = vec_array.at(idx).getNVector(); + } +} + +realtype *AmiVectorArray::data(int pos) { return vec_array.at(pos).data(); } + +const realtype *AmiVectorArray::data(int pos) const { + return vec_array.at(pos).data(); +} + +realtype &AmiVectorArray::at(int ipos, int jpos) { + return vec_array.at(jpos).at(ipos); +} + +const realtype &AmiVectorArray::at(int ipos, int jpos) const { + return vec_array.at(jpos).at(ipos); +} + +N_Vector *AmiVectorArray::getNVectorArray() { return nvec_array.data(); } + +N_Vector AmiVectorArray::getNVector(int pos) { return nvec_array.at(pos); } + +AmiVector &AmiVectorArray::operator[](int pos) { return vec_array.at(pos); } + +const AmiVector &AmiVectorArray::operator[](int pos) const { + return vec_array.at(pos); +} + +int AmiVectorArray::getLength() const { + return static_cast(vec_array.size()); +} + +void AmiVectorArray::reset() { + for (auto &v : vec_array) + v.reset(); +} + +void AmiVectorArray::flatten_to_vector(std::vector &vec) const { + int n_outer = vec_array.size(); + if (n_outer == 0) + return; // nothing to do ... + int n_inner = vec_array.at(0).getLength(); + + if (static_cast(vec.size()) != n_inner * n_outer) { + throw AmiException("Dimension of AmiVectorArray (%ix%i) does not " + "match target vector dimension (%u)", + n_inner, n_outer, vec.size()); + } + + for (int outer = 0; outer < n_outer; ++outer) { + for (int inner = 0; inner < n_inner; ++inner) + vec.at(inner + outer * n_inner) = this->at(inner, outer); + } +} + +void AmiVectorArray::copy(const AmiVectorArray &other) { + if (getLength() != other.getLength()) + throw AmiException("Dimension of AmiVectorArray (%i) does not " + "match input dimension (%i)", + getLength(), other.getLength()); + + for (int iv = 0; iv < getLength(); ++iv) { + vec_array.at(iv).copy(other.vec_array.at(iv)); + nvec_array[iv] = vec_array.at(iv).getNVector(); + } +} + +} // namespace amici diff --git a/tests/cpputest/expectedResults.h5 b/tests/cpputest/expectedResults.h5 index 87cbc6e87a2c63e286ce624e32e0c9b5a00cffe2..673b46ab19c18edd13d98d82fb02568962a4bae9 100644 GIT binary patch delta 289293 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

>s-VA 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�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 delta 285561 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 z z-FAzA2^7vvIiHn5f|Sf{7oPQzAyV`ChIpoN*eI{d>x;kn#A0%B{Mc$ICUL4;J>MJ! z*m1g_xkd=_4A*N`-ry6JQj>DZ8}VB+kMjho>GY2Q;1MW^l$hwmR@M4{nVm<1=DW_E zD%?nLR`^NB%QiCXkS!dsTQd$9cCrhruMU9M%whVqU7gtDnAD}dzxa9Sn~Cq45Pxsq zr%i+=GF)t}S1h3$2S>t@PilJ^iO{${pM9aC6Z^1PEzl}|O|KjI*mM88{ApUg60SB%cGQ1i! zqEp3h&k3624~4YZi6E?Sc9u1|6Z5{OZk76%Ap3i4+4ukw$SLik6ygsQFSt4{SP^eBNKYj#$0VnHI!H?gs~xVls5jJLun^rxKv2hwUvt zFA@541g1{8bz-erDs1t830C$u9;v8A0&{tB_5OS^c$~d5{rDFZBJ8}n9QlZlr?4d= z@ETs8aqFgIcmCo#T1sRT^AK+`=HqY@f1>(z&*99#9|S6-*gV?|LPU@vu_kpGbz`!k9k?X{M7f_gV{t}dNZdGv$ zK?0R)J2W)#hsw`H9Q4)0XSss$=NV7Q;IHpR-uSv!uoDwXJ7OC5*RSBouS+K=zKE{~ z;H_%s`%x2u4S91$uKcQ8I;>%>OZGFD9e^)p-)5}Y=^iv-s`mq++S zk-<>o=(a9=o=-?-c%zFF5zLO#UFZMZflZCyjpYA}x0G#_sJw;v39?30UA-fxc@HrCbt)D2@)O>22nm>?FFwT{ks-b(Qr&8h3NtHTSH!f4 z;9P!gW-O%x8+|YQvhJ^I!R_4F_X5-q|MBJmJA)6tdQRZ{@oKUkU%3o?oi>GkI^|=8 zdU$bU2jc*_~{Ff737Q;!WRL_L|(q*Z<}B`&hN&)8*8vm);i*iI92WO#F-69a!p* z0Xn}^C_pf&yMoM%1V<8L?~gf?!S3jl73B_m;Zi)UkohDLb{QX-e{r<~Gdo`TZ2Sb` zYff1|JkN&sLU`I@ZHKS_AMDVnf7FUkm3p*2jy_F<2Sx##KNxjjMMh6dRsI@KxF!%i z+LuRY$AC}tMTTB$d<$ZW$J?5yAjlH9ywj8j2P;1OF4643q;?Lyv-xX4Q>k{P_t-4r zJ3k2SQNMz(|0`TJvF5>7JeFTr@}-_5LYqAE^KtnO{0j!}^ZouBQe6)ljjA0%0{?>7 z!GRaZ;GFJcuJ?tC&ybEyieDtcQ5EqnG2ssE(d$KOpaJTE*9^P^x}AvcJLIL+NHE3M zZm}Dkw`%YO2iZ>^k1rEpYruoDYwR7^+4HCG-q%NhVo8Uj@;W5Q-KKeSjS+qf47yDP zR8ql8^xQDzDiKJd@w$17?bwa%t63(xh>v}s&Z`ZGKl)_TUV@|{{(4MeWyY&AD$vJy z@$Iw5zw5JJ?hGHb9phQuImq|ds5pqlOTHol3Ai_~)$P?MLplA0y)ng9FiLJxymylb zMn%b!zMbvZKEijMY7G=%FrIIn{s+YG^!`maqOM5>hdj0Cukdxdo%IuYvh46PSyt83 znyPlJUgWs+MGOf7I-@>+3I9vL25+y9t4 z?|3Y`H-JA;Mn*;`B_WkWBqZv#l3iJaNFpl|LZZh=WMq|$Y!afBmEzbld(UUt*^vhM zoqq3I=k=%0=leb9xyL#8eeUbJjujFNEo|TICNK$(i9XaHgx`L?1Nk{_F_ujNk#|> z?gwo9aZ2j10Cz`?7|>4-^Zfd(x$ncz_pV9dMNfj^5wsLi$rJ9y_6C$U?fK98z<$R! z^_@b0csvf5rs5Z(O%3m3E=|Cg06)83czwp;F-%5(!$!{K?!^e_00j1fd2^5l6yDSa;{H$J}OB`^8`-iF%?=~@5f zVf#4U%-(BkUnhWZR{6K>(KDd^4U2bC6cJFz*B|Zv)`J-)KJd`liv+uujF(=*I`%p5eJOL>L3oIT)Ng6+;C6kA<{F`+6|BVOGxD|5;?ZvEeGa@cAFXZ=PCi z1@w?6K*iaw`Le+?;Co>d-Z37|hHU{w4s|`)a0OO5&Wbt^$y;j?P53|m|MqeEnzhF_ z1}A_bb2U$Bzzn!T{_=G;fe7xk_k8_O*n@}eJy_CXon3BU5&vZ*aP;F-3R?g7Hc0t4{$>Ky&(yTOfT39S^c`t4IdCYw z+)w{Zrw7|5#x#6q9tn!8CiUGPp$?p*vtlgCngG2Y9#6l!IRoezY+ZNd5y3%$C%mT> zd$1$GYEo|k@k!UzCZxPz{?G3V==UEh>vy;NsS}_n-|}Ix^$d`-_B|>4h6r|WoC6Eu zJy_5F;H;gaNFWkb7yr!>b>Q39(l;7qtuHIbaZ2zle>QI&PIaqkwW+SLx^{E_(A{1VFEnk z$$j)m0}f=tC$!hgh@klbFN;nM(QHSbMkNe!?>(&|f~e>bV2)H*pTq3(n|0mR?!uG zR7nJb`g6{fDcx9dZIoclW5f>);H7?Dp+IzI%nWcIT2I5Oi6HRy zA-kxEZcO6mmkmjOB$!IFe=ZNxZ~mMDFA&ElbYTM6-?ebSpPB(0ndf{y*APLPhv0)2 zA9w^8s~O+lL;N{>y3F9A&q%PVtuNnR6()&1pgqnhH3NFc`#M7Eh=AM}TgdLzjfLFy zF5q)Q0)kRUCEI`MhVRDoFTa$Z0Q}86w9>_AK#hofqCf+DBpW>a)zZ8hWBQ(H;Ao9_ zeC4ULCR@5t2gpHAyaOj;j>qbiHX)H2VB@rR<#i*hjH1*jSEJL7IkT1NW|$(ujcqc(hat1?vT{pInvukspw)1^pI z6U-691G8LwEXP#$9GC$|%@i!MU}Y2i<)xx@iEb?1eR!x^{jUI*w_T-?0@HW?oPzz% zmbb+DCIB=2{i^}IGhoE<;Spys)8{^u?A&@8khsWb^H%u3vl2VZ%ylZ-1 zb~n6=o%Zbg%ne`7N}Am7wh;k^Hh8dLXE$~s{`qNdsXqc3h~$!@LWxHFS!QOT)2tIf zs7>y%3MU*#CJNVuU3W1Joem81GU|Nr)JB~?axhm4V6-Rg&mdcOx zLjK?OacQA42ART0kpJSWd};kSC}UdJVS`B~rU&S3goa>+BVD<$I?pbwF4e2Fy%}|& zsdKWcgBS6}qFM(m!8pMEEZ@IxVj4VHnd;>kCW5`%9iO}HyD&wr>blnzt!VzoF};ey z?L>lc8If&t#p9s6=7SnPX&Rhz-+7O2gb3=@ILqfvyRh%K?-zR%q7KN#Y9~n0A$}mA zeKR$09CWBZ9o`4ibav4hnoUE#?TNw$`-Lt{zU|Y9cy=qA|2_QOzuB&n;5qr{PbG3w zMC!#jc;!Ak$=(O^{Q`U=Dn{W&bSgYqS)mK_v-$d{ClPg^i~R1@i$%oGGgszTCX53i zLl%!Mozvi>meKCV1R^-TaW={Oco!xZ?Z|K=3XcR7&YP7JV@NF+{Bi7Atx zV44kECY25m&IP);i}eCs*!wcV8%hA;&5q8W79K$St;D^|RUzZxx((@OUHvqe={tIv zkwgTuW?UiM99>x6`P~iLcoYhSTjRG8a%n+=cn=3b=?CLL=8M2gLe(^2?0$8i3<_eG zhOFIbx-f@)!Ng2Q)PZYf=7TC>0@9x&*wVi3XZpQyAWxro$N}bx5lzRPEXeRKx?^~s zwAP8OA6CTU=Pi(+UhlRIdl3?R<7&=gaE5uiwyqM=CDY)|aD)~W1wK&xSgF>Z?!@+0 zlZyll5r1uOW3x>*;yH-!ns&D1ApAzvp6_p`fkn10h=zRDA#U-W!A?v-vH%};PU}Ac z_tzygiAb>eEq?Y3VZ;*sx)aNc^{>zpLj3U>9o*7A#5c>uIb@t02kUHA zj-?6HAn}Kfaxmm$ElleaQaZ6>d{n@9Q63~{5c9rl>WBoc9R61+uq2zY!f|)Qm}zj? zHClZM3M3S*?|zHy#J+mII*nsR{Eld8<8}+g*WX?za>L|v_7vgQOOeyCGSc`V?Fk|% z6J_8leb9;FMZ@=WRc=KBb;c0pbB0KuP4;5c6d4Ehwi*I{q0^wngJm)W3ii4Z3m&?5 zVxblao6g@*Nab0=9rr3N#0y_A`ssRL94LL|Au|O|1Bc*($XUpH^#mL>weG}Yp1B<~ zT(d}!*VCD(sE7on2aD?B_lyH_+rIA+zSDr_iJheABoSP3&BV(abz+8J8$@ylh%a}$ z5l}CMc=daa0!rD&L0?_TogH4&;Pmt6fg6xdn*N+2qKWUsjvF}?i*+G^jQ*hXMPVce zJe6$FML!N+;8ZLhx=jP6p&LQbP>{h-_e}6iC+04#Kap97_(t{eLIN-1=d|NACvfB7 z-gcIruQ2cTU}p2hGRWIH6{;V`i*;fZ1k-hvQY6T$3#4)0i3CHRKKp!G8v|7{=R3`A z!lYA+2d9RhAjbDyi6nm~7OV8})XiMP*D*dj#z2pFuy=&{CoF9E`nafV&5ddB`VQCm zMaY+4Xz0gl?d-(v#VwHDCnLdD4-u)C8!&a^-@hTjV1|wHF;KXpI*aexG&pdSHf|jX zbcHHX9cenT9-3D1A5n-GsCVO4Uq-yd-N~@;!((9P3TL9b@ig!#?hwRH5dlk@J)Sx7 zTL*SY%Ev||013$Ij}{jvkf6r1Sc=VJ@15zuEPn;o z>v|ymo~%jU^C83&qhlYFo5sLR@49~W3)3K#Zb)bj^7oJ6b+dPkbYN{OA0OG)&lb6+}f!_wMxEBh(zB=Dz+17z|Y}KCgFhhLgzA9>Jm_haD z2%5YWs4prR1KrPLe4>@7!F$^Fykf|Q;NJw9-e$cqkarQ^;iV;LmqiLRMZNJD~tD!;Px31cuA)kj@KVj6&V z688^5ftsii4Rve>W>fUkL*^*rZ?MJ3FvK9fHAB2n;K>+>ysPt8S7aJ~a|#cfm>}Xo zhtr`e4}v?eq*r$zF!Cb7{%Jqm!4M?48WVK%OVAk5b=6mJIW!FdFT02*z=7m?v^)dL z{SNF1Q*cB#E8=5it9bK#5U=Ijw^8OZ1`hf!Q-|?QgZ>FIKaB}IT(4(NVtC=$fu%@1 z8;sbB1c`EfTLN5>fYtP*?gRHRkZZ+O&^;2n?ELnqxaK+3O&nhb2AS- z30)aw@4&2*tisY>pbl&ez0|W+2=Ns9MOhEHO^0ZWBUjwg6tLgKXC+0$M?mX-sX6hNjt zvI3u`@PLpAcMyQj_3savC~ke#vQpZcbP1k5`VIH}(wf#ZV`j>1(6kaAJ#53GZE z0@t6U6u=h?l{6m{@mKAb)EQB35gEigDSaHgkcRjjOy+wOXDL9#Z1a>>1%3)_qk1|^ zhOb6)2l%zEliD%oA!ipOVI(+kH%lff6bZVU@|K#(6wnYY7|&Qb1(XyCA@qZ{BnF=0W@(o!v5w?uZvn+WksrfC5&%cWkZ4zncQC_SPYX;k%tG;cdhh|8}gw zo?bs`2NG;(Js-Vmjs!6hYbL#IuwXeGosVbk6tFtGe-(o-mky>$J?HMWW2*dSCyTcr zK3^i~*s><#c@MjCU#f%k!uvIN@FJO0fc#^z{XBfTJ!^sM$hg^#nYGGaX!wHi_4-}x z#P3KWLBB%MO7}+!oJQ%#`je-?!vjF*GJL&eay0j3GJ~t!%d?H0cI7Z6#@6!@9>K!ACuJ6?ncDF9=-DT%-ZPRyI`G1cIk97thjyy z&-wy{!WeJ2cMp`|3dOAYHNE@rd!NmA@|jN`kihz*oBu2c2~Jer*?1F80qSop{VxYh zfz=%=635^QN#o6;JB%h_@Q*mXJ%*fH&0Kd`9a6JFw*JRSj3Ee4CbkEzq@N z7CXxnZ~Gy^kd25fODGcb+%C+uaHN20PxCX6?Wcg=g-qTExJp*PHBv$KdmEO)m^!C< z2k{O0vmPsMh-b09+|_)8f(KepuHR;{g2^JKgy>6fosKGz_gXa!N>}?ie^0;)3B0U! z*iy`qU_gkvMa+Z(^s~}sADc}9+1dJ8&K4rL#JfxTVnZADFzGPo>Se^=S#gpb(L%h> zz9X-qbn&oABwuMJtr3jV&)q8A23IP5O<8^{y>7#d3e3hT)saA?y=R$p5(zG>^7yW( zQNTPnQsJo!uYtxB#@sH9kO3E|W$2}ugX9pu*4G!cB!KuW6b3V0ygUVbE4URl zrwP+-7?vI$flIAUxk37NFfrkL%wVaOC=&3!_`%1x0}1q6HVocKQ9ymp3a^pM6sQ*e zRNPVzV?e^}rGmz9_&O*#7>D`CwlwRz?g}lxjl!mbwnA?_P*M zkC)EWX81w|qg)Hoqg+!!)LQ4GUm1L}!Ry|Ufa`N+r}OoOJ|ID7xs$D(4HD!Cd@?tl zCIh+*z0@NxLRz78#%}Qg{B~S2lb!go71NI7llIC*yhu*wz?2^1@%;y+1+ocb@ae?O zCa3LFAfdD9jsg5=l>MQ7bx%YqHY`A3+?#|1ErC5cx8;%G5H3Dxt%nTvi00W{3OVXlhkN=mook?k45$e^&)Y9dg6NpzBd^}UwO*$h zQjJ6_w$p|!>YgPMRPKIMzhzk?ieVFMI;*;V?k(Go0^00kea=MR*Z3Y==P>SWcVR$S-KY>>qe)*I67=Kt0F;_q^(Ox9qNFO#UZ`?crvgbJ}yr+Gzp$^m8aXlPp{WwxZCc}w_vKNt2%ibI&vHgCg{M|mT;x=>2S_BzfJ|>lS6xIP?R-$i>e+7r)&n4YKeJz;znXLW- zVI+{tf9I`~h&qr>J;r}JkPIe^mBMvlaH`r|a(aC>+z-H1&*f!B3uamUjsFY};vXFe z&N~+LfBygN<5=t^r+n^{VY%J*1=qSs@T4SJeC{P&EDp3!?#pVy)E#eR)&8dphqZUb zw}Y;z1JBhQH)~zUz$H+nHyTU=^>&{7jG096(=be97hISanQsizz@ZKl5mU{C%zyL$ zw2#}BLw2QyRj+%lZqa&IG6_n?4n_%P5W!3K^VYVWE!bjW(sW1Xw^QR#HZ~Hj^ z6ngL&?&b8w>>dj_dlF=yT|0F)g$QKx#VQrgw_s!AZTRkPB#^UbF>pSJI#6<^Jf~fY z3}j4-Zmp!ja2%@-1dSvjD71Y;ZaCS3C1#ud(5pi{f6r$um2DXN(>_kAIa`cPnGCAs zMITTnzteRW7D3-P|go1T21hwy&|u_^;HPsGT;wpV3|D-4bU z*pR1eEPN`qaPh2K{e(?u^Lw_(A;HR(ypG#_r~?73f&<+L$>5h4)fJvVSc>86G5RCV z;BCk#DIZGsghj}@T+<3cyxd-Ojh1RW67bnqsBrMWQ@|>o%nOUsJm-whV0#K*uf1{^ zY#Kjdzgk{pHhLk!t+`7x5^oS+(o7E1XD5U2VpVnfJm3$|TQpjYBm#D~)(-u5pRj2= z8QZhB5HI9>?sW$KIT9SU%&L4!PX@k8vHjf6@EWLJ7w-%wf=A_%{<;aDu#S`yH5Fz^ z;CR(Jj{gzjzs<8v_iU2Dd%dtx_M7k;n0zRa^%(AyxN~#2dB7)3bMZ#ItPbK?-|@Y| zXWu~rw^ulJPFOx)FW?r5-f|M03)Hsu2!Ri!qWr!-w?APH&(38PDItN z;Q2|gr(Z0h*&n{zjr=^OEcOXA?p>umvZ>$yA4q`QKO7T&3Jyd~MPT|q?0_M? z&+~<5?EYGzaO^A+6rPN+h@T()D;)Sin=+J7g09eE9mQ z$TlQMX!7B-X+V50OA(t{A_>I4dA&_ka1v}DCJ*(%h|07swtbIYH)F{Kp6_>5{^jww zJNu+E%?go#>g?4mX-`O?qCB;lb|0+oWfVN?aEA!GuK#4vj%&tT7x^|q-~JWg0wYbX zTu(*(9J$MVB8UVoozxm0g@K7A!L`R!&hW)#N3DW@e>0X{et$1h`X3&T!%eAOmb@K? z1acfH503bd0ISgQrFT1EvdGUT&Ebx4GAa1-md>#mD=c#r4So7YfWws*yNCyQBK~~c zNwHgQBv{+A@r*y+B)IpeC4096{B&ErQ}>rqGbXy8p~f8WFAv+t`SvcH&$B^-)TX6} z1@>?k3@%)(-WC|$3aBI#?BI5pCjrM+m1Zm~=h}mCchrFv)77vM1H}K_PWR-;brP6q zD0blcF#(=8hB@uAA%e)LsQKOE%^22Evchir5C6M;oaRQ-B9}4}+|PS%u3$t0c}}zq zpO+^<1WTjclN&^^r4ud^?rp{ne06yfV~jcw8u;DOL>%#H%0(N27vNp6?dPj^pC{m{ zSU*f>K?Dp|cKLT`n=!|3?v{O8|L6bTK8{N2=Z?I6NU%9C@7SS20?#6&1n-VbfaQIT zZ(Xm!&u%9Oz8{vGux0AcS6`e#9eB|i`iY(q@vA}1wcMvkfL*U%Ms8#R7!6sl5KW1| z(APwUb+ie4n@;vRCia{Er+wTS`B8@B_W}5ifBqzfdTA^rNZ{y4pQUwJtMTDSb`9k# zM7Z!+*x=gOgzcsKC|k<+pAP&gjwMeZo;P1w{*4d`T*s&BeQ1S|m8!38(+uIK*M!Og zvjt7qqst4qD(sN|w|!jbPrS$J=qmjvD>Q`x!IPk>a}>uwtcFoFS?E8I?Q z!cJlDrngd5Exbc9ECZq{#3*6YW z_*#$Vf0f=#jQ3t6L0QF9{uo9QIMW~BK2-vP%5+oQQ!f$$tqe~{wp$aX*5F6GHd+7o zh^e#BT-}|3_-wr7B(RK+*E;zf&VGhMW*4(3z!CcNFbPc}h|tSdS?$B6z=t`S*W+fq$BNi_{Gy(C4|N zH3FB{3G<@{`7p51GO>I8jS3N@UcArMf3OLo62Q~zE3KxK;7Iyo^H3986V_UGlC+3VLW2FW)+~JrNO0M&*)agd{Rq5NTcQhv zMQ8nv;r1vH0SEa`Z1b;1?EQikcX$Ni4@Owm6dyzU)m*W_Q5gG`(v*^y5910`R+w5< z&%kB6jh$x0GmY47=9)UZxE~VGUq0`f#)AY)-Ok&j;oIz_+C{s|uxc&tqVm9PS$Gwz z?QvZ1ZN!RqS$^wrL3}brLN$)zKRktYIUK%MD?D!?!PpA#&gb9z|DKY5!nsScIdEz9y$mk-)&!ubkgPA1gl{%pzNc+pH)2Ai zN5l_aMtt|gjANER!Ac3DW(-gX4e=eG;a{e;hggl%VfC-))#wicOB9j5X9;kTcA zmj3A;5zJR=P3Nmm04urfTg-=uKz&k$-CwH_!>hX-2u@-_0s+p6@zBReAp7)Kg18fW z3f4EzR8yJ&d0Jw4wgWJXrq8LF=Tsx+_S#9qhzjxiyba02o`|PvH`9u-hHp}z$QcCL z2_QJq$I8YBKi@mlS=1hE#ACFkUk~nDsr%dG-Q~g;r0kHOQtwk4?kW+yF6#g6d}0FZ zf9Y_HX&(_3CHHsTV{gR1*q;*_7)Sj1Q!kg|3=u!~ZT+jQK74){Rr010fdg4e1c7=F z5gg!GV^YB38!^Fz2K$6QB*9kVEb~f&{rG&M#8;ZvuH&8ji*do{G|{tBW^RUtVje_4^-bA+&2L;A6q!acES*hy9@bi_<;tj zEtoKH>;n?09htG&E{FsMJQ_I#@U{s!m~U9X2{$IYu$kS$3Zvs?!)d1L8nCHINr7Ll z5P$D-eQ6v!;=c-D#s}d=n_wwoFTY~~jMdIAzGa3_)tr{!@Kf*LBy#aIxh)Y17Vc9m zNm3!f+{!^;;5qAS{|LJ3H~lOlaG zfDImUjjh&0-^M|(MU%MNHaM9yN;1jcYrsAzYd9LYAl@utvR}0o@gGD>h&Xy8;9?K4 zy0$b9UQ$W;^H33irwg8UtA}+1mOnffb?OEZR4Q0J+EIoCUv5)Neg8!OhM;$Oe0m(H zKKOcNXoCP~B!kSV^x-LRk#yd78Sz6a_N=5F#FH|DM^-))Kx)+k78~+7cvtkz#(9kZ zitrtFT)UMTFxwNMduY^=;BW$;Ls=XW{E%0yU+ISh12B>LnZa>j7|JS7y+iyoq)< zFXAV2E6k&;5N~lzBa=Cq0IZIhS6S7JgBtqa?Y<*0|NqneQ!E8oJ9OJ_Te+j0*K#6Q9bLZ2diG# zOQv0ffsKQ5>ccGfY695IR5ESk;6=BVy1fx@ zGAR`Go%-5G0@!Y|Y?_`>k0qZU&l+h)d{bczzd04+U%r#%D-$Jv;BV#1*fY4JingdL zJ_~LMahi@y%#|U5!`Y;R*-O2Dk4VFT{;HWh1aPsS_EJF@45?bDQJBdi zfFsGLSv~I7V@)baE>v$2AJ<1WvQ9+2%G)$09(n>e5jvxGIB*=;wm!i-3A`i#G2I6R zcdhEN?|gSn_oN~LRgAZyQ9BZFO)tLDgSA_>vR>wk_Z|nFF+;obQwZSt@gl(h{d(+l z-{aROpCEpMJxjL=AijiQnJa=a3T&yw#u)Fy*wQ|HhXOv10Keg$HcC~h$GU?Cos1qL zfuYe}Cdt=GaLeGGe`otBXf_Xy@wyH7^C&j^nG!_+w=#myHH+6{J@g{)yxkGcxA&&q z+eE}4P})PgyL=R|dU`0&*^C1(Je!4XSqQv}qxVp@?61cxB}MP$*dl>mGoRh5$4IdJ zY38Lx&M0u3**b4*J`S?nvM+V`6F}6pTl^}__1N@Hlb*pVh>tEUr)%*-{N;CLZiO+U zK;wl_e527gz&{aP+jawGsj^9XH=fjTUhGqw1E1QI;>5`DWy6A2=>OFPt?!8%@{*6RZ%C1_~;Srx)01F{4t?3`p=zro82;k7Ry(2wW>oCR}u|_3bh=1*|Nb_k3 z@xF4=nOg}X;My}9{0S2CIB1YqIy@jv0I|^~9=Y0e*mA+F|K2(zSYDOB-`R`=Ry6y) zB0h}(MYsXeS=w>H;_P7_D@Fiv3ZJpa({)(bXmX){3F4=k4p~t?ApYyLY~v?@z~bmVetYV$3!BEuRtbpDKj3%&z;ncR$PRlOJskn__)h*S4vTP;x%xuO zx7=`k1_?Z2+v_la_W}ikFeC^py3~@EQXFD4g1{t97at zv*WO0BDx?!NWag{?wd%E`<(yqWmwhn;LdBh5d&kOu5Q#xNI&iB$- zTOpphT1Q{N1o5ZE9p{)dNAMuv=T4rbjxkVO$y_6_F$&DzT&t}!kg7mkUY`0&&tU}uO< zr|r8jz;u1iLyQ0?7boXM@SqkuZD_Uo>H#D;W_wFleFqY(C7jM%g|%U4glabGb6|Y1 z_G_*5zESWw{9qX7Qj2x0H>}WbA)ashTjRdX?!V)}dgxhpeEaAya7bEvdmw!b@Jk-& zV1u*U7|n&A=Iihm9Bq{;php5nc8?T?MI_KZd${1ur(xjS#k=PHd<^W;UK+XEFbd{g z$iBDGt;J;PO&4ygSO0A=KL7cC7MO_mXDAkXz>1H1Rtifjf46EIhQXnS7>o;sDqwB4 z0y**RO0}3NmGWNR1tbt|o$mes6A}LiW^XdLK6p6{@?yTop9~!XDm)$O(y%(7$Vjfl z$K$n_Mfu_SVG`nx96nlly9)7w<#;AT_vm3D95Z?$${)sddc6D)nh$3u$)zF>{#s0} zH}-2%HxgX16Z$0n4hbq4iAqinhJm2+>8G5;)EEk@-?lW$#* z_yv_zn)NiqN8mT4pE)_f3dNWAnyI>u!B?ZO(8H;)KwwJzZXT*yY)GSGU8)obk{;+- zwm(6F)nN>zdDCCBj~fNA(m#*mmTRzs?CXT>xrhh*mMD4th{vnp zUdKMvhNpnmMsOVlm(G#j2fvCO1&pr=j7xA1IP{{!Y%B>0vJJ%UKXXNbvD_rXIJseP zA+jk(|LPdf#)@`N1dW1-d%qscb<|*|Y~=i_BN1QFRCX-L>MxJS9e(jXt>DNoe3E(6 zkauYe*ksXiar=z|UHV?-uaz~JM#LW7m;OjF9;~?X@bX^)PCE0z2yXb50+9|{`S#z1n*-BUl|BbxpT!)U7$HE_32Y_5<6@s(ab zO^$Z`=KpCQXCC`*UFhx*s9TCx+s`ltJojDP`$J+BsF>{LpN2!Q@R+23H#HIn2DQwE z)SwP*2`H6%Y&8Tvim9}@QICQ3u&ntn!tgnt+ds^eqXy$ERKb1!TJ`sc{YtI9x>gAJ zf7{2=Psr(i*B=5@c{=KCKPjM*bM51lz$mCU_U_q113NIa6YiFV1owijytd0k9azg} zy=tdC1Rh7|fP*U(Fkz7r*}rEL97|lCDfn8ANyrGl)g3{+gC}kObW9go{}*dpH5xlU z1RUtvkKc#ku||v3_o~_9MWokTXE|AoMRKn%J!(gStqLc5ZU>_Y4RyLC*JKJOR;OnGXDL`Ep! z)D!;vAnH*-lTejA(@>4wy~C?dSBwOeVWd+@Fq84mT(B@QBzO1MAn=Tl&VSHD0n1sJ zIjnw;0Qu6c7v=A(v6(`G_l0c4Gct?BDPP4SL5#hzBJ6G2B7{n%iXGpZPKZ zqzsG|6SJ$az0p#6i3vzxXP5rr3(REv(*d_T+(y|$gW&qT#2*qcyf)KdDwFl|2xzjl zcY73HjhQd(CoP8~{*-)H+$+2y64?8n4$^8G1OkG@s^D@8@RD_&C6Hm+?aBkEy+f+8 z)4dOPv>qTqI8WU1TPF}d3b4+SBi2@=!I3zz;j(~CtFSXVS)fl}_dCBrcB;cTrav1#C`S%pi(X+pw zavlU*CbpI-!`-t}4q7#*z8?V|>I){n6soa6jU{8dvxqNK$DcK6nL~mzu_xAw%m=~Z zi;u1e;V`uF1O0dX{1I?>NU~K%q8b}06yG;<5(z{P5T+Uli086nbq%>V2oi-KS$z$p z0LEJ%8@9s2<(V?^5#9%?G1_0BsBRra{1tqA4r6-<5?mc-lC_Z^1ZoGOqt5%lL#TZG z?CazaKsn)<)X7neUC;l?_=6V-zSFH#6RHvaXrG&g*0Diw;RD^~OBmmK(~j+~e#{7% zIm@_kjJ6s}E+%2Y?1;x(asHzJUWf#zZuq@Axo;4>d%l@VNX}kjIJqv`FAZU>e->67hE2I@g7n20_Y3Wcn3b3dp_hEdDfL1Wdi(CC&&3FpArT zaK(?xKc@_jJM`V+m}(pnm{8BU2(Ax+`LyndB6AA(IZN9t3Ln$o9yMeO9;(7P8#q2+ zpZ_bsfkMS?_d^gbc2{ZQ;AglG*j$@CE8GKqwn9Xu#&ra=HsRPgTBZQEs%X|I^a5z54 z2fQ@55r6rj|4*!O01#=nJg?WFfICxN+bNbK;J{b=3+ivGu=3554la%V^00keoc0x? z%h!?MXxI45OC?y>LsZ%*O`wsxa zT6OJ{Fuu6vQteW|4@j$_l(5=I4m>2=L-)7 zxLEV$6OWGo7P^SQC8H|rim*J_8SmfxKkegc*jgq>d-2w1mmx;(lP6`;k zwQWY{$OtGFVLvpgS%r~=ISux^pbnV%+K>{rBA!;E#9z+j`Rc_t0O-LB8HY_WSUW6i7X`QRc&135 zF~(noy}4UIF?+re&Hpml$}Ew?NHE8rNFcHdfH;TGa;)i7Na zMjfzPtGDcjnUH@v00SO%r3w9@DwWrLiAV-Z+DcxG^TR-2zjNC6b0zj|uhJ-PKOPAV z&8>~U%0zmB0W#2S;ifkq9|k2gOFwHyDlyfj@?wsii1(`_h6TkT zKJ>+xQH652HS4mveNhJ)a2z-*TrdpZlo}hKv9?!Yk*gPG#qe}U@X9yE^mZr`)P)ph zcINhj!H+aeyPL>h&imRNRrfHsntry>x~dXm3-R>QS+Dr}i$j+#XR-iHVtpKqc-xVFBq*vkcxh#b1Yec4`ps|lgM8X&&bc{cxM}?HmA(0J zPbY?+I1zaNvmHC*xvK^7vRvQB?`a}_T0K^p`$|8Um>1h%PbY(d{yqz1xLzQ2Sg@hw zekG>eieFkZtV9Cw?K_2IF(g=>X}dI_(GN^=J0$KsCxb`F74xE#hQSZsedn}qS7Mj* zw>PB}BL3QiDkpFp@h=1WWs_z5!Oew9Zpsrfu*Ovnw8Hg<0P)@(^Vch}0(@68-C`CJ zjDFl*GB1b(=yCbFo>wj+2ywr-v4imBj`0PkU;KRDtp}k5-6R&*)Y@82Y@ zct>fs+tOBI^55?tuD^`7ojEB!|KN9_KGMT@_d`%Z;%wM-We$+8wJH zkN6AgcMSFJ^?{`tS+{i=GHAEbY6&_z3>Md2@$J5K71)bT=7V9pNWibGr#So=3AkxY zcKO)#f$c{Nd>xLH!Hx8uHwgm6AeY@UGUI&(*30bPE4CByMplAZ_k9uXdg$fHLgPNL z2&`|Cj*vmBqTfm`_b_12#?Oj(zN)}(?O9*#r$YjFkLL|Su1H`St)RNB(FX(v7uuL@hKZQfTT1J*B4;KLq6D{TidsRbW5zw;58KB0(LGpKa+* zBrtUD+W6Ai3z9yM=k1&%0p~uR#Gu)KRKkz2L``}ma0z5X2HA8i^7y@Q#Qe;!Dh z-d|sH1HHhnZ!gm`w9a~;o;d%;x; zky|0{B%u2%D+&JwuG?i~5M7BJ6`1El-Og*%IP6i7t$0^_KZLWV{X zpmGmuNy!)jH@}Cx3Zk#TYSmA^y}VHVx5MvCTU~qe5ueeJAJOO23vPae#Y;M#|8^0bBvhf-!`6u(A* z9W%j~hvXuGL&3|V!?s9(|GAsp+vy_ZUNHRFq4rrC37o{+e;`S_4}nmxPn^9m03)ma1PeY$FkbmE zeN+|+xafU9`bzc!%4)hL&vSSLsL7#w;a06N+Uvsc-T*tIHW7H@CgQ16lUCgiApV)b z$#8>%z2Fv~_r7!(Wsl$AXe+;IIRrSG2t7{L02|$}DE!a>38p9Xmj#%Rz-3L}@;Fy7 zaJDX977itW&@tI9#4AIfR!N%1q ze-a)jri=HN>%zt3^_D!tlK^|e)V_9J7zs{M9jMP4MS_RZS)5egdjL4grFB%52$U zIo6Wn`gwGw?C)K`es3z;ITrD3Y~vqRn|nZA=kks*ycN8U9~tBxIyMBh(QHmp!>ga` zTycn34-zam$~F1;B7xma&de=kJ-~?dVuh0#90^CCT5SjpfrATDTvsZ~F(ncMl(q~3w-aKoZeK6QFlL7u13Sbg9*UzcXF)vmX?#qfQxEuPgs0`u zmL-7z^@+^&Y_1o%4$V^914; zq;3w+4}z>HoXpgTa%^5S()g4D;x}$TRL-Lye(UMnN=f}5@Cq-XEqL!J2^jSrOMfvw z2l1Mqq$%p`JoNWfr>|DjaZAb2>lJ1A+P3`;c? zb-qP~1pKV7zG2BoAW>c{9ue^+ve){8R0>+5KhMN<&*}+}y{% zcLC+aU8)N~i1%sQu#M*K!2`}G#X4imBtTH)e^^m52ttk?xl>&O)DKiluh zdF}b^x`FQoqap#(`*AQ|=NRAU|CoC3K&qqvf1H#uLmIY1GD?(+=wyU66-gqA1`VOD zZbnvCNTEX6WoECZy~njT*Shy|uaHpY@4P?n@8@%W{yq2cJjZ#R*Lj`sd^{f7*m^y? zoD{4aT!@bQUG=)1!U*7$o^fB-8ozuG|M)21KAe{pte`<9jUF+Gs-r>DdH zWg$A7FB?)B#=!6PnX{cyWZ=DjZt!?QAhp5zz4^SBGbDI$?p0M;cn?He&3ie1vk(ne z$QdfSGXk8EB|TskW(3g99T{M0X#?}eGd1fcNZ@zlk+ySS54=CQ$I0w`A)4%IE{=c5 zz(+}DsqbWE;MGl-@q19#hR^r@b;J*2!SJN@FVwwyV4qf3`LIeMdKmM%Ti%Qjz>7P- z?))@ESmb3CXVk&WHn=@1a`bF3mS&9KXaASb0~suy`p+Z^(M5u9c%dExzt80$Pe2a? zU!{~lVDFA?1CuY`{{CqtL30{?fBNGdxNf($CeBredZN&J(}RovFXU{P2g?}&NLTEh zz4_P%{U_>qKi6Zz@^*zHn|nRr^^T{jd%ge#8xeUnh%@kQEiv!aQyBQjeS{ghigz0X z|7-KqslaLW0{!Bp|5T3k_WNOd`e`hC-E@H~`op%x5e~$IQ z!wk~tN1g@fT1>4Qe?B7s+m-H&H7!N}^Gnh^Gl&tO_t@-7cY*&4z{GRH?Jk>i8~BFrt=b%jv)W6kw>C=jK!tR)5sPjCQkLJk zONhY0546^VTCD$HJb{Uc4|Hc%cC=x)>&_SDn5}A?al$4`paM@H$)<_1u*N;Srx-S@HBhnV#Y6OrZkQBF%i%+?atc zR3F)*g{heTgU9tVrFnBNtS+}gL$~lzB_|RzG7-M>ZtMY6bSqqR^Ih#+Q79+s6 zx(V4+6^sU~H?V%pG1Uskr+QD)97!M^@nE5Fr5h5ZNlOXS`Dil!t7)qO18=b+{?Ib@ zzxe;u&ouH<@weDeE9i^z6JxM{hd~hi_MJc7P&2OlSFJZ6nFv|LiVHIWOhy0d^9*M+ zVCI1P9kte0_{!bTbRF{qADy#d<;Pbr9hSM4q>6l0G+2B4`6dQ_$6>H4a{nLv|J2X) zJL_JrMMW#@zt4V340Am`#Ts@L4t2wK-sXM5sre}NxV~hPljkmn=Odo}K%I+23_P#7|F3=~@6;QYHT_y) zb0tklQj-L%PENjN1>K-zapoZF)qM1tDKUR8mJvYn_}%N@#TX4Rtq}548Bz{l z1(Oy*wSz-#wDnzjHu_5bLP~NN1@&q%S)&(qz zoQ>>;TET-xCq(j*fbNN}8}BjjZ)E%h+=puk_!<27?^yr*o^Qsju<}FQPD2V)rnBv> ze&b81TWMW)|1)?-RI=uYCX4{?Uc06>8W{~(^3zw@tltU?zcs=+F+b>B@7Jv%zTIGV z()2OR=3%YZ_Rq084E!xwH&@O)0wciK$Fd`?YOO$++hNqbjRe`MUk|IebwgIBvo-%< z9;)oj3GzI^2*6r$tadDhfj{NPvwLk{E36}jN5pN$Q+W-6*!#R2>}7=ZJ*&+_x2o>6 zJr-x+b2pLl8VK(g0W1`|)?9YBLV|Ln(-TaO`F2E;{PJNpa40gBPiE$!`IT|ghg^&R zF|~hBmA_=*KY8YsbMv{GW|vj zyr`xa`>}s55ZN3jGc<;|A}5m1UpMN83j>e(5}xKET5F|!VjBZ5y`N0Dv8KTYpiq(T zF+^*DqfV{izMX^MUHkPt^XYDIPtMtAW|fD;7dq(eC5!+mm8CoO?_=QeZ+)Hc8EAn? zu5oSk8tjmoxa(0AzJ`%>Q+1qwL-*OJyzaa&N`S*dN$n z6m1bADRjf^*nz8U(s{`B^3;t)Mp0G^bn80&xb|fb&Z}&n ze6_n9+D0|LS8T~cpGNDWRvt3~aH-m@xC}Dz(4u_WE1?BU9eXYc2MmIPM1!4(NH@H9 z>N2fd%SG>+f6=Y27Mbd==q<$AGm+vi$n=Qb`8n$T*M+}Z9|d& zzW_{|9ri@$y<_0pU94SM9<@NsDf)|8bb+>`>^jTTTr|N);eIXnzj!?V zRbRZrdiND0z=7?*ljd%L!G1|$xZ(ofQc!D zaMfMTmVtjSdOB(93LXP4v@Z>+_z?!FUTzxgg5MsVlkE7}k4mXNYM;yb@0h{$Gx-;k z3H~!?1h|Ld2{UI};FzGdTa^4D1a6u!y4%|YyS4=~J${sn9O5?Z3mo}>0WjWE$11N) zn}OdRdr)HOcndtWlB;(wGf zMt~pN1wuFvwm|Xue-AG53_|y9)88@G_!jJ0nDn!=H~>vCSOFLfco^|yK2wx|FI@km zLrS^@{{G$Oq_}Png0IbmYeE-JW($+#J(`R1pPC=xN%|lB|J2WP!`l6IJ_jQ}NBEs# z9l;iuNr;^0`-4*{xvm`?%2y(z%G|sa^eY7^4Bo4_Rj#|1#L{lf4^q9&ok5 z0_{6-VQc`7Y5y&L@uLf@l`rMoWYzYO10?$0o@(ny5Q4a-6KlOxkydnQ`*1Bj0Ol>0AsaNgF7hA;1zxnuf7L> zydrO85YPojB*SefJvpdZaHr_*i;Mztb!=8DI_Y5+&MO&7?0aj|0d&p}3G zq(7XTj0Pxhd=0*GgMk;X>uza?Ylaeo?91Vguw%MN;DW{VE-XxTP+8t72Td9pNmwls zvhg7J@1IL^u-o`~Mu2}T+SY2}&A@5f;2v=c6BC3RSXYg^z%_Kk6YD!UDBbBVZxxk+ z_f}bf_LV1l=#>=>Yrp>4cX?fLWqkW_l~#MC=ihd)kJ48}3zjOMP+);Z zK8ut2;_HgD?!A`2ofU|Nc^_`n$Pk8dzpML4j*7tVhJqVXRAqS6Kf$E|N~fb;>Dq0?aoV6wA#|B+s%Vkw;)`PMBtNM)Y& z2HOUL4O}<9WG4OP5)jUx8LW?GD)uN=<``ltK(ib-#ygVjV7z3PG~dyyK$vee8nwse zJYakIFrbA)tC2j_j_|kPqM7mG>p(wo!r~=jDi%FbanF8l3G&k?pRPUi40;T-LoS3{ z!9BsN4I!LN#oY1R{migC20+DcXzt8zK*b9BGgo+CEisws+N`xDjC5t?$f`O_kx3P z_ICj6wVq6tVSf&gUaLQ1y8^3HXYaLhH=vV;$*$j813@tI&o(!r6FiFXU909=0eTo+ zO7L+bk`FjUFbb>S{ulLacp4u=v{nbw#I_=FSlF ze$(nD!(}i`o^%YwchOxR4RiJ`d<5;$PyyB%7eH<>v-o!j(lUFq{IGbo+Pb^8opRxD zpj^VCHP8*FewF@k`?3UA3?prgW}6X_OxP>F)$uc=3!C~yYq>*?bP&&P?Ij=|Fy@&J zZ$WE^BXw+=A|Nu{Qc{J*6LwgKD5={WZJRRoX(Lk6DEb6@d;V<}XQs4XOeJ8RtFR@MyNQ9R) z6f54PAm}g?r#wIL7n+mZc(diY5SobDKEd__^nzbc3~YZ7m$aTcUj4HG!YMBN{m_Np zPT%jd_fCR{l-<39sv&UW=;rqv(F<^OF-CUFg>C|}OffpXjV}e%?ugYd-1q=3{%V%) zh6_+6;H7Rp+>J~I1Wukmp9+&PzA@}U9|1Dj|J80-0L?#ZvSoHXXnWPaXH$Y{P^*3; zX1OX9cA~SdI63B#x^Q^#`P{Zdn|a_qy0LlEgV2i> zoTATk<)%aJw#6VGtxrI*4Wl0yori=JB1a)lAF}r3L z@YJkI=6>iy8fpH!)>&tOX#UAa)3z_*9Ll^k!($FkQbS6}!u=>pXprsCu}r8cH*5Fe zi-akM#bzJ%IY_jMd!bK=>_?H1c8I9HfZc$_i zBC!-u#0JsjiY>AEB{|^AG;=4j;VbY!5CvOY@v zTlz8|ct7w77R*M&Ns-MeRr<4#^I)zp!(%*2RKELbZUlQsZP~RJHrU)L9b_875 z7Y7ZLff5hGZ#X@7K)qFQ2o(wpUUJs}kY{}uFgP6tt>sD)yVQTfK%(B)<2FO+IY(|= zicm4w2+DW5XU2oSxV-f3r5O;>B4lqHi5f!T?mVGxABtgxgTf6HcBg=Z{i+CNyTT4OWu{h$Eb3?#LeHq z>r?-~J*qRHTx>5WqCJeR+qUoC&Ql83UGIAI`@X}M;DBdrOVhBMp!#k#?&&ZR4-U`Y z?_UZZo-~y-CjJ1w)K7gr1=Co0Rd4p;k6{$e>3n^|<}&zEn7>QzMH0|$TqDw6O~Z4i zgbAm?VN@-7%G<}k3|b1DpBSAZeF6AXzfe^ z{`3cW-Rq};;JwK&&`fItU0>Wv4hSrV_Y2If6Z0uBCDigT{oNF7zIlY_u;U2I(htd8 z;jaLy_kC;eTdDBynvB}G;1n!B!L;J&5j0L(Mav&5;9Z6w|CiQO_`q$$VHooZ3?*5M zDq3*^o^4NZ6RHI38NSQo$J0RV9+7!^|1SbW2;PZ&wQdyMNea{TkEq11-BbE2$!Sn+ zp`n*oI0?a#U!vdaA4NXDN}c#6t3YV0+}3uHbjY+)on8!+q7DI=V-wa3vjr!i^UAYzcHY$Qby4SZN7H@_hLgldlT zymkIGP`q4v?gi%Uxs9?2uvHHL`FcBap*>tUYI^w{WQ7U*$_%oFsmX$74vfOmRz45h#46?!yT4;SZJ zm~L}tLym!5s7(qLVu|y$d!)!HmP_Gb=%WT`)V%U()0=FF*tLG-gFY2((@5PF`ebC? z(QY}k(f|SRmb(uuXG7ZWq>GJY3Rs?b)mi<3j6H4Kd3D_zVZ+ug&k=_l*e$R|u;Xx} zz?b~JCI^Da$T+wuubjIH&W}0dZKdY`?S4Sz>0K0fXzM!7nnOkgdGu_<4^8lt5?)kc zl?w~&Q^)*E$S^*4LGDB^85QxR75&{z1m{$Zn5q6;*s*lcy5%|<%DS_IrdG)4#qF~V z`i4Z1(=YY*H_n5PPYGPZyewpB6c_Qh90YCD@D@ z^0}8COULXzbxfUvH-S2#8IMpW3@dL0uYOn`Iuiz}#fgvl|DBfZ*48 zq}@wl1lr;c9j6LYkz1!nr)*veK*)=&E~!P3Kz^g4gA=8h-;$X{RjH_A*_LnnY75v% zjhp`73vi)>`8U;d7{q=X{b{;HMQlv+7w8(TQ1EtoJoP(3b<+332MWXBK!5vW&7O)1 zA28X+zi9<)wu%G&{KW(~HR1KTt9Jblb(TJ`k;;OEx6Ek?nQR-mcoS#5DeDCahlZNN~r9BbNCDVf- zWHy)mRCXL~J2p~nd9@v^rn{W{n@iwbnzo zOX1Qf_8;x|IN)RqK|JirI1(1^zU?>E4vF*}`(T4o*a@=gL~XJp+?-+t(9-Mo?PIga=Xcsbc_cEE+y19Q{OrQp6}hiK5>K74q_ z%zQ0k9C_^eR<#z_0RlHH?i1Ab%5d&^dd@}bK3IFKpno%S9N8(3=vIt(01caXgkC5E z7ID7~(XG9ZJj1$urFI;#sx8hN%XPwRiB1V?P#H931L z=|xe#ZrEVJrQp1O0_B?i5N5T*`OPVcZ|LpiV0OZD^p#f^?35lT^wpX`5~rL4^isQk zX@DS{^JQBF%-6gn7_IAq!MNK~ikBwPey&2kv(q?BpV-`QY+M1{yLmr-aq5JftieO~ zZ%-f}i+>lk?(c>pN0u|UeXIay`_bG>e>;GSB{=28^9gj0M&I=LK{sq&{k*=Xs{*>G zmo1(@>Hv$QQxCJ>PN1Qar~iy3cY{Bnk|i)wpb`=e*z9Ee(+G=aW2fA6DC zc7vr)uI!yFl^|}f5j5}64wv4%-(#0FfrLA*y-UOws(dDd2&M2!2xfhINpB9{cWx{{ zo?AG9TGp_7 z#D@BG-Eu4ZezI3lwtoT@k$ z!rn&m#K|vJ@K|!Ql;EaTu+q-HF1|E@I%_+hn_BjQ;^vD_SO=>hiflL|9)M5r9iy27 z*l5UO!Z5-<0;h^zxGgJKC{_)wHY)ay1X@5*aPRWB?KGtJWg`db!^yDvH;eCDR6|_2 z{Y(DnW|%9nyl5sxLtTC9Re&ktp9=b~@JCgH?q#9rMTKVg=tf&ts6<0%3mZpg4f|ln zs_H=cP&J5M|C%gSNCcxF8#k0A8nWdOecBU1=z|5l8BN_?HQ->E8~yGS5v96%G9rWj~Rj*$?ZZKE)J{)ByQo{3n_$AyzR|zls!S0fm zpO|DVd`mqjxJ#k|cpIrtI$qFF1izMQdUiipq(8E;x2gq|60UN&)AdkIQoh&kPD6wp z2L>~-V#{cIj&5djE#!FZ*b@At4(LMervl&6(9gB&r*C4uvTW(zBb3ovNW5Hq+$g3N zG&@rl=fY`d@~BcRq10*s#=2w-HcHh&WiVgxo%R}#JAT=MEtZB{yBlI_KMz2kgTr@z z>pI}{J9oWqV+{nl{<_wXLPKkNHXnM~F#r#Pj@;mmse{)cLhO&!s)0<)>(R`mA!6p8 z=5FpmSp2r(X6J`0Bm!rByx5O4=2TQ!2o_nZLKKm4<$({VW*6T31(6hTe%q z*TZ6DZsH563V8Lldvr%14ei+8?J!Cogy+2XjwFuML!AF35moDQ*!;>mm2Z@WnywO} zBr34#p2!rNoG|WsLWX&TSX>#{`P||wqtTFQ|CY-%eDV3~bkQeA%LZ7L_+}6BtABt}{d>o3xf?)vSKsg@*|)fDY+Cp7@c z)7#7ASqWS$j-8TVrlSzTzC$YKUSb{MWkZ2}u|{wzsV26U72|B&`R1&RbmWvRw5J#g zp65ozmsOcJ!i_+S2Z}uStLL=Ia6LC2Nu7DA%#x19FiUL(-+yid#TEHH*}@`lqeL9z z;-{mPUFPyTa1u?wCdE>_uMxf&&8q%0D+I~Tpvx@+bVTS@-O#f-Mgnf73uA49P4GzL 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 diff --git a/tests/cpputest/jakstat_adjoint/tests1.cpp b/tests/cpputest/jakstat_adjoint/tests1.cpp index 7d3104b28d..6bf40b88bb 100644 --- a/tests/cpputest/jakstat_adjoint/tests1.cpp +++ b/tests/cpputest/jakstat_adjoint/tests1.cpp @@ -66,7 +66,7 @@ IGNORE_TEST(groupJakstatAdjoint, testSensitivityAdjointUnusedNanOutputs) { for (int it = 0; it < edata->nt(); ++it) { for (int iy = 0; iy < edata->nytrue(); ++iy) { if(iy == 1) - d[it * edata->nytrue() + it] = NAN; + d[it * edata->nytrue() + iy] = NAN; } } edata->setObservedData(d); @@ -76,3 +76,64 @@ IGNORE_TEST(groupJakstatAdjoint, testSensitivityAdjointUnusedNanOutputs) { for(int i = 0; i < model->nplist(); ++i) CHECK_FALSE(std::isnan(rdata->sllh[i])); } + + +TEST(groupJakstatAdjoint, testSensitivityReplicates) { + // Check that we can handle replicates correctly + + auto model = getModel(); + auto solver = model->getSolver(); + amici::hdf5::readModelDataFromHDF5( + NEW_OPTION_FILE, *model, + "/model_jakstat_adjoint/sensiadjoint/options"); + amici::hdf5::readSolverSettingsFromHDF5( + NEW_OPTION_FILE, *solver, + "/model_jakstat_adjoint/sensiadjoint/options"); + amici::ExpData edata(*model); + + // No replicate, no sensi + edata.setTimepoints({10.0}); + auto d = edata.getObservedData(); + for (int it = 0; it < edata.nt(); ++it) { + for (int iy = 0; iy < edata.nytrue(); ++iy) { + if(iy == 0) { + d[it * edata.nytrue() + iy] = 1.0; + } else { + d[it * edata.nytrue() + iy] = NAN; + } + } + } + edata.setObservedData(d); + edata.setObservedDataStdDev(1.0); + + solver->setSensitivityOrder(amici::SensitivityOrder::none); + auto rdata1 = runAmiciSimulation(*solver, &edata, *model); + auto llh1 = rdata1->llh; + + // forward + replicates + edata.setTimepoints({10.0, 10.0}); + d = edata.getObservedData(); + for (int it = 0; it < edata.nt(); ++it) { + for (int iy = 0; iy < edata.nytrue(); ++iy) { + if(iy == 0) { + d[it * edata.nytrue() + iy] = 1.0; + } else { + d[it * edata.nytrue() + iy] = NAN; + } + } + } + edata.setObservedData(d); + edata.setObservedDataStdDev(1.0); + + solver->setSensitivityOrder(amici::SensitivityOrder::first); + solver->setSensitivityMethod(amici::SensitivityMethod::forward); + auto rdata2 = runAmiciSimulation(*solver, &edata, *model); + auto llh2 = rdata2->llh; + DOUBLES_EQUAL(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); +} diff --git a/tests/cpputest/neuron/tests1.cpp b/tests/cpputest/neuron/tests1.cpp index b8bccd4a9e..82e0038585 100644 --- a/tests/cpputest/neuron/tests1.cpp +++ b/tests/cpputest/neuron/tests1.cpp @@ -19,9 +19,11 @@ TEST_GROUP(groupNeuron) TEST(groupNeuron, testSimulation) { - amici::simulateVerifyWrite("/model_neuron/nosensi/", 10*TEST_ATOL, 10*TEST_RTOL); + amici::simulateVerifyWrite("/model_neuron/nosensi/", + 100*TEST_ATOL, 100*TEST_RTOL); } TEST(groupNeuron, testSensitivityForward) { - amici::simulateVerifyWrite("/model_neuron/sensiforward/", 10*TEST_ATOL, 10*TEST_RTOL); + amici::simulateVerifyWrite("/model_neuron/sensiforward/", + 10*TEST_ATOL, 10*TEST_RTOL); } diff --git a/tests/cpputest/steadystate/tests1.cpp b/tests/cpputest/steadystate/tests1.cpp index 8ccbb927ac..d9b5bec421 100644 --- a/tests/cpputest/steadystate/tests1.cpp +++ b/tests/cpputest/steadystate/tests1.cpp @@ -108,7 +108,8 @@ TEST(groupSteadystate, testRethrow) { } TEST(groupSteadystate, testSimulation) { - amici::simulateVerifyWrite("/model_steadystate/nosensi/"); + amici::simulateVerifyWrite("/model_steadystate/nosensi/", + 100*TEST_ATOL, 100*TEST_RTOL); } TEST(groupSteadystate, testSensitivityForward) { diff --git a/tests/cpputest/testfunctions.h b/tests/cpputest/testfunctions.h index 32a70252b4..ac07dd2a0e 100644 --- a/tests/cpputest/testfunctions.h +++ b/tests/cpputest/testfunctions.h @@ -88,31 +88,41 @@ class Model_Test : public Model { virtual std::unique_ptr getSolver() override { throw AmiException("not implemented"); } - virtual void froot(realtype t, AmiVector *x, AmiVector *dx, realtype *root) override { + virtual void froot(const realtype t, const AmiVector &x, + const AmiVector &dx, gsl::span root) override { throw AmiException("not implemented"); } - virtual void fxdot(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot) override { + virtual void fxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, AmiVector &xdot) override { throw AmiException("not implemented"); } - virtual void fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) override { + virtual void fsxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, const int ip, const AmiVector &sx, + const AmiVector &sdx, AmiVector &sxdot) override { throw AmiException("not implemented"); } - virtual void fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, AmiVector *xdot, SUNMatrix J) override { + virtual void fJ(const realtype t, const realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, SUNMatrix J) + override { throw AmiException("not implemented"); } - virtual void fJSparse(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) override { + virtual void fJSparse(const realtype t, const realtype cj, + const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, SUNMatrix J) override { throw AmiException("not implemented"); } - virtual void fJDiag(realtype t, AmiVector *Jdiag, realtype cj, AmiVector *x, - AmiVector *dx) override { + virtual void fJDiag(const realtype t, AmiVector &Jdiag, + const realtype cj, const AmiVector &x, + const AmiVector &dx) override { throw AmiException("not implemented"); } - virtual void fdxdotdp(realtype t, AmiVector *x, AmiVector *dx) override { + virtual void fdxdotdp(const realtype t, const AmiVector &x, + const AmiVector &dx) override { throw AmiException("not implemented"); } - virtual void fJv(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot, - AmiVector *v, AmiVector *nJv, realtype cj) override { + virtual void fJv(const realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot,const AmiVector &v, AmiVector &nJv, + const realtype cj) override { throw AmiException("not implemented"); } diff --git a/tests/cpputest/unittests/tests1.cpp b/tests/cpputest/unittests/tests1.cpp index e8190e50b2..5a17aa114f 100644 --- a/tests/cpputest/unittests/tests1.cpp +++ b/tests/cpputest/unittests/tests1.cpp @@ -655,13 +655,13 @@ TEST(solver, testSettersGettersWithSetup) static_cast(sensi_meth)); auto rdata = - std::unique_ptr(new ReturnData(solver, &testModel)); + 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(&x, &dx, &sx, &sdx, &testModel); + solver.setup(0, &testModel, x, dx, sx, sdx); testSolverGetterSetters(solver, sensi_meth, @@ -808,7 +808,7 @@ TEST_GROUP(sunmatrixwrapper) SUNMatrixWrapper A = SUNMatrixWrapper(3, 2); // result std::vector d{1.3753, 1.5084, 1.1655}; - + void setup() { SM_ELEMENT_D(A.get(), 0, 0) = 0.69; SM_ELEMENT_D(A.get(), 1, 0) = 0.32; @@ -817,7 +817,7 @@ TEST_GROUP(sunmatrixwrapper) SM_ELEMENT_D(A.get(), 1, 1) = 0.44; SM_ELEMENT_D(A.get(), 2, 1) = 0.38; } - + void teardown() {} }; @@ -827,7 +827,7 @@ TEST(sunmatrixwrapper, sparse_multiply) auto c(a); //copy c A_sparse.multiply(c, b); checkEqualArray(d, c, TEST_ATOL, TEST_RTOL, "multiply"); - + A_sparse = SUNMatrixWrapper(A, 0.0, CSC_MAT); c = a; //copy c A_sparse.multiply(c, b); diff --git a/tests/cpputest/unittests/testsSerialization.cpp b/tests/cpputest/unittests/testsSerialization.cpp index aae97bdce8..423ed3e1eb 100644 --- a/tests/cpputest/unittests/testsSerialization.cpp +++ b/tests/cpputest/unittests/testsSerialization.cpp @@ -158,7 +158,7 @@ TEST(dataSerialization, testString) { std::vector(nx,0.0), std::vector(nz,0)); - amici::ReturnData r(solver, &m); + amici::ReturnData r(solver, m); std::string serialized = amici::serializeToString(r); @@ -182,7 +182,3 @@ TEST(dataSerialization, testStdVec) { CHECK_TRUE(solver == v); } - - - - diff --git a/tests/testModels.py b/tests/testModels.py index bf11007d46..29f4bfba9e 100755 --- a/tests/testModels.py +++ b/tests/testModels.py @@ -78,10 +78,12 @@ def assert_fun(x): rdata = amici.runAmiciSimulation(self.model, self.solver, edata) + check_derivative_opts = dict() + if model_name == 'model_nested_events': - rtol = 1e-2 - else: - rtol = 1e-4 + check_derivative_opts['rtol'] = 1e-2 + elif model_name == 'model_events': + check_derivative_opts['atol'] = 1e-3 if edata \ and self.solver.getSensitivityMethod() \ @@ -90,26 +92,23 @@ def assert_fun(x): and not model_name.startswith('model_neuron') \ and not case.endswith('byhandpreeq'): check_derivatives(self.model, self.solver, edata, - assert_fun, rtol=rtol) + assert_fun, **check_derivative_opts) - if model_name == 'model_neuron_o2': - verify_simulation_results( - rdata, expected_results[subTest][case]['results'], - assert_fun, - atol=1e-5, rtol=1e-3 - ) - elif model_name == 'model_robertson' and \ + verify_simulation_opts = dict() + + if model_name.startswith('model_neuron'): + verify_simulation_opts['atol'] = 1e-5 + verify_simulation_opts['rtol'] = 1e-2 + + if model_name.startswith('model_robertson') and \ case == 'sensiforwardSPBCG': - verify_simulation_results( - rdata, expected_results[subTest][case]['results'], - assert_fun, - atol=1e-3, rtol=1e-3 - ) - else: - verify_simulation_results( - rdata, expected_results[subTest][case]['results'], - assert_fun, - ) + verify_simulation_opts['atol'] = 1e-3 + verify_simulation_opts['rtol'] = 1e-3 + + verify_simulation_results( + rdata, expected_results[subTest][case]['results'], + assert_fun, **verify_simulation_opts + ) if model_name == 'model_steadystate' and \ case == 'sensiforwarderrorint': @@ -132,12 +131,12 @@ def assert_fun(x): verify_simulation_results( rdatas[0], expected_results[subTest][case]['results'], - assert_fun, + assert_fun, **verify_simulation_opts ) verify_simulation_results( rdatas[1], expected_results[subTest][case]['results'], - assert_fun, + assert_fun, **verify_simulation_opts ) self.assertRaises( From 40116edcc3d980d9149274c346a3f58e3dd4848f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Sun, 5 May 2019 21:09:31 -0400 Subject: [PATCH 02/26] reuse solver, fixes #541 (#630) * cvodes implementation * idas implementation * overhaul solver interface and actually reuse solver memory * improve cvodes and idas solver interfaces * bugfixes * refactor solver interfaces * cleanup * bugfixes * bugfixes * fix doc * fix undefined symbol * refactor solver code * refactor solver, forwardproblem, backwardproblem * make compilable * fix initialization of xB * fix synchronisation of NVectors and reset t0 on setup * fixes for steadystateproblem and post discontinuity reinitialization * refactor passing of AmiVectors from pointers to by-references * add more consts to AmiVector in Model and improve solver interface * fix storage of solver time for adjoint problem * fix DAE evaluation * fix extraction of results from steadystate solver * fix timepoint extraction in forwardproblem * fix solver documentation * loosen tolerances for steadystate and neuron * fix reInitPostProcessB * Add test for replicate data handling with forward and adjoint sensitivities * fix test tolerances * adress review comments * fix cppcheck issues * fix folding * Some refactoring, cleanup, doc; part 1 * Some refactoring, cleanup, doc; done * Re-add required python/examples/example_presimulation/model_presimulation.xml * fix passing of initials to ss problem and extraction of variables from solver * fixes doxygen * clang format heavily refactored code * Fix merge: const * Tidy up * Tidy up * Make backtrace code reusable * Fix checking of wrong parameter scaling vector * Use gsl::span, add make_span, cleanup checkFinite * Change const members to non-const avoid const_casts which potentially result in UB * Cleanup * Refactor(core) Move static Solver_* members (SUNDIALS callbacks) out of class * Cleanup * Show location of used AMICI library * remove calcIC in from IDASolver:reInit * fix doxygen and clang format respective headers * fix reinitialization for DAE problems, update expected results --- .travis.yml | 2 +- CMakeLists.txt | 2 + include/amici/abstract_model.h | 126 +- include/amici/amici.h | 58 +- include/amici/backwardproblem.h | 28 +- include/amici/cblas.h | 16 +- include/amici/defines.h | 8 +- include/amici/edata.h | 22 +- include/amici/exception.h | 353 ++--- include/amici/forwardproblem.h | 24 +- include/amici/misc.h | 53 +- include/amici/model.h | 302 ++-- include/amici/model_dae.h | 699 +++++---- include/amici/model_ode.h | 809 +++++----- include/amici/newton_solver.h | 29 +- include/amici/rdata.h | 73 +- include/amici/serialization.h | 87 +- include/amici/solver.h | 891 +++++++---- include/amici/solver_cvodes.h | 235 ++- include/amici/solver_idas.h | 237 ++- include/amici/steadystateproblem.h | 46 +- include/amici/sundials_linsol_wrapper.h | 4 +- include/amici/sundials_matrix_wrapper.h | 25 +- include/amici/vector.h | 481 +++--- matlab/@amimodel/compileAndLinkModel.m | 5 +- models/model_calvetti/CMakeLists.txt | 1 + python/amici/gradient_check.py | 2 +- src/CMakeLists.template.cmake | 1 + src/abstract_model.cpp | 6 +- src/amici.cpp | 25 +- src/backwardproblem.cpp | 66 +- src/edata.cpp | 18 +- src/exception.cpp | 55 + src/forwardproblem.cpp | 151 +- src/misc.cpp | 126 +- src/model.cpp | 1395 ++++++++--------- src/model_dae.cpp | 488 +++--- src/model_ode.cpp | 594 +++---- src/newton_solver.cpp | 74 +- src/rdata.cpp | 20 +- src/solver.cpp | 814 ++++++---- src/solver_cvodes.cpp | 1099 +++++++------ src/solver_idas.cpp | 1188 ++++++++------ src/steadystateproblem.cpp | 77 +- src/sundials_linsol_wrapper.cpp | 4 +- src/sundials_matrix_wrapper.cpp | 7 +- src/vector.cpp | 149 ++ tests/cpputest/expectedResults.h5 | Bin 4134156 -> 4843500 bytes tests/cpputest/jakstat_adjoint/tests1.cpp | 63 +- tests/cpputest/neuron/tests1.cpp | 6 +- tests/cpputest/steadystate/tests1.cpp | 3 +- tests/cpputest/testfunctions.h | 32 +- tests/cpputest/unittests/tests1.cpp | 10 +- .../cpputest/unittests/testsSerialization.cpp | 6 +- tests/testModels.py | 45 +- 55 files changed, 6174 insertions(+), 4966 deletions(-) create mode 100644 src/exception.cpp create mode 100644 src/vector.cpp diff --git a/.travis.yml b/.travis.yml index 3e82aebbaa..3c59121593 100644 --- a/.travis.yml +++ b/.travis.yml @@ -113,7 +113,7 @@ script: - if [[ "$CI_MODE" == "test" ]]; then $FOLD notebooks "cd $BASE_DIR && scripts/runNotebook.sh python/examples/example_*/"; fi - if [[ "$TRAVIS_OS_NAME" != "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD cpputest ./scripts/run-cpputest.sh; fi - if [[ "$TRAVIS_OS_NAME" != "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD python-tests ./scripts/run-python-tests.sh; fi - - if [[ "$TRAVIS_OS_NAME" != "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD python-tests ./tests/testCMakeCompilation.sh; fi + - if [[ "$TRAVIS_OS_NAME" != "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD cmake ./tests/testCMakeCompilation.sh; fi - if [[ "$CI_MODE" == "deploy" ]]; then $FOLD doxygen ./scripts/run-doxygen.sh; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD cppcheck ./scripts/run-cppcheck.sh; fi - if [[ "$TRAVIS_OS_NAME" == "linux" ]] && [[ "$CI_MODE" == "test" ]]; then $FOLD valgrind ./scripts/run-valgrind.sh; fi diff --git a/CMakeLists.txt b/CMakeLists.txt index 336615b52e..b6f0d78d7c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ set(AMICI_SRC_LIST ${CMAKE_SOURCE_DIR}/src/misc.cpp ${CMAKE_SOURCE_DIR}/src/rdata.cpp ${CMAKE_SOURCE_DIR}/src/edata.cpp + ${CMAKE_SOURCE_DIR}/src/exception.cpp ${CMAKE_SOURCE_DIR}/src/hdf5.cpp ${CMAKE_SOURCE_DIR}/src/spline.cpp ${CMAKE_SOURCE_DIR}/src/solver.cpp @@ -123,6 +124,7 @@ set(AMICI_SRC_LIST ${CMAKE_SOURCE_DIR}/src/sundials_matrix_wrapper.cpp ${CMAKE_SOURCE_DIR}/src/sundials_linsol_wrapper.cpp ${CMAKE_SOURCE_DIR}/src/abstract_model.cpp + ${CMAKE_SOURCE_DIR}/src/vector.cpp ) add_library(${PROJECT_NAME} ${AMICI_SRC_LIST}) diff --git a/include/amici/abstract_model.h b/include/amici/abstract_model.h index 7ed668e2fe..0db1fba0a0 100644 --- a/include/amici/abstract_model.h +++ b/include/amici/abstract_model.h @@ -28,34 +28,34 @@ class AbstractModel { virtual ~AbstractModel() = default; /** - * Retrieves the solver object + * @brief Retrieves the solver object * @return The Solver instance */ virtual std::unique_ptr getSolver() = 0; /** - * Root function + * @brief Root function * @param t time * @param x state * @param dx time derivative of state (DAE only) * @param root array to which values of the root function will be written */ - virtual void froot(realtype t, AmiVector *x, AmiVector *dx, - realtype *root) = 0; + virtual void froot(const realtype t, const AmiVector &x, + const AmiVector &dx, gsl::span root) = 0; /** - * Residual function + * @brief Residual function * @param t time * @param x state * @param dx time derivative of state (DAE only) * @param xdot array to which values of the residual function will be * written */ - virtual void fxdot(realtype t, AmiVector *x, AmiVector *dx, - AmiVector *xdot) = 0; + virtual void fxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, AmiVector &xdot) = 0; /** - * Sensitivity Residual function + * @brief Sensitivity Residual function * @param t time * @param x state * @param dx time derivative of state (DAE only) @@ -65,11 +65,12 @@ class AbstractModel { * @param sxdot array to which values of the sensitivity residual function * will be written */ - virtual void fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, - AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) = 0; + virtual void fsxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, int ip, const AmiVector &sx, + const AmiVector &sdx, AmiVector &sxdot) = 0; /** - * Dense Jacobian function + * @brief Dense Jacobian function * @param t time * @param cj scaling factor (inverse of timestep, DAE only) * @param x state @@ -77,11 +78,12 @@ class AbstractModel { * @param xdot values of residual function (unused) * @param J dense matrix to which values of the jacobian will be written */ - virtual void fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) = 0; + virtual void fJ(const realtype t, realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, + SUNMatrix J) = 0; /** - * Sparse Jacobian function + * @brief Sparse Jacobian function * @param t time * @param cj scaling factor (inverse of timestep, DAE only) * @param x state @@ -89,11 +91,12 @@ class AbstractModel { * @param xdot values of residual function (unused) * @param J sparse matrix to which values of the Jacobian will be written */ - virtual void fJSparse(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) = 0; + virtual void fJSparse(const realtype t, realtype cj, + const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, SUNMatrix J) = 0; /** - * Diagonal Jacobian function + * @brief Diagonal Jacobian function * @param t time * @param Jdiag array to which the diagonal of the Jacobian will be written * @param cj scaling factor (inverse of timestep, DAE only) @@ -101,20 +104,22 @@ class AbstractModel { * @param dx time derivative of state (DAE only) * @return flag indicating successful evaluation */ - virtual void fJDiag(realtype t, AmiVector *Jdiag, realtype cj, AmiVector *x, - AmiVector *dx) = 0; + virtual void fJDiag(const realtype t, AmiVector &Jdiag, + realtype cj, const AmiVector &x, + const AmiVector &dx) = 0; /** - * Parameter derivative of residual function + * @brief Parameter derivative of residual function * @param t time * @param x state * @param dx time derivative of state (DAE only) * @return flag indicating successful evaluation */ - virtual void fdxdotdp(realtype t, AmiVector *x, AmiVector *dx) = 0; + virtual void fdxdotdp(const realtype t, const AmiVector &x, + const AmiVector &dx) = 0; /** - * Jacobian multiply function + * @brief Jacobian multiply function * @param t time * @param x state * @param dx time derivative of state (DAE only) @@ -123,8 +128,9 @@ class AbstractModel { * @param nJv array to which result of multiplication will be written * @param cj scaling factor (inverse of timestep, DAE only) */ - virtual void fJv(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot, - AmiVector *v, AmiVector *nJv, realtype cj) = 0; + virtual void fJv(const realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, const AmiVector &v, AmiVector &nJv, + realtype cj) = 0; /** * @brief Returns the amici version that was used to generate the model @@ -149,7 +155,7 @@ class AbstractModel { const realtype *k); /** - * Function indicating whether reinitialization of states depending on + * @brief Function indicating whether reinitialization of states depending on * fixed parameters is permissible * @return flag inidication whether reinitialization of states depending on * fixed parameters is permissible @@ -177,7 +183,7 @@ class AbstractModel { **/ virtual void fsx0_fixedParameters(realtype *sx0, const realtype t, const realtype *x0, const realtype *p, - const realtype *k, const int ip); + const realtype *k, int ip); /** * @brief Model specific implementation of fsx0 @@ -189,15 +195,15 @@ class AbstractModel { * @param ip sensitivity index **/ virtual void fsx0(realtype *sx0, const realtype t, const realtype *x0, - const realtype *p, const realtype *k, const int ip); + const realtype *p, const realtype *k, int ip); /** - * Initial value for time derivative of states (only necessary for DAEs) + * @brief Initial value for time derivative of states (only necessary for DAEs) * @param x0 Vector with the initial states * @param dx0 Vector to which the initial derivative states will be * written (only DAE) **/ - virtual void fdx0(AmiVector *x0, AmiVector *dx0); + virtual void fdx0(AmiVector &x0, AmiVector &dx0); /** * @brief Model specific implementation of fstau @@ -213,7 +219,7 @@ class AbstractModel { **/ virtual void fstau(realtype *stau, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *sx, const int ip, const int ie); + const realtype *sx, int ip, int ie); /** * @brief Model specific implementation of fy @@ -243,7 +249,7 @@ class AbstractModel { **/ virtual void fdydp(realtype *dydp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const int ip, const realtype *w, const realtype *dwdp); + int ip, const realtype *w, const realtype *dwdp); /** * @brief Model specific implementation of fdydx @@ -270,7 +276,7 @@ class AbstractModel { * @param k constant vector * @param h heavyside vector **/ - virtual void fz(realtype *z, const int ie, const realtype t, + virtual void fz(realtype *z, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h); @@ -286,9 +292,9 @@ class AbstractModel { * @param sx current state sensitivity * @param ip sensitivity index **/ - virtual void fsz(realtype *sz, const int ie, const realtype t, + virtual void fsz(realtype *sz, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const realtype *sx, const int ip); + const realtype *h, const realtype *sx, int ip); /** * @brief Model specific implementation of frz @@ -301,7 +307,7 @@ class AbstractModel { * @param k constant vector * @param h heavyside vector **/ - virtual void frz(realtype *rz, const int ie, const realtype t, + virtual void frz(realtype *rz, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h); @@ -317,9 +323,9 @@ class AbstractModel { * @param h heavyside vector * @param ip sensitivity index **/ - virtual void fsrz(realtype *srz, const int ie, const realtype t, + virtual void fsrz(realtype *srz, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const realtype *sx, const int ip); + const realtype *h, const realtype *sx, int ip); /** * @brief Model specific implementation of fdzdp @@ -333,9 +339,9 @@ class AbstractModel { * @param h heavyside vector * @param ip parameter index w.r.t. which the derivative is requested **/ - virtual void fdzdp(realtype *dzdp, const int ie, const realtype t, + virtual void fdzdp(realtype *dzdp, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const int ip); + const realtype *h, int ip); /** * @brief Model specific implementation of fdzdx @@ -348,7 +354,7 @@ class AbstractModel { * @param k constant vector * @param h heavyside vector **/ - virtual void fdzdx(realtype *dzdx, const int ie, const realtype t, + virtual void fdzdx(realtype *dzdx, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h); @@ -364,9 +370,9 @@ class AbstractModel { * @param h heavyside vector * @param ip parameter index w.r.t. which the derivative is requested **/ - virtual void fdrzdp(realtype *drzdp, const int ie, const realtype t, + virtual void fdrzdp(realtype *drzdp, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const int ip); + const realtype *h, int ip); /** * @brief Model specific implementation of fdrzdx @@ -378,7 +384,7 @@ class AbstractModel { * @param k constant vector * @param h heavyside vector **/ - virtual void fdrzdx(realtype *drzdx, const int ie, const realtype t, + virtual void fdrzdx(realtype *drzdx, int ie, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h); @@ -396,7 +402,7 @@ class AbstractModel { **/ virtual void fdeltax(realtype *deltax, const realtype t, const realtype *x, const realtype *p, const realtype *k, - const realtype *h, const int ie, const realtype *xdot, + const realtype *h, int ie, const realtype *xdot, const realtype *xdot_old); /** @@ -418,7 +424,7 @@ class AbstractModel { virtual void fdeltasx(realtype *deltasx, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *w, const int ip, const int ie, + const realtype *w, int ip, int ie, const realtype *xdot, const realtype *xdot_old, const realtype *sx, const realtype *stau); @@ -437,7 +443,7 @@ class AbstractModel { **/ virtual void fdeltaxB(realtype *deltaxB, const realtype t, const realtype *x, const realtype *p, - const realtype *k, const realtype *h, const int ie, + const realtype *k, const realtype *h, int ie, const realtype *xdot, const realtype *xdot_old, const realtype *xB); @@ -457,8 +463,8 @@ class AbstractModel { **/ virtual void fdeltaqB(realtype *deltaqB, const realtype t, const realtype *x, const realtype *p, - const realtype *k, const realtype *h, const int ip, - const int ie, const realtype *xdot, + const realtype *k, const realtype *h, int ip, + int ie, const realtype *xdot, const realtype *xdot_old, const realtype *xB); /** @@ -480,7 +486,7 @@ class AbstractModel { * @param ip sensitivity index **/ virtual void fdsigmaydp(realtype *dsigmaydp, const realtype t, - const realtype *p, const realtype *k, const int ip); + const realtype *p, const realtype *k, int ip); /** * @brief Model specific implementation of fsigmaz @@ -502,7 +508,7 @@ class AbstractModel { * @param ip sensitivity index **/ virtual void fdsigmazdp(realtype *dsigmazdp, const realtype t, - const realtype *p, const realtype *k, const int ip); + const realtype *p, const realtype *k, int ip); /** * @brief Model specific implementation of fJy @@ -514,7 +520,7 @@ class AbstractModel { * @param sigmay measurement standard deviation at timepoint * @param my measurements at timepoint **/ - virtual void fJy(realtype *nllh, const int iy, const realtype *p, + virtual void fJy(realtype *nllh, int iy, const realtype *p, const realtype *k, const realtype *y, const realtype *sigmay, const realtype *my); @@ -528,7 +534,7 @@ class AbstractModel { * @param sigmaz event measurement standard deviation at timepoint * @param mz event measurements at timepoint **/ - virtual void fJz(realtype *nllh, const int iz, const realtype *p, + virtual void fJz(realtype *nllh, int iz, const realtype *p, const realtype *k, const realtype *z, const realtype *sigmaz, const realtype *mz); @@ -541,7 +547,7 @@ class AbstractModel { * @param z model event output at timepoint * @param sigmaz event measurement standard deviation at timepoint **/ - virtual void fJrz(realtype *nllh, const int iz, const realtype *p, + virtual void fJrz(realtype *nllh, int iz, const realtype *p, const realtype *k, const realtype *z, const realtype *sigmaz); @@ -556,7 +562,7 @@ class AbstractModel { * @param sigmay measurement standard deviation at timepoint * @param my measurement at timepoint **/ - virtual void fdJydy(realtype *dJydy, const int iy, const realtype *p, + virtual void fdJydy(realtype *dJydy, int iy, const realtype *p, const realtype *k, const realtype *y, const realtype *sigmay, const realtype *my); @@ -571,7 +577,7 @@ class AbstractModel { * @param sigmay measurement standard deviation at timepoint * @param my measurement at timepoint **/ - virtual void fdJydsigma(realtype *dJydsigma, const int iy, + virtual void fdJydsigma(realtype *dJydsigma, int iy, const realtype *p, const realtype *k, const realtype *y, const realtype *sigmay, const realtype *my); @@ -587,7 +593,7 @@ class AbstractModel { * @param sigmaz event measurement standard deviation at timepoint * @param mz event measurement at timepoint **/ - virtual void fdJzdz(realtype *dJzdz, const int iz, const realtype *p, + virtual void fdJzdz(realtype *dJzdz, int iz, const realtype *p, const realtype *k, const realtype *z, const realtype *sigmaz, const realtype *mz); @@ -602,7 +608,7 @@ class AbstractModel { * @param sigmaz event measurement standard deviation at timepoint * @param mz event measurement at timepoint **/ - virtual void fdJzdsigma(realtype *dJzdsigma, const int iz, + virtual void fdJzdsigma(realtype *dJzdsigma, int iz, const realtype *p, const realtype *k, const realtype *z, const realtype *sigmaz, const realtype *mz); @@ -616,7 +622,7 @@ class AbstractModel { * @param rz model root output at timepoint * @param sigmaz event measurement standard deviation at timepoint **/ - virtual void fdJrzdz(realtype *dJrzdz, const int iz, const realtype *p, + virtual void fdJrzdz(realtype *dJrzdz, int iz, const realtype *p, const realtype *k, const realtype *rz, const realtype *sigmaz); @@ -630,7 +636,7 @@ class AbstractModel { * @param rz model root output at timepoint * @param sigmaz event measurement standard deviation at timepoint **/ - virtual void fdJrzdsigma(realtype *dJrzdsigma, const int iz, + virtual void fdJrzdsigma(realtype *dJrzdsigma, int iz, const realtype *p, const realtype *k, const realtype *rz, const realtype *sigmaz); @@ -681,7 +687,7 @@ class AbstractModel { virtual void fdwdp(realtype *dwdp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w, const realtype *tcl, - const realtype *stcl, const int ip); + const realtype *stcl, int ip); /** * @brief Model specific implementation of dwdx, data part diff --git a/include/amici/amici.h b/include/amici/amici.h index 2252608d33..2bddad9252 100644 --- a/include/amici/amici.h +++ b/include/amici/amici.h @@ -1,23 +1,45 @@ #ifndef amici_h #define amici_h -#include "amici/model.h" -#include "amici/solver.h" -#include "amici/exception.h" +#include "amici/cblas.h" #include "amici/defines.h" -#include "amici/rdata.h" #include "amici/edata.h" +#include "amici/exception.h" +#include "amici/model.h" +#include "amici/rdata.h" +#include "amici/solver.h" #include "amici/symbolic_functions.h" -#include "amici/cblas.h" namespace amici { -void printErrMsgIdAndTxt(const char *identifier, const char *format, ...); +/*! + * @brief printErrMsgIdAndTxt prints a specified error message associated to the + * specified identifier + * + * @param[in] identifier error identifier @type char + * @param[in] format string with error message printf-style format + * @param ... arguments to be formatted + * @return void + */ +void +printErrMsgIdAndTxt(const char* identifier, const char* format, ...); -void printWarnMsgIdAndTxt(const char *identifier, const char *format, ...); +/*! + * @brief printErrMsgIdAndTxt prints a specified warning message associated to + * the specified identifier + * + * @param[in] identifier warning identifier @type char + * @param[in] format string with error message printf-style format + * @param ... arguments to be formatted + * @return void + */ +void +printWarnMsgIdAndTxt(const char* identifier, const char* format, ...); -// function pointers to process errors / warnings +/** errMsgIdAndTxt is a function pointer for printErrMsgIdAndTxt */ extern msgIdAndTxtFp errMsgIdAndTxt; + +/** warnMsgIdAndTxt is a function pointer for printWarnMsgIdAndTxt */ extern msgIdAndTxtFp warnMsgIdAndTxt; /*! @@ -30,10 +52,15 @@ extern msgIdAndTxtFp warnMsgIdAndTxt; * @param rethrow rethrow integration exceptions? * @return rdata pointer to return data object */ -std::unique_ptr runAmiciSimulation(Solver &solver, const ExpData *edata, Model &model, bool rethrow=false); +std::unique_ptr +runAmiciSimulation(Solver& solver, + const ExpData* edata, + Model& model, + bool rethrow = false); /*! - * runAmiciSimulations does the same as runAmiciSimulation, but for multiple ExpData instances. + * runAmiciSimulations does the same as runAmiciSimulation, but for multiple + * ExpData instances. * * @param solver Solver instance * @param edatas experimental data objects @@ -42,11 +69,12 @@ std::unique_ptr runAmiciSimulation(Solver &solver, const ExpData *ed * @param num_threads number of threads for parallel execution * @return vector of pointers to return data objects */ -std::vector> runAmiciSimulations(Solver const& solver, - const std::vector &edatas, - Model const& model, - const bool failfast, - int num_threads); +std::vector> +runAmiciSimulations(Solver const& solver, + const std::vector& edatas, + Model const& model, + bool failfast, + int num_threads); } // namespace amici diff --git a/include/amici/backwardproblem.h b/include/amici/backwardproblem.h index 90e93e5739..540655ef0f 100644 --- a/include/amici/backwardproblem.h +++ b/include/amici/backwardproblem.h @@ -62,28 +62,6 @@ class BackwardProblem { return &which; } - /** accessor for pointer to xB - * @return &xB - */ - AmiVector *getxBptr() { - return &xB; - } - - /** - * @brief accessor for pointer to xQB - * @return &xQB - */ - AmiVector *getxQBptr() { - return &xQB; - } - - /** accessor for pointer to dxB - * @return &dxB - */ - AmiVector *getdxBptr() { - return &dxB; - } - /** * @brief Accessor for dJydx * @return dJydx @@ -107,7 +85,7 @@ class BackwardProblem { * * @param it index of data point @type int */ - void handleDataPointB(const int it); + void handleDataPointB(int it); /** @@ -122,7 +100,7 @@ class BackwardProblem { * @param model pointer to model specification object @type Model * @return tnext next timepoint @type realtype */ - realtype getTnext(std::vector const& troot, const int iroot, const int it); + realtype getTnext(std::vector const& troot, int iroot, int it); /** * @brief Compute likelihood sensitivities. @@ -150,7 +128,7 @@ class BackwardProblem { /** array of old differential state vectors at discontinuities*/ const AmiVectorArray xdot_old_disc; /** sensitivity state vector array */ - AmiVectorArray sx; + AmiVectorArray sx0; /** array of number of found roots for a certain event type */ std::vector nroots; /** array containing the time-points of discontinuities*/ diff --git a/include/amici/cblas.h b/include/amici/cblas.h index 5f08a20d19..597bcf84ef 100644 --- a/include/amici/cblas.h +++ b/include/amici/cblas.h @@ -6,17 +6,17 @@ namespace amici { void amici_dgemv(BLASLayout layout, BLASTranspose TransA, - const int M, const int N, const double alpha, const double *A, - const int lda, const double *X, const int incX, - const double beta, double *Y, const int incY); + int M, int N, double alpha, const double *A, + int lda, const double *X, int incX, + double beta, double *Y, int incY); void amici_dgemm(BLASLayout layout, BLASTranspose TransA, - BLASTranspose TransB, const int M, const int N, - const int K, const double alpha, const double *A, - const int lda, const double *B, const int ldb, - const double beta, double *C, const int ldc); + BLASTranspose TransB, int M, int N, + int K, double alpha, const double *A, + int lda, const double *B, int ldb, + double beta, double *C, int ldc); -void amici_daxpy(int n, double alpha, const double *x, const int incx, double *y, int incy); +void amici_daxpy(int n, double alpha, const double *x, int incx, double *y, int incy); } // namespace amici diff --git a/include/amici/defines.h b/include/amici/defines.h index 15fe280acc..7482578645 100644 --- a/include/amici/defines.h +++ b/include/amici/defines.h @@ -1,5 +1,6 @@ #ifndef AMICI_DEFINES_H #define AMICI_DEFINES_H + #include namespace amici { @@ -48,8 +49,9 @@ constexpr double pi = 3.14159265358979323846; #define TRUE 1 #endif -/** defines variable type for simulation variables (determines numerical accuracy) */ -typedef double realtype; +/** defines variable type for simulation variables + * (determines numerical accuracy) */ +using realtype = double; /** BLAS Matrix Layout, affects dgemm and gemv calls */ enum class BLASLayout{ @@ -152,7 +154,7 @@ enum class NewtonStatus { * @param format string with error message printf-style format * @param ... arguments to be formatted */ -typedef void (*msgIdAndTxtFp)(const char *identifier, const char *format, ...); +using msgIdAndTxtFp = void (*)(const char *, const char *, ...); // clang-format on diff --git a/include/amici/edata.h b/include/amici/edata.h index 31db0c88ba..fdc659d8d7 100644 --- a/include/amici/edata.h +++ b/include/amici/edata.h @@ -16,7 +16,7 @@ class ExpData { public: /** @brief default constructor */ - ExpData(); + ExpData() = default; /** @brief Copy constructor, needs to be declared to be generated in * swig*/ @@ -208,7 +208,7 @@ class ExpData { * * @param stdDev standard deviation (dimension: scalar) */ - void setObservedDataStdDev(const realtype stdDev); + void setObservedDataStdDev(realtype stdDev); /** * @brief set function that copies standard deviation of observed data for @@ -228,7 +228,7 @@ class ExpData { * @param stdDev standard deviation (dimension: scalar) * @param iy observed data index */ - void setObservedDataStdDev(const realtype stdDev, int iy); + void setObservedDataStdDev(realtype stdDev, int iy); /** * @brief get function that checks whether standard deviation of data at @@ -316,7 +316,7 @@ class ExpData { * * @param stdDev standard deviation (dimension: scalar) */ - void setObservedEventsStdDev(const realtype stdDev); + void setObservedEventsStdDev(realtype stdDev); /** * @brief set function that copies standard deviation of observed data for @@ -337,7 +337,7 @@ class ExpData { * @param stdDev standard deviation (dimension: scalar) * @param iz observed data index */ - void setObservedEventsStdDev(const realtype stdDev, int iz); + void setObservedEventsStdDev(realtype stdDev, int iz); /** * @brief get function that checks whether standard deviation of even data @@ -396,7 +396,7 @@ class ExpData { std::vector pscale; /** @brief condition-specific parameter list */ std::vector plist; - + /** * @brief duration of pre-simulation * if this is > 0, presimualation will be performed from @@ -442,13 +442,13 @@ class ExpData { const char *fieldname) const; /** @brief number of observables */ - int nytrue_; + int nytrue_{0}; /** @brief number of event observables */ - int nztrue_; + int nztrue_{0}; /** @brief maximal number of event occurences */ - int nmaxevent_; + int nmaxevent_{0}; /** @brief observation timepoints (dimension: nt) */ std::vector ts; @@ -482,7 +482,7 @@ void checkSigmaPositivity(std::vector const &sigmaVector, * @param sigma input to be checked * @param sigmaName name of the input */ -void checkSigmaPositivity(const realtype sigma, const char *sigmaName); +void checkSigmaPositivity(realtype sigma, const char *sigmaName); /** * @brief The ConditionContext class applies condition-specific amici::Model @@ -526,7 +526,7 @@ class ConditionContext { std::vector originalTimepoints; std::vector originalParameterList; std::vector originalScaling; - + }; } // namespace amici diff --git a/include/amici/exception.h b/include/amici/exception.h index 648ebd8b8c..5417304692 100644 --- a/include/amici/exception.h +++ b/include/amici/exception.h @@ -4,217 +4,166 @@ #include "amici/defines.h" // necessary for realtype #include -#include -#include -#include -#include -#if defined(_WIN32) - #define PLATFORM_WINDOWS // Windows -#elif defined(_WIN64) - #define PLATFORM_WINDOWS // Windows -#elif defined(__CYGWIN__) && !defined(_WIN32) - #define PLATFORM_WINDOWS // Windows (Cygwin POSIX under Microsoft Window) -#else - #include - #include // for dladdr - #include // for __cxa_demangle -#endif namespace amici { - /** @brief amici exception handler class - * - * has a printf style interface to allow easy generation of error messages +/** + * @brief AMICI exception class + * + * Has a printf style interface to allow easy generation of error messages + */ +class AmiException : public std::exception { +public: + /** + * @brief Constructor with printf style interface + * @param fmt error message with printf format + * @param ... printf formating variables */ - class AmiException : public std::exception { - public: - AmiException(char const* fmt, ...) : std::exception() { - /** constructor with printf style interface - * @param fmt error message with printf format - * @param ... printf formating variables - */ - va_list ap; - va_start(ap, fmt); - vsnprintf(msg, sizeof(msg), fmt, ap); - va_end(ap); - storeBacktrace(12); - } - - AmiException(const AmiException& old) { - /** copy constructor - * @param old object to copy from - */ - snprintf(msg, sizeof(msg), "%s", old.msg); - snprintf(trace, sizeof(trace), "%s", old.trace); - } - - /** override of default error message function - * @return msg error message - */ - const char* what() const throw() { - return msg; - } - - /** returns the stored backtrace - * @return trace backtrace - */ - const char *getBacktrace() const { - return trace; - } - - /** stores the current backtrace - * @param nMaxFrames number of frams to go back in stacktrace - */ - void storeBacktrace(const int nMaxFrames) { - std::ostringstream trace_buf; - - #ifdef PLATFORM_WINDOWS - - trace_buf << "stacktrace not available on windows platforms\n"; - - #else - - void *callstack[nMaxFrames]; - char buf[1024]; - int nFrames = backtrace(callstack, nMaxFrames); - char **symbols = backtrace_symbols(callstack, nFrames); - - for (int i = 2; i < nFrames; - i++) { // start at 2 to omit AmiException and storeBacktrace - // call - Dl_info info; - if (dladdr(callstack[i], &info) && info.dli_sname) { - char *demangled = NULL; - int status = -1; - if (info.dli_sname[0] == '_') - demangled = abi::__cxa_demangle(info.dli_sname, NULL, 0, - &status); - snprintf(buf, sizeof(buf), "%-3d %*p %s + %zd\n", i - 2, - int(2 + sizeof(void *) * 2), callstack[i], - status == 0 ? demangled - : info.dli_sname == 0 ? symbols[i] - : info.dli_sname, - static_cast((char *)callstack[i] - - (char *)info.dli_saddr)); - free(demangled); - } else { - snprintf(buf, sizeof(buf), "%-3d %*p %s\n", i - 2, - int(2 + sizeof(void *) * 2), callstack[i], - symbols[i]); - } - trace_buf << buf; - } - free(symbols); - if (nFrames == nMaxFrames) - trace_buf << "[truncated]\n"; - - #endif - - snprintf(trace, sizeof(trace), "%s", trace_buf.str().c_str()); - } - - private: - char msg[500]; - char trace[500]; - }; - - /** @brief cvode exception handler class + AmiException(char const* fmt, ...); + + /** + * @brief Copy constructor + * @param old object to copy from + */ + AmiException(const AmiException& old); + + /** + * @brief Override of default error message function + * @return msg error message + */ + const char* what() const noexcept override; + + /** + * @brief Returns the stored backtrace + * @return trace backtrace */ - class CvodeException : public AmiException { - public: - /** constructor - * @param error_code error code returned by cvode function - * @param function cvode function name - */ - CvodeException(const int error_code, const char *function) : - AmiException("Cvode routine %s failed with error code %i",function,error_code){} - }; - - /** @brief ida exception handler class + const char *getBacktrace() const; + + /** + * @brief Stores the current backtrace + * @param nMaxFrames number of frames to go back in stacktrace + */ + void storeBacktrace(int nMaxFrames); + +private: + char msg[500]{}; + char trace[500]{}; +}; + + +/** + * @brief cvode exception handler class + */ +class CvodeException : public AmiException { +public: + /** + * @brief Constructor + * @param error_code error code returned by cvode function + * @param function cvode function name + */ + CvodeException(int error_code, const char *function); +}; + + +/** + * @brief ida exception handler class + */ +class IDAException : public AmiException { +public: + /** + * @brief Constructor + * @param error_code error code returned by ida function + * @param function ida function name */ - class IDAException : public AmiException { - public: - /** constructor - * @param error_code error code returned by ida function - * @param function ida function name - */ - IDAException(const int error_code, const char *function) : - AmiException("IDA routine %s failed with error code %i",function,error_code){} - }; - - /** @brief integration failure exception for the forward problem - * this exception should be thrown when an integration failure occured - * for this exception we can assume that we can recover from the exception - * and return a solution struct to the user + IDAException(int error_code, const char *function); +}; + + +/** + * @brief Integration failure exception for the forward problem + * + * This exception should be thrown when an integration failure occured + * for this exception we can assume that we can recover from the exception + * and return a solution struct to the user + */ +class IntegrationFailure : public AmiException { + public: + /** + * @brief Constructor + * @param code error code returned by cvode/ida + * @param t time of integration failure */ - class IntegrationFailure : public AmiException { - public: - /** error code returned by cvode/ida */ - int error_code; - /** time of integration failure */ - realtype time; - /** constructor - * @param code error code returned by cvode/ida - * @param t time of integration failure - */ - IntegrationFailure(int code, realtype t) : - AmiException("AMICI failed to integrate the forward problem"), - error_code(code), time(t) {} - }; - - /** @brief integration failure exception for the backward problem - * this exception should be thrown when an integration failure occured - * for this exception we can assume that we can recover from the exception - * and return a solution struct to the user + IntegrationFailure(int code, realtype t); + + /** error code returned by cvodes/idas */ + int error_code; + + /** time of integration failure */ + realtype time; +}; + + +/** + * @brief Integration failure exception for the backward problem + * + * This exception should be thrown when an integration failure occured + * for this exception we can assume that we can recover from the exception + * and return a solution struct to the user + */ +class IntegrationFailureB : public AmiException { + public: + /** + * @brief Constructor + * @param code error code returned by cvode/ida + * @param t time of integration failure */ - class IntegrationFailureB : public AmiException { - public: - /** error code returned by cvode/ida */ - int error_code; - /** time of integration failure */ - realtype time; - /** constructor - * @param code error code returned by cvode/ida - * @param t time of integration failure - */ - IntegrationFailureB(int code, realtype t) : - AmiException("AMICI failed to integrate the backward problem"), - error_code(code), time(t) {} - }; - - /** @brief setup failure exception - * this exception should be thrown when the solver setup failed - * for this exception we can assume that we cannot recover from the exception - * and an error will be thrown + IntegrationFailureB(int code, realtype t); + + /** error code returned by cvode/ida */ + int error_code; + + /** time of integration failure */ + realtype time; +}; + + +/** + * @brief Setup failure exception + * + * This exception should be thrown when the solver setup failed + * for this exception we can assume that we cannot recover from the exception + * and an error will be thrown + */ +class SetupFailure : public AmiException { +public: + /** + * @brief Constructor, simply calls AmiException constructor + * @param msg */ - class SetupFailure : public AmiException { - public: - /** constructor, simply calls AmiException constructor - * @param msg - */ - explicit SetupFailure(const char *msg) : AmiException(msg) {} - }; - - /** @brief newton failure exception - * this exception should be thrown when the steady state computation - * failed to converge for this exception we can assume that we can - * recover from the exception and return a solution struct to the user + explicit SetupFailure(const char *msg) : AmiException(msg) {} +}; + + +/** + * @brief Newton failure exception + * + * This exception should be thrown when the steady state computation + * failed to converge for this exception we can assume that we can + * recover from the exception and return a solution struct to the user + */ +class NewtonFailure : public AmiException { +public: + /** + * @brief Constructor, simply calls AmiException constructor + * @param function name of the function in which the error occured + * @param code error code */ - class NewtonFailure : public AmiException { - public: - /** error code returned by solver */ - int error_code; - /** constructor, simply calls AmiException constructor - * @param function name of the function in which the error occured - * @param code error code - */ - NewtonFailure(int code, const char *function) : - AmiException("NewtonSolver routine %s failed with error code %i",function,code) { - error_code = code; - } - }; - - -} + NewtonFailure(int code, const char *function); + + /** error code returned by solver */ + int error_code; +}; + +} // namespace amici #endif /* amici_exception_h */ diff --git a/include/amici/forwardproblem.h b/include/amici/forwardproblem.h index 05687b1adf..612a7a6448 100644 --- a/include/amici/forwardproblem.h +++ b/include/amici/forwardproblem.h @@ -163,10 +163,13 @@ class ForwardProblem { /** pointer to model instance */ Model *model; + /** pointer to return data instance */ ReturnData *rdata; + /** pointer to solver instance */ Solver *solver; + /** pointer to experimental data instance */ const ExpData *edata; @@ -178,7 +181,7 @@ class ForwardProblem { void updateAndReinitStatesAndSensitivities(bool isSteadystate); - void handlePresimulation(int *ncheck); + void handlePresimulation(); /** * @brief Execute everything necessary for the handling of events @@ -186,7 +189,7 @@ class ForwardProblem { * @param tlastroot pointer to the timepoint of the last event */ - void handleEvent(realtype *tlastroot,const bool seflag); + void handleEvent(realtype *tlastroot,bool seflag); /** * @brief Evaluates the Jacobian and differential equation right hand side, @@ -258,11 +261,14 @@ class ForwardProblem { /** array of index which root has been found * (dimension: ne * ne * nmaxevent, ordering = ?) */ std::vector rootidx; + /** array of number of found roots for a certain event type * (dimension: ne) */ std::vector nroots; + /** array of values of the root function (dimension: ne) */ std::vector rootvals; + /** temporary rootval storage to check crossing in secondary event * (dimension: ne) */ std::vector rvaltmp; @@ -270,6 +276,7 @@ class ForwardProblem { /** array containing the time-points of discontinuities * (dimension: nmaxevent x ne, ordering = ?) */ std::vector discs; + /** array containing the index of discontinuities * (dimension: nmaxevent x ne, ordering = ?) */ std::vector irdiscs; @@ -281,9 +288,11 @@ class ForwardProblem { /** array of state vectors at discontinuities * (dimension nx x nMaxEvent * ne, ordering =?) */ AmiVectorArray x_disc; + /** array of differential state vectors at discontinuities * (dimension nx x nMaxEvent * ne, ordering =?) */ AmiVectorArray xdot_disc; + /** array of old differential state vectors at discontinuities * (dimension nx x nMaxEvent * ne, ordering =?) */ AmiVectorArray xdot_old_disc; @@ -291,6 +300,7 @@ class ForwardProblem { /** state derivative of data likelihood * (dimension nJ x nx x nt, ordering =?) */ std::vector dJydx; + /** state derivative of event likelihood * (dimension nJ x nx x nMaxEvent, ordering =?) */ std::vector dJzdx; @@ -312,27 +322,33 @@ class ForwardProblem { /** state vector (dimension: nx_solver) */ AmiVector x; + /** state vector, including states eliminated from conservation laws * (dimension: nx) */ AmiVector x_rdata; + /** old state vector (dimension: nx_solver) */ AmiVector x_old; + /** differential state vector (dimension: nx_solver) */ AmiVector dx; + /** old differential state vector (dimension: nx_solver) */ AmiVector dx_old; + /** time derivative state vector (dimension: nx_solver) */ AmiVector xdot; + /** old time derivative state vector (dimension: nx_solver) */ AmiVector xdot_old; - - /** sensitivity state vector array (dimension: nx_cl x nplist, row-major) */ AmiVectorArray sx; + /** full sensitivity state vector array, including states eliminated from * conservation laws (dimension: nx x nplist, row-major) */ AmiVectorArray sx_rdata; + /** differential sensitivity state vector array * (dimension: nx_cl x nplist, row-major) */ AmiVectorArray sdx; diff --git a/include/amici/misc.h b/include/amici/misc.h index 629703aad5..dc2aca7a3d 100644 --- a/include/amici/misc.h +++ b/include/amici/misc.h @@ -8,53 +8,34 @@ #include #include -namespace amici { +#include -/** Checks the values in an array for NaNs and Infs - * - * @param array array - * @param fun name of calling function - * @return AMICI_RECOVERABLE_ERROR if a NaN/Inf value was found, AMICI_SUCCESS otherwise - */ -int checkFinite(std::vector const& array, const char* fun); +namespace amici { -/** Checks the values in an array for NaNs and Infs +/** + * @brief Checks the values in an array for NaNs and Infs * - * @param N number of elements in array * @param array array * @param fun name of calling function * @return AMICI_RECOVERABLE_ERROR if a NaN/Inf value was found, AMICI_SUCCESS otherwise */ -int checkFinite(const int N, const realtype *array, const char* fun); +int checkFinite(gsl::span array, const char* fun); /** * @brief Remove parameter scaling according to the parameter scaling in pscale * + * All vectors must be of same length. + * * @param bufferScaled scaled parameters * @param pscale parameter scaling - * @param n number of elements in bufferScaled, pscale and bufferUnscaled * @param bufferUnscaled unscaled parameters are written to the array * * @return status flag indicating success of execution @type int */ -void unscaleParameters(const double *bufferScaled, - const ParameterScaling *pscale, - int n, - double *bufferUnscaled); - -/** - * @brief Remove parameter scaling according to the parameter scaling in pscale. - * - * All vectors must be of same length - * - * @param bufferScaled scaled parameters - * @param pscale parameter scaling - * @param bufferUnscaled unscaled parameters are written to the array - */ -void unscaleParameters(std::vector const& bufferScaled, - std::vector const& pscale, - std::vector & bufferUnscaled); +void unscaleParameters(gsl::span bufferScaled, + gsl::span pscale, + gsl::span bufferUnscaled); /** * @brief Remove parameter scaling according to `scaling` @@ -82,9 +63,17 @@ double getScaledParameter(double unscaledParameter, ParameterScaling scaling); * @param pscale parameter scaling * @param bufferScaled destination */ -void scaleParameters(const std::vector &bufferUnscaled, - const std::vector &pscale, - std::vector &bufferScaled); +void scaleParameters(gsl::span bufferUnscaled, + gsl::span pscale, + gsl::span bufferScaled); + +/** + * @brief Returns the current backtrace as std::string + * @param maxFrames Number of frames to include + * @return Backtrace + */ +std::string backtraceString(int maxFrames); + } // namespace amici diff --git a/include/amici/model.h b/include/amici/model.h index 9e2169761c..3b954dae10 100644 --- a/include/amici/model.h +++ b/include/amici/model.h @@ -22,7 +22,7 @@ class Solver; namespace boost { namespace serialization { template -void serialize(Archive &ar, amici::Model &u, const unsigned int version); +void serialize(Archive &ar, amici::Model &u, unsigned int version); } } // namespace boost @@ -39,7 +39,7 @@ class Model : public AbstractModel { Model(); /** - * Constructor with model dimensions + * @brief Constructor with model dimensions * @param nx_rdata number of state variables * @param nxtrue_rdata number of state variables of the non-augmented model * @param nx_solver number of state variables with conservation laws applied @@ -69,18 +69,18 @@ class Model : public AbstractModel { * @param idlist indexes indicating algebraic components (DAE only) * @param z2event mapping of event outputs to events */ - Model(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, - const int nxtrue_solver, const int ny, const int nytrue, const int nz, - const int nztrue, const int ne, const int nJ, const int nw, - const int ndwdx, const int ndwdp, const int ndxdotdw, - std::vector ndJydy, const int nnz, - const int ubw, const int lbw, amici::SecondOrderMode o2mode, + Model(int nx_rdata, int nxtrue_rdata, int nx_solver, + int nxtrue_solver, int ny, int nytrue, int nz, + int nztrue, int ne, int nJ, int nw, + int ndwdx, int ndwdp, int ndxdotdw, + std::vector ndJydy, int nnz, + int ubw, int lbw, amici::SecondOrderMode o2mode, const std::vector &p, std::vector k, const std::vector &plist, std::vector idlist, std::vector z2event); /** destructor */ - virtual ~Model() = default; + ~Model() override = default; /** * Copy assignment is disabled until const members are removed @@ -135,29 +135,6 @@ class Model : public AbstractModel { using AbstractModel::fy; using AbstractModel::fz; - /** - * Model specific implementation of fdJydy colptrs - * @param indexptrs column pointers - * @param index ytrue index - */ - virtual void fdJydy_colptrs(sunindextype *indexptrs, int index) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** - * Model specific implementation of fdxdotdw row vals - * @param indexptrs row val pointers - * @param index ytrue index - */ - virtual void fdJydy_rowvals(sunindextype *indexptrs, int index) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** * Expands conservation law for states * @param x_rdata pointer to state variables with conservation laws @@ -165,7 +142,7 @@ class Model : public AbstractModel { * @param x_solver pointer to state variables with conservation laws * applied (solver returns this) */ - void fx_rdata(AmiVector *x_rdata, const AmiVector *x_solver); + void fx_rdata(AmiVector &x_rdata, const AmiVector &x_solver); /** * Expands conservation law for state sensitivities @@ -174,26 +151,26 @@ class Model : public AbstractModel { * @param sx_solver pointer to state variable sensitivities with * conservation laws applied (solver returns this) */ - void fsx_rdata(AmiVectorArray *sx_rdata, const AmiVectorArray *sx_solver); + void fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx_solver); /** * Initial states * @param x pointer to state variables */ - void fx0(AmiVector *x); + void fx0(AmiVector &x); /** * Sets only those initial states that are specified via fixedParmeters * @param x pointer to state variables */ - void fx0_fixedParameters(AmiVector *x); + void fx0_fixedParameters(AmiVector &x); /** * Initial value for initial state sensitivities * @param sx pointer to state sensitivity variables * @param x pointer to state variables **/ - void fsx0(AmiVectorArray *sx, const AmiVector *x); + void fsx0(AmiVectorArray &sx, const AmiVector &x); /** * Sets only those initial states sensitivities that are affected from fx0 @@ -201,7 +178,7 @@ class Model : public AbstractModel { * @param sx pointer to state sensitivity variables * @param x pointer to state variables **/ - void fsx0_fixedParameters(AmiVectorArray *sx, const AmiVector *x); + void fsx0_fixedParameters(AmiVectorArray &sx, const AmiVector &x); /** * Sensitivity of derivative initial states sensitivities sdx0 (only @@ -216,8 +193,8 @@ class Model : public AbstractModel { * @param x pointer to state variables * @param sx pointer to state sensitivity variables */ - void fstau(const realtype t, const int ie, const AmiVector *x, - const AmiVectorArray *sx); + void fstau(realtype t, int ie, const AmiVector &x, + const AmiVectorArray &sx); /** * Observables / measurements @@ -226,7 +203,7 @@ class Model : public AbstractModel { * @param x current state * @param rdata pointer to return data instance */ - void fy(const realtype t, const int it, const AmiVector *x, + void fy(realtype t, int it, const AmiVector &x, ReturnData *rdata); /** @@ -234,14 +211,14 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdydp(const realtype t, const AmiVector *x); + void fdydp(realtype t, const AmiVector &x); /** * Partial derivative of observables y w.r.t. state variables x * @param t current timepoint * @param x current state */ - void fdydx(const realtype t, const AmiVector *x); + void fdydx(realtype t, const AmiVector &x); /** Event-resolved output * @param nroots number of events for event index @@ -250,8 +227,8 @@ class Model : public AbstractModel { * @param x current state * @param rdata pointer to return data instance */ - void fz(const int nroots, const int ie, const realtype t, - const AmiVector *x, ReturnData *rdata); + void fz(int nroots, int ie, realtype t, + const AmiVector &x, ReturnData *rdata); /** Sensitivity of z, total derivative * @param nroots number of events for event index @@ -261,8 +238,8 @@ class Model : public AbstractModel { * @param sx current state sensitivities * @param rdata pointer to return data instance */ - void fsz(const int nroots, const int ie, const realtype t, - const AmiVector *x, const AmiVectorArray *sx, ReturnData *rdata); + void fsz(int nroots, int ie, realtype t, + const AmiVector &x, const AmiVectorArray &sx, ReturnData *rdata); /** * Event root function of events (equal to froot but does not include @@ -273,8 +250,8 @@ class Model : public AbstractModel { * @param x current state * @param rdata pointer to return data instance */ - void frz(const int nroots, const int ie, const realtype t, - const AmiVector *x, ReturnData *rdata); + void frz(int nroots, int ie, realtype t, + const AmiVector &x, ReturnData *rdata); /** * Sensitivity of rz, total derivative @@ -285,8 +262,8 @@ class Model : public AbstractModel { * @param sx current state sensitivities * @param rdata pointer to return data instance */ - void fsrz(const int nroots, const int ie, const realtype t, - const AmiVector *x, const AmiVectorArray *sx, ReturnData *rdata); + void fsrz(int nroots, int ie, realtype t, + const AmiVector &x, const AmiVectorArray &sx, ReturnData *rdata); /** * Partial derivative of event-resolved output z w.r.t. to model parameters @@ -295,7 +272,7 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdzdp(const realtype t, const int ie, const AmiVector *x); + void fdzdp(realtype t, int ie, const AmiVector &x); /** * Partial derivative of event-resolved output z w.r.t. to model states x @@ -303,7 +280,7 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdzdx(const realtype t, const int ie, const AmiVector *x); + void fdzdx(realtype t, int ie, const AmiVector &x); /** * Sensitivity of event-resolved root output w.r.t. to model parameters p @@ -311,7 +288,7 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdrzdp(const realtype t, const int ie, const AmiVector *x); + void fdrzdp(realtype t, int ie, const AmiVector &x); /** * Sensitivity of event-resolved measurements rz w.r.t. to model states x @@ -319,7 +296,7 @@ class Model : public AbstractModel { * @param t current timepoint * @param x current state */ - void fdrzdx(const realtype t, const int ie, const AmiVector *x); + void fdrzdx(realtype t, int ie, const AmiVector &x); /** * State update functions for events @@ -329,8 +306,8 @@ class Model : public AbstractModel { * @param xdot current residual function values * @param xdot_old value of residual function before event */ - void fdeltax(const int ie, const realtype t, const AmiVector *x, - const AmiVector *xdot, const AmiVector *xdot_old); + void fdeltax(int ie, realtype t, const AmiVector &x, + const AmiVector &xdot, const AmiVector &xdot_old); /** * Sensitivity update functions for events, total derivative @@ -341,9 +318,9 @@ class Model : public AbstractModel { * @param xdot current residual function values * @param xdot_old value of residual function before event */ - void fdeltasx(const int ie, const realtype t, const AmiVector *x, - const AmiVectorArray *sx, const AmiVector *xdot, - const AmiVector *xdot_old); + void fdeltasx(int ie, realtype t, const AmiVector &x, + const AmiVectorArray &sx, const AmiVector &xdot, + const AmiVector &xdot_old); /** * Adjoint state update functions for events @@ -354,9 +331,9 @@ class Model : public AbstractModel { * @param xdot current residual function values * @param xdot_old value of residual function before event */ - void fdeltaxB(const int ie, const realtype t, const AmiVector *x, - const AmiVector *xB, const AmiVector *xdot, - const AmiVector *xdot_old); + void fdeltaxB(int ie, realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, + const AmiVector &xdot_old); /** * Quadrature state update functions for events @@ -367,9 +344,9 @@ class Model : public AbstractModel { * @param xdot current residual function values * @param xdot_old value of residual function before event */ - void fdeltaqB(const int ie, const realtype t, const AmiVector *x, - const AmiVector *xB, const AmiVector *xdot, - const AmiVector *xdot_old); + void fdeltaqB(int ie, realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, + const AmiVector &xdot_old); /** * Standard deviation of measurements @@ -377,7 +354,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fsigmay(const int it, ReturnData *rdata, const ExpData *edata); + void fsigmay(int it, ReturnData *rdata, const ExpData *edata); /** * Partial derivative of standard deviation of measurements w.r.t. model @@ -385,7 +362,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to ExpData data instance holding sigma values */ - void fdsigmaydp(const int it, ReturnData *rdata, const ExpData *edata); + void fdsigmaydp(int it, ReturnData *rdata, const ExpData *edata); /** * Standard deviation of events @@ -395,7 +372,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fsigmaz(const realtype t, const int ie, const int *nroots, + void fsigmaz(realtype t, int ie, const int *nroots, ReturnData *rdata, const ExpData *edata); /** @@ -407,7 +384,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdsigmazdp(const realtype t, const int ie, const int *nroots, + void fdsigmazdp(realtype t, int ie, const int *nroots, ReturnData *rdata, const ExpData *edata); /** @@ -416,7 +393,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fJy(const int it, ReturnData *rdata, const ExpData *edata); + void fJy(int it, ReturnData *rdata, const ExpData *edata); /** * Negative log-likelihood of event-resolved measurements z @@ -424,7 +401,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fJz(const int nroots, ReturnData *rdata, const ExpData *edata); + void fJz(int nroots, ReturnData *rdata, const ExpData *edata); /** * Regularization of negative log-likelihood with roots of event-resolved @@ -433,7 +410,21 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fJrz(const int nroots, ReturnData *rdata, const ExpData *edata); + void fJrz(int nroots, ReturnData *rdata, const ExpData *edata); + + /** + * Model specific implementation of fdJydy colptrs + * @param indexptrs column pointers + * @param index ytrue index + */ + virtual void fdJydy_colptrs(sunindextype *indexptrs, int index); + + /** + * Model specific implementation of fdxdotdw row vals + * @param indexptrs row val pointers + * @param index ytrue index + */ + virtual void fdJydy_rowvals(sunindextype *indexptrs, int index); /** * Partial derivative of time-resolved measurement negative log-likelihood @@ -442,7 +433,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJydy(const int it, const ReturnData *rdata, const ExpData *edata); + void fdJydy(int it, const ReturnData *rdata, const ExpData *edata); /** * Sensitivity of time-resolved measurement negative log-likelihood Jy @@ -451,7 +442,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJydsigma(const int it, const ReturnData *rdata, + void fdJydsigma(int it, const ReturnData *rdata, const ExpData *edata); /** @@ -460,7 +451,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJzdz(const int nroots, const ReturnData *rdata, + void fdJzdz(int nroots, const ReturnData *rdata, const ExpData *edata); /** @@ -470,7 +461,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJzdsigma(const int nroots, const ReturnData *rdata, + void fdJzdsigma(int nroots, const ReturnData *rdata, const ExpData *edata); /** @@ -479,7 +470,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJrzdz(const int nroots, const ReturnData *rdata, + void fdJrzdz(int nroots, const ReturnData *rdata, const ExpData *edata); /** @@ -489,7 +480,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @param edata pointer to experimental data instance */ - void fdJrzdsigma(const int nroots, const ReturnData *rdata, + void fdJrzdsigma(int nroots, const ReturnData *rdata, const ExpData *edata); /** @@ -498,7 +489,7 @@ class Model : public AbstractModel { * @param sx pointer to state sensitivities * @param rdata pointer to return data instance */ - void fsy(const int it, const AmiVectorArray *sx, ReturnData *rdata); + void fsy(int it, const AmiVectorArray &sx, ReturnData *rdata); /** * Sensitivity of z at final timepoint (ignores sensitivity of timepoint), @@ -507,7 +498,7 @@ class Model : public AbstractModel { * @param ie event index * @param rdata pointer to return data instance */ - void fsz_tf(const int *nroots, const int ie, ReturnData *rdata); + void fsz_tf(const int *nroots, int ie, ReturnData *rdata); /** * Sensitivity of time-resolved measurement negative log-likelihood Jy, @@ -517,8 +508,8 @@ class Model : public AbstractModel { * @param dJydx vector with values of state derivative of Jy * @param rdata pointer to return data instance */ - void fsJy(const int it, const std::vector &dJydx, - const AmiVectorArray *sx, ReturnData *rdata); + void fsJy(int it, const std::vector &dJydx, + const AmiVectorArray &sx, ReturnData *rdata); /** * Compute sensitivity of time-resolved measurement negative log-likelihood @@ -528,7 +519,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fdJydp(const int it, ReturnData *rdata, const ExpData *edata); + void fdJydp(int it, ReturnData *rdata, const ExpData *edata); /** * Sensitivity of time-resolved measurement negative log-likelihood Jy @@ -537,7 +528,7 @@ class Model : public AbstractModel { * @param it timepoint index * @param edata pointer to experimental data instance */ - void fdJydx(std::vector &dJydx, const int it, + void fdJydx(std::vector &dJydx, int it, const ExpData *edata); /** @@ -548,8 +539,8 @@ class Model : public AbstractModel { * @param sx pointer to state sensitivities * @param rdata pointer to return data instance */ - void fsJz(const int nroots, const std::vector &dJzdx, - const AmiVectorArray *sx, ReturnData *rdata); + void fsJz(int nroots, const std::vector &dJzdx, + const AmiVectorArray &sx, ReturnData *rdata); /** * Sensitivity of event-resolved measurement negative log-likelihood Jz @@ -559,7 +550,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fdJzdp(const int nroots, realtype t, const ExpData *edata, + void fdJzdp(int nroots, realtype t, const ExpData *edata, const ReturnData *rdata); /** @@ -571,7 +562,7 @@ class Model : public AbstractModel { * @param edata pointer to experimental data instance * @param rdata pointer to return data instance */ - void fdJzdx(std::vector *dJzdx, const int nroots, realtype t, + void fdJzdx(std::vector *dJzdx, int nroots, realtype t, const ExpData *edata, const ReturnData *rdata); /** @@ -584,21 +575,29 @@ class Model : public AbstractModel { * @param computeSensitivities flag indicating whether sensitivities * are to be computed */ - void initialize(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, - AmiVectorArray *sdx, bool computeSensitivities); + void initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx, + AmiVectorArray &sdx, bool computeSensitivities); + + /** + * Initialization of model properties + * @param xB adjoint state variables + * @param dxB time derivative of adjoint states (DAE only) + * @param xQB adjoint quadratures + */ + void initializeB(AmiVector &xB, AmiVector &dxB, AmiVector &xQB); /** * Initialization of initial states * @param x pointer to state variables */ - void initializeStates(AmiVector *x); + void initializeStates(AmiVector &x); /** * Initialization of initial state sensitivities * @param sx pointer to state variable sensititivies * @param x pointer to state variables */ - void initializeStateSensitivities(AmiVectorArray *sx, AmiVector *x); + void initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x); /** * Initialises the heaviside variables h at the intial time t0 @@ -606,7 +605,7 @@ class Model : public AbstractModel { * @param x pointer to state variables * @param dx pointer to time derivative of states (DAE only) */ - void initHeaviside(AmiVector *x, AmiVector *dx); + void initHeaviside(AmiVector &x, AmiVector &dx); /** * @brief Number of parameters wrt to which sensitivities are computed @@ -871,7 +870,7 @@ class Model : public AbstractModel { * @param t timepoint * @param x array with the states */ - void fw(const realtype t, const realtype *x); + void fw(realtype t, const realtype *x); /** * @brief Recurring terms in xdot, parameter derivative @@ -880,14 +879,14 @@ class Model : public AbstractModel { * @return flag indicating whether dwdp will be returned in dense storage * dense: true, sparse: false */ - void fdwdp(const realtype t, const realtype *x); + void fdwdp(realtype t, const realtype *x); /** * @brief Recurring terms in xdot, state derivative * @param t timepoint * @param x array with the states */ - void fdwdx(const realtype t, const realtype *x); + void fdwdx(realtype t, const realtype *x); /** * Residual function @@ -895,14 +894,14 @@ class Model : public AbstractModel { * @param rdata ReturnData instance to which result will be written * @param edata ExpData instance containing observable data */ - void fres(const int it, ReturnData *rdata, const ExpData *edata); + void fres(int it, ReturnData *rdata, const ExpData *edata); /** * Chi-squared function * @param it time index * @param rdata ReturnData instance to which result will be written */ - void fchi2(const int it, ReturnData *rdata); + void fchi2(int it, ReturnData *rdata); /** * Residual sensitivity function @@ -910,14 +909,14 @@ class Model : public AbstractModel { * @param rdata ReturnData instance to which result will be written * @param edata ExpData instance containing observable data */ - void fsres(const int it, ReturnData *rdata, const ExpData *edata); + void fsres(int it, ReturnData *rdata, const ExpData *edata); /** * Fisher information matrix function * @param it time index * @param rdata ReturnData instance to which result will be written */ - void fFIM(const int it, ReturnData *rdata); + void fFIM(int it, ReturnData *rdata); /** * Update the heaviside variables h on event occurences @@ -940,12 +939,12 @@ class Model : public AbstractModel { /** * @brief Serialize Model (see boost::serialization::serialize) * @param ar Archive to serialize to - * @param r Data to serialize + * @param u Data to serialize * @param version Version number */ template - friend void boost::serialization::serialize(Archive &ar, Model &r, - const unsigned int version); + friend void boost::serialization::serialize(Archive &ar, Model &u, + unsigned int version); /** * @brief Check equality of data members @@ -961,18 +960,19 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @return current timepoint */ - realtype gett(const int it, const ReturnData *rdata) const; + realtype gett(int it, const ReturnData *rdata) const; /** * @brief Check if the given array has only finite elements. + * * If not try to give hints by which other fields this could be caused. - * @param N number of datapoints in array + * * @param array arrays of values * @param fun name of the fucntion that generated the values * @return AMICI_RECOVERABLE_ERROR if a NaN/Inf value was found, * AMICI_SUCCESS otherwise */ - int checkFinite(const int N, const realtype *array, const char *fun) const; + int checkFinite(gsl::span array, const char *fun) const; /** * @brief Reports whether the model has parameter names set. @@ -1130,7 +1130,7 @@ class Model : public AbstractModel { * simulation * @param mode steadyStateSensitivityMode */ - void setSteadyStateSensitivityMode(const SteadyStateSensitivityMode mode); + void setSteadyStateSensitivityMode(SteadyStateSensitivityMode mode); /** * @brief Gets the mode how sensitivities are computed in the steadystate @@ -1176,49 +1176,69 @@ class Model : public AbstractModel { } /** number of states */ - const int nx_rdata; + int nx_rdata{0}; + /** number of states in the unaugmented system */ - const int nxtrue_rdata; + int nxtrue_rdata{0}; + /** number of states with conservation laws applied */ - const int nx_solver; + int nx_solver{0}; + /** number of states in the unaugmented system with conservation laws * applied */ - const int nxtrue_solver; + int nxtrue_solver{0}; + /** number of observables */ - const int ny; + int ny{0}; + /** number of observables in the unaugmented system */ - const int nytrue; + int nytrue{0}; + /** number of event outputs */ - const int nz; + int nz{0}; + /** number of event outputs in the unaugmented system */ - const int nztrue; + int nztrue{0}; + /** number of events */ - const int ne; + int ne{0}; + /** number of common expressions */ - const int nw; + int nw{0}; + /** number of derivatives of common expressions wrt x */ - const int ndwdx; + int ndwdx{0}; + /** number of derivatives of common expressions wrt p */ - const int ndwdp; + int ndwdp{0}; + /** number of nonzero entries in dxdotdw */ - const int ndxdotdw; + int ndxdotdw{0}; + /** number of nonzero entries in dJydy */ std::vector ndJydy; + /** number of nonzero entries in jacobian */ - const int nnz; + int nnz{0}; + /** dimension of the augmented objective function for 2nd order ASA */ - const int nJ; + int nJ{0}; + /** upper bandwith of the jacobian */ - const int ubw; + int ubw{0}; + /** lower bandwith of the jacobian */ - const int lbw; + int lbw{0}; + /** flag indicating whether for sensi == AMICI_SENSI_ORDER_SECOND * directional or full second order derivative will be computed */ - const SecondOrderMode o2mode; + SecondOrderMode o2mode{SecondOrderMode::none}; + /** index indicating to which event an event output belongs */ - const std::vector z2event; + std::vector z2event; + /** flag array for DAE equations */ - const std::vector idlist; + std::vector idlist; /** data standard deviation for current timepoint (dimension: ny) */ std::vector sigmay; @@ -1285,7 +1305,7 @@ class Model : public AbstractModel { * @param ip sensitivity index **/ virtual void fsx_rdata(realtype *sx_rdata, const realtype *sx_solver, - const realtype *stcl, const int ip); + const realtype *stcl, int ip); /** * Model specific implementation of fx_solver @@ -1319,40 +1339,40 @@ class Model : public AbstractModel { * @param ip sensitivity index **/ virtual void fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, - const int ip); + int ip); /** create my slice at timepoint * @param it timepoint index * @param edata pointer to experimental data instance */ - void getmy(const int it, const ExpData *edata); + void getmy(int it, const ExpData *edata); /** create mz slice at event * @param nroots event occurence * @param edata pointer to experimental data instance */ - void getmz(const int nroots, const ExpData *edata); + void getmz(int nroots, const ExpData *edata); /** create y slice at timepoint * @param it timepoint index * @param rdata pointer to return data instance * @return y y-slice from rdata instance */ - const realtype *gety(const int it, const ReturnData *rdata) const; + const realtype *gety(int it, const ReturnData *rdata) const; /** create z slice at event * @param nroots event occurence * @param rdata pointer to return data instance * @return z slice */ - const realtype *getz(const int nroots, const ReturnData *rdata) const; + const realtype *getz(int nroots, const ReturnData *rdata) const; /** create rz slice at event * @param nroots event occurence * @param rdata pointer to return data instance * @return rz slice */ - const realtype *getrz(const int nroots, const ReturnData *rdata) const; + const realtype *getrz(int nroots, const ReturnData *rdata) const; /** create sz slice at event * @param nroots event occurence @@ -1360,7 +1380,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @return z slice */ - const realtype *getsz(const int nroots, const int ip, + const realtype *getsz(int nroots, int ip, const ReturnData *rdata) const; /** create srz slice at event @@ -1369,7 +1389,7 @@ class Model : public AbstractModel { * @param rdata pointer to return data instance * @return rz slice */ - const realtype *getsrz(const int nroots, const int ip, + const realtype *getsrz(int nroots, int ip, const ReturnData *rdata) const; /** @@ -1383,7 +1403,7 @@ class Model : public AbstractModel { * @return state vector with negative values replaced by 0 according to * stateIsNonNegative */ - N_Vector computeX_pos(N_Vector x); + N_Vector computeX_pos(const_N_Vector x); /** Sparse Jacobian (dimension: nnz)*/ SUNMatrixWrapper J; @@ -1494,11 +1514,11 @@ class Model : public AbstractModel { std::vector total_cl; /** sensitivities of total abundances for conservation laws - (dimension: (nx_rdata-nx_solver) * np, ordering = row-major)*/ + (dimension: (nx_rdata-nx_solver) * np, ordering = row-major) */ std::vector stotal_cl; - /** indexes of parameters wrt to which sensitivities are computed (dimension - * nplist) */ + /** indexes of parameters wrt to which sensitivities are computed + * (dimension nplist) */ std::vector plist_; /** state initialisation (size nx_solver) */ diff --git a/include/amici/model_dae.h b/include/amici/model_dae.h index 177eab99f2..e5f4738bf7 100644 --- a/include/amici/model_dae.h +++ b/include/amici/model_dae.h @@ -6,300 +6,433 @@ #include #include -#include #include +#include +#include #include namespace amici { - extern msgIdAndTxtFp warnMsgIdAndTxt; +extern msgIdAndTxtFp warnMsgIdAndTxt; + +class ExpData; +class IDASolver; + +/** + * @brief The Model class represents an AMICI DAE model. + * + * The model does not contain any data, but represents the state + * of the model at a specific time t. The states must not always be + * in sync, but may be updated asynchroneously. + */ +class Model_DAE : public Model { + public: + /** default constructor */ + Model_DAE() = default; + + /** + * @brief Constructor with model dimensions + * @param nx_rdata number of state variables + * @param nxtrue_rdata number of state variables of the non-augmented model + * @param nx_solver number of state variables with conservation laws applied + * @param nxtrue_solver number of state variables of the non-augmented model + with conservation laws applied + * @param ny number of observables + * @param nytrue number of observables of the non-augmented model + * @param nz number of event observables + * @param nztrue number of event observables of the non-augmented model + * @param ne number of events + * @param nJ number of objective functions + * @param nw number of repeating elements + * @param ndwdx number of nonzero elements in the x derivative of the + * repeating elements + * @param ndwdp number of nonzero elements in the p derivative of the + * repeating elements + * @param ndxdotdw number of nonzero elements dxdotdw + * @param ndJydy number of nonzero elements dJydy + * @param nnz number of nonzero elements in Jacobian + * @param ubw upper matrix bandwidth in the Jacobian + * @param lbw lower matrix bandwidth in the Jacobian + * @param o2mode second order sensitivity mode + * @param p parameters + * @param k constants + * @param plist indexes wrt to which sensitivities are to be computed + * @param idlist indexes indicating algebraic components (DAE only) + * @param z2event mapping of event outputs to events + */ + Model_DAE(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, + const int nxtrue_solver, const int ny, const int nytrue, + const int nz, const int nztrue, const int ne, const int nJ, + const int nw, const int ndwdx, const int ndwdp, + const int ndxdotdw, std::vector ndJydy, const int nnz, + const int ubw, const int lbw, const SecondOrderMode o2mode, + std::vector const &p, std::vector const &k, + std::vector const &plist, + std::vector const &idlist, + std::vector const &z2event) + : Model(nx_rdata, nxtrue_rdata, nx_solver, nxtrue_solver, ny, nytrue, + nz, nztrue, ne, nJ, nw, ndwdx, ndwdp, ndxdotdw, + std::move(ndJydy), nnz, ubw, lbw, o2mode, p, k, plist, idlist, + z2event) {} + + void fJ(realtype t, realtype cj, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, SUNMatrix J) override; - class ExpData; - class IDASolver; + /** + * @brief Jacobian of xdot with respect to states x + * @param t timepoint + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + * @param J Matrix to which the Jacobian will be written + **/ + void fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xdot, + SUNMatrix J); /** - * @brief The Model class represents an AMICI DAE model. - * The model does not contain any data, but represents the state - * of the model at a specific time t. The states must not always be - * in sync, but may be updated asynchroneously. + * @brief Jacobian of xBdot with respect to adjoint state xB + * @param t timepoint + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param JB Matrix to which the Jacobian will be written + **/ + + void fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, SUNMatrix JB); + + void fJSparse(realtype t, realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, + SUNMatrix J) override; + + /** + * @brief J in sparse form (for sparse solvers from the SuiteSparse Package) + * @param t timepoint + * @param cj scalar in Jacobian (inverse stepsize) + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param J Matrix to which the Jacobian will be written + */ + void fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, + SUNMatrix J); + + /** JB in sparse form (for sparse solvers from the SuiteSparse Package) + * @param t timepoint + * @param cj scalar in Jacobian + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param JB Matrix to which the Jacobian will be written */ - class Model_DAE : public Model { - public: - /** default constructor */ - Model_DAE() : Model() {} - - /** constructor with model dimensions - * @param nx_rdata number of state variables - * @param nxtrue_rdata number of state variables of the non-augmented model - * @param nx_solver number of state variables with conservation laws applied - * @param nxtrue_solver number of state variables of the non-augmented model - with conservation laws applied - * @param ny number of observables - * @param nytrue number of observables of the non-augmented model - * @param nz number of event observables - * @param nztrue number of event observables of the non-augmented model - * @param ne number of events - * @param nJ number of objective functions - * @param nw number of repeating elements - * @param ndwdx number of nonzero elements in the x derivative of the - * repeating elements - * @param ndwdp number of nonzero elements in the p derivative of the - * repeating elements - * @param ndxdotdw number of nonzero elements dxdotdw - * @param ndJydy number of nonzero elements dJydy - * @param nnz number of nonzero elements in Jacobian - * @param ubw upper matrix bandwidth in the Jacobian - * @param lbw lower matrix bandwidth in the Jacobian - * @param o2mode second order sensitivity mode - * @param p parameters - * @param k constants - * @param plist indexes wrt to which sensitivities are to be computed - * @param idlist indexes indicating algebraic components (DAE only) - * @param z2event mapping of event outputs to events - */ - Model_DAE(const int nx_rdata, const int nxtrue_rdata, - const int nx_solver, const int nxtrue_solver, const int ny, - const int nytrue, const int nz, const int nztrue, - const int ne, const int nJ, const int nw, const int ndwdx, - const int ndwdp, const int ndxdotdw, std::vector ndJydy, - const int nnz, - const int ubw, const int lbw, const SecondOrderMode o2mode, - std::vector const &p, - std::vector const &k, std::vector const &plist, - std::vector const &idlist, - std::vector const &z2event) - : Model(nx_rdata, nxtrue_rdata, nx_solver, nxtrue_solver, ny, - nytrue, nz, nztrue, ne, nJ, nw, ndwdx, ndwdp, ndxdotdw, - ndJydy, nnz, - ubw, lbw, o2mode, p, k, plist, idlist, z2event) {} - - virtual void fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) override; - void fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xdot, - SUNMatrix J); - - void fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, SUNMatrix JB); - - virtual void fJSparse(realtype t, realtype cj, AmiVector *x, - AmiVector *dx, AmiVector *xdot, - SUNMatrix J) override; - void fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, - SUNMatrix J); - - void fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, SUNMatrix JB); - - virtual void fJDiag(realtype t, AmiVector *JDiag, realtype cj, - AmiVector *x, AmiVector *dx) override; - - virtual void fJv(realtype t, AmiVector *x, AmiVector *dx, - AmiVector *xdot, AmiVector *v, AmiVector *nJv, - realtype cj) override; - void fJv(realtype t, N_Vector x, N_Vector dx, N_Vector v, N_Vector Jv, - realtype cj); - - void fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector vB, N_Vector JvB, realtype cj); - - virtual void froot(realtype t, AmiVector *x, AmiVector *dx, realtype *root) override; - void froot(realtype t, N_Vector x, N_Vector dx, realtype *root); - - virtual void fxdot(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot) override; - void fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot); - - void fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector xBdot); - - void fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector qBdot); - - void fdxdotdp(const realtype t, const N_Vector x, const N_Vector dx); - virtual void fdxdotdp(realtype t, AmiVector *x, AmiVector *dx) override { - fdxdotdp(t,x->getNVector(),dx->getNVector()); - }; - - void fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, - AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) override; - void fsxdot(realtype t, N_Vector x, N_Vector dx, int ip, N_Vector sx, N_Vector sdx, N_Vector sxdot); - - void fM(realtype t, const N_Vector x); - - - - virtual std::unique_ptr getSolver() override; - protected: - - /** model specific implementation for fJ - * @param J Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param dx Vector with the derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJ(realtype *J, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype cj, const realtype *dx, const realtype *w, const realtype *dwdx) = 0; - - /** model specific implementation for fJB - * @param JB Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param xB Vector with the adjoint states - * @param dx Vector with the derivative states - * @param dxB Vector with the adjoint derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJB(realtype *JB, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype cj, const realtype *xB, const realtype *dx, const realtype *dxB, - const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - } - - /** model specific implementation for fJSparse - * @param JSparse Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param dx Vector with the derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparse(SUNMatrixContent_Sparse JSparse, const realtype t, - const realtype *x, const double *p, - const double *k, const realtype *h, - const realtype cj, const realtype *dx, - const realtype *w, const realtype *dwdx) = 0; - - /** model specific implementation for fJSparseB - * @param JSparseB Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param xB Vector with the adjoint states - * @param dx Vector with the derivative states - * @param dxB Vector with the adjoint derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparseB(SUNMatrixContent_Sparse JSparseB, - const realtype t, const realtype *x, - const double *p, const double *k, - const realtype *h, const realtype cj, - const realtype *xB, const realtype *dx, - const realtype *dxB, const realtype *w, - const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); - } - - /** model specific implementation for fJDiag - * @param JDiag array to which the Jacobian diagonal will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param dx Vector with the derivative states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJDiag(realtype *JDiag, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype cj, const realtype *dx, const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - } - - /** model specific implementation for fJvB - * @param JvB Matrix vector product of JB with a vector v - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param cj scaling factor, inverse of the step size - * @param xB Vector with the adjoint states - * @param dx Vector with the derivative states - * @param dxB Vector with the adjoint derivative states - * @param vB Vector with which the Jacobian is multiplied - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJvB(realtype *JvB, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype cj, const realtype *xB, const realtype *dx, const realtype *dxB, - const realtype *vB, const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); // not implemented - } - - /** model specific implementation for froot - * @param root values of the trigger function - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param dx Vector with the derivative states - **/ - virtual void froot(realtype *root, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype *dx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); // not implemented - } - - /** model specific implementation for fxdot - * @param xdot residual function - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dx Vector with the derivative states - **/ - virtual void fxdot(realtype *xdot, const realtype t, const realtype *x, const double *p, const double *k, const realtype *h, - const realtype *dx, const realtype *w) = 0; - - /** model specific implementation of fdxdotdp - * @param dxdotdp partial derivative xdot wrt p - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param ip parameter index - * @param dx Vector with the derivative states - * @param w vector with helper variables - * @param dwdp derivative of w wrt p - */ - virtual void fdxdotdp(realtype *dxdotdp, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const int ip, const realtype *dx, const realtype *w, const realtype *dwdp) { - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - }; - - /** model specific implementation of fM - * @param M mass matrix - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - */ - virtual void fM(realtype *M, const realtype t, const realtype *x, const realtype *p, - const realtype *k) {}; + void fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, SUNMatrix JB); + + /** diagonalized Jacobian (for preconditioning) + * @param t timepoint + * @param JDiag Vector to which the Jacobian diagonal will be written + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @return status flag indicating successful execution + **/ + + void fJDiag(realtype t, AmiVector &JDiag, realtype cj, const AmiVector &x, + const AmiVector &dx) override; + + void fJv(realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, const AmiVector &v, AmiVector &nJv, + realtype cj) override; + + /** Matrix vector product of J with a vector v (for iterative solvers) + * @param t timepoint @type realtype + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param v Vector with which the Jacobian is multiplied + * @param Jv Vector to which the Jacobian vector product will be + * written + **/ + void fJv(realtype t, N_Vector x, N_Vector dx, N_Vector v, N_Vector Jv, + realtype cj); + + /** Matrix vector product of JB with a vector v (for iterative solvers) + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param vB Vector with which the Jacobian is multiplied + * @param JvB Vector to which the Jacobian vector product will be + *written + * @param cj scalar in Jacobian (inverse stepsize) + **/ + + void fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, + N_Vector vB, N_Vector JvB, realtype cj); + + void froot(realtype t, const AmiVector &x, const AmiVector &dx, + gsl::span root) override; + + /** Event trigger function for events + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param root array with root function values + */ + void froot(realtype t, N_Vector x, N_Vector dx, gsl::span root); + + void fxdot(realtype t, const AmiVector &x, const AmiVector &dx, + AmiVector &xdot) override; + /** + * @brief Residual function of the DAE + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + */ + void fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot); + + /** Right hand side of differential equation for adjoint state xB + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param xBdot Vector with the adjoint right hand side + */ + void fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, + N_Vector xBdot); + + /** Right hand side of integral equation for quadrature states qB + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param qBdot Vector with the adjoint quadrature right hand side + */ + void fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, + N_Vector qBdot); + + /** Sensitivity of dx/dt wrt model parameters p + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @return status flag indicating successful execution + */ + void fdxdotdp(realtype t, const N_Vector x, const N_Vector dx); + void fdxdotdp(const realtype t, const AmiVector &x, + const AmiVector &dx) override { + fdxdotdp(t, x.getNVector(), dx.getNVector()); }; + + void fsxdot(realtype t, const AmiVector &x, const AmiVector &dx, int ip, + const AmiVector &sx, const AmiVector &sdx, + AmiVector &sxdot) override; + /** Right hand side of differential equation for state sensitivities sx + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param ip parameter index + * @param sx Vector with the state sensitivities + * @param sdx Vector with the derivative state sensitivities + * @param sxdot Vector with the sensitivity right hand side + */ + void fsxdot(realtype t, N_Vector x, N_Vector dx, int ip, N_Vector sx, + N_Vector sdx, N_Vector sxdot); + + /** + * @brief Mass matrix for DAE systems + * @param t timepoint + * @param x Vector with the states + */ + void fM(realtype t, const N_Vector x); + + std::unique_ptr getSolver() override; + + protected: + /** + * @brief Model specific implementation for fJ + * @param J Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param dx Vector with the derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJ(realtype *J, realtype t, const realtype *x, const double *p, + const double *k, const realtype *h, realtype cj, + const realtype *dx, const realtype *w, + const realtype *dwdx) = 0; + + /** + * @brief model specific implementation for fJB + * @param JB Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param xB Vector with the adjoint states + * @param dx Vector with the derivative states + * @param dxB Vector with the adjoint derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJB(realtype *JB, realtype t, const realtype *x, + const double *p, const double *k, const realtype *h, + realtype cj, const realtype *xB, const realtype *dx, + const realtype *dxB, const realtype *w, + const realtype *dwdx); + + /** + * @brief model specific implementation for fJSparse + * @param JSparse Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param dx Vector with the derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparse(SUNMatrixContent_Sparse JSparse, realtype t, + const realtype *x, const double *p, const double *k, + const realtype *h, realtype cj, const realtype *dx, + const realtype *w, const realtype *dwdx) = 0; + + /** + * @brief Model specific implementation for fJSparseB + * @param JSparseB Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param xB Vector with the adjoint states + * @param dx Vector with the derivative states + * @param dxB Vector with the adjoint derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparseB(SUNMatrixContent_Sparse JSparseB, const realtype t, + const realtype *x, const double *p, const double *k, + const realtype *h, const realtype cj, + const realtype *xB, const realtype *dx, + const realtype *dxB, const realtype *w, + const realtype *dwdx); + + /** + * @brief Model specific implementation for fJDiag + * @param JDiag array to which the Jacobian diagonal will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param dx Vector with the derivative states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJDiag(realtype *JDiag, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + realtype cj, const realtype *dx, const realtype *w, + const realtype *dwdx); + + /** + * Model specific implementation for fJvB + * @param JvB Matrix vector product of JB with a vector v + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param cj scaling factor, inverse of the step size + * @param xB Vector with the adjoint states + * @param dx Vector with the derivative states + * @param dxB Vector with the adjoint derivative states + * @param vB Vector with which the Jacobian is multiplied + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJvB(realtype *JvB, realtype t, const realtype *x, + const double *p, const double *k, const realtype *h, + realtype cj, const realtype *xB, const realtype *dx, + const realtype *dxB, const realtype *vB, + const realtype *w, const realtype *dwdx); + + /** + * @brief Model specific implementation for froot + * @param root values of the trigger function + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param dx Vector with the derivative states + **/ + virtual void froot(realtype *root, realtype t, const realtype *x, + const double *p, const double *k, const realtype *h, + const realtype *dx); + + /** + * @brief Model specific implementation for fxdot + * @param xdot residual function + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dx Vector with the derivative states + **/ + virtual void fxdot(realtype *xdot, realtype t, const realtype *x, + const double *p, const double *k, const realtype *h, + const realtype *dx, const realtype *w) = 0; + + /** + * @brief model specific implementation of fdxdotdp + * @param dxdotdp partial derivative xdot wrt p + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param ip parameter index + * @param dx Vector with the derivative states + * @param w vector with helper variables + * @param dwdp derivative of w wrt p + */ + virtual void fdxdotdp(realtype *dxdotdp, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, int ip, const realtype *dx, + const realtype *w, const realtype *dwdp); + + /** + * @brief model specific implementation of fM + * @param M mass matrix + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + */ + virtual void fM(realtype *M, const realtype t, const realtype *x, + const realtype *p, const realtype *k){}; +}; } // namespace amici #endif // MODEL_H diff --git a/include/amici/model_ode.h b/include/amici/model_ode.h index 798e948a27..0f0d44bfab 100644 --- a/include/amici/model_ode.h +++ b/include/amici/model_ode.h @@ -7,383 +7,456 @@ #include #include -#include #include +#include +#include #include namespace amici { - extern msgIdAndTxtFp warnMsgIdAndTxt; +extern msgIdAndTxtFp warnMsgIdAndTxt; + +class CVodeSolver; + +/** + * @brief The Model class represents an AMICI ODE model. + * + * The model does not contain any data, but represents the state + * of the model at a specific time t. The states must not always be + * in sync, but may be updated asynchroneously. + */ +class Model_ODE : public Model { + public: + /** default constructor */ + Model_ODE() = default; + + /** + * @brief Constructor with model dimensions + * @param nx_rdata number of state variables + * @param nxtrue_rdata number of state variables of the non-augmented model + * @param nx_solver number of state variables with conservation laws applied + * @param nxtrue_solver number of state variables of the non-augmented model + with conservation laws applied + * @param ny number of observables + * @param nytrue number of observables of the non-augmented model + * @param nz number of event observables + * @param nztrue number of event observables of the non-augmented model + * @param ne number of events + * @param nJ number of objective functions + * @param nw number of repeating elements + * @param ndwdx number of nonzero elements in the x derivative of the + * repeating elements + * @param ndwdp number of nonzero elements in the p derivative of the + * repeating elements + * @param ndxdotdw number of nonzero elements dxdotdw + * @param ndJydy number of nonzero elements dJydy + * @param nnz number of nonzero elements in Jacobian + * @param ubw upper matrix bandwidth in the Jacobian + * @param lbw lower matrix bandwidth in the Jacobian + * @param o2mode second order sensitivity mode + * @param p parameters + * @param k constants + * @param plist indexes wrt to which sensitivities are to be computed + * @param idlist indexes indicating algebraic components (DAE only) + * @param z2event mapping of event outputs to events + */ + Model_ODE(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, + const int nxtrue_solver, const int ny, const int nytrue, + const int nz, const int nztrue, const int ne, const int nJ, + const int nw, const int ndwdx, const int ndwdp, + const int ndxdotdw, std::vector ndJydy, const int nnz, + const int ubw, const int lbw, const SecondOrderMode o2mode, + std::vector const &p, std::vector const &k, + std::vector const &plist, + std::vector const &idlist, + std::vector const &z2event) + : Model(nx_rdata, nxtrue_rdata, nx_solver, nxtrue_solver, ny, nytrue, + nz, nztrue, ne, nJ, nw, ndwdx, ndwdp, ndxdotdw, + std::move(ndJydy), nnz, ubw, lbw, o2mode, p, k, plist, idlist, + z2event) {} + + void fJ(realtype t, realtype cj, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, SUNMatrix J) override; + + /** + * @brief Implementation of fJ at the N_Vector level + * + * This function provides an + * interface to the model specific routines for the solver + * implementation as well as the AmiVector level implementation + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + * @param J Matrix to which the Jacobian will be written + **/ + void fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J); + + /** implementation of fJB at the N_Vector level, this function provides an + *interface to the model specific routines for the solver implementation + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + * @param JB Matrix to which the Jacobian will be written + **/ + void fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, SUNMatrix JB); + + void fJSparse(realtype t, realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, + SUNMatrix J) override; + + /** + * Implementation of fJSparse at the N_Vector level, this function + * provides + * an interface to the model specific routines for the solver implementation + * aswell as the AmiVector level implementation + * @param t timepoint + * @param x Vector with the states + * @param J Matrix to which the Jacobian will be written + */ + void fJSparse(realtype t, N_Vector x, SUNMatrix J); + + /** implementation of fJSparseB at the N_Vector level, this function + * provides an interface to the model specific routines for the solver + * implementation + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + * @param JB Matrix to which the Jacobian will be written + */ + void fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB); + + /** implementation of fJDiag at the N_Vector level, this function provides + *an interface to the model specific routines for the solver implementation + * @param t timepoint + * @param JDiag Vector to which the Jacobian diagonal will be written + * @param x Vector with the states + **/ + void fJDiag(realtype t, N_Vector JDiag, N_Vector x); + + /** + * @brief diagonalized Jacobian (for preconditioning) + * @param t timepoint + * @param JDiag Vector to which the Jacobian diagonal will be written + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @return status flag indicating successful execution + **/ + void fJDiag(realtype t, AmiVector &JDiag, realtype cj, const AmiVector &x, + const AmiVector &dx) override; + + void fJv(realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, const AmiVector &v, AmiVector &nJv, + realtype cj) override; + + /** implementation of fJv at the N_Vector level. + * @param t timepoint + * @param x Vector with the states + * @param v Vector with which the Jacobian is multiplied + * @param Jv Vector to which the Jacobian vector product will be + * written + **/ + void fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x); + + /** + * @brief implementation of fJvB at the N_Vector level + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param vB Vector with which the Jacobian is multiplied + * @param JvB Vector to which the Jacobian vector product will be written + **/ + void fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, N_Vector xB); + + void froot(realtype t, const AmiVector &x, const AmiVector &dx, + gsl::span root) override; + + /** + * @brief implementation of froot at the N_Vector level + * + * This function provides an interface to the model specific routines for + * the solver implementation aswell as the AmiVector level implementation + * @param t timepoint + * @param x Vector with the states + * @param root array with root function values + */ + void froot(realtype t, N_Vector x, gsl::span root); + + void fxdot(realtype t, const AmiVector &x, const AmiVector &dx, + AmiVector &xdot) override; + + /** implementation of fxdot at the N_Vector level, this function provides an + * interface to the model specific routines for the solver implementation + * aswell as the AmiVector level implementation + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + */ + void fxdot(realtype t, N_Vector x, N_Vector xdot); + + /** implementation of fxBdot at the N_Vector level + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + */ + void fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot); + + /** implementation of fqBdot at the N_Vector level + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param qBdot Vector with the adjoint quadrature right hand side + */ + void fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot); - class CVodeSolver; + /** Sensitivity of dx/dt wrt model parameters w + * @param t timepoint + * @param x Vector with the states + * @return status flag indicating successful execution + */ + void fdxdotdw(realtype t, const N_Vector x); + + /** Sensitivity of dx/dt wrt model parameters p + * @param t timepoint + * @param x Vector with the states + * @return status flag indicating successful execution + */ + void fdxdotdp(realtype t, const N_Vector x); + + void fdxdotdp(realtype t, const AmiVector &x, const AmiVector &dx) override; + + void fsxdot(realtype t, const AmiVector &x, const AmiVector &dx, int ip, + const AmiVector &sx, const AmiVector &sdx, + AmiVector &sxdot) override; + + /** + * @brief implementation of fsxdot at the N_Vector level + * @param t timepoint + * @param x Vector with the states + * @param ip parameter index + * @param sx Vector with the state sensitivities + * @param sxdot Vector with the sensitivity right hand side + */ + void fsxdot(realtype t, N_Vector x, int ip, N_Vector sx, N_Vector sxdot); + + std::unique_ptr getSolver() override; + + protected: + /** model specific implementation for fJ + * @param J Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJ(realtype *J, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + const realtype *w, const realtype *dwdx) = 0; + + /** model specific implementation for fJB + * @param JB Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param xB Vector with the adjoint states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJB(realtype *JB, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + const realtype *xB, const realtype *w, + const realtype *dwdx); + + /** model specific implementation for fJSparse + * @param JSparse Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparse(SUNMatrixContent_Sparse JSparse, realtype t, + const realtype *x, const realtype *p, + const realtype *k, const realtype *h, + const realtype *w, const realtype *dwdx); + + /** model specific implementation for fJSparse, data only + * @param JSparse Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparse(realtype *JSparse, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, const realtype *w, + const realtype *dwdx); + + /** + * @brief model specific implementation for fJSparse, column pointers + * @param indexptrs column pointers + **/ + virtual void fJSparse_colptrs(sunindextype *indexptrs); + + /** + * @brief Model specific implementation for fJSparse, row values + * @param indexvals row values + **/ + virtual void fJSparse_rowvals(sunindextype *indexvals); + + /** + * @brief Model specific implementation for fJSparseB + * @param JSparseB Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param xB Vector with the adjoint states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparseB(SUNMatrixContent_Sparse JSparseB, realtype t, + const realtype *x, const realtype *p, + const realtype *k, const realtype *h, + const realtype *xB, const realtype *w, + const realtype *dwdx); + + /** model specific implementation for fJSparseB + * @param JSparseB data array + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param xB Vector with the adjoint states + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJSparseB(realtype *JSparseB, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, const realtype *xB, + const realtype *w, const realtype *dwdx); + + /** + * @brief Model specific implementation for fJSparse, column pointers + * @param indexptrs column pointers + **/ + virtual void fJSparseB_colptrs(sunindextype *indexptrs); + + /** + * @brief Model specific implementation for fJSparse, row values + * @param indexvals row values + **/ + virtual void fJSparseB_rowvals(sunindextype *indexvals); + + /** + * @brief Model specific implementation for fJDiag + * @param JDiag Matrix to which the Jacobian will be written + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + * @param dwdx derivative of w wrt x + **/ + virtual void fJDiag(realtype *JDiag, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + const realtype *w, const realtype *dwdx); /** - * @brief The Model class represents an AMICI ODE model. - * The model does not contain any data, but represents the state - * of the model at a specific time t. The states must not always be - * in sync, but may be updated asynchroneously. + * @brief model specific implementation for froot + * @param root values of the trigger function + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + **/ + virtual void froot(realtype *root, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h); + + /** model specific implementation for fxdot + * @param xdot residual function + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + **/ + virtual void fxdot(realtype *xdot, realtype t, const realtype *x, + const realtype *p, const realtype *k, const realtype *h, + const realtype *w) = 0; + + /** model specific implementation of fdxdotdp, with w chainrule + * @param dxdotdp partial derivative xdot wrt p + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param ip parameter index + * @param w vector with helper variables + * @param dwdp derivative of w wrt p + */ + virtual void fdxdotdp(realtype *dxdotdp, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, int ip, const realtype *w, + const realtype *dwdp); + + /** model specific implementation of fdxdotdp, without w chainrule + * @param dxdotdp partial derivative xdot wrt p + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param ip parameter index + * @param w vector with helper variables + */ + virtual void fdxdotdp(realtype *dxdotdp, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, int ip, const realtype *w); + + /** model specific implementation of fdxdotdw, data part + * @param dxdotdw partial derivative xdot wrt w + * @param t timepoint + * @param x Vector with the states + * @param p parameter vector + * @param k constants vector + * @param h heavyside vector + * @param w vector with helper variables + */ + virtual void fdxdotdw(realtype *dxdotdw, realtype t, const realtype *x, + const realtype *p, const realtype *k, + const realtype *h, const realtype *w); + + /** model specific implementation of fdxdotdw, colptrs part + * @param indexptrs column pointers + */ + virtual void fdxdotdw_colptrs(sunindextype *indexptrs); + + /** model specific implementation of fdxdotdw, colptrs part + * @param indexvals row values */ - class Model_ODE : public Model { - public: - /** default constructor */ - Model_ODE() : Model() {} - - /** constructor with model dimensions - * @param nx_rdata number of state variables - * @param nxtrue_rdata number of state variables of the non-augmented model - * @param nx_solver number of state variables with conservation laws applied - * @param nxtrue_solver number of state variables of the non-augmented model - with conservation laws applied - * @param ny number of observables - * @param nytrue number of observables of the non-augmented model - * @param nz number of event observables - * @param nztrue number of event observables of the non-augmented model - * @param ne number of events - * @param nJ number of objective functions - * @param nw number of repeating elements - * @param ndwdx number of nonzero elements in the x derivative of the - * repeating elements - * @param ndwdp number of nonzero elements in the p derivative of the - * repeating elements - * @param ndxdotdw number of nonzero elements dxdotdw - * @param ndJydy number of nonzero elements dJydy - * @param nnz number of nonzero elements in Jacobian - * @param ubw upper matrix bandwidth in the Jacobian - * @param lbw lower matrix bandwidth in the Jacobian - * @param o2mode second order sensitivity mode - * @param p parameters - * @param k constants - * @param plist indexes wrt to which sensitivities are to be computed - * @param idlist indexes indicating algebraic components (DAE only) - * @param z2event mapping of event outputs to events - */ - Model_ODE(const int nx_rdata, const int nxtrue_rdata, - const int nx_solver, const int nxtrue_solver, const int ny, - const int nytrue, const int nz, const int nztrue, - const int ne, const int nJ, const int nw, const int ndwdx, - const int ndwdp, const int ndxdotdw, std::vector ndJydy, - const int nnz, - const int ubw, const int lbw, const SecondOrderMode o2mode, - std::vector const &p, - std::vector const &k, std::vector const &plist, - std::vector const &idlist, - std::vector const &z2event) - : Model(nx_rdata, nxtrue_rdata, nx_solver, nxtrue_solver, ny, - nytrue, nz, nztrue, ne, nJ, nw, ndwdx, ndwdp, ndxdotdw, - ndJydy, nnz, - ubw, lbw, o2mode, p, k, plist, idlist, z2event) {} - - virtual void fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) override; - void fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J); - - void fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - SUNMatrix JB); - - virtual void fJSparse(realtype t, realtype cj, AmiVector *x, - AmiVector *dx, AmiVector *xdot, - SUNMatrix J) override; - void fJSparse(realtype t, N_Vector x, SUNMatrix J); - - void fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - SUNMatrix JB); - - void fJDiag(realtype t, N_Vector JDiag, N_Vector x); - virtual void fJDiag(realtype t, AmiVector *Jdiag, realtype cj, - AmiVector *x, AmiVector *dx) override; - - virtual void fJv(realtype t, AmiVector *x, AmiVector *dx, - AmiVector *xdot, AmiVector *v, AmiVector *nJv, - realtype cj) override; - void fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x); - - void fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, - N_Vector xB); - - virtual void froot(realtype t, AmiVector *x, AmiVector *dx, realtype *root) override; - - void froot(realtype t, N_Vector x, realtype *root); - - virtual void fxdot(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot) override; - void fxdot(realtype t, N_Vector x, N_Vector xdot); - - void fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot); - - void fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot); - - void fdxdotdw(const realtype t, const N_Vector x); - - void fdxdotdp(const realtype t, const N_Vector x); - virtual void fdxdotdp(realtype t, AmiVector *x, AmiVector *dx) override { - fdxdotdp(t,x->getNVector()); - } - - void fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, - AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) override; - void fsxdot(realtype t, N_Vector x, int ip, N_Vector sx, N_Vector sxdot); - - virtual std::unique_ptr getSolver() override; - protected: - - /** model specific implementation for fJ - * @param J Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJ(realtype *J, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *w, const realtype *dwdx) = 0; - - /** model specific implementation for fJB - * @param JB Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param xB Vector with the adjoint states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJB(realtype *JB, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *xB, const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - } - - /** model specific implementation for fJSparse - * @param JSparse Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparse(SUNMatrixContent_Sparse JSparse, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const realtype *w, const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, data only - * @param JSparse Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparse(realtype *JSparse, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const realtype *w, const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, column pointers - * @param indexptrs column pointers - **/ - virtual void fJSparse_colptrs(sunindextype *indexptrs) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, row values - * @param indexvals row values - **/ - virtual void fJSparse_rowvals(sunindextype *indexvals) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparseB - * @param JSparseB Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param xB Vector with the adjoint states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparseB(SUNMatrixContent_Sparse JSparseB, - const realtype t, const realtype *x, - const realtype *p, const realtype *k, - const realtype *h, const realtype *xB, - const realtype *w, const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparseB - * @param JSparseB data array - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param xB Vector with the adjoint states - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJSparseB(realtype *JSparseB, - const realtype t, const realtype *x, - const realtype *p, const realtype *k, - const realtype *h, const realtype *xB, - const realtype *w, const realtype *dwdx) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, column pointers - * @param indexptrs column pointers - **/ - virtual void fJSparseB_colptrs(sunindextype *indexptrs) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJSparse, row values - * @param indexvals row values - **/ - virtual void fJSparseB_rowvals(sunindextype *indexvals) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation for fJDiag - * @param JDiag Matrix to which the Jacobian will be written - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - * @param dwdx derivative of w wrt x - **/ - virtual void fJDiag(realtype *JDiag, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, - const realtype *w, const realtype *dwdx){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); - } - - /** model specific implementation for froot - * @param root values of the trigger function - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - **/ - virtual void froot(realtype *root, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h){ - throw AmiException("Requested functionality is not supported as %s is not implemented for this model!",__func__); // not implemented - } - - /** model specific implementation for fxdot - * @param xdot residual function - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - **/ - virtual void fxdot(realtype *xdot, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w) = 0; - - /** model specific implementation of fdxdotdp, with w chainrule - * @param dxdotdp partial derivative xdot wrt p - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param ip parameter index - * @param w vector with helper variables - * @param dwdp derivative of w wrt p - */ - virtual void fdxdotdp(realtype *dxdotdp, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const int ip, const realtype *w, - const realtype *dwdp) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation of fdxdotdp, without w chainrule - * @param dxdotdp partial derivative xdot wrt p - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param ip parameter index - * @param w vector with helper variables - */ - virtual void fdxdotdp(realtype *dxdotdp, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const int ip, const realtype *w) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation of fdxdotdw, data part - * @param dxdotdw partial derivative xdot wrt w - * @param t timepoint - * @param x Vector with the states - * @param p parameter vector - * @param k constants vector - * @param h heavyside vector - * @param w vector with helper variables - */ - virtual void fdxdotdw(realtype *dxdotdw, const realtype t, - const realtype *x, const realtype *p, - const realtype *k, const realtype *h, - const realtype *w) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation of fdxdotdw, colptrs part - * @param indexptrs column pointers - */ - virtual void fdxdotdw_colptrs(sunindextype *indexptrs) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - - /** model specific implementation of fdxdotdw, colptrs part - * @param indexvals row values - */ - virtual void fdxdotdw_rowvals(sunindextype *indexvals) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented - } - }; + virtual void fdxdotdw_rowvals(sunindextype *indexvals); +}; } // namespace amici diff --git a/include/amici/newton_solver.h b/include/amici/newton_solver.h index efc5a1d6bc..c0c1232b31 100644 --- a/include/amici/newton_solver.h +++ b/include/amici/newton_solver.h @@ -48,8 +48,13 @@ class NewtonSolver { * @param rtol relative tolerance * @return solver NewtonSolver according to the specified linsolType */ - static std::unique_ptr getSolver(realtype *t, AmiVector *x, LinearSolver linsolType, Model *model, - ReturnData *rdata, int maxlinsteps, int maxsteps, double atol, double rtol); + static std::unique_ptr getSolver(realtype *t, AmiVector *x, + LinearSolver linsolType, + Model *model, + ReturnData *rdata, + int maxlinsteps, + int maxsteps, + double atol, double rtol); /** * Computes the solution of one Newton iteration @@ -60,14 +65,14 @@ class NewtonSolver { * @param delta containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - void getStep(int ntry, int nnewt, AmiVector *delta); + void getStep(int ntry, int nnewt, AmiVector &delta); /** * Computes steady state sensitivities * * @param sx pointer to state variable sensitivities */ - void computeNewtonSensis(AmiVectorArray *sx); + void computeNewtonSensis(AmiVectorArray &sx); /** * Writes the Jacobian for the Newton iteration and passes it to the linear @@ -85,7 +90,7 @@ class NewtonSolver { * @param rhs containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - virtual void solveLinearSystem(AmiVector *rhs) = 0; + virtual void solveLinearSystem(AmiVector &rhs) = 0; virtual ~NewtonSolver() = default; @@ -108,7 +113,7 @@ class NewtonSolver { ReturnData *rdata; /** right hand side AmiVector */ AmiVector xdot; - /** current state*/ + /** current state */ AmiVector *x; /** current state time derivative (DAE) */ AmiVector dx; @@ -142,7 +147,7 @@ class NewtonSolverDense : public NewtonSolver { * @param rhs containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - void solveLinearSystem(AmiVector *rhs) override; + void solveLinearSystem(AmiVector &rhs) override; /** * Writes the Jacobian for the Newton iteration and passes it to the linear @@ -185,10 +190,10 @@ class NewtonSolverSparse : public NewtonSolver { /** * Solves the linear system for the Newton step * - * @param rhs containing the RHS of the linear system,will be + * @param rhs containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - void solveLinearSystem(AmiVector *rhs) override; + void solveLinearSystem(AmiVector &rhs) override; /** * Writes the Jacobian for the Newton iteration and passes it to the linear @@ -224,7 +229,7 @@ class NewtonSolverIterative : public NewtonSolver { * @param rdata pointer to the return data object */ NewtonSolverIterative(realtype *t, AmiVector *x, Model *model, ReturnData *rdata); - virtual ~NewtonSolverIterative() = default; + ~NewtonSolverIterative() override = default; /** * Solves the linear system for the Newton step by passing it to @@ -233,7 +238,7 @@ class NewtonSolverIterative : public NewtonSolver { * @param rhs containing the RHS of the linear system, will be * overwritten by solution to the linear system */ - void solveLinearSystem(AmiVector *rhs) override; + void solveLinearSystem(AmiVector &rhs) override; /** * Writes the Jacobian for the Newton iteration and passes it to the linear @@ -256,7 +261,7 @@ class NewtonSolverIterative : public NewtonSolver { * @param nnewt integer number of current Newton step * @param ns_delta Newton step */ - void linsolveSPBCG(int ntry, int nnewt, AmiVector *ns_delta); + void linsolveSPBCG(int ntry, int nnewt, AmiVector &ns_delta); private: /** number of tries */ diff --git a/include/amici/rdata.h b/include/amici/rdata.h index 9d77ebeac8..9f24b59c4c 100644 --- a/include/amici/rdata.h +++ b/include/amici/rdata.h @@ -14,12 +14,13 @@ class Solver; namespace boost { namespace serialization { template -void serialize(Archive &ar, amici::ReturnData &u, const unsigned int version); +void serialize(Archive &ar, amici::ReturnData &u, unsigned int version); }} namespace amici { -/** @brief Stores all data to be returned by amici::runAmiciSimulation. +/** + * @brief Stores all data to be returned by amici::runAmiciSimulation. * * NOTE: multidimensional arrays are stored in row-major order * (FORTRAN-style) @@ -29,7 +30,7 @@ class ReturnData { /** * @brief default constructor */ - ReturnData(); + ReturnData() = default; /** * @brief ReturnData @@ -64,11 +65,11 @@ class ReturnData { /** * @brief constructor that uses information from model and solver to * appropriately initialize fields - * @param solver solver - * @param model pointer to model specification object + * @param solver solver instance + * @param model model instance * bool */ - ReturnData(Solver const& solver, const Model *model); + ReturnData(Solver const& solver, const Model &model); ~ReturnData() = default; @@ -82,7 +83,7 @@ class ReturnData { * sensitivities to NaN (typically after integration failure) * @param t time of integration failure */ - void invalidate(const realtype t); + void invalidate(realtype t); /** * @brief Set likelihood and chi2 to NaN @@ -105,7 +106,7 @@ class ReturnData { applyChainRuleFactorToSimulationResults(const Model *model); /** timepoints (dimension: nt) */ - const std::vector ts; + std::vector ts; /** time derivative (dimension: nx) */ std::vector xdot; @@ -256,44 +257,61 @@ class ReturnData { int status = 0; /** total number of model parameters */ - const int np; + int np{0}; + /** number of fixed parameters */ - const int nk; + int nk{0}; + /** number of states */ - const int nx; + int nx{0}; + /** number of states with conservation laws applied */ - const int nx_solver; + int nx_solver{0}; + /** number of states in the unaugmented system */ - const int nxtrue; + int nxtrue{0}; + /** number of observables */ - const int ny; + int ny{0}; + /** number of observables in the unaugmented system */ - const int nytrue; + int nytrue{0}; + /** number of event outputs */ - const int nz; + int nz{0}; + /** number of event outputs in the unaugmented system */ - const int nztrue; + int nztrue{0}; + /** number of events */ - const int ne; + int ne{0}; + /** dimension of the augmented objective function for 2nd order ASA */ - const int nJ; + int nJ{0}; /** number of parameter for which sensitivities were requested */ - const int nplist; + int nplist{0}; + /** maximal number of occuring events (for every event type) */ - const int nmaxevent; + int nmaxevent{0}; + /** number of considered timepoints */ - const int nt; + int nt{0}; + /** maximal number of newton iterations for steady state calculation */ - const int newton_maxsteps; + int newton_maxsteps{0}; + /** scaling of parameterization (lin,log,log10) */ std::vector pscale; + /** flag indicating whether second order sensitivities were requested */ - const SecondOrderMode o2mode; + SecondOrderMode o2mode{SecondOrderMode::none}; + /** sensitivity order */ - const SensitivityOrder sensi; + SensitivityOrder sensi{SensitivityOrder::none}; + /** sensitivity method */ - const SensitivityMethod sensi_meth; + SensitivityMethod sensi_meth{SensitivityMethod::none}; /** * @brief Serialize ReturnData (see boost::serialization::serialize) @@ -302,7 +320,8 @@ class ReturnData { * @param version Version number */ template - friend void boost::serialization::serialize(Archive &ar, ReturnData &r, const unsigned int version); + friend void boost::serialization::serialize(Archive &ar, ReturnData &r, + unsigned int version); }; } // namespace amici diff --git a/include/amici/serialization.h b/include/amici/serialization.h index dfa02abb33..5c0842ab78 100644 --- a/include/amici/serialization.h +++ b/include/amici/serialization.h @@ -75,26 +75,26 @@ void serialize(Archive &ar, amici::CVodeSolver &u, const unsigned int version) { template void serialize(Archive &ar, amici::Model &u, const unsigned int version) { - ar &const_cast(u.nx_rdata); - ar &const_cast(u.nxtrue_rdata); - ar &const_cast(u.nx_solver); - ar &const_cast(u.nxtrue_solver); - ar &const_cast(u.ny); - ar &const_cast(u.nytrue); - ar &const_cast(u.nz); - ar &const_cast(u.nztrue); - ar &const_cast(u.ne); - ar &const_cast(u.nw); - ar &const_cast(u.ndwdx); - ar &const_cast(u.ndwdp); - ar &const_cast(u.ndxdotdw); - ar &const_cast(u.nnz); - ar &const_cast(u.nJ); - ar &const_cast(u.ubw); - ar &const_cast(u.lbw); - ar &const_cast(u.o2mode); - ar &const_cast &>(u.z2event); - ar &const_cast &>(u.idlist); + ar &u.nx_rdata; + ar &u.nxtrue_rdata; + ar &u.nx_solver; + ar &u.nxtrue_solver; + ar &u.ny; + ar &u.nytrue; + ar &u.nz; + ar &u.nztrue; + ar &u.ne; + ar &u.nw; + ar &u.ndwdx; + ar &u.ndwdp; + ar &u.ndxdotdw; + ar &u.nnz; + ar &u.nJ; + ar &u.ubw; + ar &u.lbw; + ar &u.o2mode; + ar &u.z2event; + ar &u.idlist; ar &u.h; ar &u.unscaledParameters; ar &u.originalParameters; @@ -112,28 +112,27 @@ void serialize(Archive &ar, amici::Model &u, const unsigned int version) { template void serialize(Archive &ar, amici::ReturnData &r, const unsigned int version) { - - ar &const_cast(r.np); - ar &const_cast(r.nk); - ar &const_cast(r.nx); - ar &const_cast(r.nx_solver); - ar &const_cast(r.nxtrue); - ar &const_cast(r.ny); - ar &const_cast(r.nytrue); - ar &const_cast(r.nz); - ar &const_cast(r.nztrue); - ar &const_cast(r.ne); - ar &const_cast(r.nJ); - ar &const_cast(r.nplist); - ar &const_cast(r.nmaxevent); - ar &const_cast(r.nt); - ar &const_cast(r.newton_maxsteps); + ar &r.np; + ar &r.nk; + ar &r.nx; + ar &r.nx_solver; + ar &r.nxtrue; + ar &r.ny; + ar &r.nytrue; + ar &r.nz; + ar &r.nztrue; + ar &r.ne; + ar &r.nJ; + ar &r.nplist; + ar &r.nmaxevent; + ar &r.nt; + ar &r.newton_maxsteps; ar &r.pscale; - ar &const_cast(r.o2mode); - ar &const_cast(r.sensi); - ar &const_cast(r.sensi_meth); + ar &r.o2mode; + ar &r.sensi; + ar &r.sensi_meth; - ar &const_cast &>(r.ts); + ar &r.ts; ar &r.xdot; ar &r.J; ar &r.z & r.sigmaz; @@ -252,7 +251,7 @@ std::string serializeToString(T const& data) { ::boost::iostreams::back_insert_device> s(inserter); ::boost::archive::binary_oarchive oar(s); - + oar << data; s.flush(); @@ -280,9 +279,9 @@ std::vector serializeToStdVec(T const& data) { oar << data; s.flush(); - + std::vector buf(serialized.begin(), serialized.end()); - + return buf; } catch(::boost::archive::archive_exception const& e) { throw AmiException("Serialization to StdVec failed: %s", e.what()); @@ -305,7 +304,7 @@ T deserializeFromString(std::string const& serialized) { device); ::boost::archive::binary_iarchive iar(s); T deserialized; - + iar >> deserialized; return deserialized; diff --git a/include/amici/solver.h b/include/amici/solver.h index b5b9b89ac1..0e3fd13177 100644 --- a/include/amici/solver.h +++ b/include/amici/solver.h @@ -1,13 +1,13 @@ #ifndef AMICI_SOLVER_H #define AMICI_SOLVER_H -#include "amici/vector.h" #include "amici/defines.h" -#include "amici/symbolic_functions.h" #include "amici/sundials_linsol_wrapper.h" +#include "amici/symbolic_functions.h" +#include "amici/vector.h" -#include #include +#include namespace amici { @@ -18,20 +18,23 @@ class Model; class Solver; } // namespace amici - // for serialization friend in Solver -namespace boost { namespace serialization { +namespace boost { +namespace serialization { template -void serialize(Archive &ar, amici::Solver &u, const unsigned int version); -}} // namespace boost::serialization - +void serialize(Archive &ar, amici::Solver &u, unsigned int version); +} +} // namespace boost::serialization namespace amici { /** - * The Solver class provides a generic interface to CVode and IDA solvers, + * The Solver class provides a generic interface to CVODES and IDAS solvers, * individual realizations are realized in the CVodeSolver and the IDASolver - * class. + * class. All transient private/protected members (CVODES/IDAS memory, interface + * variables and status flags) are specified as mutable and not included in + * serialization or equality checks. No solver setting parameter should be + * marked mutable. * * NOTE: Any changes in data members here must be propagated to copy ctor, * equality operator, serialization functions in serialization.h, and @@ -53,36 +56,59 @@ class Solver { * @brief Clone this instance * @return The clone */ - virtual Solver* clone() const = 0; + virtual Solver *clone() const = 0; /** - * @brief Initialises the ami memory object and applies specified options - * @param x state vector - * @param dx state derivative vector (DAE only) - * @param sx state sensitivity vector - * @param sdx state derivative sensitivity vector (DAE only) - * @param model pointer to the model object + * @brief runs a forward simulation until the specified timepoint + * + * @param tout next timepooint + * @return status flag */ + int run(realtype tout) const; - void setup(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, - AmiVectorArray *sdx, Model *model); + /** + * @brief makes a single step in the simulation + * + * @param tout next timepooint + * @return status flag + */ + int step(realtype tout) const; /** - * @brief Initialises the AMI memory object for the backwards problem - * @param bwd pointer to backward problem - * @param model pointer to the model object + * @brief runs a backward simulation until the specified timepoint + * + * @param tout next timepooint + * @return status flag + */ + void runB(realtype tout) const; + + /** + * @brief Initialises the ami memory object and applies specified options + * @param t0 initial timepoint + * @param model pointer to the model instance + * @param x0 initial states + * @param dx0 initial derivative states + * @param sx0 initial state sensitivities + * @param sdx0 initial derivative state sensitivities */ - void setupB(BackwardProblem *bwd, Model *model); + void setup(realtype t0, Model *model, const AmiVector &x0, + const AmiVector &dx0, const AmiVectorArray &sx0, + const AmiVectorArray &sdx0) const; /** - * @brief Extracts diagnosis information from solver memory block and - * writes them into the return data instance - * - * @param tret time at which the sensitivities should be computed - * @param yySout vector with sensitivities + * @brief Initialises the AMI memory object for the backwards problem + * @param which index of the backward problem, will be set by this routine + * @param tf final timepoint (initial timepoint for the bwd problem) + * @param model pointer to the model instance + * @param xB0 initial adjoint states + * @param dxB0 initial adjoint derivative states + * @param xQB0 initial adjoint quadratures */ - virtual void getSens(realtype *tret, AmiVectorArray *yySout) const = 0; + + void setupB(int *which, realtype tf, Model *model, + const AmiVector &xB0, const AmiVector &dxB0, + const AmiVector &xQB0) const; /** * @brief Extracts diagnosis information from solver memory block and @@ -91,7 +117,7 @@ class Solver { * @param it time-point index * @param rdata pointer to the return data object */ - void getDiagnosis(const int it, ReturnData *rdata) const; + void getDiagnosis(int it, ReturnData *rdata) const; /** * @brief Extracts diagnosis information from solver memory block and @@ -101,7 +127,7 @@ class Solver { * @param rdata pointer to the return data object * @param which identifier of the backwards problem */ - void getDiagnosisB(const int it, ReturnData *rdata, int which) const; + void getDiagnosisB(int it, ReturnData *rdata, int which) const; /** * getRootInfo extracts information which event occured @@ -112,155 +138,35 @@ class Solver { virtual void getRootInfo(int *rootsfound) const = 0; /** - * @brief Reinitializes the states in the solver after an event occurence + * @brief Calculates consistent initial conditions, assumes initial + * states to be correct (DAE only) * - * @param t0 new timepoint - * @param yy0 new state variables - * @param yp0 new derivative state variables (DAE only) - */ - virtual void reInit(realtype t0, AmiVector *yy0, AmiVector *yp0) = 0; - - /** - * @brief reInitPostProcessF - * @param t - * @param yout - * @param ypout - * @param tnext - */ - virtual void reInitPostProcessF(realtype *t, AmiVector *yout, - AmiVector *ypout, realtype tnext) = 0; - - /** - * @brief reInitPostProcessB - * @param which - * @param t - * @param yBout - * @param ypBout - * @param tnext + * @param tout1 next timepoint to be computed (sets timescale) */ - virtual void reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector *ypBout, realtype tnext) = 0; + virtual void calcIC(realtype tout1) const = 0; /** - * @brief Reinitializes the state sensitivites in the solver after an - * event occurence + * @brief Calculates consistent initial conditions for the backwards + * problem, assumes initial states to be correct (DAE only) * - * @param yS0 new state sensitivity - * @param ypS0 new derivative state sensitivities (DAE only) + * @param which identifier of the backwards problem + * @param tout1 next timepoint to be computed (sets timescale) */ - virtual void sensReInit(AmiVectorArray *yS0, AmiVectorArray *ypS0) = 0; + virtual void calcICB(int which, realtype tout1) const = 0; /** - * @brief Calculates consistent initial conditions, assumes initial - * states to be correct (DAE only) + * @brief Solves the backward problem until a predefined timepoint + * (adjoint only) * - * @param tout1 next timepoint to be computed (sets timescale) - * @param x initial state variables - * @param dx initial derivative state variables (DAE only) - */ - virtual void calcIC(realtype tout1, AmiVector *x, AmiVector *dx) = 0; - - /** - * @brief Calculates consistent initial conditions for the backwards - * problem, assumes initial states to be correct (DAE only) - * - * @param which identifier of the backwards problem - * @param tout1 next timepoint to be computed (sets timescale) - * @param xB states of final solution of the forward problem - * @param dxB derivative states of final solution of the forward - * problem (DAE only) - */ - virtual void calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) = 0; - - /** - * @brief Solves the forward problem until a predefined timepoint - * - * @param tout timepoint until which simulation should be performed - * @param yret states - * @param ypret derivative states (DAE only) - * @param tret pointer to the time variable - * @param itask task identifier, can be CV_NORMAL or CV_ONE_STEP - * @return status flag indicating success of execution - */ - virtual int solve(realtype tout, AmiVector *yret, AmiVector *ypret, - realtype *tret, int itask) = 0; - - /** - * @brief Solves the forward problem until a predefined timepoint - * (adjoint only) - * - * @param tout timepoint until which simulation should be performed - * @param yret states - * @param ypret derivative states (DAE only) - * @param tret pointer to the time variable - * @param itask task identifier, can be CV_NORMAL or CV_ONE_STEP - * @param ncheckPtr pointer to a number that counts the internal - * checkpoints - * @return status flag indicating success of execution - */ - virtual int solveF(realtype tout, AmiVector *yret, AmiVector *ypret, - realtype *tret, int itask, int *ncheckPtr) = 0; - - /** - * @brief Solves the backward problem until a predefined timepoint - * (adjoint only) - * - * @param tBout timepoint until which simulation should be performed - * @param itaskB task identifier, can be CV_NORMAL or CV_ONE_STEP - */ - virtual void solveB(realtype tBout, int itaskB) = 0; - - /** - * @brief Sets a timepoint at which the simulation will be stopped - * - * @param tstop timepoint until which simulation should be performed - */ - virtual void setStopTime(realtype tstop) = 0; - - /** - * @brief Reinitializes the adjoint states after an event occurence - * - * @param which identifier of the backwards problem - * @param tB0 new timepoint - * @param yyB0 new adjoint state variables - * @param ypB0 new adjoint derivative state variables (DAE only) - */ - virtual void reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector *ypB0) = 0; - - /** - * @brief Returns the current adjoint states - * - * @param which identifier of the backwards problem - * @param tret time at which the adjoint states should be computed - * @param yy adjoint state variables - * @param yp adjoint derivative state variables (DAE only) - */ - virtual void getB(int which, realtype *tret, AmiVector *yy, - AmiVector *yp) const = 0; - - /** - * @brief Returns the current adjoint states - * - * @param which identifier of the backwards problem - * @param tret time at which the adjoint states should be computed - * @param qB adjoint quadrature state variables - */ - virtual void getQuadB(int which, realtype *tret, AmiVector *qB) const = 0; - - /** - * @brief Reinitialize the adjoint states after an event occurence - * - * @param which identifier of the backwards problem - * @param yQB0 new adjoint quadrature state variables - */ - virtual void quadReInitB(int which, AmiVector *yQB0) = 0; + * @param tBout timepoint until which simulation should be performed + * @param itaskB task identifier, can be CV_NORMAL or CV_ONE_STEP + */ + virtual void solveB(realtype tBout, int itaskB) const = 0; /** - * @brief Disable rootfinding - */ - virtual void turnOffRootFinding() = 0; + * @brief Disable rootfinding + */ + virtual void turnOffRootFinding() const = 0; /** * @brief Return current sensitivity method @@ -367,7 +273,8 @@ class Solver { void setAbsoluteTolerance(double atol); /** - * @brief Returns the relative tolerances for the forward sensitivity problem + * @brief Returns the relative tolerances for the forward sensitivity + * problem * @return relative tolerances */ double getRelativeToleranceFSA() const; @@ -379,7 +286,8 @@ class Solver { void setRelativeToleranceFSA(double rtol); /** - * @brief Returns the absolute tolerances for the forward sensitivity problem + * @brief Returns the absolute tolerances for the forward sensitivity + * problem * @return absolute tolerances */ double getAbsoluteToleranceFSA() const; @@ -391,7 +299,8 @@ class Solver { void setAbsoluteToleranceFSA(double atol); /** - * @brief Returns the relative tolerances for the adjoint sensitivity problem + * @brief Returns the relative tolerances for the adjoint sensitivity + * problem * @return relative tolerances */ double getRelativeToleranceB() const; @@ -497,25 +406,26 @@ class Solver { * problem * @return maximum number of solver steps */ - int getMaxSteps() const; + long int getMaxSteps() const; /** * @brief sets the maximum number of solver steps for the forward problem * @param maxsteps maximum number of solver steps (non-negative number) */ - void setMaxSteps(int maxsteps); + void setMaxSteps(long int maxsteps); /** * @brief returns the maximum number of solver steps for the backward * problem * @return maximum number of solver steps */ - int getMaxStepsBackwardProblem() const; + long int getMaxStepsBackwardProblem() const; + /** * @brief sets the maximum number of solver steps for the backward problem * @param maxsteps maximum number of solver steps (non-negative number) */ - void setMaxStepsBackwardProblem(int maxsteps); + void setMaxStepsBackwardProblem(long int maxsteps); /** * @brief returns the linear system multistep method @@ -609,6 +519,136 @@ class Solver { */ void setInternalSensitivityMethod(InternalSensitivityMethod ism); + /** + * @brief write solution from forward simulation + * @param t time + * @param x state + * @param dx derivative state + * @param sx state sensitivity + */ + void writeSolution(realtype *t, AmiVector &x, AmiVector &dx, + AmiVectorArray &sx) const; + + /** + * @brief write solution from forward simulation + * @param t time + * @param xB adjoint state + * @param dxB adjoint derivative state + * @param xQB adjoint quadrature + * @param which index of adjoint problem + */ + void writeSolutionB(realtype *t, AmiVector &xB, AmiVector &dxB, + AmiVector &xQB, int which) const; + + /** + * @brief Access state solution at time t + * @param t time + * @return x or interpolated solution dky + */ + const AmiVector &getState(realtype t) const; + + /** + * @brief Access derivative state solution at time t + * @param t time + * @return dx or interpolated solution dky + */ + const AmiVector &getDerivativeState(realtype t) const; + + /** + * @brief Access state sensitivity solution at time t + * @param t time + * @return (interpolated) solution sx + */ + const AmiVectorArray &getStateSensitivity(realtype t) const; + + /** + * @brief Access adjoint solution at time t + * @param which adjoint problem index + * @param t time + * @return (interpolated) solution xB + */ + const AmiVector &getAdjointState(int which, realtype t) const; + + /** + * @brief Access adjoint derivative solution at time t + * @param which adjoint problem index + * @param t time + * @return (interpolated) solution dxB + */ + const AmiVector &getAdjointDerivativeState(int which, + realtype t) const; + + /** + * @brief Access adjoint quadrature solution at time t + * @param which adjoint problem index + * @param t time + * @return (interpolated) solution xQB + */ + const AmiVector &getAdjointQuadrature(int which, realtype t) const; + + /** + * @brief Reinitializes the states in the solver after an event occurence + * + * @param t0 reinitialization timepoint + * @param yy0 inital state variables + * @param yp0 initial derivative state variables (DAE only) + */ + virtual void reInit(realtype t0, const AmiVector &yy0, + const AmiVector &yp0) const = 0; + + /** + * @brief Reinitializes the state sensitivites in the solver after an + * event occurence + * + * @param yyS0 new state sensitivity + * @param ypS0 new derivative state sensitivities (DAE only) + */ + virtual void sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray &ypS0) const = 0; + + /** + * @brief Reinitializes the adjoint states after an event occurence + * + * @param which identifier of the backwards problem + * @param tB0 reinitialization timepoint + * @param yyB0 new adjoint state + * @param ypB0 new adjoint derivative state + */ + virtual void reInitB(int which, realtype tB0, + const AmiVector &yyB0, const AmiVector &ypB0) const = 0; + + /** + * @brief Reinitialize the adjoint states after an event occurence + * + * @param which identifier of the backwards problem + * @param yQB0 new adjoint quadrature state + */ + virtual void quadReInitB(int which, const AmiVector &yQB0) const = 0; + + /** + * @brief current solver timepoint + * @return t + */ + realtype gett() const; + + /** + * @brief number of states with which the solver was initialized + * @return x.getLength() + */ + int nx() const; + + /** + * @brief number of parameters with which the solver was initialized + * @return sx.getLength() + */ + int nplist() const; + + /** + * @brief number of quadratures with which the solver was initialized + * @return xQB.getLength() + */ + int nquad() const; + /** * @brief Serialize Solver (see boost::serialization::serialize) * @param ar Archive to serialize to @@ -616,7 +656,8 @@ class Solver { * @param version Version number */ template - friend void boost::serialization::serialize(Archive &ar, Solver &r, const unsigned int version); + friend void boost::serialization::serialize(Archive &ar, Solver &r, + unsigned int version); /** * @brief Check equality of data members excluding solver memory @@ -624,128 +665,175 @@ class Solver { * @param b * @return */ - friend bool operator ==(const Solver &a, const Solver &b); + friend bool operator==(const Solver &a, const Solver &b); protected: + /** + * @brief Sets a timepoint at which the simulation will be stopped + * + * @param tstop timepoint until which simulation should be performed + */ + virtual void setStopTime(realtype tstop) const = 0; + + /** + * @brief Solves the forward problem until a predefined timepoint + * + * @param tout timepoint until which simulation should be performed + * @param itask task identifier, can be CV_NORMAL or CV_ONE_STEP + * @return status flag indicating success of execution + */ + virtual int solve(realtype tout, int itask) const = 0; + + /** + * @brief Solves the forward problem until a predefined timepoint + * (adjoint only) + * + * @param tout timepoint until which simulation should be performed + * @param itask task identifier, can be CV_NORMAL or CV_ONE_STEP + * @param ncheckPtr pointer to a number that counts the internal + * checkpoints + * @return status flag indicating success of execution + */ + virtual int solveF(realtype tout, int itask, + int *ncheckPtr) const = 0; + + /** + * @brief reInitPostProcessF postprocessing of the solver memory after a + * discontinuity in the forward problem + * @param tnext next timepoint (defines integration direction) + */ + virtual void reInitPostProcessF(realtype tnext) const = 0; + + /** + * @brief reInitPostProcessB postprocessing of the solver memory after a + * discontinuity in the backward problem + * @param tnext next timepoint (defines integration direction) + */ + virtual void reInitPostProcessB(realtype tnext) const = 0; + + /** + * @brief extracts the state sensitivity at the current timepoint from + * solver memory and writes it to the sx member variable + */ + virtual void getSens() const = 0; + + /** + * @brief extracts the adjoint state at the current timepoint from + * solver memory and writes it to the xB member variable + * @param which index of the backwards problem + */ + virtual void getB(int which) const = 0; + + /** + * @brief extracts the adjoint quadrature state at the current timepoint + * from solver memory and writes it to the xQB member variable + * @param which index of the backwards problem + */ + virtual void getQuadB(int which) const = 0; + /** * @brief Initialises the states at the specified initial timepoint * - * @param x initial state variables - * @param dx initial derivative state variables (DAE only) - * @param t initial timepoint + * @param t0 initial timepoint + * @param x0 initial states + * @param dx0 initial derivative states + */ + virtual void init(realtype t0, const AmiVector &x0, + const AmiVector &dx0) const = 0; + + /** + * @brief initialises the forward sensitivities + * @param sx0 initial states semsitivities + * @param sdx0 initial derivative states sensitivities */ - virtual void init(AmiVector *x, AmiVector *dx, realtype t) = 0; + virtual void sensInit1(const AmiVectorArray &sx0, + const AmiVectorArray &sdx0) const = 0; /** * @brief Initialise the adjoint states at the specified final timepoint * * @param which identifier of the backwards problem - * @param xB initial adjoint state variables - * @param dxB initial adjoint derivative state variables (DAE only) - * @param t final timepoint + * @param tf final timepoint + * @param xB0 initial adjoint state + * @param dxB0 initial adjoint derivative state */ - virtual void binit(int which, AmiVector *xB, AmiVector *dxB, realtype t) = 0; + virtual void binit(int which, realtype tf, const AmiVector &xB0, + const AmiVector &dxB0) const = 0; /** * @brief Initialise the quadrature states at the specified final timepoint * * @param which identifier of the backwards problem - * @param qBdot initial adjoint quadrature state variables + * @param xQB0 intial adjoint quadrature state */ - virtual void qbinit(int which, AmiVector *qBdot) = 0; + virtual void qbinit(int which, const AmiVector &xQB0) const = 0; /** * @brief Initialises the rootfinding for events * * @param ne number of different events */ - virtual void rootInit(int ne) = 0; - - /** - * @brief initialises the sensitivities at the specified initial - * timepoint - * - * @param sx initial state sensitivities - * @param sdx initial derivative state sensitivities (DAE only) - * @param nplist number parameter wrt which sensitivities are to be computed - */ - virtual void sensInit1(AmiVectorArray *sx, AmiVectorArray *sdx, int nplist) = 0; + virtual void rootInit(int ne) const = 0; /** * @brief Initalize non-linear solver for sensitivities - * @param x - * @param model + * @param model Model instance */ - void initalizeNonLinearSolverSens(AmiVector *x, Model *model); + void initializeNonLinearSolverSens(const Model *model) const; /** * @brief Set the dense Jacobian function */ - virtual void setDenseJacFn() = 0; + virtual void setDenseJacFn() const = 0; /** * @brief sets the sparse Jacobian function */ - virtual void setSparseJacFn() = 0; + virtual void setSparseJacFn() const = 0; /** * @brief sets the banded Jacobian function */ - virtual void setBandJacFn() = 0; + virtual void setBandJacFn() const = 0; /** * @brief sets the Jacobian vector multiplication function */ - virtual void setJacTimesVecFn() = 0; + virtual void setJacTimesVecFn() const = 0; /** * @brief sets the dense Jacobian function * * @param which identifier of the backwards problem */ - virtual void setDenseJacFnB(int which) = 0; + virtual void setDenseJacFnB(int which) const = 0; /** * @brief sets the sparse Jacobian function * * @param which identifier of the backwards problem */ - virtual void setSparseJacFnB(int which) = 0; + virtual void setSparseJacFnB(int which) const = 0; /** * @brief sets the banded Jacobian function * * @param which identifier of the backwards problem */ - virtual void setBandJacFnB(int which) = 0; + virtual void setBandJacFnB(int which) const = 0; /** * @brief sets the Jacobian vector multiplication function * * @param which identifier of the backwards problem */ - virtual void setJacTimesVecFnB(int which) = 0; - - /** - * @brief Extracts diagnosis information from solver memory block and - * writes them into the return data object for the backward problem - * - * @param error_code error identifier - * @param module name of the module in which the error occured - * @param function name of the function in which the error occured @type - * char - * @param msg error message - * @param eh_data unused input - */ - static void wrapErrHandlerFn(int error_code, const char *module, - const char *function, char *msg, - void *eh_data); + virtual void setJacTimesVecFnB(int which) const = 0; /** * @brief Create specifies solver method and initializes solver memory for * the forward problem */ - virtual void allocateSolver() = 0; + virtual void allocateSolver() const = 0; /** * @brief sets scalar relative and absolute tolerances for the forward @@ -754,7 +842,8 @@ class Solver { * @param rtol relative tolerances * @param atol absolute tolerances */ - virtual void setSStolerances(double rtol, double atol) = 0; + virtual void setSStolerances(double rtol, + double atol) const = 0; /** * @brief activates sets scalar relative and absolute tolerances for the @@ -763,7 +852,8 @@ class Solver { * @param rtol relative tolerances * @param atol array of absolute tolerances for every sensitivy variable */ - virtual void setSensSStolerances(double rtol, double *atol) = 0; + virtual void setSensSStolerances(double rtol, + const double *atol) const = 0; /** * SetSensErrCon specifies whether error control is also enforced for @@ -771,7 +861,7 @@ class Solver { * * @param error_corr activation flag */ - virtual void setSensErrCon(bool error_corr) = 0; + virtual void setSensErrCon(bool error_corr) const = 0; /** * @brief Specifies whether error control is also enforced for the @@ -780,31 +870,31 @@ class Solver { * @param which identifier of the backwards problem * @param flag activation flag */ - virtual void setQuadErrConB(int which, bool flag) = 0; + virtual void setQuadErrConB(int which, bool flag) const = 0; /** * @brief Attaches the error handler function (errMsgIdAndTxt) * to the solver * */ - virtual void setErrHandlerFn() = 0; + virtual void setErrHandlerFn() const = 0; /** * @brief Attaches the user data instance (here this is a Model) to the * forward problem * - * @param model Model instance, + * @param model Model instance */ - virtual void setUserData(Model *model) = 0; + virtual void setUserData(Model *model) const = 0; /** * @brief attaches the user data instance (here this is a Model) to the * backward problem * * @param which identifier of the backwards problem - * @param model Model instance, + * @param model Model instance */ - virtual void setUserDataB(int which, Model *model) = 0; + virtual void setUserDataB(int which, Model *model) const = 0; /** * @brief specifies the maximum number of steps for the forward @@ -812,7 +902,7 @@ class Solver { * * @param mxsteps number of steps */ - virtual void setMaxNumSteps(long int mxsteps) = 0; + virtual void setMaxNumSteps(long int mxsteps) const = 0; /** * @brief specifies the maximum number of steps for the forward @@ -821,7 +911,7 @@ class Solver { * @param which identifier of the backwards problem * @param mxstepsB number of steps */ - virtual void setMaxNumStepsB(int which, long int mxstepsB) = 0; + virtual void setMaxNumStepsB(int which, long int mxstepsB) const = 0; /** * @brief activates stability limit detection for the forward @@ -830,7 +920,7 @@ class Solver { * @param stldet flag for stability limit detection (TRUE or FALSE) * */ - virtual void setStabLimDet(int stldet) = 0; + virtual void setStabLimDet(int stldet) const = 0; /** * @brief activates stability limit detection for the backward @@ -840,21 +930,21 @@ class Solver { * @param stldet flag for stability limit detection (TRUE or FALSE) * */ - virtual void setStabLimDetB(int which, int stldet) = 0; + virtual void setStabLimDetB(int which, int stldet) const = 0; /** * @brief specify algebraic/differential components (DAE only) * * @param model model specification */ - virtual void setId(Model *model) = 0; + virtual void setId(const Model *model) const = 0; /** * @brief deactivates error control for algebraic components (DAE only) * * @param flag deactivation flag */ - virtual void setSuppressAlg(bool flag) = 0; + virtual void setSuppressAlg(bool flag) const = 0; /** * @brief specifies the scaling and indexes for sensitivity @@ -864,7 +954,37 @@ class Solver { * @param pbar parameter scaling constants * @param plist parameter index list */ - virtual void setSensParams(realtype *p, realtype *pbar, int *plist) = 0; + virtual void setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const = 0; + + /** + * @brief interpolates the (derivative of the) solution at the requested + * timepoint + * + * @param t timepoint + * @param k derivative order + */ + virtual void getDky(realtype t, int k) const = 0; + + /** + * @brief interpolates the (derivative of the) solution at the requested + * timepoint + * + * @param t timepoint + * @param k derivative order + * @param which index of backward problem + */ + virtual void getDkyB(realtype t, int k, + int which) const = 0; + + /** + * @brief interpolates the (derivative of the) solution at the requested + * timepoint + * + * @param t timepoint + * @param k derivative order + */ + virtual void getSensDky(realtype t, int k) const = 0; /** * @brief interpolates the (derivative of the) solution at the requested @@ -872,15 +992,16 @@ class Solver { * * @param t timepoint * @param k derivative order - * @param dky interpolated solution + * @param which index of backward problem */ - virtual void getDky(realtype t, int k, AmiVector *dky) const = 0; + virtual void getQuadDkyB(realtype t, int k, + int which) const = 0; /** * @brief initializes the adjoint problem * */ - virtual void adjInit() = 0; + virtual void adjInit() const = 0; /** * @brief Specifies solver method and initializes solver memory for the @@ -888,7 +1009,7 @@ class Solver { * * @param which identifier of the backwards problem */ - virtual void allocateSolverB(int *which) = 0; + virtual void allocateSolverB(int *which) const = 0; /** * @brief sets relative and absolute tolerances for the backward @@ -899,7 +1020,7 @@ class Solver { * @param absTolB absolute tolerances */ virtual void setSStolerancesB(int which, realtype relTolB, - realtype absTolB) = 0; + realtype absTolB) const = 0; /** * @brief sets relative and absolute tolerances for the quadrature @@ -910,7 +1031,7 @@ class Solver { * @param abstolQB absolute tolerances */ virtual void quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) = 0; + realtype abstolQB) const = 0; /** * @brief reports the number of solver steps @@ -919,7 +1040,7 @@ class Solver { * forward or backward problem) * @param numsteps output array */ - virtual void getNumSteps(void *ami_mem, long int *numsteps) const = 0; + virtual void getNumSteps(const void *ami_mem, long int *numsteps) const = 0; /** * @brief reports the number of right hand evaluations @@ -928,7 +1049,8 @@ class Solver { * forward or backward problem) * @param numrhsevals output array */ - virtual void getNumRhsEvals(void *ami_mem, long int *numrhsevals) const = 0; + virtual void getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const = 0; /** * @brief reports the number of local error test failures @@ -937,8 +1059,8 @@ class Solver { * forward or backward problem) * @param numerrtestfails output array */ - virtual void getNumErrTestFails(void *ami_mem, - long int *numerrtestfails) const = 0; + virtual void getNumErrTestFails(const void *ami_mem, + long int *numerrtestfails) const = 0; /** * @brief reports the number of nonlinear convergence failures @@ -948,8 +1070,8 @@ class Solver { * @param numnonlinsolvconvfails output array */ virtual void - getNumNonlinSolvConvFails(void *ami_mem, - long int *numnonlinsolvconvfails) const = 0; + getNumNonlinSolvConvFails(const void *ami_mem, + long int *numnonlinsolvconvfails) const = 0; /** * @brief Reports the order of the integration method during the @@ -959,137 +1081,145 @@ class Solver { * forward or backward problem) * @param order output array */ - virtual void getLastOrder(void *ami_mem, int *order) const = 0; + virtual void getLastOrder(const void *ami_mem, int *order) const = 0; /** * @brief Initializes and sets the linear solver for the forward problem * * @param model pointer to the model object - * @param x */ - void initializeLinearSolver(const Model *model, AmiVector *x); + void initializeLinearSolver(const Model *model) const; /** * @brief Sets the non-linear solver - * @param x */ - void initializeNonLinearSolver(AmiVector *x); + void initializeNonLinearSolver() const; /** * @brief Sets the linear solver for the forward problem */ - virtual void setLinearSolver() = 0; + virtual void setLinearSolver() const = 0; /** * @brief Sets the linear solver for the backward problem - * @param which + * @param which index of the backward problem */ - virtual void setLinearSolverB(int which) = 0; + virtual void setLinearSolverB(int which) const = 0; /** * @brief Set the non-linear solver for the forward problem */ - virtual void setNonLinearSolver() = 0; + virtual void setNonLinearSolver() const = 0; /** * @brief Set the non-linear solver for the backward problem - * @param which + * @param which index of the backward problem */ - virtual void setNonLinearSolverB(int which) = 0; + virtual void setNonLinearSolverB(int which) const = 0; /** * @brief Set the non-linear solver for sensitivities */ - virtual void setNonLinearSolverSens() = 0; + virtual void setNonLinearSolverSens() const = 0; /** * @brief Initializes the linear solver for the backward problem * * @param model pointer to the model object - * @param xB * @param which index of the backward problem */ - void initializeLinearSolverB(const Model *model, AmiVector *xB, - const int which); + void initializeLinearSolverB(const Model *model, int which) const; /** * @brief Initializes the non-linear solver for the backward problem - * @param xB - * @param which + * @param which index of the backward problem */ - void initializeNonLinearSolverB(AmiVector *xB, const int which); + void initializeNonLinearSolverB(int which) const; /** - * @brief Accessor function to the number of sensitivity parameters in the - * model stored in the user data + * Accessor function to the model stored in the user data * - * @return number of sensitivity parameters + * @return user data model */ - virtual int nplist() const = 0; + virtual const Model *getModel() const = 0; + /** - * @brief Accessor function to the number of state variables in the model - * stored in the user data + * @brief checks whether memory for the forward problem has been allocated * - * @return number of state variables + * @return proxy for solverMemory->(cv|ida)_MallocDone */ - virtual int nx() const = 0; + bool getInitDone() const; + /** - * Accessor function to the model stored in the user data + * @brief checks whether memory for forward sensitivities has been allocated * - * @return user data model + * @return proxy for solverMemory->(cv|ida)_SensMallocDone */ - virtual const Model *getModel() const = 0; + bool getSensInitDone() const; /** - * @brief checks whether memory for the forward problem has been allocated + * @brief checks whether memory for forward interpolation has been allocated * - * @return solverMemory->(cv|ida)__MallocDone + * @return proxy for solverMemory->(cv|ida)_adjMallocDone */ - virtual bool getMallocDone() const = 0; + bool getAdjInitDone() const; /** * @brief checks whether memory for the backward problem has been allocated - * - * @return solverMemory->(cv|ida)__adjMallocDone + * @param which adjoint problem index + * @return proxy for solverMemoryB->(cv|ida)_MallocDone + */ + bool getInitDoneB(int which) const; + + /** + * @brief checks whether memory for backward quadratures has been allocated + * @param which adjoint problem index + * @return proxy for solverMemoryB->(cv|ida)_QuadMallocDone */ - virtual bool getAdjMallocDone() const = 0; + bool getQuadInitDoneB(int which) const; /** * @brief attaches a diagonal linear solver to the forward problem */ - virtual void diag() = 0; + virtual void diag() const = 0; /** * @brief attaches a diagonal linear solver to the backward problem * * @param which identifier of the backwards problem */ - virtual void diagB(int which) = 0; + virtual void diagB(int which) const = 0; - -protected: + /** + * @brief resets solverMemory and solverMemoryB + * @param nx new number of state variables + * @param nplist new number of sensitivity parameters + * @param nquad new number of quadratures (only differs from nplist for + * higher order senisitivity computation) + */ + void resetMutableMemory(int nx, int nplist, int nquad) const; /** - * @brief retrieves the solver memory instance for the backward problem + * @brief Retrieves the solver memory instance for the backward problem * * @param which identifier of the backwards problem * @param ami_mem pointer to the forward solver memory instance - * @return ami_memB pointer to the backward solver memory instance + * @return pointer to the backward solver memory instance */ - virtual void *getAdjBmem(void *ami_mem, int which) = 0; + virtual void *getAdjBmem(void *ami_mem, int which) const = 0; /** * @brief updates solver tolerances according to the currently specified * member variables */ - void applyTolerances(); + void applyTolerances() const; /** * @brief updates FSA solver tolerances according to the currently * specified member variables */ - void applyTolerancesFSA(); + void applyTolerancesFSA() const; /** * @brief updates ASA solver tolerances according to the currently @@ -1097,7 +1227,7 @@ class Solver { * * @param which identifier of the backwards problem */ - void applyTolerancesASA(int which); + void applyTolerancesASA(int which) const; /** * @brief updates ASA quadrature solver tolerances according to the @@ -1105,23 +1235,20 @@ class Solver { * * @param which identifier of the backwards problem */ - void applyQuadTolerancesASA(int which); + void applyQuadTolerancesASA(int which) const; /** * @brief updates all senstivivity solver tolerances according to the * currently specified member variables */ - void applySensitivityTolerances(); - + void applySensitivityTolerances() const; /** pointer to solver memory block */ - std::unique_ptr> solverMemory; + mutable std::unique_ptr> solverMemory; /** pointer to solver memory block */ - std::vector>> solverMemoryB; - - /** flag indicating whether the solver was called */ - bool solverWasCalled = false; + mutable std::vector>> + solverMemoryB; /** internal sensitivity method flag used to select the sensitivity solution * method. Only applies for Forward Sensitivities. */ @@ -1142,22 +1269,91 @@ class Solver { InterpolationType interpType = InterpolationType::hermite; /** maximum number of allowed integration steps */ - int maxsteps = 10000; + long int maxsteps = 10000; /** linear solver for the forward problem */ - std::unique_ptr linearSolver; + mutable std::unique_ptr linearSolver; + /** linear solver for the backward problem */ - std::unique_ptr linearSolverB; + mutable std::unique_ptr linearSolverB; /** non-linear solver for the forward problem */ - std::unique_ptr nonLinearSolver; + mutable std::unique_ptr nonLinearSolver; + /** non-linear solver for the backward problem */ - std::unique_ptr nonLinearSolverB; - /** non-linear solver for the sensitivities*/ - std::unique_ptr nonLinearSolverSens; + mutable std::unique_ptr nonLinearSolverB; + + /** non-linear solver for the sensitivities */ + mutable std::unique_ptr nonLinearSolverSens; + + /** flag indicating whether the forward solver has been called */ + mutable bool solverWasCalledF = false; + + /** flag indicating whether the backward solver has been called */ + mutable bool solverWasCalledB = false; + /** + * @brief sets that memory for the forward problem has been allocated + */ + void setInitDone() const; -private: + /** + * @brief sets that memory for forward sensitivities has been allocated + */ + void setSensInitDone() const; + + /** + * @brief sets that memory for forward interpolation has been allocated + */ + void setAdjInitDone() const; + + /** + * @brief sets that memory for the backward problem has been allocated + * @param which adjoint problem index + */ + void setInitDoneB(int which) const; + + /** + * @brief sets that memory for backward quadratures has been allocated + * @param which adjoint problem index + */ + void setQuadInitDoneB(int which) const; + + /** state (dimension: nx_solver) */ + mutable AmiVector x = AmiVector(0); + + /** state interface variable (dimension: nx_solver) */ + mutable AmiVector dky = AmiVector(0); + + /** state derivative dummy (dimension: nx_solver) */ + mutable AmiVector dx = AmiVector(0); + + /** state sensititivities interface variable (dimension: nx_solver x nplist) + */ + mutable AmiVectorArray sx = AmiVectorArray(0, 0); + /** state derivative sensititivities dummy (dimension: nx_solver x nplist) + */ + mutable AmiVectorArray sdx = AmiVectorArray(0, 0); + + /** adjoint state interface variable (dimension: nx_solver) */ + mutable AmiVector xB = AmiVector(0); + + /** adjoint derivative dummy variable (dimension: nx_solver) */ + mutable AmiVector dxB = AmiVector(0); + + /** adjoint quadrature interface variable (dimension: nJ x nplist) */ + mutable AmiVector xQB = AmiVector(0); + + /** integration time of the forward problem */ + mutable realtype t; + + /** flag to force reInitPostProcessF before next call to solve */ + mutable bool forceReInitPostProcessF = false; + + /** flag to force reInitPostProcessB before next call to solveB */ + mutable bool forceReInitPostProcessB = false; + + private: /** method for sensitivity computation */ SensitivityMethod sensi_meth = SensitivityMethod::forward; @@ -1169,11 +1365,11 @@ class Solver { int ordering = static_cast(SUNLinSolKLU::StateOrdering::AMD); /** maximum number of allowed Newton steps for steady state computation */ - int newton_maxsteps = 0; + long int newton_maxsteps = 0; /** maximum number of allowed linear steps per Newton step for steady state * computation */ - int newton_maxlinsteps = 0; + long int newton_maxlinsteps = 0; /** Preequilibration of model via Newton solver? */ bool newton_preeq = false; @@ -1182,50 +1378,83 @@ class Solver { LinearSolver linsol = LinearSolver::KLU; /** absolute tolerances for integration */ - double atol = 1e-16; + realtype atol = 1e-16; /** relative tolerances for integration */ - double rtol = 1e-8; + realtype rtol = 1e-8; /** absolute tolerances for forward sensitivity integration */ - double atol_fsa = NAN; + realtype atol_fsa = NAN; /** relative tolerances for forward sensitivity integration */ - double rtol_fsa = NAN; + realtype rtol_fsa = NAN; /** absolute tolerances for adjoint sensitivity integration */ - double atolB = NAN; + realtype atolB = NAN; /** relative tolerances for adjoint sensitivity integration */ - double rtolB = NAN; + realtype rtolB = NAN; /** absolute tolerances for backward quadratures */ - double quad_atol = 1e-12; + realtype quad_atol = 1e-12; /** relative tolerances for backward quadratures */ - double quad_rtol = 1e-8; + realtype quad_rtol = 1e-8; /** absolute tolerances for steadystate computation */ - double ss_atol = NAN; + realtype ss_atol = NAN; /** relative tolerances for steadystate computation */ - double ss_rtol = NAN; + realtype ss_rtol = NAN; /** absolute tolerances for steadystate computation */ - double ss_atol_sensi = NAN; + realtype ss_atol_sensi = NAN; /** relative tolerances for steadystate computation */ - double ss_rtol_sensi = NAN; + realtype ss_rtol_sensi = NAN; /** maximum number of allowed integration steps for backward problem */ - int maxstepsB = 0; + long int maxstepsB = 0; /** flag indicating whether sensitivities are supposed to be computed */ SensitivityOrder sensi = SensitivityOrder::none; + /** flag indicating whether init was called */ + mutable bool initialized = false; + + /** flag indicating whether sensInit1 was called */ + mutable bool sensInitialized = false; + + /** flag indicating whether adjInit was called */ + mutable bool adjInitialized = false; + + /** vector of flags indicating whether binit was called for respective + which */ + mutable std::vector initializedB{false}; + + /** vector of flags indicating whether qbinit was called for respective + which */ + mutable std::vector initializedQB{false}; + + /** number of checkpoints in the forward problem */ + mutable int ncheckPtr; }; -bool operator ==(const Solver &a, const Solver &b); +bool operator==(const Solver &a, const Solver &b); + +/** + * @brief Extracts diagnosis information from solver memory block and + * writes them into the return data object for the backward problem + * + * @param error_code error identifier + * @param module name of the module in which the error occured + * @param function name of the function in which the error occured @type + * char + * @param msg error message + * @param eh_data unused input + */ +void wrapErrHandlerFn(int error_code, const char *module, + const char *function, char *msg, void *eh_data); } // namespace amici diff --git a/include/amici/solver_cvodes.h b/include/amici/solver_cvodes.h index 63796de340..ce2278c3d8 100644 --- a/include/amici/solver_cvodes.h +++ b/include/amici/solver_cvodes.h @@ -1,26 +1,26 @@ #ifndef AMICI_SOLVER_CVODES_h #define AMICI_SOLVER_CVODES_h -#include "amici/solver.h" #include "amici/defines.h" +#include "amici/solver.h" #include "amici/vector.h" #include namespace amici { - class ExpData; class ReturnData; class Model_ODE; class CVodeSolver; -} +} // namespace amici // for serialization friend in Solver -namespace boost { namespace serialization { +namespace boost { +namespace serialization { template -void serialize(Archive &ar, amici::CVodeSolver &u, const unsigned int version); -}} - +void serialize(Archive &ar, amici::CVodeSolver &u, unsigned int version); +} +} // namespace boost::serialization namespace amici { @@ -34,215 +34,174 @@ class CVodeSolver : public Solver { * @brief Clone this instance * @return The clone */ - virtual Solver* clone() const override; - - void reInitPostProcessF(realtype *t, AmiVector *yout, AmiVector *ypout, - realtype tnext) override; - - void reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector *ypBout, realtype tnext) override; + Solver *clone() const override; - void reInit(realtype t0, AmiVector *yy0, AmiVector *yp0) override; + void reInit(realtype t0, const AmiVector &yy0, + const AmiVector &yp0) const override; - void sensReInit( AmiVectorArray *yS0, AmiVectorArray *ypS0) override; + void sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray &ypS0) const override; - void reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector *ypB0) override; + void reInitB(int which, realtype tB0, + const AmiVector &yyB0, const AmiVector &ypB0) const override; - void quadReInitB(int which, AmiVector *yQB0) override; + void quadReInitB(int which, const AmiVector &yQB0) const override; - int solve(realtype tout, AmiVector *yret, AmiVector *ypret, realtype *tret, - int itask) override; + int solve(realtype tout, int itask) const override; - int solveF(realtype tout, AmiVector *yret, AmiVector *ypret, realtype *tret, - int itask, int *ncheckPtr) override; + int solveF(realtype tout, int itask, + int *ncheckPtr) const override; - void solveB(realtype tBout, int itaskB) override; + void solveB(realtype tBout, int itaskB) const override; - void getB(int which, realtype *tret, AmiVector *yy, AmiVector *yp) const override; + void getDky(realtype t, int k) const override; - void getDky(realtype t, int k, AmiVector *dky) const override; + void getSensDky(realtype t, int k) const override; - void getSens(realtype *tret, AmiVectorArray *yySout) const override; + void getQuadDkyB(realtype t, int k, + int which) const override; - void getQuadB(int which, realtype *tret, AmiVector *qB) const override; + void getDkyB(realtype t, int k, int which) const override; void getRootInfo(int *rootsfound) const override; - void calcIC(realtype tout1, AmiVector *x, AmiVector *dx) override; + void setStopTime(realtype tstop) const override; - void calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) override; - - void setStopTime(realtype tstop) override; - - void turnOffRootFinding() override; - - int nplist() const override; - - int nx() const override; + void turnOffRootFinding() const override; const Model *getModel() const override; - bool getMallocDone() const override; - - bool getAdjMallocDone() const override; - - static int fxdot(realtype t, N_Vector x, N_Vector xdot, void *user_data); - - static int fJSparse(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, - void *user_data, N_Vector tmp1, N_Vector tmp2, - N_Vector tmp3); - - static int fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, - void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); - - using Solver::setLinearSolver; + using Solver::setLinearSolverB; - void setLinearSolver() override; + void setLinearSolver() const override; - void setLinearSolverB(int which) override; + void setLinearSolverB(int which) const override; - void setNonLinearSolver() override; + void setNonLinearSolver() const override; - void setNonLinearSolverSens() override; + void setNonLinearSolverSens() const override; - void setNonLinearSolverB(int which) override; + void setNonLinearSolverB(int which) const override; protected: - - void reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, - realtype tout); - - void allocateSolver() override; - void setSStolerances(double rtol, double atol) override; + void calcIC(realtype tout1) const override; - void setSensSStolerances(double rtol, double *atol) override; + void calcICB(int which, realtype tout1) const override; - void setSensErrCon(bool error_corr) override; + void getB(int which) const override; - void setQuadErrConB(int which, bool flag) override; + void getSens() const override; - void setErrHandlerFn() override; + void getQuadB(int which) const override; - void setUserData(Model *model) override; + void reInitPostProcessF(realtype tnext) const override; - void setUserDataB(int which, Model *model) override; + void reInitPostProcessB(realtype tnext) const override; - void setMaxNumSteps(long int mxsteps) override; + void reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, + realtype tout) const; - void setStabLimDet(int stldet) override; + void allocateSolver() const override; - void setStabLimDetB(int which, int stldet) override; + void setSStolerances(double rtol, double atol) const override; - void setId(Model *model) override; + void setSensSStolerances(double rtol, + const double *atol) const override; - void setSuppressAlg(bool flag) override; - - void resetState(void *cv_mem, N_Vector y0); + void setSensErrCon(bool error_corr) const override; - void setSensParams(realtype *p, realtype *pbar, int *plist) override; + void setQuadErrConB(int which, bool flag) const override; - void adjInit() override; + void setErrHandlerFn() const override; - void allocateSolverB(int *which) override; + void setUserData(Model *model) const override; - void setSStolerancesB(int which, realtype relTolB, - realtype absTolB) override; + void setUserDataB(int which, Model *model) const override; - void quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) override; - - void setMaxNumStepsB(int which, long int mxstepsB) override; + void setMaxNumSteps(long int mxsteps) const override; - void diag() override; + void setStabLimDet(int stldet) const override; - void diagB(int which) override; + void setStabLimDetB(int which, int stldet) const override; - void getNumSteps(void *ami_mem, long int *numsteps) const override; + void setId(const Model *model) const override; - void getNumRhsEvals(void *ami_mem, long int *numrhsevals) const override; + void setSuppressAlg(bool flag) const override; - void getNumErrTestFails(void *ami_mem, - long int *numerrtestfails) const override; + void resetState(void *cv_mem, const_N_Vector y0) const; - void getNumNonlinSolvConvFails(void *ami_mem, - long int *numnonlinsolvconvfails) const override; + void setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const override; - void getLastOrder(void *ami_ami_mem, int *order) const override; + void adjInit() const override; - void *getAdjBmem(void *ami_mem, int which) override; + void allocateSolverB(int *which) const override; - template - friend void boost::serialization::serialize(Archive &ar, CVodeSolver &r, const unsigned int version); + void setSStolerancesB(int which, realtype relTolB, + realtype absTolB) const override; - friend bool operator ==(const CVodeSolver &a, const CVodeSolver &b); + void quadSStolerancesB(int which, realtype reltolQB, + realtype abstolQB) const override; - void init(AmiVector *x, AmiVector *dx, realtype t) override; + void setMaxNumStepsB(int which, long int mxstepsB) const override; - void binit(int which, AmiVector *xB, AmiVector *dxB, realtype t) override; + void diag() const override; - void qbinit(int which, AmiVector *qBdot) override; + void diagB(int which) const override; - void rootInit(int ne) override; + void getNumSteps(const void *ami_mem, long int *numsteps) const override; - void sensInit1(AmiVectorArray *sx, AmiVectorArray *sdx, int nplist) override; + void getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const override; - void setDenseJacFn() override; + void getNumErrTestFails(const void *ami_mem, + long int *numerrtestfails) const override; - void setSparseJacFn() override; + void + getNumNonlinSolvConvFails(const void *ami_mem, + long int *numnonlinsolvconvfails) const override; - void setBandJacFn() override; + void getLastOrder(const void *ami_ami_mem, int *order) const override; - void setJacTimesVecFn() override; + void *getAdjBmem(void *ami_mem, int which) const override; - void setDenseJacFnB(int which) override; + template + friend void boost::serialization::serialize(Archive &ar, CVodeSolver &r, + unsigned int version); - void setSparseJacFnB(int which) override; + friend bool operator==(const CVodeSolver &a, const CVodeSolver &b); - void setBandJacFnB(int which) override; + void init(realtype t0, const AmiVector &x0, const AmiVector &dx0) + const override; - void setJacTimesVecFnB(int which) override; + void sensInit1(const AmiVectorArray &sx0, const AmiVectorArray &sdx0) + const override; - static int fJB(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void binit(int which, realtype tf, const AmiVector &xB0, + const AmiVector &dxB0) const override; - static int fJSparseB(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void qbinit(int which, const AmiVector &xQB0) const override; - static int fJBand(realtype t, N_Vector x, N_Vector xdot, - SUNMatrix J, void *user_data, N_Vector tmp1, - N_Vector tmp2, N_Vector tmp3); + void rootInit(int ne) const override; - static int fJBandB(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void setDenseJacFn() const override; - static int fJDiag(realtype t, N_Vector JDiag, N_Vector x, void *user_data); + void setSparseJacFn() const override; - static int fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, N_Vector xdot, - void *user_data, N_Vector tmp); + void setBandJacFn() const override; - static int fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - void *user_data, N_Vector tmpB); + void setJacTimesVecFn() const override; - static int froot(realtype t, N_Vector x, realtype *root, - void *user_data); + void setDenseJacFnB(int which) const override; - static int fxBdot(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, void *user_data); + void setSparseJacFnB(int which) const override; - static int fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot, - void *user_data); + void setBandJacFnB(int which) const override; - static int fsxdot(int Ns, realtype t, N_Vector x, N_Vector xdot, int ip, - N_Vector sx, N_Vector sxdot, void *user_data, - N_Vector tmp1, N_Vector tmp2); + void setJacTimesVecFnB(int which) const override; }; } // namespace amici diff --git a/include/amici/solver_idas.h b/include/amici/solver_idas.h index c1870bdd32..21648ddd88 100644 --- a/include/amici/solver_idas.h +++ b/include/amici/solver_idas.h @@ -4,7 +4,21 @@ #include "amici/solver.h" #include -#include + +namespace amici { +class ExpData; +class ReturnData; +class Model_DAE; +class IDASolver; +} // namespace amici + +// for serialization friend in Solver +namespace boost { +namespace serialization { +template +void serialize(Archive &ar, amici::IDASolver &u, unsigned int version); +} +} // namespace boost::serialization namespace amici { @@ -17,213 +31,164 @@ class IDASolver : public Solver { * @brief Clone this instance * @return The clone */ - virtual Solver* clone() const override; - - void reInitPostProcessF(realtype *t, AmiVector *yout, AmiVector *ypout, - realtype tnext) override; - - void reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector *ypBout, realtype tnext) override; - - void reInit(realtype t0, AmiVector *yy0, AmiVector *yp0) override; - - void sensReInit(AmiVectorArray *yS0, AmiVectorArray *ypS0) override; + Solver *clone() const override; - void reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector *ypB0) override; + void reInitPostProcessF(realtype tnext) const override; - void quadReInitB(int which, AmiVector *yQB0) override; + void reInitPostProcessB(realtype tnext) const override; - void quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) override; - - int solve(realtype tout, AmiVector *yret, AmiVector *ypret, realtype *tret, - int itask) override; + void reInit(realtype t0, const AmiVector &yy0, + const AmiVector &yp0) const override; - int solveF(realtype tout, AmiVector *yret, AmiVector *ypret, realtype *tret, - int itask, int *ncheckPtr) override; + void sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray &ypS0) const override; - void solveB(realtype tBout, int itaskB) override; + void reInitB(int which, realtype tB0, + const AmiVector &yyB0, const AmiVector &ypB0) const override; - void getRootInfo(int *rootsfound) const override; + void quadReInitB(int which, const AmiVector &yQB0) const override; - void getDky(realtype t, int k, AmiVector *dky) const override; + void quadSStolerancesB(int which, realtype reltolQB, + realtype abstolQB) const override; - void getSens(realtype *tret, AmiVectorArray *yySout) const override; + int solve(realtype tout, int itask) const override; - void getB(int which, realtype *tret, AmiVector *yy, AmiVector *yp) const override; + int solveF(realtype tout, int itask, + int *ncheckPtr) const override; - void getQuadB(int which, realtype *tret, AmiVector *qB) const override; + void solveB(realtype tBout, int itaskB) const override; - void calcIC(realtype tout1, AmiVector *x, AmiVector *dx) override; + void getRootInfo(int *rootsfound) const override; - void calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) override; + void getDky(realtype t, int k) const override; - void setStopTime(realtype tstop) override; + void getSens() const override; - void turnOffRootFinding() override; + void getSensDky(realtype t, int k) const override; - int nplist() const override; + void getB(int which) const override; - int nx() const override; + void getDkyB(realtype t, int k, int which) const override; - const Model *getModel() const override; + void getQuadB(int which) const override; - bool getMallocDone() const override; + void getQuadDkyB(realtype t, int k, int which) const override; - bool getAdjMallocDone() const override; + void calcIC(realtype tout1) const override; - static int fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, - void *user_data); + void calcICB(int which, realtype tout1) const override; - static int fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xdot, SUNMatrix J, void *user_data, N_Vector tmp1, - N_Vector tmp2, N_Vector tmp3); + void setStopTime(realtype tstop) const override; - static int fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xdot, SUNMatrix J, void *user_data, - N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + void turnOffRootFinding() const override; - void setLinearSolver() override; + const Model *getModel() const override; - void setLinearSolverB(int which) override; + void setLinearSolver() const override; - void setNonLinearSolver() override; + void setLinearSolverB(int which) const override; - void setNonLinearSolverSens() override; + void setNonLinearSolver() const override; - void setNonLinearSolverB(int which) override; + void setNonLinearSolverSens() const override; + void setNonLinearSolverB(int which) const override; protected: - void reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, - AmiVector *ypout, realtype tout); + AmiVector *ypout, realtype tout) const; + + void allocateSolver() const override; - void allocateSolver() override; + void setSStolerances(realtype rtol, + realtype atol) const override; - void setSStolerances(double rtol, double atol) override; + void setSensSStolerances(realtype rtol, + const realtype *atol) const override; - void setSensSStolerances(double rtol, double *atol) override; + void setSensErrCon(bool error_corr) const override; - void setSensErrCon(bool error_corr) override; + void setQuadErrConB(int which, bool flag) const override; - void setQuadErrConB(int which, bool flag) override; + void setErrHandlerFn() const override; - void setErrHandlerFn() override; + void setUserData(Model *model) const override; - void setUserData(Model *model) override; + void setUserDataB(int which, Model *model) const override; - void setUserDataB(int which, Model *model) override; + void setMaxNumSteps(long int mxsteps) const override; - void setMaxNumSteps(long int mxsteps) override; + void setStabLimDet(int stldet) const override; - void setStabLimDet(int stldet) override; + void setStabLimDetB(int which, int stldet) const override; - void setStabLimDetB(int which, int stldet) override; + void setId(const Model *model) const override; - void setId(Model *model) override; + void setSuppressAlg(bool flag) const override; - void setSuppressAlg(bool flag) override; - - void resetState(void *ida_mem, N_Vector yy0, N_Vector yp0); + void resetState(void *ida_mem, const_N_Vector yy0, + const_N_Vector yp0) const; - void setSensParams(realtype *p, realtype *pbar, int *plist) override; + void setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const override; - void adjInit() override; + void adjInit() const override; - void allocateSolverB(int *which) override; + void allocateSolverB(int *which) const override; - void setMaxNumStepsB(int which, long int mxstepsB) override; + void setMaxNumStepsB(int which, + long int mxstepsB) const override; void setSStolerancesB(int which, realtype relTolB, - realtype absTolB) override; + realtype absTolB) const override; - void diag() override; + void diag() const override; - void diagB(int which) override; + void diagB(int which) const override; - void getNumSteps(void *ami_mem, long int *numsteps) const override; + void getNumSteps(const void *ami_mem, long int *numsteps) const override; - void getNumRhsEvals(void *ami_mem, long int *numrhsevals) const override; + void getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const override; - void getNumErrTestFails(void *ami_mem, + void getNumErrTestFails(const void *ami_mem, long int *numerrtestfails) const override; - void getNumNonlinSolvConvFails(void *ami_mem, - long int *numnonlinsolvconvfails) const override; - - void getLastOrder(void *ami_mem, int *order) const override; - - void *getAdjBmem(void *ami_mem, int which) override; - - void init(AmiVector *x, AmiVector *dx, realtype t) override; - - void binit(int which, AmiVector *xB, AmiVector *dxB, realtype t) override; - - void qbinit(int which, AmiVector *qBdot) override; - - void rootInit(int ne) override; - - void sensInit1(AmiVectorArray *sx, AmiVectorArray *sdx, int nplist) override; - - void setDenseJacFn() override; - - void setSparseJacFn() override; - - void setBandJacFn() override; + void + getNumNonlinSolvConvFails(const void *ami_mem, + long int *numnonlinsolvconvfails) const override; - void setJacTimesVecFn() override; + void getLastOrder(const void *ami_mem, int *order) const override; - void setDenseJacFnB(int which) override; + void *getAdjBmem(void *ami_mem, int which) const override; - void setSparseJacFnB(int which) override; + void init(realtype t0, const AmiVector &x0, + const AmiVector &dx0) const override; - void setBandJacFnB(int which) override; + void sensInit1(const AmiVectorArray &sx0, const AmiVectorArray &sdx0) const override; - void setJacTimesVecFnB(int which) override; + void binit(int which, realtype tf, + const AmiVector &xB0, const AmiVector &dxB0) const override; - static int fJB(realtype t, realtype cj, N_Vector x, - N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector xBdot, - SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void qbinit(int which, const AmiVector &xQB0) const override; - static int fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, N_Vector xBdot, - SUNMatrix JB, void *user_data, N_Vector tmp1B, - N_Vector tmp2B, N_Vector tmp3B); + void rootInit(int ne) const override; - static int fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xdot, SUNMatrix J, void *user_data, - N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + void setDenseJacFn() const override; - static int fJBandB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, N_Vector xBdot, SUNMatrix JB, - void *user_data, N_Vector tmp1B, N_Vector tmp2B, - N_Vector tmp3B); + void setSparseJacFn() const override; - static int fJv(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, - N_Vector v, N_Vector Jv, realtype cj, void *user_data, - N_Vector tmp1, N_Vector tmp2); + void setBandJacFn() const override; - static int fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector xBdot, N_Vector vB, N_Vector JvB, - realtype cj, void *user_data, N_Vector tmpB1, - N_Vector tmpB2); + void setJacTimesVecFn() const override; - static int froot(realtype t, N_Vector x, N_Vector dx, realtype *root, - void *user_data); + void setDenseJacFnB(int which) const override; - static int fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector xBdot, void *user_data); + void setSparseJacFnB(int which) const override; - static int fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector qBdot, - void *user_data); + void setBandJacFnB(int which) const override; - static int fsxdot(int Ns, realtype t, N_Vector x, N_Vector dx, N_Vector xdot, - N_Vector *sx, N_Vector *sdx, N_Vector *sxdot, void *user_data, - N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + void setJacTimesVecFnB(int which) const override; }; } // namespace amici diff --git a/include/amici/steadystateproblem.h b/include/amici/steadystateproblem.h index 9d04388946..a5d58d238f 100644 --- a/include/amici/steadystateproblem.h +++ b/include/amici/steadystateproblem.h @@ -97,33 +97,29 @@ class SteadystateProblem { * @param model pointer to the AMICI model object * @return solver instance */ - std::unique_ptr createSteadystateSimSolver(Solver *solver, - Model *model); + std::unique_ptr createSteadystateSimSolver(const Solver *solver, + Model *model) const; - /** default constructor - * @param t pointer to time variable - * @param x pointer to state variables - * @param sx pointer to state sensitivity variables + /** + * @brief constructor + * @param solver pointer to Solver instance + */ + explicit SteadystateProblem(const Solver *solver); + + /** + * @brief routine that writes solutions of steadystate problem to target + vectors + * @param t final timepoint + * @param x steadystate state + * @param sx steadystate state sensitivity */ - SteadystateProblem(realtype *t, AmiVector *x, AmiVectorArray *sx) : - delta(x->getLength()), - ewt(x->getLength()), - rel_x_newton(x->getLength()), - x_newton(x->getLength()), - x_old(x->getLength()), - dx(x->getLength()), - xdot(x->getLength()), - xdot_old(x->getLength()), - sdx(x->getLength(),sx->getLength()) - { - this->t = t; - this->x = x; - this->sx = sx; - } + void writeSolution(realtype *t, AmiVector &x, AmiVectorArray &sx) const; + private: - realtype *t; - /** newton step? */ + /** time variable for simulation steadystate finding */ + realtype t; + /** newton step */ AmiVector delta; /** error weights */ AmiVector ewt; @@ -132,7 +128,7 @@ class SteadystateProblem { /** container for absolute error calcuation? */ AmiVector x_newton; /** state vector */ - AmiVector *x; + AmiVector x; /** old state vector */ AmiVector x_old; /** differential state vector */ @@ -142,7 +138,7 @@ class SteadystateProblem { /** old time derivative state vector */ AmiVector xdot_old; /** state sensitivities */ - AmiVectorArray *sx; + AmiVectorArray sx; /** state differential sensitivities */ AmiVectorArray sdx; diff --git a/include/amici/sundials_linsol_wrapper.h b/include/amici/sundials_linsol_wrapper.h index e1351d9806..e7933a334c 100644 --- a/include/amici/sundials_linsol_wrapper.h +++ b/include/amici/sundials_linsol_wrapper.h @@ -837,7 +837,7 @@ class SUNNonLinSolFixedPoint : public SUNNonLinSolWrapper { * @param x template for cloning vectors needed within the solver. * @param m number of acceleration vectors to use */ - SUNNonLinSolFixedPoint(const N_Vector x, int m = 0); + SUNNonLinSolFixedPoint(const_N_Vector x, int m = 0); /** * @brief Create fixed-point solver for use with sensitivity analysis @@ -848,7 +848,7 @@ class SUNNonLinSolFixedPoint : public SUNNonLinSolWrapper { * @param x template for cloning vectors needed within the solver. * @param m number of acceleration vectors to use */ - SUNNonLinSolFixedPoint(int count, const N_Vector x, int m = 0); + SUNNonLinSolFixedPoint(int count, const_N_Vector x, int m = 0); /** * @brief Get function to evaluate the fixed point function G(y) = y diff --git a/include/amici/sundials_matrix_wrapper.h b/include/amici/sundials_matrix_wrapper.h index 0d773f0944..f39c510129 100644 --- a/include/amici/sundials_matrix_wrapper.h +++ b/include/amici/sundials_matrix_wrapper.h @@ -5,12 +5,12 @@ #include // SUNMatrix_Dense #include // SUNMatrix_Sparse -#include - #include #include +#include "amici/vector.h" + namespace amici { /** @@ -144,7 +144,7 @@ class SUNMatrixWrapper { * @param c output vector, may already contain values * @param b multiplication vector */ - void multiply(N_Vector c, const N_Vector b) const; + void multiply(N_Vector c, const_N_Vector b) const; /** * @brief Perform matrix vector multiplication c += A*b @@ -169,4 +169,23 @@ class SUNMatrixWrapper { } // namespace amici +namespace gsl { +/** + * @brief Create span from SUNMatrix + * @param nv + * @return + */ +inline span make_span(SUNMatrix m) +{ + switch (SUNMatGetID(m)) { + case SUNMATRIX_DENSE: + return span(SM_DATA_D(m), SM_LDATA_D(m)); + case SUNMATRIX_SPARSE: + return span(SM_DATA_S(m), SM_NNZ_S(m)); + default: + throw amici::AmiException("Unimplemented SUNMatrix type for make_span"); + } +} +} // namespace gsl + #endif // AMICI_SUNDIALS_MATRIX_WRAPPER_H diff --git a/include/amici/vector.h b/include/amici/vector.h index 86deba8cd7..daf8f6e002 100644 --- a/include/amici/vector.h +++ b/include/amici/vector.h @@ -2,321 +2,318 @@ #define AMICI_VECTOR_H #include +#include -#include #include +#include + +#include + namespace amici { +/** Since const N_Vector is not what we want */ +using const_N_Vector = + std::add_const::type *>::type; + /** AmiVector class provides a generic interface to the NVector_Serial struct */ class AmiVector { -public: + public: /** - * Creates an std::vector and attaches the - * data pointer to a newly created N_Vector_Serial. - * Using N_VMake_Serial ensures that the N_Vector - * module does not try to deallocate the data vector - * when calling N_VDestroy_Serial - * @param length number of elements in vector - * @return new AmiVector instance - */ - + * @brief Default constructor + * @return new empty AmiVectorArray instance + */ + AmiVector() = default; + + /** Creates an std::vector and attaches the + * data pointer to a newly created N_Vector_Serial. + * Using N_VMake_Serial ensures that the N_Vector + * module does not try to deallocate the data vector + * when calling N_VDestroy_Serial + * @brief emmpty constructor + * @param length number of elements in vector + * @return new AmiVector instance + */ explicit AmiVector(const long int length) : vec(static_cast(length), 0.0), - nvec(N_VMake_Serial(length,vec.data())) - { - } + nvec(N_VMake_Serial(length, vec.data())) {} - /** constructor from std::vector, copies data from std::vector - * and constructs an nvec that points to the data - * @param rvec vector from which the data will be copied - * @return new AmiVector instance - */ + /** Copies data from std::vector and constructs an nvec that points to the + * data + * @brief constructor from std::vector, + * @param rvec vector from which the data will be copied + * @return new AmiVector instance + */ explicit AmiVector(std::vector rvec) : vec(std::move(rvec)), - nvec(N_VMake_Serial(static_cast(vec.size()), vec.data())) - { - } + nvec(N_VMake_Serial(static_cast(vec.size()), vec.data())) {} - /** copy constructor - * @param vold vector from which the data will be copied - */ - AmiVector(const AmiVector& vold): vec(vold.vec) { - nvec = N_VMake_Serial(static_cast(vold.vec.size()), vec.data()); + /** + * @brief copy constructor + * @param vold vector from which the data will be copied + */ + AmiVector(const AmiVector &vold) : vec(vold.vec) { + nvec = + N_VMake_Serial(static_cast(vold.vec.size()), vec.data()); } - /** copy assignment operator - * @param other right hand side - * @return left hand side - */ - AmiVector& operator=(AmiVector const& other) { - vec = other.vec; - if(nvec) - N_VDestroy_Serial(nvec); - nvec = N_VMake_Serial(static_cast(vec.size()),vec.data()); - return *this; - } + /** + * @brief copy assignment operator + * @param other right hand side + * @return left hand side + */ + AmiVector &operator=(AmiVector const &other); - /** data accessor - * @return pointer to data array - */ - realtype *data() { - return vec.data(); - } + /** + * @brief data accessor + * @return pointer to data array + */ + realtype *data(); - /** const data accessor - * @return const pointer to data array - */ - const realtype *data() const { - return vec.data(); - } + /** + * @brief const data accessor + * @return const pointer to data array + */ + const realtype *data() const; - /** N_Vector accessor - * @return N_Vector - */ - N_Vector getNVector() const { - return nvec; - } + /** + * @brief N_Vector accessor + * @return N_Vector + */ + N_Vector getNVector(); - /** Vector accessor - * @return Vector - */ - std::vector const& getVector() const { - return vec; - } + /** + * @brief N_Vector accessor + * @return N_Vector + */ + const_N_Vector getNVector() const; - /** returns the length of the vector - * @return length - */ - int getLength() const { - return static_cast(vec.size()); - } + /** + * @brief Vector accessor + * @return Vector + */ + std::vector const &getVector(); - /** resets the Vector by filling with zero values - */ - void reset() { - set(0.0); - } + /** + * @brief returns the length of the vector + * @return length + */ + int getLength() const; - /** changes the sign of data elements - */ - void minus() { - for(std::vector::iterator it = vec.begin(); - it != vec.end(); ++it) - *it = -*it; - } + /** + * @brief resets the Vector by filling with zero values + */ + void reset(); - /** sets all data elements to a specific value - * @param val value for data elements - */ - void set(realtype val) { - std::fill(vec.begin(), vec.end(), val); - } + /** + * @brief changes the sign of data elements + */ + void minus(); - /** accessor to data elements of the vector - * @param pos index of element - * @return element - */ - realtype& operator[](int pos) { - return vec.at(static_cast(pos)); - } + /** + * @brief sets all data elements to a specific value + * @param val value for data elements + */ + void set(realtype val); - /** accessor to data elements of the vector - * @param pos index of element - * @return element - */ - realtype& at(int pos) { - return vec.at(static_cast(pos)); - } + /** + * @brief accessor to data elements of the vector + * @param pos index of element + * @return element + */ + realtype &operator[](int pos); + /** + * @brief accessor to data elements of the vector + * @param pos index of element + * @return element + */ + realtype &at(int pos); - /** accessor to data elements of the vector + /** + * @brief accessor to data elements of the vector * @param pos index of element * @return element */ - const realtype& at(int pos) const { - return vec.at(static_cast(pos)); - } + const realtype &at(int pos) const; - ~AmiVector(){ - N_VDestroy_Serial(nvec); - } + /** + * @brief copies data from another AmiVector + * @param other data source + */ + void copy(const AmiVector &other); + + /** + * @brief destructor + */ + ~AmiVector(); -private: + private: /** main data storage */ std::vector vec; - /** N_Vector, will be synchronised such that it points to - * data in vec */ + + /** N_Vector, will be synchronised such that it points to data in vec */ N_Vector nvec = nullptr; -}; + /** + * @brief reconstructs nvec such that data pointer points to vec data array + */ + void synchroniseNVector(); +}; -/** AmiVectorArray class. - provides a generic interface to arrays of NVector_Serial structs -*/ +/** + * @brief AmiVectorArray class. + * + * Provides a generic interface to arrays of NVector_Serial structs + */ class AmiVectorArray { -public: - + public: /** * @brief Default constructor * @return new empty AmiVectorArray instance */ - AmiVectorArray() {} - - /** creates an std::vector and attaches the - * data pointer to a newly created N_VectorArray - * using CloneVectorArrayEmpty ensures that the N_Vector - * module does not try to deallocate the data vector - * when calling N_VDestroyVectorArray_Serial - * @param length_inner length of vectors - * @param length_outer number of vectors - * @return New AmiVectorArray instance - */ - AmiVectorArray(long int length_inner, long int length_outer) - : vec_array(length_outer, AmiVector(length_inner)) - { - nvec_array.resize(length_outer); - for (int idx = 0; idx < length_outer; idx++) { - nvec_array.at(idx) = vec_array.at(idx).getNVector(); - } - } - - /** + AmiVectorArray() = default; + + /** + * Creates an std::vector and attaches the + * data pointer to a newly created N_VectorArray + * using CloneVectorArrayEmpty ensures that the N_Vector + * module does not try to deallocate the data vector + * when calling N_VDestroyVectorArray_Serial + * @brief empty constructor + * @param length_inner length of vectors + * @param length_outer number of vectors + * @return New AmiVectorArray instance + */ + AmiVectorArray(long int length_inner, long int length_outer); + + /** * @brief copy assignment operator * @param other right hand side * @return left hand side */ - AmiVectorArray& operator=(AmiVectorArray const& other) { - vec_array = other.vec_array; - nvec_array.resize(other.getLength()); - for (int idx = 0; idx < other.getLength(); idx++) { - nvec_array.at(idx) = vec_array.at(idx).getNVector(); - } - return *this; - } + AmiVectorArray &operator=(AmiVectorArray const &other); - /** copy constructor - * @param vaold object to copy from - * @return new AmiVectorArray instance - */ - AmiVectorArray(const AmiVectorArray& vaold) : vec_array(vaold.vec_array) { - nvec_array.resize(vaold.getLength()); - for (int idx = 0; idx < vaold.getLength(); idx++) { - nvec_array.at(idx) = vec_array.at(idx).getNVector(); - } - } + /** + * @brief copy constructor + * @param vaold object to copy from + * @return new AmiVectorArray instance + */ + AmiVectorArray(const AmiVectorArray &vaold); - /** accessor to data of AmiVector elements - * @param pos index of AmiVector - * @return pointer to data array - */ - realtype *data(int pos) { - return vec_array.at(pos).data(); - } + /** + * @brief accessor to data of AmiVector elements + * @param pos index of AmiVector + * @return pointer to data array + */ + realtype *data(int pos); - /** const accessor to data of AmiVector elements - * @param pos index of AmiVector - * @return const pointer to data array - */ - const realtype *data(int pos) const { - return vec_array.at(pos).data(); - } + /** + * @brief const accessor to data of AmiVector elements + * @param pos index of AmiVector + * @return const pointer to data array + */ + const realtype *data(int pos) const; - /** accessor to elements of AmiVector elements + /** + * @brief accessor to elements of AmiVector elements * @param ipos inner index in AmiVector * @param jpos outer index in AmiVectorArray * @return element */ - realtype& at(int ipos, int jpos) { - return vec_array.at(jpos).at(ipos); - } + realtype &at(int ipos, int jpos); - /** accessor to elements of AmiVector elements - * @param ipos inner index in AmiVector - * @param jpos outer index in AmiVectorArray - * @return element - */ - const realtype& at(int ipos, int jpos) const { - return vec_array.at(jpos).at(ipos); - } + /** + * @brief const accessor to elements of AmiVector elements + * @param ipos inner index in AmiVector + * @param jpos outer index in AmiVectorArray + * @return element + */ + const realtype &at(int ipos, int jpos) const; - /** accessor to NVectorArray - * @return N_VectorArray - */ - N_Vector *getNVectorArray() { - return nvec_array.data(); - } + /** + * @brief accessor to NVectorArray + * @return N_VectorArray + */ + N_Vector *getNVectorArray(); - /** accessor to NVector element - * @param pos index of corresponding AmiVector - * @return N_Vector - */ - N_Vector getNVector(int pos) { - return nvec_array.at(pos); - } + /** + * @brief accessor to NVector element + * @param pos index of corresponding AmiVector + * @return N_Vector + */ + N_Vector getNVector(int pos); - /** accessor to AmiVector elements - * @param pos index of AmiVector - * @return AmiVector - */ - AmiVector& operator[](int pos) { - return vec_array.at(pos); - } + /** + * @brief const accessor to NVector element + * @param pos index of corresponding AmiVector + * @return N_Vector + */ + const_N_Vector getNVector(int pos) const; - /** const accessor to AmiVector elements - * @param pos index of AmiVector - * @return const AmiVector - */ - const AmiVector& operator[](int pos) const { - return vec_array.at(pos); - } + /** + * @brief accessor to AmiVector elements + * @param pos index of AmiVector + * @return AmiVector + */ + AmiVector &operator[](int pos); - /** length of AmiVectorArray - * @return length - */ - int getLength() const { - return static_cast(vec_array.size()); - } + /** + * @brief const accessor to AmiVector elements + * @param pos index of AmiVector + * @return const AmiVector + */ + const AmiVector &operator[](int pos) const; - /** resets every AmiVector in AmiVectorArray */ - void reset() { - for(auto &v: vec_array) - v.reset(); - } + /** + * @brief length of AmiVectorArray + * @return length + */ + int getLength() const; + + /** + * @brief resets every AmiVector in AmiVectorArray + */ + void reset(); - /** flattens the AmiVectorArray to a vector in row-major format + /** + * @brief flattens the AmiVectorArray to a vector in row-major format * @param vec vector into which the AmiVectorArray will be flattened. Must * have length equal to number of elements. */ - void flatten_to_vector(std::vector& vec) const { - int n_outer = vec_array.size(); - if(n_outer == 0) - return; //nothing to do ... - int n_inner = vec_array.at(0).getLength(); - - if (static_cast(vec.size()) != n_inner * n_outer) { - throw AmiException("Dimension of AmiVectorArray (%ix%i) does not " - "match target vector dimension (%u)", - n_inner, n_outer, vec.size()); - } - - for (int outer = 0; outer < n_outer; ++outer) { - for (int inner = 0; inner < n_inner; ++inner) - vec.at(inner + outer * n_inner) = this->at(inner,outer); - } - } + void flatten_to_vector(std::vector &vec) const; + + /** + * @brief copies data from another AmiVectorArray + * @param other data source + */ + void copy(const AmiVectorArray &other); ~AmiVectorArray() = default; -private: + private: /** main data storage */ std::vector vec_array; - /** N_Vector array, will be synchronised such that it points to - * respective elements in the vec_array - */ + + /** + * N_Vector array, will be synchronised such that it points to + * respective elements in the vec_array + */ std::vector nvec_array; }; -} +} // namespace amici + +namespace gsl { +/** + * @brief Create span from N_Vector + * @param nv + * @return + */ +inline span make_span(N_Vector nv) +{ + return span(N_VGetArrayPointer(nv), N_VGetLength_Serial(nv)); +} +} // namespace gsl #endif /* AMICI_VECTOR_H */ diff --git a/matlab/@amimodel/compileAndLinkModel.m b/matlab/@amimodel/compileAndLinkModel.m index f6aceaf6f8..1e709f9ac7 100644 --- a/matlab/@amimodel/compileAndLinkModel.m +++ b/matlab/@amimodel/compileAndLinkModel.m @@ -179,12 +179,13 @@ function compileAndLinkModel(modelname, modelSourceFolder, coptim, debug, funs, % generate hash for file and append debug string if we have an md5 % file, check this hash against the contained hash cppsrc = {'amici', 'symbolic_functions','spline', ... - 'edata','rdata', ... + 'edata','rdata', 'exception', ... 'interface_matlab', 'misc', ... 'solver', 'solver_cvodes', 'solver_idas', ... 'model', 'model_ode', 'model_dae', 'returndata_matlab', ... 'forwardproblem', 'steadystateproblem', 'backwardproblem', 'newton_solver', ... - 'abstract_model', 'sundials_matrix_wrapper', 'sundials_linsol_wrapper' + 'abstract_model', 'sundials_matrix_wrapper', 'sundials_linsol_wrapper', ... + 'vector' }; % to be safe, recompile everything if headers have changed. otherwise % would need to check the full include hierarchy diff --git a/models/model_calvetti/CMakeLists.txt b/models/model_calvetti/CMakeLists.txt index 23760acd3c..854aec73a2 100644 --- a/models/model_calvetti/CMakeLists.txt +++ b/models/model_calvetti/CMakeLists.txt @@ -24,6 +24,7 @@ foreach(FLAG ${MY_CXX_FLAGS}) endforeach(FLAG) find_package(Amici HINTS ${CMAKE_CURRENT_LIST_DIR}/../../build) +message(STATUS "Found AMICI ${Amici_DIR}") set(MODEL_DIR ${CMAKE_CURRENT_LIST_DIR}) diff --git a/python/amici/gradient_check.py b/python/amici/gradient_check.py index 8418aaa396..1a6f73483a 100644 --- a/python/amici/gradient_check.py +++ b/python/amici/gradient_check.py @@ -108,7 +108,7 @@ def check_close(result, expected, assert_fun, atol, rtol, field, ip=None): index_str = f'at index ip={ip} ' check_type = 'FD check ' print(f'{check_type} failed for {field} {index_str}for ' - f'{close.sum()} indices:') + f'{close.size - close.sum()} indices:') adev = np.abs(result - expected) rdev = np.abs((result - expected)/(expected + atol)) print(f'max(adev): {adev.max()}, max(rdev): {rdev.max()}') diff --git a/src/CMakeLists.template.cmake b/src/CMakeLists.template.cmake index a878674ff8..f0d9b18331 100644 --- a/src/CMakeLists.template.cmake +++ b/src/CMakeLists.template.cmake @@ -24,6 +24,7 @@ foreach(FLAG ${MY_CXX_FLAGS}) endforeach(FLAG) find_package(Amici HINTS ${CMAKE_CURRENT_LIST_DIR}/../../build) +message(STATUS "Found AMICI ${Amici_DIR}") set(MODEL_DIR ${CMAKE_CURRENT_LIST_DIR}) diff --git a/src/abstract_model.cpp b/src/abstract_model.cpp index e5ac53d31d..ba89b0f53a 100644 --- a/src/abstract_model.cpp +++ b/src/abstract_model.cpp @@ -35,7 +35,7 @@ void AbstractModel::fsx0(realtype * /*sx0*/, const realtype /*t*/, const realty __func__); } -void AbstractModel::fdx0(AmiVector *x0, AmiVector *dx0) {} +void AbstractModel::fdx0(AmiVector &x0, AmiVector &dx0) {} void AbstractModel::fstau(realtype * /*stau*/, const realtype /*t*/, const realtype * /*x*/, const realtype * /*p*/, const realtype * /*k*/, @@ -302,8 +302,8 @@ void AbstractModel::fdwdx(realtype *dwdx, const realtype t, const realtype *x, const realtype *p, const realtype *k, const realtype *h, const realtype *w, const realtype *tcl) {} - -void AbstractModel::fdwdx_colptrs(sunindextype *indexvals) {} + +void AbstractModel::fdwdx_colptrs(sunindextype *indexptrs) {} void AbstractModel::fdwdx_rowvals(sunindextype *indexvals) {} diff --git a/src/amici.cpp b/src/amici.cpp index 319c356835..4a3d290e00 100644 --- a/src/amici.cpp +++ b/src/amici.cpp @@ -32,11 +32,10 @@ static_assert(AMICI_ONE_STEP == CV_ONE_STEP, "AMICI_ONE_STEP != CV_ONE_STEP"); static_assert(std::is_same::value, "Definition of realtype does not match"); + namespace amici { -/** errMsgIdAndTxt is a function pointer for printErrMsgIdAndTxt */ msgIdAndTxtFp errMsgIdAndTxt = &printErrMsgIdAndTxt; -/** warnMsgIdAndTxt is a function pointer for printWarnMsgIdAndTxt */ msgIdAndTxtFp warnMsgIdAndTxt = &printWarnMsgIdAndTxt; @@ -48,7 +47,7 @@ std::unique_ptr runAmiciSimulation(Solver &solver, const ExpData *ed ConditionContext conditionContext(&model, edata); try{ - rdata = std::unique_ptr(new ReturnData(solver,&model)); + rdata = std::unique_ptr(new ReturnData(solver, model)); if (model.nx_solver <= 0) { return rdata; @@ -87,15 +86,6 @@ std::unique_ptr runAmiciSimulation(Solver &solver, const ExpData *ed return rdata; } -/*! - * printErrMsgIdAndTxt prints a specified error message associated to the - * specified identifier - * - * @param[in] identifier error identifier @type char - * @param[in] format string with error message printf-style format - * @param ... arguments to be formatted - * @return void - */ void printErrMsgIdAndTxt(const char *identifier, const char *format, ...) { if(identifier && *identifier != '\0') fprintf(stderr, "[Error] %s: ", identifier); @@ -108,15 +98,6 @@ void printErrMsgIdAndTxt(const char *identifier, const char *format, ...) { fprintf(stderr, "\n"); } -/*! - * printErrMsgIdAndTxt prints a specified warning message associated to the - * specified identifier - * - * @param[in] identifier warning identifier @type char - * @param[in] format string with error message printf-style format - * @param ... arguments to be formatted - * @return void - */ void printWarnMsgIdAndTxt(const char *identifier, const char *format, ...) { if(identifier && *identifier != '\0') printf("[Warning] %s: ", identifier); @@ -155,7 +136,7 @@ std::vector > runAmiciSimulations(const Solver &solv if (failed) { ConditionContext conditionContext(myModel.get(), edatas[i]); results[i] = - std::unique_ptr(new ReturnData(solver, &model)); + std::unique_ptr(new ReturnData(solver, model)); } results[i] = runAmiciSimulation(*mySolver, edatas[i], *myModel); diff --git a/src/backwardproblem.cpp b/src/backwardproblem.cpp index 64ff9b5ea3..10f8d32689 100644 --- a/src/backwardproblem.cpp +++ b/src/backwardproblem.cpp @@ -12,6 +12,10 @@ namespace amici { BackwardProblem::BackwardProblem(const ForwardProblem *fwd) : + model(fwd->model), + rdata(fwd->rdata), + solver(fwd->solver), + t(fwd->getTime()), llhS0(static_cast(fwd->model->nJ * fwd->model->nplist()), 0.0), xB(fwd->model->nx_solver), dxB(fwd->model->nx_solver), @@ -19,33 +23,32 @@ BackwardProblem::BackwardProblem(const ForwardProblem *fwd) : x_disc(fwd->getStatesAtDiscontinuities()), xdot_disc(fwd->getRHSAtDiscontinuities()), xdot_old_disc(fwd->getRHSBeforeDiscontinuities()), - sx(fwd->getStateSensitivity()), + sx0(fwd->getStateSensitivity()), nroots(fwd->getNumberOfRoots()), discs(fwd->getDiscontinuities()), irdiscs(fwd->getDiscontinuities()), + iroot(fwd->getRootCounter()), rootidx(fwd->getRootIndexes()), dJydx(fwd->getDJydx()), - dJzdx(fwd->getDJzdx()) - { - t = fwd->getTime(); - model = fwd->model; - solver = fwd->solver; - rdata = fwd->rdata; - iroot = fwd->getRootCounter(); - } + dJzdx(fwd->getDJzdx()) {} void BackwardProblem::workBackwardProblem() { - if (model->nx_solver <= 0 || solver->getSensitivityOrder() < SensitivityOrder::first || - solver->getSensitivityMethod() != SensitivityMethod::adjoint || model->nplist() == 0) { + if (model->nx_solver <= 0 || + solver->getSensitivityOrder() < SensitivityOrder::first || + solver->getSensitivityMethod() != SensitivityMethod::adjoint || + model->nplist() == 0) { return; } - - solver->setupB(this, model); - - int it = rdata->nt - 2; + + int it = rdata->nt - 1; + model->initializeB(xB, dxB, xQB); + handleDataPointB(it); + solver->setupB(&which, rdata->ts[it], model, xB, dxB, xQB); + + --it; --iroot; while (it >= 0 || iroot >= 0) { @@ -54,9 +57,8 @@ void BackwardProblem::workBackwardProblem() { double tnext = getTnext(discs, iroot, it); if (tnext < t) { - solver->solveB(tnext, AMICI_NORMAL); - solver->getB(which, &t, &xB, &dxB); - solver->getQuadB(which, &t, &xQB); + solver->runB(tnext); + solver->writeSolutionB(&t, xB, dxB, xQB, this->which); solver->getDiagnosisB(it, rdata, this->which); } @@ -75,22 +77,15 @@ void BackwardProblem::workBackwardProblem() { } /* reinit states */ - solver->reInitB(which, t, &xB, &dxB); - solver->quadReInitB(which, &xQB); - solver->calcICB(which, t, &xB, &dxB); - - /* if we have to integrate further we need to postprocess for step size - computation */ - if (t > model->t0()) - solver->reInitPostProcessB(which, &t, &xB, &dxB, model->t0()); + solver->reInitB(which, t, xB, dxB); + solver->quadReInitB(which, xQB); } /* we still need to integrate from first datapoint to tstart */ if (t > model->t0()) { /* solve for backward problems */ - solver->solveB(model->t0(), AMICI_NORMAL); - solver->getQuadB(which, &(t), &xQB); - solver->getB(which, &(t), &xB, &dxB); + solver->runB(model->t0()); + solver->writeSolutionB(&t, xB, dxB, xQB, this->which); solver->getDiagnosisB(0, rdata, this->which); } @@ -105,8 +100,10 @@ void BackwardProblem::handleEventB(const int iroot) { continue; } - model->fdeltaqB(ie, t, &x_disc[iroot],&xB,&xdot_disc[iroot], &xdot_old_disc[iroot]); - model->fdeltaxB(ie, t, &x_disc[iroot],&xB,&xdot_disc[iroot], &xdot_old_disc[iroot]); + model->fdeltaqB(ie, t, x_disc[iroot], xB, xdot_disc[iroot], + xdot_old_disc[iroot]); + model->fdeltaxB(ie, t, x_disc[iroot], xB, xdot_disc[iroot], + xdot_old_disc[iroot]); for (int ix = 0; ix < model->nxtrue_solver; ++ix) { for (int iJ = 0; iJ < model->nJ; ++iJ) { @@ -142,7 +139,6 @@ void BackwardProblem::handleDataPointB(const int it) { } } - realtype BackwardProblem::getTnext(std::vector const& troot, const int iroot, const int it) { if (it < 0 @@ -161,7 +157,7 @@ void BackwardProblem::computeLikelihoodSensitivities() for (int ip = 0; ip < model->nplist(); ++ip) { llhS0[ip] = 0.0; for (int ix = 0; ix < model->nxtrue_solver; ++ix) { - llhS0[ip] += xB[ix] * sx.at(ix,ip); + llhS0[ip] += xB[ix] * sx0.at(ix,ip); } } } else { @@ -169,8 +165,8 @@ void BackwardProblem::computeLikelihoodSensitivities() llhS0[ip + iJ * model->nplist()] = 0.0; for (int ix = 0; ix < model->nxtrue_solver; ++ix) { llhS0[ip + iJ * model->nplist()] += - xB[ix + iJ * model->nxtrue_solver] * sx.at(ix,ip)+ - xB[ix] * sx.at(ix + iJ * model->nxtrue_solver,ip); + xB[ix + iJ * model->nxtrue_solver] * sx0.at(ix,ip)+ + xB[ix] * sx0.at(ix + iJ * model->nxtrue_solver,ip); } } } diff --git a/src/edata.cpp b/src/edata.cpp index e474b8303d..67c772062e 100644 --- a/src/edata.cpp +++ b/src/edata.cpp @@ -11,8 +11,6 @@ namespace amici { -ExpData::ExpData() : nytrue_(0), nztrue_(0), nmaxevent_(0) {} - ExpData::ExpData(int nytrue, int nztrue, int nmaxevent) : nytrue_(nytrue), nztrue_(nztrue), nmaxevent_(nmaxevent) { @@ -344,12 +342,12 @@ void ConditionContext::applyCondition(const ExpData *edata) { if(!edata) return; - + // this needs to go first, otherwise nplist will not have the right // dimension for all other fields that depend on Model::nplist if(!edata->plist.empty()) model->setParameterList(edata->plist); - + // this needs to go second as setParameterScale will reset sx0 if(!edata->pscale.empty()) { if(edata->pscale.size() != (unsigned) model->np()) @@ -357,9 +355,9 @@ void ConditionContext::applyCondition(const ExpData *edata) " match ExpData (%zd).", model->np(), edata->pscale.size()); model->setParameterScale(edata->pscale); - + } - + if(!edata->x0.empty()) { if(edata->x0.size() != (unsigned) model->nx_rdata) throw AmiException("Number of initial conditions (%d) in model does" @@ -367,7 +365,7 @@ void ConditionContext::applyCondition(const ExpData *edata) model->nx_rdata, edata->x0.size()); model->setInitialStates(edata->x0); } - + if(!edata->sx0.empty()) { if(edata->sx0.size() != (unsigned) model->nx_rdata * model->nplist()) throw AmiException("Number of initial conditions sensitivities (%d)" @@ -376,7 +374,7 @@ void ConditionContext::applyCondition(const ExpData *edata) edata->sx0.size()); model->setInitialStateSensitivities(edata->sx0); } - + if(!edata->parameters.empty()) { if(edata->parameters.size() != (unsigned) model->np()) throw AmiException("Number of parameters (%d) in model does not" @@ -384,7 +382,7 @@ void ConditionContext::applyCondition(const ExpData *edata) model->np(), edata->parameters.size()); model->setParameters(edata->parameters); } - + if(!edata->fixedParameters.empty()) { // fixed parameter in model are superseded by those provided in edata if(edata->fixedParameters.size() != (unsigned) model->nk()) @@ -411,7 +409,7 @@ void ConditionContext::restore() model->setParameters(originalParameters); model->setFixedParameters(originalFixedParameters); model->setTimepoints(originalTimepoints); - + } diff --git a/src/exception.cpp b/src/exception.cpp new file mode 100644 index 0000000000..e2ea1e9b1c --- /dev/null +++ b/src/exception.cpp @@ -0,0 +1,55 @@ +#include "amici/exception.h" +#include "amici/misc.h" + +#include +#include +#include + + +namespace amici { + +AmiException::AmiException(const char *fmt, ...) { + va_list ap; + va_start(ap, fmt); + vsnprintf(msg, sizeof(msg), fmt, ap); + va_end(ap); + storeBacktrace(12); +} + +AmiException::AmiException(const amici::AmiException &old) { + snprintf(msg, sizeof(msg), "%s", old.msg); + snprintf(trace, sizeof(trace), "%s", old.trace); +} + +const char *AmiException::what() const noexcept { + return msg; +} + +const char *AmiException::getBacktrace() const { + return trace; +} + +void AmiException::storeBacktrace(const int nMaxFrames) { + snprintf(trace, sizeof(trace), "%s", backtraceString(nMaxFrames).c_str()); +} + +CvodeException::CvodeException(const int error_code, const char *function) : + AmiException("Cvode routine %s failed with error code %i",function,error_code){} + +IDAException::IDAException(const int error_code, const char *function) : + AmiException("IDA routine %s failed with error code %i",function,error_code){} + +IntegrationFailure::IntegrationFailure(int code, realtype t) : + AmiException("AMICI failed to integrate the forward problem"), + error_code(code), time(t) {} + +IntegrationFailureB::IntegrationFailureB(int code, realtype t) : + AmiException("AMICI failed to integrate the backward problem"), + error_code(code), time(t) {} + +NewtonFailure::NewtonFailure(int code, const char *function) : + AmiException("NewtonSolver routine %s failed with error code %i",function,code) { + error_code = code; +} + +} // namespace amici diff --git a/src/forwardproblem.cpp b/src/forwardproblem.cpp index 5196084de7..79f449b910 100644 --- a/src/forwardproblem.cpp +++ b/src/forwardproblem.cpp @@ -52,18 +52,18 @@ ForwardProblem::ForwardProblem(ReturnData *rdata, const ExpData *edata, void ForwardProblem::workForwardProblem() { + bool computeSensitivities = + solver->getSensitivityOrder() >= SensitivityOrder::first && + model->nx_solver > 0; - try { - solver->setup(&x, &dx, &sx, &sdx, model); - } catch (std::exception const& ex) { - throw AmiException("AMICI setup failed:\n(%s)",ex.what()); - } catch (...) { - throw AmiException("AMICI setup failed due to an unknown error"); - } + model->initialize(x, dx, sx, sdx, computeSensitivities); + solver->setup(model->t0(), model, x, dx, sx, sdx); + // update x0 after computing consistence IC, only important for DAEs + x.copy(solver->getState(model->t0())); - model->fx_rdata(&x_rdata, &x); + model->fx_rdata(x_rdata, x); if(solver->getSensitivityOrder() >= SensitivityOrder::first) { - model->fsx_rdata(&sx_rdata, &sx); + model->fsx_rdata(sx_rdata, sx); } if(edata){ @@ -74,11 +74,11 @@ void ForwardProblem::workForwardProblem() { if (solver->getNewtonPreequilibration() || (edata && !edata->fixedParametersPreequilibration.empty())) { handlePreequilibration(); } else { - model->fx_rdata(&x_rdata, &x); - rdata->x0 = std::move(x_rdata.getVector()); + model->fx_rdata(x_rdata, x); + rdata->x0 = x_rdata.getVector(); if (solver->getSensitivityMethod() == SensitivityMethod::forward && solver->getSensitivityOrder() >= SensitivityOrder::first) { - model->fsx_rdata(&sx_rdata, &sx); + model->fsx_rdata(sx_rdata, sx); for (int ix = 0; ix < rdata->nx; ix++) { for (int ip = 0; ip < model->nplist(); ip++) rdata->sx0[ip*rdata->nx + ix] = sx_rdata.at(ix,ip); @@ -86,18 +86,14 @@ void ForwardProblem::workForwardProblem() { } } - int ncheck = 0; /* the number of (internal) checkpoints stored so far */ - /* perform presimulation if necessary */ if (edata && edata->t_presim > 0) - handlePresimulation(&ncheck); + handlePresimulation(); /* loop over timepoints */ for (int it = 0; it < model->nt(); it++) { auto nextTimepoint = model->t(it); - solver->setStopTime(nextTimepoint); - if (nextTimepoint > model->t0()) { if (model->nx_solver == 0) { t = nextTimepoint; @@ -107,19 +103,14 @@ void ForwardProblem::workForwardProblem() { // Solve for nextTimepoint while (t < nextTimepoint) { if (std::isinf(nextTimepoint)) { - SteadystateProblem sstate = SteadystateProblem(&t, &x, &sx); + SteadystateProblem sstate = SteadystateProblem(solver); sstate.workSteadyStateProblem(rdata, solver, model, it); + sstate.writeSolution(&t, x, sx); } else { - int status; - if (solver->getSensitivityMethod() == SensitivityMethod::adjoint && - solver->getSensitivityOrder() >= SensitivityOrder::first) { - status = solver->solveF(nextTimepoint, &x, &dx, - &t, AMICI_NORMAL, &ncheck); - } else { - status = solver->solve(nextTimepoint, &x, &dx, - &t, AMICI_NORMAL); - } - + int status = solver->run(nextTimepoint); + solver->writeSolution(&t, x, dx, sx); + /* sx will be copied from solver on demand if sensitivities + are computed */ if (status == AMICI_ILL_INPUT) { /* clustering of roots => turn off rootfinding */ solver->turnOffRootFinding(); @@ -163,9 +154,10 @@ void ForwardProblem::handlePreequilibration() } // pre-equilibrate - SteadystateProblem sstate = SteadystateProblem(&t, &x, &sx); + SteadystateProblem sstate = SteadystateProblem(solver); sstate.workSteadyStateProblem(rdata, solver, model, -1); + sstate.writeSolution(&t, x, sx); if(overrideFixedParameters) { // Restore @@ -175,41 +167,39 @@ void ForwardProblem::handlePreequilibration() updateAndReinitStatesAndSensitivities(true); } - void ForwardProblem::updateAndReinitStatesAndSensitivities(bool isSteadystate) { if (isSteadystate) { - model->fx_rdata(&x_rdata, &x); - rdata->x_ss = std::move(x_rdata.getVector()); + model->fx_rdata(x_rdata, x); + rdata->x_ss = x_rdata.getVector(); } - model->fx0_fixedParameters(&x); - solver->reInit(t, &x, &dx); - model->fx_rdata(&x_rdata, &x); + model->fx0_fixedParameters(x); + solver->reInit(t, x, dx); + model->fx_rdata(x_rdata, x); - rdata->x0 = std::move(x_rdata.getVector()); + rdata->x0 = x_rdata.getVector(); if (solver->getSensitivityOrder() >= SensitivityOrder::first) { if (isSteadystate) { - model->fsx_rdata(&sx_rdata, &sx); + model->fsx_rdata(sx_rdata, sx); for (int ip = 0; ip < model->nplist(); ip++) std::copy_n(sx_rdata.data(ip), rdata->nx, &rdata->sx_ss.at(ip * rdata->nx)); } - model->fsx0_fixedParameters(&sx, &x); - model->fsx_rdata(&sx_rdata, &sx); + model->fsx0_fixedParameters(sx, x); + model->fsx_rdata(sx_rdata, sx); for (int ip = 0; ip < model->nplist(); ip++) std::copy_n(sx_rdata.data(ip), rdata->nx, &rdata->sx0.at(ip * rdata->nx)); if (solver->getSensitivityMethod() == SensitivityMethod::forward) - solver->sensReInit(&sx, &sdx); + solver->sensReInit(sx, sdx); } } - -void ForwardProblem::handlePresimulation(int *ncheck) +void ForwardProblem::handlePresimulation() { // Are there dedicated condition preequilibration parameters provided? bool overrideFixedParameters = edata && !edata->fixedParametersPresimulation.empty(); @@ -226,14 +216,7 @@ void ForwardProblem::handlePresimulation(int *ncheck) t = model->t0() - edata->t_presim; updateAndReinitStatesAndSensitivities(false); - if (solver->getSensitivityMethod() == SensitivityMethod::adjoint && - solver->getSensitivityOrder() >= SensitivityOrder::first) { - solver->solveF(model->t0(), &x, &dx, - &(t), AMICI_NORMAL, ncheck); - } else { - solver->solve(model->t0(), &x, &dx, - &(t), AMICI_NORMAL); - } + solver->run(model->t0()); if(overrideFixedParameters) { model->setFixedParameters(originalFixedParameters); @@ -245,7 +228,7 @@ void ForwardProblem::handlePresimulation(int *ncheck) void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { /* store heaviside information at event occurence */ - model->froot(t, &x, &dx, rootvals.data()); + model->froot(t, x, dx, rootvals); if (!seflag) { solver->getRootInfo(rootsfound.data()); @@ -261,7 +244,7 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { /* only extract in the first event fired */ if (solver->getSensitivityOrder() >= SensitivityOrder::first && solver->getSensitivityMethod() == SensitivityMethod::forward) { - solver->getSens(&(t), &sx); + sx.copy(solver->getStateSensitivity(t)); } /* only check this in the first event fired, otherwise this will always @@ -281,9 +264,9 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { * x and the old xdot */ if (solver->getSensitivityOrder() >= SensitivityOrder::first) { /* store x and xdot to compute jump in sensitivities */ - x_old = x; + x_old = solver->getState(t); if (solver->getSensitivityMethod() == SensitivityMethod::forward) { - model->fxdot(t, &x, &dx, &xdot); + model->fxdot(t, x, dx, xdot); xdot_old = xdot; dx_old = dx; @@ -295,7 +278,7 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { for (int ie = 0; ie < model->ne; ie++) { if (rootsfound.at(ie) == 1) { /* only consider transitions false -> true */ - model->fstau(t, ie, &x, &sx); + model->fstau(t, ie, x, sx); } } } @@ -322,21 +305,21 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { "occured events exceeded (nmaxevents)*(number of " "events in model definition)!"); /* reinitialise so that we can continue in peace */ - solver->reInit(t, &x, &dx); + solver->reInit(t, x, dx); return; } if (solver->getSensitivityOrder() >= SensitivityOrder::first && solver->getSensitivityMethod() == SensitivityMethod::forward) { /* compute the new xdot */ - model->fxdot(t, &x, &dx, &xdot); + model->fxdot(t, x, dx, xdot); applyEventSensiBolusFSA(); } int secondevent = 0; /* check whether we need to fire a secondary event */ - model->froot(t, &x, &dx, rootvals.data()); + model->froot(t, x, dx, rootvals); for (int ie = 0; ie < model->ne; ie++) { /* the same event should not trigger itself */ if (rootsfound.at(ie) == 0) { @@ -363,24 +346,22 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { /* only reinitialise in the first event fired */ if (!seflag) { - solver->reInit(t, &x, &dx); + solver->reInit(t, x, dx); if (solver->getSensitivityOrder() >= SensitivityOrder::first) { if (solver->getSensitivityMethod() == SensitivityMethod::forward) { - solver->sensReInit(&sx, &sdx); + solver->sensReInit(sx, sdx); } } - solver->reInitPostProcessF(&t, &x, &dx, - model->gett(model->nt() - 1, rdata)); } } void ForwardProblem::storeJacobianAndDerivativeInReturnData() { - model->fxdot(t, &x, &dx, &xdot); + model->fxdot(t, x, dx, xdot); rdata->xdot = xdot.getVector(); - model->fJ(t, 0.0, &x, &dx, &xdot, Jtmp.get()); + model->fJ(t, 0.0, x, dx, xdot, Jtmp.get()); // CVODES uses colmajor, so we need to transform to rowmajor for (int ix = 0; ix < model->nx_solver; ix++) { for (int jx = 0; jx < model->nx_solver; jx++) { @@ -394,7 +375,7 @@ void ForwardProblem::storeJacobianAndDerivativeInReturnData() { void ForwardProblem::getEventOutput() { if (t == model->gett(model->nt() - 1,rdata)) { // call from fillEvent at last timepoint - model->froot(t, &x, &dx, rootvals.data()); + model->froot(t, x, dx, rootvals); } /* EVENT OUTPUT */ @@ -409,11 +390,11 @@ void ForwardProblem::getEventOutput() { } /* get event output */ - model->fz(nroots.at(ie), ie, t, &x, rdata); + model->fz(nroots.at(ie), ie, t, x, rdata); /* if called from fillEvent at last timepoint, then also get the root function value */ if (t == model->gett(model->nt() - 1,rdata)) - model->frz(nroots.at(ie), ie, t, &x, rdata); + model->frz(nroots.at(ie), ie, t, x, rdata); if (edata) { model->fsigmaz(t, ie, nroots.data(), rdata, edata); @@ -453,13 +434,13 @@ void ForwardProblem::prepEventSensis(int ie) { if(model->z2event[iz] - 1 != ie) continue; - model->fdzdp(t, ie, &x); + model->fdzdp(t, ie, x); - model->fdzdx(t, ie, &x); + model->fdzdx(t, ie, x); if (t == model->gett(model->nt() - 1,rdata)) { - model->fdrzdp(t, ie, &x); - model->fdrzdx(t, ie, &x); + model->fdrzdp(t, ie, x); + model->fdrzdx(t, ie, x); } /* extract the value for the standard deviation, if the data value is NaN, use the parameter value. Store this value in the return @@ -490,19 +471,19 @@ void ForwardProblem::getEventSensisFSA(int ie) { if (t == model->t(model->nt() - 1)) { // call from fillEvent at last timepoint model->fsz_tf(nroots.data(),ie, rdata); - model->fsrz(nroots.at(ie),ie,t,&x,&sx,rdata); + model->fsrz(nroots.at(ie), ie, t, x, sx, rdata); } else { - model->fsz(nroots.at(ie),ie,t,&x,&sx,rdata); + model->fsz(nroots.at(ie), ie, t, x, sx, rdata); } if (edata) { - model->fsJz(nroots.at(ie),dJzdx,&sx,rdata); + model->fsJz(nroots.at(ie), dJzdx, sx, rdata); } } void ForwardProblem::handleDataPoint(int it) { - model->fx_rdata(&x_rdata, &x); + model->fx_rdata(x_rdata, x); std::copy_n(x_rdata.data(), rdata->nx, &rdata->x.at(it*rdata->nx)); if (model->t(it) > model->t0()) { @@ -514,7 +495,7 @@ void ForwardProblem::handleDataPoint(int it) { void ForwardProblem::getDataOutput(int it) { - model->fy(rdata->ts[it], it, &x, rdata); + model->fy(rdata->ts[it], it, x, rdata); model->fsigmay(it, rdata, edata); model->fJy(it, rdata, edata); model->fres(it, rdata, edata); @@ -529,8 +510,8 @@ void ForwardProblem::getDataOutput(int it) { void ForwardProblem::prepDataSensis(int it) { - model->fdydx(rdata->ts[it], &x); - model->fdydp(rdata->ts[it], &x); + model->fdydx(rdata->ts[it], x); + model->fdydp(rdata->ts[it], x); if (!edata) return; @@ -544,11 +525,7 @@ void ForwardProblem::prepDataSensis(int it) { void ForwardProblem::getDataSensisFSA(int it) { - if (!std::isinf(model->t(it)) && model->t(it) > model->t0()) { - solver->getSens(&(t), &sx); - } - - model->fsx_rdata(&sx_rdata, &sx); + model->fsx_rdata(sx_rdata, sx); for (int ix = 0; ix < rdata->nx; ix++) { for (int ip = 0; ip < model->nplist(); ip++) { rdata->sx[(it * model->nplist() + ip) * rdata->nx + ix] = @@ -558,9 +535,9 @@ void ForwardProblem::getDataSensisFSA(int it) { model->fdsigmaydp(it, rdata, edata); - model->fsy(it, &sx, rdata); + model->fsy(it, sx, rdata); if (edata) { - model->fsJy(it, dJydx, &sx, rdata); + model->fsJy(it, dJydx, sx, rdata); model->fsres(it, rdata, edata); model->fFIM(it, rdata); } @@ -571,7 +548,7 @@ void ForwardProblem::applyEventBolus() { for (int ie = 0; ie < model->ne; ie++) { if (rootsfound.at(ie) == 1) { /* only consider transitions false -> true */ - model->fdeltax(ie, t, &x, &xdot, &xdot_old); + model->fdeltax(ie, t, x, xdot, xdot_old); amici_daxpy(model->nx_solver, 1.0, model->deltax.data(), 1, x.data(), 1); } @@ -583,7 +560,7 @@ void ForwardProblem::applyEventSensiBolusFSA() { for (int ie = 0; ie < model->ne; ie++) { if (rootsfound.at(ie) == 1) { /* only consider transitions false -> true */ - model->fdeltasx(ie, t, &x_old, &sx, &xdot, &xdot_old); + model->fdeltasx(ie, t, x_old, sx, xdot, xdot_old); for (int ip = 0; ip < model->nplist(); ip++) { amici_daxpy(model->nx_solver, 1.0, &model->deltasx[model->nx_solver * ip], 1, sx.data(ip), 1); diff --git a/src/misc.cpp b/src/misc.cpp index 6b89288b7b..d7a80ccff6 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -4,29 +4,21 @@ #include #include +#include +#if defined(_WIN32) +#define PLATFORM_WINDOWS // Windows +#elif defined(_WIN64) +#define PLATFORM_WINDOWS // Windows +#elif defined(__CYGWIN__) && !defined(_WIN32) +#define PLATFORM_WINDOWS // Windows (Cygwin POSIX under Microsoft Window) +#else +#include +#include // for dladdr +#include // for __cxa_demangle +#endif namespace amici { -int checkFinite(const int N, const realtype *array, const char *fun) { - for (int idx = 0; idx < N; idx++) { - if (isNaN(array[idx])) { - warnMsgIdAndTxt( - "AMICI:NaN", - "AMICI encountered a NaN value at index %i of %i in %s!", idx, - N, fun); - return AMICI_RECOVERABLE_ERROR; - } - if (isInf(array[idx])) { - warnMsgIdAndTxt( - "AMICI:Inf", - "AMICI encountered an Inf value at index %i of %i in %s!", idx, - N, fun); - return AMICI_RECOVERABLE_ERROR; - } - } - return AMICI_SUCCESS; -} - double getUnscaledParameter(double scaledParameter, ParameterScaling scaling) { switch (scaling) { @@ -41,20 +33,19 @@ double getUnscaledParameter(double scaledParameter, ParameterScaling scaling) throw AmiException("Invalid value for ParameterScaling."); } -void unscaleParameters(const double *bufferScaled, const ParameterScaling *pscale, int n, double *bufferUnscaled) +void unscaleParameters(gsl::span bufferScaled, + gsl::span pscale, + gsl::span bufferUnscaled) { - for (int ip = 0; ip < n; ++ip) { + Expects(bufferScaled.size() == pscale.size()); + Expects(bufferScaled.size() == bufferUnscaled.size()); + + for (gsl::span::index_type ip = 0; + ip < bufferScaled.size(); ++ip) { bufferUnscaled[ip] = getUnscaledParameter(bufferScaled[ip], pscale[ip]); } - } -void unscaleParameters(const std::vector &bufferScaled, const std::vector &pscale, std::vector &bufferUnscaled) -{ - if(bufferScaled.size() != pscale.size() || pscale.size() != bufferUnscaled.size()) - throw AmiException("Vector size mismatch in unscaleParameters."); - unscaleParameters(bufferScaled.data(), pscale.data(), bufferScaled.size(), bufferUnscaled.data()); -} double getScaledParameter(double unscaledParameter, ParameterScaling scaling) { @@ -71,19 +62,84 @@ double getScaledParameter(double unscaledParameter, ParameterScaling scaling) } -void scaleParameters(const std::vector &bufferUnscaled, const std::vector &pscale, std::vector &bufferScaled) +void scaleParameters(gsl::span bufferUnscaled, + gsl::span pscale, + gsl::span bufferScaled) { - if(bufferScaled.size() != pscale.size() || pscale.size() != bufferUnscaled.size()) - throw AmiException("Vector size mismatch in scaleParameters."); - for (int ip = 0; ip < (int) bufferUnscaled.size(); ++ip) { + Expects(bufferScaled.size() == pscale.size()); + Expects(bufferScaled.size() == bufferUnscaled.size()); + + for (gsl::span::index_type ip = 0; + ip < bufferUnscaled.size(); ++ip) { bufferScaled[ip] = getScaledParameter(bufferUnscaled[ip], pscale[ip]); } } -int checkFinite(const std::vector &array, const char *fun) +int checkFinite(gsl::span array, const char *fun) { - return checkFinite(array.size(), array.data(), fun); + for (int idx = 0; idx < (int) array.size(); idx++) { + if (isNaN(array[idx])) { + warnMsgIdAndTxt( + "AMICI:NaN", + "AMICI encountered a NaN value at index %i of %i in %s!", idx, + (int) array.size(), fun); + return AMICI_RECOVERABLE_ERROR; + } + if (isInf(array[idx])) { + warnMsgIdAndTxt( + "AMICI:Inf", + "AMICI encountered an Inf value at index %i of %i in %s!", idx, + (int) array.size(), fun); + return AMICI_RECOVERABLE_ERROR; + } + } + return AMICI_SUCCESS; +} + +std::string backtraceString(const int maxFrames) +{ + std::ostringstream trace_buf; + +#ifdef PLATFORM_WINDOWS + trace_buf << "stacktrace not available on windows platforms\n"; +#else + void *callstack[maxFrames]; + char buf[1024]; + int nFrames = backtrace(callstack, maxFrames); + char **symbols = backtrace_symbols(callstack, nFrames); + + // start at 2 to omit AmiException and storeBacktrace + for (int i = 2; i < nFrames; i++) { + // call + Dl_info info; + if (dladdr(callstack[i], &info) && info.dli_sname) { + char *demangled = nullptr; + int status = -1; + if (info.dli_sname[0] == '_') + demangled = abi::__cxa_demangle(info.dli_sname, nullptr, nullptr, + &status); + snprintf(buf, sizeof(buf), "%-3d %*p %s + %zd\n", i - 2, + int(2 + sizeof(void *) * 2), callstack[i], + status == 0 ? demangled + : info.dli_sname == nullptr ? symbols[i] + : info.dli_sname, + static_cast((char *)callstack[i] - + (char *)info.dli_saddr)); + free(demangled); + } else { + snprintf(buf, sizeof(buf), "%-3d %*p %s\n", i - 2, + int(2 + sizeof(void *) * 2), callstack[i], + symbols[i]); + } + trace_buf << buf; + } + free(symbols); + + if (nFrames == maxFrames) + trace_buf << "[truncated]\n"; +#endif + return trace_buf.str(); } } // namespace amici diff --git a/src/model.cpp b/src/model.cpp index 64183ea04a..c43e70d73f 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -1,59 +1,132 @@ #include "amici/model.h" #include "amici/amici.h" -#include "amici/misc.h" #include "amici/exception.h" +#include "amici/misc.h" #include "amici/symbolic_functions.h" -#include #include -#include #include +#include +#include +#include #include #include -#include namespace amici { -void Model::fsy(const int it, const AmiVectorArray *sx, ReturnData *rdata) { +Model::Model() : dxdotdp(0, 0), x_pos_tmp(0) {} + +Model::Model(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, + const int nxtrue_solver, const int ny, const int nytrue, + const int nz, const int nztrue, const int ne, const int nJ, + const int nw, const int ndwdx, const int ndwdp, const int ndxdotdw, + std::vector ndJydy, const int nnz, const int ubw, + const int lbw, SecondOrderMode o2mode, + const std::vector &p, std::vector k, + const std::vector &plist, std::vector idlist, + std::vector z2event) + : nx_rdata(nx_rdata), nxtrue_rdata(nxtrue_rdata), nx_solver(nx_solver), + nxtrue_solver(nxtrue_solver), ny(ny), nytrue(nytrue), nz(nz), + nztrue(nztrue), ne(ne), nw(nw), ndwdx(ndwdx), ndwdp(ndwdp), + ndxdotdw(ndxdotdw), ndJydy(std::move(ndJydy)), nnz(nnz), nJ(nJ), ubw(ubw), + lbw(lbw), o2mode(o2mode), z2event(std::move(z2event)), + idlist(std::move(idlist)), sigmay(ny, 0.0), + dsigmaydp(ny * plist.size(), 0.0), sigmaz(nz, 0.0), + dsigmazdp(nz * plist.size(), 0.0), dJydp(nJ * plist.size(), 0.0), + dJzdp(nJ * plist.size(), 0.0), deltax(nx_solver, 0.0), + deltasx(nx_solver * plist.size(), 0.0), deltaxB(nx_solver, 0.0), + deltaqB(nJ * plist.size(), 0.0), dxdotdp(nx_solver, plist.size()), + J(nx_solver, nx_solver, nnz, CSC_MAT), + dxdotdw(nx_solver, nw, ndxdotdw, CSC_MAT), + dwdx(nw, nx_solver, ndwdx, CSC_MAT), M(nx_solver, nx_solver), + my(nytrue, 0.0), mz(nztrue, 0.0), dJydsigma(nJ * nytrue * ny, 0.0), + dJzdz(nJ * nztrue * nz, 0.0), dJzdsigma(nJ * nztrue * nz, 0.0), + dJrzdz(nJ * nztrue * nz, 0.0), dJrzdsigma(nJ * nztrue * nz, 0.0), + dzdx(nz * nx_solver, 0.0), dzdp(nz * plist.size(), 0.0), + drzdx(nz * nx_solver, 0.0), drzdp(nz * plist.size(), 0.0), + dydp(ny * plist.size(), 0.0), dydx(ny * nx_solver, 0.0), w(nw, 0.0), + dwdp(ndwdp, 0.0), stau(plist.size(), 0.0), + sx(nx_solver * plist.size(), 0.0), x_rdata(nx_rdata, 0.0), + sx_rdata(nx_rdata, 0.0), h(ne, 0.0), unscaledParameters(p), + originalParameters(p), fixedParameters(std::move(k)), + total_cl(nx_rdata - nx_solver), stotal_cl((nx_rdata - nx_solver) * np()), + plist_(plist), stateIsNonNegative(nx_solver, false), x_pos_tmp(nx_solver), + pscale(std::vector(p.size(), ParameterScaling::none)) { + + // Can't use derivedClass::wasPythonGenerated() in ctor. + // Guess we are using Python if ndJydy is not empty + if (!this->ndJydy.empty()) { + if (static_cast(nytrue) != this->ndJydy.size()) + throw std::runtime_error( + "Number of elements in ndJydy is not equal " + " nytrue."); + + for (int iytrue = 0; iytrue < nytrue; ++iytrue) + dJydy.emplace_back( + SUNMatrixWrapper(nJ, ny, this->ndJydy[iytrue], CSC_MAT)); + } else { + dJydy_matlab = std::vector(nJ * nytrue * ny, 0.0); + } + + requireSensitivitiesForAllParameters(); +} + +void Model::fdJydy_colptrs(sunindextype * /*indexptrs*/, int /*index*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model::fdJydy_rowvals(sunindextype * /*indexptrs*/, int /*index*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model::fsy(const int it, const AmiVectorArray &sx, ReturnData *rdata) { if (!ny) return; // copy dydp for current time to sy std::copy(dydp.begin(), dydp.end(), &rdata->sy[it * nplist() * ny]); - sx->flatten_to_vector(this->sx); + sx.flatten_to_vector(this->sx); // compute sy = 1.0*dydx*sx + 1.0*sy // dydx A[ny,nx_solver] * sx B[nx_solver,nplist] = sy C[ny,nplist] // M K K N M N // lda ldb ldc - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, ny, nplist(), nx_solver, - 1.0, dydx.data(), ny, this->sx.data(), nx_solver, 1.0, - &rdata->sy[it*nplist()*ny], ny); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, ny, nplist(), nx_solver, 1.0, + dydx.data(), ny, this->sx.data(), nx_solver, 1.0, + &rdata->sy[it * nplist() * ny], ny); - if(alwaysCheckFinite) - checkFinite(nplist()*ny, &rdata->sy[it*nplist()*ny], "sy"); + if (alwaysCheckFinite) + checkFinite(gsl::make_span(&rdata->sy[it * nplist() * ny], + nplist() * ny), "sy"); } void Model::fsz_tf(const int *nroots, const int ie, ReturnData *rdata) { for (int iz = 0; iz < nz; ++iz) if (z2event[iz] - 1 == ie) for (int ip = 0; ip < nplist(); ++ip) - rdata->sz.at((nroots[ie]*nplist()+ip)*nz + iz) = 0.0; + rdata->sz.at((nroots[ie] * nplist() + ip) * nz + iz) = 0.0; } -void Model::fsJy(const int it, const std::vector& dJydx, const AmiVectorArray *sx, ReturnData *rdata) { +void Model::fsJy(const int it, const std::vector &dJydx, + const AmiVectorArray &sx, ReturnData *rdata) { // Compute dJydx*sx for current 'it' // dJydx rdata->nt x nJ x nx_solver // sx rdata->nt x nx_solver x nplist() std::vector multResult(nJ * nplist(), 0); - sx->flatten_to_vector(this->sx); + sx.flatten_to_vector(this->sx); // C := alpha*op(A)*op(B) + beta*C, - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, nJ, - nplist(), nx_solver, 1.0, &dJydx.at(it*nJ*nx_solver), nJ, this->sx.data(), nx_solver, 0.0, - multResult.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nx_solver, 1.0, + &dJydx.at(it * nJ * nx_solver), nJ, this->sx.data(), nx_solver, + 0.0, multResult.data(), nJ); // multResult nJ x nplist() // dJydp nJ x nplist() @@ -64,11 +137,12 @@ void Model::fsJy(const int it, const std::vector& dJydx, const AmiVect for (int iJ = 0; iJ < nJ; ++iJ) { if (iJ == 0) for (int ip = 0; ip < nplist(); ++ip) - rdata->sllh.at(ip) -= multResult.at(ip * nJ) + dJydp.at(ip * nJ); + rdata->sllh.at(ip) -= + multResult.at(ip * nJ) + dJydp.at(ip * nJ); else for (int ip = 0; ip < nplist(); ++ip) rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - multResult.at(iJ + ip * nJ) + dJydp.at(iJ + ip * nJ); + multResult.at(iJ + ip * nJ) + dJydp.at(iJ + ip * nJ); } } @@ -85,32 +159,30 @@ void Model::fdJydp(const int it, ReturnData *rdata, const ExpData *edata) { if (isNaN(my.at(iyt))) continue; - if(wasPythonGenerated()) { + if (wasPythonGenerated()) { // dJydp = 1.0 * dJydp + 1.0 * dJydy * dydp - for(int iplist = 0; iplist < nplist(); ++iplist) { + for (int iplist = 0; iplist < nplist(); ++iplist) { dJydy[iyt].multiply( - gsl::span(&dJydp.at(iplist * nJ), nJ), - gsl::span(&dydp.at(iplist * ny), ny)); + gsl::span(&dJydp.at(iplist * nJ), nJ), + gsl::span(&dydp.at(iplist * ny), ny)); } } else { - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), ny, - 1.0, &dJydy_matlab.at(iyt*nJ*ny), nJ, - dydp.data(), ny, + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), ny, 1.0, + &dJydy_matlab.at(iyt * nJ * ny), nJ, dydp.data(), ny, 1.0, dJydp.data(), nJ); } // dJydp = 1.0 * dJydp + 1.0 * dJydsigma * dsigmaydp - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), ny, - 1.0, &dJydsigma.at(iyt*nJ*ny), nJ, - dsigmaydp.data(), ny, - 1.0, dJydp.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), ny, 1.0, + &dJydsigma.at(iyt * nJ * ny), nJ, dsigmaydp.data(), ny, 1.0, + dJydp.data(), nJ); } if (rdata->sensi_meth != SensitivityMethod::adjoint) return; - if(!ny) + if (!ny) return; for (int iJ = 0; iJ < nJ; iJ++) { @@ -119,13 +191,14 @@ void Model::fdJydp(const int it, ReturnData *rdata, const ExpData *edata) { rdata->sllh.at(ip) -= dJydp[ip * nJ]; } else { rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - dJydp[iJ + ip * nJ]; + dJydp[iJ + ip * nJ]; } } } } -void Model::fdJydx(std::vector& dJydx, const int it, const ExpData *edata) { +void Model::fdJydx(std::vector &dJydx, const int it, + const ExpData *edata) { // dJydy: nJ, ny x nytrue // dydx : ny x nx_solver @@ -140,26 +213,28 @@ void Model::fdJydx(std::vector& dJydx, const int it, const ExpData *ed // M K K N M N // lda ldb ldc - if(wasPythonGenerated()) { - for(int ix = 0; ix < nx_solver; ++ix) { + if (wasPythonGenerated()) { + for (int ix = 0; ix < nx_solver; ++ix) { dJydy[iyt].multiply( - gsl::span(&dJydx.at(it * nx_solver * nJ - + ix * nJ), nJ), - gsl::span(&dydx.at(ix * ny), ny)); + gsl::span( + &dJydx.at(it * nx_solver * nJ + ix * nJ), nJ), + gsl::span(&dydx.at(ix * ny), ny)); } } else { - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nx_solver, ny, 1.0, &dJydy_matlab.at(iyt*ny*nJ), nJ, dydx.data(), ny, 1.0, - &dJydx.at(it*nx_solver*nJ), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nx_solver, ny, 1.0, + &dJydy_matlab.at(iyt * ny * nJ), nJ, dydx.data(), ny, + 1.0, &dJydx.at(it * nx_solver * nJ), nJ); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJydx, "dJydx"); } } -void Model::fsJz(const int nroots, const std::vector& dJzdx, const AmiVectorArray *sx, ReturnData *rdata) { +void Model::fsJz(const int nroots, const std::vector &dJzdx, + const AmiVectorArray &sx, ReturnData *rdata) { // sJz nJ x nplist() // dJzdp nJ x nplist() // dJzdx nmaxevent x nJ x nx_solver @@ -170,22 +245,24 @@ void Model::fsJz(const int nroots, const std::vector& dJzdx, const Ami // sx rdata->nt x nx_solver x nplist() std::vector multResult(nJ * nplist(), 0); - sx->flatten_to_vector(this->sx); + sx.flatten_to_vector(this->sx); // C := alpha*op(A)*op(B) + beta*C, - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, nJ, - nplist(), nx_solver, 1.0, &dJzdx.at(nroots*nx_solver*nJ), nJ, this->sx.data(), nx_solver, 1.0, - multResult.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nx_solver, 1.0, + &dJzdx.at(nroots * nx_solver * nJ), nJ, this->sx.data(), + nx_solver, 1.0, multResult.data(), nJ); // sJy += multResult + dJydp for (int iJ = 0; iJ < nJ; ++iJ) { if (iJ == 0) for (int ip = 0; ip < nplist(); ++ip) - rdata->sllh.at(ip) -= multResult.at(ip * nJ) + dJzdp.at(ip * nJ); + rdata->sllh.at(ip) -= + multResult.at(ip * nJ) + dJzdp.at(ip * nJ); else for (int ip = 0; ip < nplist(); ++ip) rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - multResult.at(iJ + ip * nJ) + dJzdp.at(iJ + ip * nJ); + multResult.at(iJ + ip * nJ) + dJzdp.at(iJ + ip * nJ); } } @@ -196,40 +273,44 @@ void Model::fdJzdp(const int nroots, realtype t, const ExpData *edata, // dzdp nz x nplist() // dJzdp nJ x nplist() - getmz(nroots,edata); - std::fill(dJzdp.begin(),dJzdp.end(),0.0); + getmz(nroots, edata); + std::fill(dJzdp.begin(), dJzdp.end(), 0.0); for (int izt = 0; izt < nztrue; ++izt) { if (isNaN(mz.at(izt))) continue; if (t < rdata->ts.at(rdata->ts.size() - 1)) { // with z - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), nz, 1.0, &dJzdz.at(izt*nz*nJ), nJ, dzdp.data(), nz, - 1.0, dJzdp.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJzdz.at(izt * nz * nJ), nJ, dzdp.data(), nz, 1.0, + dJzdp.data(), nJ); } else { // with rz amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, - &dJrzdsigma.at(izt*nz*nJ), nJ, dsigmazdp.data(), nz, 1.0, - dJzdp.data(), nJ); - - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), nz, 1.0, &dJrzdz.at(izt*nz*nJ), nJ, dzdp.data(), nz, + &dJrzdsigma.at(izt * nz * nJ), nJ, dsigmazdp.data(), nz, 1.0, dJzdp.data(), nJ); + + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJrzdz.at(izt * nz * nJ), nJ, dzdp.data(), nz, 1.0, + dJzdp.data(), nJ); } - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, BLASTranspose::noTrans, - nJ, nplist(), nz, 1.0, &dJzdsigma.at(izt*nz*nJ), nJ, - dsigmazdp.data(), nz, 1.0, dJzdp.data(), nJ); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJzdsigma.at(izt * nz * nJ), nJ, dsigmazdp.data(), nz, 1.0, + dJzdp.data(), nJ); } } -void Model::fdJzdx(std::vector *dJzdx, const int nroots, realtype t, const ExpData *edata, const ReturnData *rdata) { +void Model::fdJzdx(std::vector *dJzdx, const int nroots, realtype t, + const ExpData *edata, const ReturnData *rdata) { // dJzdz nJ x nz x nztrue // dzdx nz x nx_solver // dJzdx nJ x nx_solver x nmaxevent - getmz(nroots,edata); + getmz(nroots, edata); for (int izt = 0; izt < nztrue; ++izt) { if (isNaN(mz.at(izt))) continue; @@ -237,35 +318,40 @@ void Model::fdJzdx(std::vector *dJzdx, const int nroots, realtype t, c if (t < rdata->ts.at(rdata->ts.size() - 1)) { // z amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, &dJzdz.at(izt*nz*nJ), nJ, - dzdx.data(), nz, 1.0, &dJzdx->at(nroots*nx_solver*nJ), nJ); + BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, + &dJzdz.at(izt * nz * nJ), nJ, dzdx.data(), nz, 1.0, + &dJzdx->at(nroots * nx_solver * nJ), nJ); } else { // rz amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, &dJrzdz.at(izt*nz*nJ), nJ, - drzdx.data(), nz, 1.0, &dJzdx->at(nroots*nx_solver*nJ), nJ); + BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, + &dJrzdz.at(izt * nz * nJ), nJ, drzdx.data(), nz, 1.0, + &dJzdx->at(nroots * nx_solver * nJ), nJ); } } } -void Model::initialize(AmiVector *x, AmiVector *dx, - AmiVectorArray *sx, AmiVectorArray * /*sdx*/, - bool computeSensitivities) { +void Model::initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx, + AmiVectorArray & /*sdx*/, bool computeSensitivities) { initializeStates(x); - if(computeSensitivities) + if (computeSensitivities) initializeStateSensitivities(sx, x); fdx0(x, dx); - if(computeSensitivities) + if (computeSensitivities) fsdx0(); - if(ne) - initHeaviside(x,dx); - + if (ne) + initHeaviside(x, dx); } +void Model::initializeB(AmiVector &xB, AmiVector &dxB, AmiVector &xQB) { + xB.reset(); + dxB.reset(); + xQB.reset(); +} -void Model::initializeStates(AmiVector *x) { +void Model::initializeStates(AmiVector &x) { if (x0data.empty()) { fx0(x); } else { @@ -273,12 +359,12 @@ void Model::initializeStates(AmiVector *x) { ftotal_cl(total_cl.data(), x0data.data()); fx_solver(x0_solver.data(), x0data.data()); for (int ix = 0; ix < nx_solver; ix++) { - (*x)[ix] = (realtype) x0_solver.at(ix); + x[ix] = (realtype)x0_solver.at(ix); } } } -void Model::initializeStateSensitivities(AmiVectorArray *sx, AmiVector *x) { +void Model::initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x) { if (sx0data.empty()) { fsx0(sx, x); } else { @@ -287,61 +373,48 @@ void Model::initializeStateSensitivities(AmiVectorArray *sx, AmiVector *x) { for (int ip = 0; ip < nplist(); ip++) { if (ncl() > 0) stcl = &stotal_cl.at(plist(ip) * ncl()); - fstotal_cl(stcl, &sx0data.at(ip*nx_rdata), plist(ip)); - fsx_solver(sx0_solver_slice.data(), &sx0data.at(ip*nx_rdata)); + fstotal_cl(stcl, &sx0data.at(ip * nx_rdata), plist(ip)); + fsx_solver(sx0_solver_slice.data(), &sx0data.at(ip * nx_rdata)); for (int ix = 0; ix < nx_solver; ix++) { - sx->at(ix,ip) = (realtype) sx0_solver_slice.at(ix); + sx.at(ix, ip) = (realtype)sx0_solver_slice.at(ix); } } } } -void Model::initHeaviside(AmiVector *x, AmiVector *dx) { - std::vector rootvals(ne,0.0); - froot(tstart, x, dx, rootvals.data()); +void Model::initHeaviside(AmiVector &x, AmiVector &dx) { + std::vector rootvals(ne, 0.0); + froot(tstart, x, dx, rootvals); for (int ie = 0; ie < ne; ie++) { if (rootvals.at(ie) < 0) { h.at(ie) = 0.0; } else if (rootvals.at(ie) == 0) { - throw AmiException("Simulation started in an event. This could lead to " - "unexpected results, aborting simulation! Please " - "specify an earlier simulation start via " - "options.t0"); + throw AmiException( + "Simulation started in an event. This could lead to " + "unexpected results, aborting simulation! Please " + "specify an earlier simulation start via " + "options.t0"); } else { h.at(ie) = 1.0; } } } -int Model::nplist() const { - return plist_.size(); -} +int Model::nplist() const { return plist_.size(); } -int Model::np() const { - return originalParameters.size(); -} +int Model::np() const { return originalParameters.size(); } -int Model::nk() const { - return fixedParameters.size(); -} +int Model::nk() const { return fixedParameters.size(); } -int Model::ncl() const {return nx_rdata-nx_solver;} +int Model::ncl() const { return nx_rdata - nx_solver; } -const double *Model::k() const{ - return fixedParameters.data(); -} +const double *Model::k() const { return fixedParameters.data(); } -int Model::nMaxEvent() const { - return nmaxevent; -} +int Model::nMaxEvent() const { return nmaxevent; } -void Model::setNMaxEvent(int nmaxevent) { - this->nmaxevent = nmaxevent; -} +void Model::setNMaxEvent(int nmaxevent) { this->nmaxevent = nmaxevent; } -int Model::nt() const { - return ts.size(); -} +int Model::nt() const { return ts.size(); } const std::vector &Model::getParameterScale() const { return pscale; @@ -353,15 +426,16 @@ void Model::setParameterScale(ParameterScaling pscale) { sx0data.clear(); } -void Model::setParameterScale(std::vector const& pscaleVec) { - if(pscale.size() != this->originalParameters.size()) - throw AmiException("Dimension mismatch. Size of parameter scaling does not match number of model parameters."); +void Model::setParameterScale(std::vector const &pscaleVec) { + if (pscaleVec.size() != this->originalParameters.size()) + throw AmiException("Dimension mismatch. Size of parameter scaling does " + "not match number of model parameters."); this->pscale = pscaleVec; scaleParameters(unscaledParameters, this->pscale, originalParameters); sx0data.clear(); } -std::vector const& Model::getParameters() const { +std::vector const &Model::getParameters() const { return originalParameters; } @@ -374,13 +448,16 @@ std::vector const& Model::getParameters() const { * @param id_name string indicating whether name or id was specified * @return value of the selected parameter */ -realtype getValueById(std::vector const& ids, std::vector const& values, - std::string const& id, const char* variable_name, const char* id_name) { +realtype getValueById(std::vector const &ids, + std::vector const &values, + std::string const &id, const char *variable_name, + const char *id_name) { auto it = std::find(ids.begin(), ids.end(), id); - if(it != ids.end()) + if (it != ids.end()) return values.at(it - ids.begin()); - throw AmiException("Could not find %s with specified %s", variable_name, id_name); + throw AmiException("Could not find %s with specified %s", variable_name, + id_name); } /** @@ -392,13 +469,16 @@ realtype getValueById(std::vector const& ids, std::vector * @param variable_name string indicating what variable we are lookin at * @param id_name string indicating whether name or id was specified */ -void setValueById(std::vector const& ids, std::vector &values, realtype value, - std::string const& id, const char* variable_name, const char* id_name) { +void setValueById(std::vector const &ids, + std::vector &values, realtype value, + std::string const &id, const char *variable_name, + const char *id_name) { auto it = std::find(ids.begin(), ids.end(), id); - if(it != ids.end()) + if (it != ids.end()) values.at(it - ids.begin()) = value; else - throw AmiException("Could not find %s with specified %s", variable_name, id_name); + throw AmiException("Could not find %s with specified %s", variable_name, + id_name); } /** @@ -411,124 +491,126 @@ void setValueById(std::vector const& ids, std::vector &va * @param id_name string indicating whether name or id was specified * @return number of matched names/ids */ -int setValueByIdRegex(std::vector const& ids, std::vector &values, realtype value, - std::string const& regex, const char* variable_name, const char* id_name) { +int setValueByIdRegex(std::vector const &ids, + std::vector &values, realtype value, + std::string const ®ex, const char *variable_name, + const char *id_name) { try { - std::regex pattern (regex); + std::regex pattern(regex); int n_found = 0; - for(const auto &id: ids) { - if(std::regex_match(id, pattern)) { + for (const auto &id : ids) { + if (std::regex_match(id, pattern)) { values.at(&id - &ids[0]) = value; ++n_found; } } - if(n_found == 0) - throw AmiException("Could not find %s with specified %s", variable_name, id_name); + if (n_found == 0) + throw AmiException("Could not find %s with specified %s", + variable_name, id_name); return n_found; - } catch (std::regex_error const& e) { - throw AmiException("Specified regex pattern could not be compiled: %s", e.what()); + } catch (std::regex_error const &e) { + throw AmiException("Specified regex pattern could not be compiled: %s", + e.what()); } } -realtype Model::getParameterById(std::string const& par_id) const { - if(!hasParameterIds()) - throw AmiException("Could not access parameters by id as they are not set"); - return getValueById(getParameterIds(), originalParameters, par_id, "parameters", "id"); +realtype Model::getParameterById(std::string const &par_id) const { + if (!hasParameterIds()) + throw AmiException( + "Could not access parameters by id as they are not set"); + return getValueById(getParameterIds(), originalParameters, par_id, + "parameters", "id"); } -realtype Model::getParameterByName(std::string const& par_name) const { - if(!hasParameterNames()) - throw AmiException("Could not access parameters by name as they are not set"); - return getValueById(getParameterNames(), - originalParameters, - par_name, - "parameters", - "name"); +realtype Model::getParameterByName(std::string const &par_name) const { + if (!hasParameterNames()) + throw AmiException( + "Could not access parameters by name as they are not set"); + return getValueById(getParameterNames(), originalParameters, par_name, + "parameters", "name"); } void Model::setParameters(const std::vector &p) { - if(p.size() != (unsigned) np()) - throw AmiException("Dimension mismatch. Size of parameters does not match number of model parameters."); + if (p.size() != (unsigned)np()) + throw AmiException("Dimension mismatch. Size of parameters does not " + "match number of model parameters."); this->originalParameters = p; this->unscaledParameters.resize(originalParameters.size()); unscaleParameters(originalParameters, pscale, unscaledParameters); } -void Model::setParameterById(std::string const& par_id, realtype value) { - if(!hasParameterIds()) - throw AmiException("Could not access parameters by id as they are not set"); +void Model::setParameterById(std::string const &par_id, realtype value) { + if (!hasParameterIds()) + throw AmiException( + "Could not access parameters by id as they are not set"); - setValueById(getParameterIds(), - originalParameters, - value, - par_id, - "parameter", - "id"); + setValueById(getParameterIds(), originalParameters, value, par_id, + "parameter", "id"); unscaleParameters(originalParameters, pscale, unscaledParameters); } -int Model::setParametersByIdRegex(std::string const& par_id_regex, realtype value) { - if(!hasParameterIds()) - throw AmiException("Could not access parameters by id as they are not set"); - int n_found = setValueByIdRegex(getParameterIds(), - originalParameters, - value, - par_id_regex, - "parameter", - "id"); +int Model::setParametersByIdRegex(std::string const &par_id_regex, + realtype value) { + if (!hasParameterIds()) + throw AmiException( + "Could not access parameters by id as they are not set"); + int n_found = setValueByIdRegex(getParameterIds(), originalParameters, + value, par_id_regex, "parameter", "id"); unscaleParameters(originalParameters, pscale, unscaledParameters); return n_found; } -void Model::setParameterByName(std::string const& par_name, realtype value) { - if(!hasParameterNames()) - throw AmiException("Could not access parameters by name as they are not set"); +void Model::setParameterByName(std::string const &par_name, realtype value) { + if (!hasParameterNames()) + throw AmiException( + "Could not access parameters by name as they are not set"); - setValueById(getParameterNames(), - originalParameters, - value, - par_name, - "parameter", - "name"); + setValueById(getParameterNames(), originalParameters, value, par_name, + "parameter", "name"); unscaleParameters(originalParameters, pscale, unscaledParameters); } -int Model::setParametersByNameRegex(std::string const& par_name_regex, realtype value) { - if(!hasParameterNames()) - throw AmiException("Could not access parameters by name as they are not set"); +int Model::setParametersByNameRegex(std::string const &par_name_regex, + realtype value) { + if (!hasParameterNames()) + throw AmiException( + "Could not access parameters by name as they are not set"); - int n_found = setValueByIdRegex(getParameterNames(), - originalParameters, - value, - par_name_regex, - "parameter", - "name"); + int n_found = setValueByIdRegex(getParameterNames(), originalParameters, + value, par_name_regex, "parameter", "name"); unscaleParameters(originalParameters, pscale, unscaledParameters); return n_found; } -bool Model::hasStateIds() const { return nx_rdata == 0 || !getStateIds().empty(); } +bool Model::hasStateIds() const { + return nx_rdata == 0 || !getStateIds().empty(); +} std::vector Model::getStateIds() const { return std::vector(); } -bool Model::hasFixedParameterIds() const { return nk() == 0 || !getFixedParameterIds().empty(); } +bool Model::hasFixedParameterIds() const { + return nk() == 0 || !getFixedParameterIds().empty(); +} std::vector Model::getFixedParameterIds() const { return std::vector(); } -bool Model::hasObservableIds() const { return ny == 0 || !getObservableIds().empty(); } +bool Model::hasObservableIds() const { + return ny == 0 || !getObservableIds().empty(); +} std::vector Model::getObservableIds() const { return std::vector(); } -void Model::setSteadyStateSensitivityMode(const SteadyStateSensitivityMode mode) { +void Model::setSteadyStateSensitivityMode( + const SteadyStateSensitivityMode mode) { steadyStateSensitivityMode = mode; } @@ -538,10 +620,11 @@ SteadyStateSensitivityMode Model::getSteadyStateSensitivityMode() const { void Model::setReinitializeFixedParameterInitialStates(bool flag) { if (flag && !isFixedParameterStateReinitializationAllowed()) - throw AmiException("State reinitialization cannot be enabled for this model" - "as this feature was disabled at compile time. Most likely," - " this was because some initial states depending on " - "fixedParameters also depended on parameters"); + throw AmiException( + "State reinitialization cannot be enabled for this model" + "as this feature was disabled at compile time. Most likely," + " this was because some initial states depending on " + "fixedParameters also depended on parameters"); reinitializeFixedParameterInitialStates = flag; } @@ -549,23 +632,25 @@ bool Model::getReinitializeFixedParameterInitialStates() const { return reinitializeFixedParameterInitialStates; } -void Model::fx_rdata(realtype *x_rdata, const realtype *x_solver, const realtype * /*tcl*/) { +void Model::fx_rdata(realtype *x_rdata, const realtype *x_solver, + const realtype * /*tcl*/) { if (nx_solver != nx_rdata) throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own fx_rdata"); + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own fx_rdata"); std::copy_n(x_solver, nx_solver, x_rdata); } -void Model::fsx_rdata(realtype *sx_rdata, const realtype *sx_solver, const realtype *stcl, const int /*ip*/) { +void Model::fsx_rdata(realtype *sx_rdata, const realtype *sx_solver, + const realtype *stcl, const int /*ip*/) { fx_rdata(sx_rdata, sx_solver, stcl); } void Model::fx_solver(realtype *x_solver, const realtype *x_rdata) { if (nx_solver != nx_rdata) throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own fx_solver"); + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own fx_solver"); std::copy_n(x_rdata, nx_rdata, x_solver); } @@ -579,11 +664,12 @@ void Model::fsx_solver(realtype *sx_solver, const realtype *sx_rdata) { void Model::ftotal_cl(realtype * /*total_cl*/, const realtype * /*x_rdata*/) { if (nx_solver != nx_rdata) throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own ftotal_cl"); + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own ftotal_cl"); } -void Model::fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, const int /*ip*/) { +void Model::fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, + const int /*ip*/) { /* for the moment we do not need an implementation of fstotal_cl as * we can simply reuse ftotal_cl and replace states by their * sensitivities */ @@ -598,85 +684,71 @@ const std::vector &Model::getFixedParameters() const { return fixedParameters; } -realtype Model::getFixedParameterById(std::string const& par_id) const { - if(!hasFixedParameterIds()) - throw AmiException("Could not access fixed parameters by id as they are not set"); +realtype Model::getFixedParameterById(std::string const &par_id) const { + if (!hasFixedParameterIds()) + throw AmiException( + "Could not access fixed parameters by id as they are not set"); - return getValueById(getFixedParameterIds(), - fixedParameters, - par_id, - "fixedParameters", - "id"); + return getValueById(getFixedParameterIds(), fixedParameters, par_id, + "fixedParameters", "id"); } -realtype Model::getFixedParameterByName(std::string const& par_name) const { - if(!hasFixedParameterNames()) - throw AmiException("Could not access fixed parameters by name as they are not set"); +realtype Model::getFixedParameterByName(std::string const &par_name) const { + if (!hasFixedParameterNames()) + throw AmiException( + "Could not access fixed parameters by name as they are not set"); - return getValueById(getFixedParameterNames(), - fixedParameters, - par_name, - "fixedParameters", - "name"); + return getValueById(getFixedParameterNames(), fixedParameters, par_name, + "fixedParameters", "name"); } void Model::setFixedParameters(const std::vector &k) { - if(k.size() != (unsigned) nk()) - throw AmiException("Dimension mismatch. Size of fixedParameters does not match number of fixed model parameters."); + if (k.size() != (unsigned)nk()) + throw AmiException("Dimension mismatch. Size of fixedParameters does " + "not match number of fixed model parameters."); this->fixedParameters = k; } -void Model::setFixedParameterById(std::string const& par_id, realtype value) { - if(!hasFixedParameterIds()) - throw AmiException("Could not access fixed parameters by id as they are not set"); +void Model::setFixedParameterById(std::string const &par_id, realtype value) { + if (!hasFixedParameterIds()) + throw AmiException( + "Could not access fixed parameters by id as they are not set"); - setValueById(getFixedParameterIds(), - fixedParameters, - value, - par_id, - "fixedParameters", - "id"); + setValueById(getFixedParameterIds(), fixedParameters, value, par_id, + "fixedParameters", "id"); } -int Model::setFixedParametersByIdRegex(std::string const& par_id_regex, realtype value) { - if(!hasFixedParameterIds()) - throw AmiException("Could not access fixed parameters by id as they are not set"); +int Model::setFixedParametersByIdRegex(std::string const &par_id_regex, + realtype value) { + if (!hasFixedParameterIds()) + throw AmiException( + "Could not access fixed parameters by id as they are not set"); - return setValueByIdRegex(getFixedParameterIds(), - fixedParameters, - value, - par_id_regex, - "fixedParameters", - "id"); + return setValueByIdRegex(getFixedParameterIds(), fixedParameters, value, + par_id_regex, "fixedParameters", "id"); } -void Model::setFixedParameterByName(std::string const& par_name, realtype value) { - if(!hasFixedParameterNames()) - throw AmiException("Could not access fixed parameters by name as they are not set"); +void Model::setFixedParameterByName(std::string const &par_name, + realtype value) { + if (!hasFixedParameterNames()) + throw AmiException( + "Could not access fixed parameters by name as they are not set"); - setValueById(getFixedParameterNames(), - fixedParameters, - value, - par_name, - "fixedParameters", - "name"); + setValueById(getFixedParameterNames(), fixedParameters, value, par_name, + "fixedParameters", "name"); } -int Model::setFixedParametersByNameRegex(std::string const& par_name_regex, realtype value) { - if(!hasFixedParameterNames()) - throw AmiException("Could not access fixed parameters by name as they are not set"); +int Model::setFixedParametersByNameRegex(std::string const &par_name_regex, + realtype value) { + if (!hasFixedParameterNames()) + throw AmiException( + "Could not access fixed parameters by name as they are not set"); - return setValueByIdRegex(getFixedParameterIds(), - fixedParameters, - value, - par_name_regex, - "fixedParameters", - "name"); + return setValueByIdRegex(getFixedParameterIds(), fixedParameters, value, + par_name_regex, "fixedParameters", "name"); } -std::vector const& Model::getTimepoints() const { - return ts; -} +std::vector const &Model::getTimepoints() const { return ts; } void Model::setTimepoints(const std::vector &ts) { if (!std::is_sorted(ts.begin(), ts.end())) @@ -686,12 +758,12 @@ void Model::setTimepoints(const std::vector &ts) { this->ts = ts; } -std::vector const& Model::getStateIsNonNegative() const { +std::vector const &Model::getStateIsNonNegative() const { return stateIsNonNegative; } -void Model::setStateIsNonNegative(std::vector const& nonNegative) { - if(nx_solver != nx_rdata) { +void Model::setStateIsNonNegative(std::vector const &nonNegative) { + if (nx_solver != nx_rdata) { throw AmiException("Nonnegative states are not supported whith" " conservation laws enabled"); } @@ -700,29 +772,24 @@ void Model::setStateIsNonNegative(std::vector const& nonNegative) { "not agree with number of state variables (%d)", stateIsNonNegative.size(), nx_rdata); } - stateIsNonNegative=nonNegative; - anyStateNonNegative=std::any_of(stateIsNonNegative.begin(), - stateIsNonNegative.end(), - [](bool x) { return x; }); + stateIsNonNegative = nonNegative; + anyStateNonNegative = + std::any_of(stateIsNonNegative.begin(), stateIsNonNegative.end(), + [](bool x) { return x; }); } -void Model::setAllStatesNonNegative() -{ +void Model::setAllStatesNonNegative() { setStateIsNonNegative(std::vector(nx_solver, true)); } -double Model::t(int idx) const { - return ts.at(idx); -} +double Model::t(int idx) const { return ts.at(idx); } -const std::vector &Model::getParameterList() const { - return plist_; -} +const std::vector &Model::getParameterList() const { return plist_; } void Model::setParameterList(const std::vector &plist) { int np = this->np(); // cannot capture 'this' in lambda expression - if(std::any_of(plist.begin(), plist.end(), - [&np](int idx){return idx < 0 || idx >= np;})) { + if (std::any_of(plist.begin(), plist.end(), + [&np](int idx) { return idx < 0 || idx >= np; })) { throw AmiException("Indices in plist must be in [0..np]"); } this->plist_ = plist; @@ -730,9 +797,7 @@ void Model::setParameterList(const std::vector &plist) { initializeVectors(); } -std::vector const& Model::getInitialStates() const { - return x0data; -} +std::vector const &Model::getInitialStates() const { return x0data; } void Model::setInitialStates(const std::vector &x0) { if (x0.size() != (unsigned)nx_rdata && !x0.empty()) @@ -768,21 +833,20 @@ void Model::setInitialStateSensitivities(const std::vector &sx0) { // revert chainrule switch (pscale.at(plist(ip))) { - case ParameterScaling::log10: - chainrulefactor = unscaledParameters.at(plist(ip)) - * log(10); - break; - case ParameterScaling::ln: - chainrulefactor = unscaledParameters.at(plist(ip)); - break; - case ParameterScaling::none: - chainrulefactor = 1.0; - break; + case ParameterScaling::log10: + chainrulefactor = unscaledParameters.at(plist(ip)) * log(10); + break; + case ParameterScaling::ln: + chainrulefactor = unscaledParameters.at(plist(ip)); + break; + case ParameterScaling::none: + chainrulefactor = 1.0; + break; } - for(int ix=0; ix < nx_rdata; ++ix) { - sx0_rdata.at(ip*nx_rdata + ix) = - sx0.at(ip*nx_rdata + ix) / chainrulefactor; + for (int ix = 0; ix < nx_rdata; ++ix) { + sx0_rdata.at(ip * nx_rdata + ix) = + sx0.at(ip * nx_rdata + ix) / chainrulefactor; } } setUnscaledInitialStateSensitivities(sx0_rdata); @@ -803,152 +867,53 @@ void Model::setUnscaledInitialStateSensitivities( sx0data = sx0; } -double Model::t0() const{ - return tstart; -} - -void Model::setT0(double t0) { - tstart = t0; -} - -int Model::plist(int pos) const{ - return plist_.at(pos); -} +double Model::t0() const { return tstart; } -bool Model::hasParameterNames() const { return np() == 0 || !getParameterNames().empty(); } +void Model::setT0(double t0) { tstart = t0; } -std::vector Model::getParameterNames() const { return std::vector(); } +int Model::plist(int pos) const { return plist_.at(pos); } -bool Model::hasStateNames() const { return nx_rdata == 0 || !getStateNames().empty(); } +bool Model::hasParameterNames() const { + return np() == 0 || !getParameterNames().empty(); +} -std::vector Model::getStateNames() const { return std::vector(); } +std::vector Model::getParameterNames() const { + return std::vector(); +} -bool Model::hasFixedParameterNames() const { return nk() == 0 || !getFixedParameterNames().empty(); } +bool Model::hasStateNames() const { + return nx_rdata == 0 || !getStateNames().empty(); +} -std::vector Model::getFixedParameterNames() const { return std::vector(); } +std::vector Model::getStateNames() const { + return std::vector(); +} -bool Model::hasObservableNames() const { return ny == 0 || !getObservableNames().empty(); } +bool Model::hasFixedParameterNames() const { + return nk() == 0 || !getFixedParameterNames().empty(); +} -std::vector Model::getObservableNames() const { return std::vector(); } +std::vector Model::getFixedParameterNames() const { + return std::vector(); +} -bool Model::hasParameterIds() const { return np() == 0 || !getParameterIds().empty(); } +bool Model::hasObservableNames() const { + return ny == 0 || !getObservableNames().empty(); +} -std::vector Model::getParameterIds() const { +std::vector Model::getObservableNames() const { return std::vector(); } +bool Model::hasParameterIds() const { + return np() == 0 || !getParameterIds().empty(); +} -Model::Model() - : nx_rdata(0), nxtrue_rdata(0), nx_solver(0), nxtrue_solver(0), ny(0), - nytrue(0), nz(0), nztrue(0), ne(0), nw(0), ndwdx(0), ndwdp(0), ndxdotdw(0), nnz(0), - nJ(0), ubw(0), lbw(0), o2mode(SecondOrderMode::none), dxdotdp(0,0), - x_pos_tmp(0) {} - -Model::Model(const int nx_rdata, - const int nxtrue_rdata, - const int nx_solver, - const int nxtrue_solver, - const int ny, - const int nytrue, - const int nz, - const int nztrue, - const int ne, - const int nJ, - const int nw, - const int ndwdx, - const int ndwdp, - const int ndxdotdw, - std::vector ndJydy, - const int nnz, - const int ubw, - const int lbw, - SecondOrderMode o2mode, - const std::vector& p, - std::vector k, - const std::vector& plist, - std::vector idlist, - std::vector z2event) - : nx_rdata(nx_rdata), nxtrue_rdata(nxtrue_rdata), - nx_solver(nx_solver), nxtrue_solver(nxtrue_solver), - ny(ny), nytrue(nytrue), - nz(nz), nztrue(nztrue), - ne(ne), - nw(nw), - ndwdx(ndwdx), - ndwdp(ndwdp), - ndxdotdw(ndxdotdw), - ndJydy(std::move(ndJydy)), - nnz(nnz), - nJ(nJ), - ubw(ubw), - lbw(lbw), - o2mode(o2mode), - z2event(std::move(z2event)), - idlist(std::move(idlist)), - sigmay(ny, 0.0), - dsigmaydp(ny*plist.size(), 0.0), - sigmaz(nz, 0.0), - dsigmazdp(nz*plist.size(), 0.0), - dJydp(nJ*plist.size(), 0.0), - dJzdp(nJ*plist.size(), 0.0), - deltax(nx_solver, 0.0), - deltasx(nx_solver*plist.size(), 0.0), - deltaxB(nx_solver, 0.0), - deltaqB(nJ*plist.size(), 0.0), - dxdotdp(nx_solver, plist.size()), - J(nx_solver, nx_solver, nnz, CSC_MAT), - dxdotdw(nx_solver, nw, ndxdotdw, CSC_MAT), - dwdx(nw, nx_solver, ndwdx, CSC_MAT), - M(nx_solver, nx_solver), - my(nytrue, 0.0), - mz(nztrue, 0.0), - dJydsigma(nJ*nytrue*ny, 0.0), - dJzdz(nJ*nztrue*nz, 0.0), - dJzdsigma(nJ*nztrue*nz, 0.0), - dJrzdz(nJ*nztrue*nz, 0.0), - dJrzdsigma(nJ*nztrue*nz, 0.0), - dzdx(nz*nx_solver, 0.0), - dzdp(nz*plist.size(), 0.0), - drzdx(nz*nx_solver, 0.0), - drzdp(nz*plist.size(), 0.0), - dydp(ny*plist.size(), 0.0), - dydx(ny*nx_solver,0.0), - w(nw, 0.0), - dwdp(ndwdp, 0.0), - stau(plist.size(), 0.0), - sx(nx_solver*plist.size(), 0.0), - x_rdata(nx_rdata, 0.0), - sx_rdata(nx_rdata, 0.0), - h(ne,0.0), - unscaledParameters(p), - originalParameters(p), - fixedParameters(std::move(k)), - total_cl(nx_rdata-nx_solver), - stotal_cl((nx_rdata-nx_solver) * np()), - plist_(plist), - stateIsNonNegative(nx_solver, false), - x_pos_tmp(nx_solver), - pscale(std::vector(p.size(), ParameterScaling::none)) -{ - // Can't use derivedClass::wasPythonGenerated() in ctor. - // Guess we are using Python if ndJydy is not empty - if(!this->ndJydy.empty()) { - if(static_cast(nytrue) != this->ndJydy.size()) - throw std::runtime_error("Number of elements in ndJydy is not equal " - " nytrue."); - - for(int iytrue = 0; iytrue < nytrue; ++iytrue) - dJydy.push_back(SUNMatrixWrapper(nJ, ny, this->ndJydy[iytrue], - CSC_MAT)); - } else { - dJydy_matlab = std::vector(nJ*nytrue*ny, 0.0); - } - - requireSensitivitiesForAllParameters(); +std::vector Model::getParameterIds() const { + return std::vector(); } -void Model::initializeVectors() -{ +void Model::initializeVectors() { dsigmaydp.resize(ny * nplist(), 0.0); dsigmazdp.resize(nz * nplist(), 0.0); dJydp.resize(nJ * nplist(), 0.0); @@ -964,248 +929,268 @@ void Model::initializeVectors() sx0data.clear(); } -void Model::fx_rdata(AmiVector *x_rdata, const AmiVector *x) { - fx_rdata(x_rdata->data(), x->data(), total_cl.data()); - if(alwaysCheckFinite) - checkFinite(x_rdata->getLength(), x_rdata->data(), "x_rdata"); +void Model::fx_rdata(AmiVector &x_rdata, const AmiVector &x) { + fx_rdata(x_rdata.data(), x.data(), total_cl.data()); + if (alwaysCheckFinite) + checkFinite(x_rdata.getVector(), "x_rdata"); } -void Model::fx0(AmiVector *x) { +void Model::fx0(AmiVector &x) { std::fill(x_rdata.begin(), x_rdata.end(), 0.0); /* this function also computes initial total abundances */ fx0(x_rdata.data(), tstart, unscaledParameters.data(), fixedParameters.data()); - fx_solver(x->data(), x_rdata.data()); + fx_solver(x.data(), x_rdata.data()); ftotal_cl(total_cl.data(), x_rdata.data()); - if(alwaysCheckFinite) { - checkFinite(x_rdata.size(), x_rdata.data(), "x0 x_rdata"); - checkFinite(x->getLength(), x->data(), "x0 x"); + if (alwaysCheckFinite) { + checkFinite(x_rdata, "x0 x_rdata"); + checkFinite(x.getVector(), "x0 x"); } } -void Model::fx0_fixedParameters(AmiVector *x) { +void Model::fx0_fixedParameters(AmiVector &x) { if (!getReinitializeFixedParameterInitialStates()) return; /* we transform to the unreduced states x_rdata and then apply x0_fixedparameters to (i) enable updates to states that were removed from conservation laws and (ii) be able to correctly compute total abundances after updating the state variables */ - fx_rdata(x_rdata.data(), x->data(), total_cl.data()); + fx_rdata(x_rdata.data(), x.data(), total_cl.data()); fx0_fixedParameters(x_rdata.data(), tstart, unscaledParameters.data(), fixedParameters.data()); - fx_solver(x->data(), x_rdata.data()); + fx_solver(x.data(), x_rdata.data()); /* update total abundances */ ftotal_cl(total_cl.data(), x_rdata.data()); } - - -void Model::fsx_rdata(AmiVectorArray *sx_full, const AmiVectorArray *sx) { +void Model::fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx) { realtype *stcl = nullptr; for (int ip = 0; ip < nplist(); ip++) { if (ncl() > 0) stcl = &stotal_cl.at(plist(ip) * ncl()); - fsx_rdata(sx_full->data(ip), sx->data(ip), stcl, ip); + fsx_rdata(sx_rdata.data(ip), sx.data(ip), stcl, ip); } } -void Model::fsx0(AmiVectorArray *sx, const AmiVector *x) { +void Model::fsx0(AmiVectorArray &sx, const AmiVector &x) { /* this function also computes initial total abundance sensitivities */ realtype *stcl = nullptr; for (int ip = 0; ip < nplist(); ip++) { if (ncl() > 0) stcl = &stotal_cl.at(plist(ip) * ncl()); std::fill(sx_rdata.begin(), sx_rdata.end(), 0.0); - fsx0(sx_rdata.data(), tstart, x->data(), unscaledParameters.data(), + fsx0(sx_rdata.data(), tstart, x.data(), unscaledParameters.data(), fixedParameters.data(), plist(ip)); - fsx_solver(sx->data(ip), sx_rdata.data()); + fsx_solver(sx.data(ip), sx_rdata.data()); fstotal_cl(stcl, sx_rdata.data(), plist(ip)); } } -void Model::fsx0_fixedParameters(AmiVectorArray *sx, const AmiVector *x) { +void Model::fsx0_fixedParameters(AmiVectorArray &sx, const AmiVector &x) { if (!getReinitializeFixedParameterInitialStates()) return; realtype *stcl = nullptr; for (int ip = 0; ip < nplist(); ip++) { if (ncl() > 0) stcl = &stotal_cl.at(plist(ip) * ncl()); - fsx_rdata(sx_rdata.data(), sx->data(ip), stcl, plist(ip)); - fsx0_fixedParameters(sx_rdata.data(), tstart, x->data(), + fsx_rdata(sx_rdata.data(), sx.data(ip), stcl, plist(ip)); + fsx0_fixedParameters(sx_rdata.data(), tstart, x.data(), unscaledParameters.data(), fixedParameters.data(), plist(ip)); - fsx_solver(sx->data(ip), sx_rdata.data()); + fsx_solver(sx.data(ip), sx_rdata.data()); fstotal_cl(stcl, sx_rdata.data(), plist(ip)); } } void Model::fsdx0() {} -void Model::fstau(const realtype t, const int ie, const AmiVector *x, const AmiVectorArray *sx) { - std::fill(stau.begin(),stau.end(),0.0); - for(int ip = 0; ip < nplist(); ip++){ - fstau(&stau.at(ip),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),sx->data(ip),plist(ip),ie); +void Model::fstau(const realtype t, const int ie, const AmiVector &x, + const AmiVectorArray &sx) { + std::fill(stau.begin(), stau.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { + fstau(&stau.at(ip), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), sx.data(ip), plist(ip), ie); } } -void Model::fy(const realtype t, const int it, const AmiVector *x, ReturnData *rdata) { +void Model::fy(const realtype t, const int it, const AmiVector &x, + ReturnData *rdata) { if (!ny) return; - fw(t,x->data()); - fy(&rdata->y.at(it*ny),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),w.data()); + fw(t, x.data()); + fy(&rdata->y.at(it * ny), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data()); - if(alwaysCheckFinite) { - amici::checkFinite(ny, &rdata->y.at(it*ny), "y"); + if (alwaysCheckFinite) { + amici::checkFinite(gsl::make_span(&rdata->y.at(it * ny), ny), "y"); } } -void Model::fdydp(const realtype t, const AmiVector *x) { +void Model::fdydp(const realtype t, const AmiVector &x) { if (!ny) return; - std::fill(dydp.begin(),dydp.end(),0.0); - fw(t, x->data()); - fdwdp(t, x->data()); + std::fill(dydp.begin(), dydp.end(), 0.0); + fw(t, x.data()); + fdwdp(t, x.data()); // if dwdp is not dense, fdydp will expect the full sparse array realtype *dwdp_tmp = dwdp.data(); - for(int ip = 0; ip < nplist(); ip++){ + for (int ip = 0; ip < nplist(); ip++) { // get dydp slice (ny) for current time and parameter if (wasPythonGenerated() && nw) dwdp_tmp = &dwdp.at(nw * ip); - fdydp(&dydp.at(ip*ny), - t, - x->data(), - unscaledParameters.data(), - fixedParameters.data(), - h.data(), - plist(ip), - w.data(), - dwdp_tmp); + fdydp(&dydp.at(ip * ny), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip), w.data(), dwdp_tmp); } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dydp, "dydp"); } } -void Model::fdydx(const realtype t, const AmiVector *x) { +void Model::fdydx(const realtype t, const AmiVector &x) { if (!ny) return; - std::fill(dydx.begin(),dydx.end(),0.0); - fw(t,x->data()); - fdwdx(t,x->data()); - fdydx(dydx.data(),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),w.data(),dwdx.data()); + std::fill(dydx.begin(), dydx.end(), 0.0); + fw(t, x.data()); + fdwdx(t, x.data()); + fdydx(dydx.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data(), dwdx.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dydx, "dydx"); } } -void Model::fz(const int nroots, const int ie, const realtype t, const AmiVector *x, ReturnData *rdata) { - fz(&rdata->z.at(nroots*nz),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data()); +void Model::fz(const int nroots, const int ie, const realtype t, + const AmiVector &x, ReturnData *rdata) { + fz(&rdata->z.at(nroots * nz), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); } -void Model::fsz(const int nroots, const int ie, const realtype t, const AmiVector *x, const AmiVectorArray *sx, ReturnData *rdata) { - for(int ip = 0; ip < nplist(); ip++ ){ - fsz(&rdata->sz.at((nroots*nplist()+ip)*nz),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),sx->data(ip),plist(ip)); +void Model::fsz(const int nroots, const int ie, const realtype t, + const AmiVector &x, const AmiVectorArray &sx, + ReturnData *rdata) { + for (int ip = 0; ip < nplist(); ip++) { + fsz(&rdata->sz.at((nroots * nplist() + ip) * nz), ie, t, x.data(), + unscaledParameters.data(), fixedParameters.data(), h.data(), + sx.data(ip), plist(ip)); } } -void Model::frz(const int nroots, const int ie, const realtype t, const AmiVector *x, ReturnData *rdata) { - frz(&rdata->rz.at(nroots*nz),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data()); +void Model::frz(const int nroots, const int ie, const realtype t, + const AmiVector &x, ReturnData *rdata) { + frz(&rdata->rz.at(nroots * nz), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); } -void Model::fsrz(const int nroots, const int ie, const realtype t, const AmiVector *x, const AmiVectorArray *sx, ReturnData *rdata) { - for(int ip = 0; ip < nplist(); ip++ ){ - fsrz(&rdata->srz.at((nroots*nplist()+ip)*nz),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),sx->data(ip),plist(ip)); +void Model::fsrz(const int nroots, const int ie, const realtype t, + const AmiVector &x, const AmiVectorArray &sx, + ReturnData *rdata) { + for (int ip = 0; ip < nplist(); ip++) { + fsrz(&rdata->srz.at((nroots * nplist() + ip) * nz), ie, t, x.data(), + unscaledParameters.data(), fixedParameters.data(), h.data(), + sx.data(ip), plist(ip)); } } -void Model::fdzdp(const realtype t, const int ie, const AmiVector *x) { - std::fill(dzdp.begin(),dzdp.end(),0.0); - for(int ip = 0; ip < nplist(); ip++){ - fdzdp(dzdp.data(),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),plist(ip)); +void Model::fdzdp(const realtype t, const int ie, const AmiVector &x) { + std::fill(dzdp.begin(), dzdp.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { + fdzdp(dzdp.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip)); } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dzdp, "dzdp"); } } -void Model::fdzdx(const realtype t, const int ie, const AmiVector *x) { - std::fill(dzdx.begin(),dzdx.end(),0.0); - fdzdx(dzdx.data(),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data()); +void Model::fdzdx(const realtype t, const int ie, const AmiVector &x) { + std::fill(dzdx.begin(), dzdx.end(), 0.0); + fdzdx(dzdx.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dzdx, "dzdx"); } } -void Model::fdrzdp(const realtype t, const int ie, const AmiVector *x) { - std::fill(drzdp.begin(),drzdp.end(),0.0); - for(int ip = 0; ip < nplist(); ip++){ - fdrzdp(drzdp.data(),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),plist(ip)); +void Model::fdrzdp(const realtype t, const int ie, const AmiVector &x) { + std::fill(drzdp.begin(), drzdp.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { + fdrzdp(drzdp.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip)); } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(drzdp, "drzdp"); } } +void Model::fdrzdx(const realtype t, const int ie, const AmiVector &x) { + std::fill(drzdx.begin(), drzdx.end(), 0.0); + fdrzdx(drzdx.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); -void Model::fdrzdx(const realtype t, const int ie, const AmiVector *x) { - std::fill(drzdx.begin(),drzdx.end(),0.0); - fdrzdx(drzdx.data(),ie,t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data()); - - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(drzdx, "drzdx"); } } -void Model::fdeltax(const int ie, const realtype t, const AmiVector *x, - const AmiVector *xdot, const AmiVector *xdot_old) { - std::fill(deltax.begin(),deltax.end(),0.0); - fdeltax(deltax.data(),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),ie,xdot->data(),xdot_old->data()); +void Model::fdeltax(const int ie, const realtype t, const AmiVector &x, + const AmiVector &xdot, const AmiVector &xdot_old) { + std::fill(deltax.begin(), deltax.end(), 0.0); + fdeltax(deltax.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), ie, xdot.data(), xdot_old.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(deltax, "deltax"); } } -void Model::fdeltasx(const int ie, const realtype t, const AmiVector *x, const AmiVectorArray *sx, - const AmiVector *xdot, const AmiVector *xdot_old) { - fw(t,x->data()); - std::fill(deltasx.begin(),deltasx.end(),0.0); - for(int ip = 0; ip < nplist(); ip++) - fdeltasx(&deltasx.at(nx_solver*ip),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),w.data(), - plist(ip),ie,xdot->data(),xdot_old->data(),sx->data(ip),&stau.at(ip)); +void Model::fdeltasx(const int ie, const realtype t, const AmiVector &x, + const AmiVectorArray &sx, const AmiVector &xdot, + const AmiVector &xdot_old) { + fw(t, x.data()); + std::fill(deltasx.begin(), deltasx.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) + fdeltasx(&deltasx.at(nx_solver * ip), t, x.data(), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data(), plist(ip), ie, xdot.data(), xdot_old.data(), + sx.data(ip), &stau.at(ip)); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(deltasx, "deltasx"); } } -void Model::fdeltaxB(const int ie, const realtype t, const AmiVector *x, const AmiVector *xB, - const AmiVector *xdot, const AmiVector *xdot_old) { - std::fill(deltaxB.begin(),deltaxB.end(),0.0); - fdeltaxB(deltaxB.data(),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(),ie,xdot->data(),xdot_old->data(),xB->data()); +void Model::fdeltaxB(const int ie, const realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, + const AmiVector &xdot_old) { + std::fill(deltaxB.begin(), deltaxB.end(), 0.0); + fdeltaxB(deltaxB.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), ie, xdot.data(), xdot_old.data(), + xB.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(deltaxB, "deltaxB"); } } -void Model::fdeltaqB(const int ie, const realtype t, const AmiVector *x, const AmiVector *xB, - const AmiVector *xdot, const AmiVector *xdot_old) { - std::fill(deltaqB.begin(),deltaqB.end(),0.0); - for(int ip = 0; ip < nplist(); ip++) - fdeltaqB(deltaqB.data(),t,x->data(), unscaledParameters.data(),fixedParameters.data(),h.data(), - plist(ip),ie,xdot->data(),xdot_old->data(),xB->data()); +void Model::fdeltaqB(const int ie, const realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, + const AmiVector &xdot_old) { + std::fill(deltaqB.begin(), deltaqB.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) + fdeltaqB(deltaqB.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip), ie, xdot.data(), + xdot_old.data(), xB.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(deltaqB, "deltaqB"); } } @@ -1214,11 +1199,12 @@ void Model::fsigmay(const int it, ReturnData *rdata, const ExpData *edata) { if (!ny) return; - std::fill(sigmay.begin(),sigmay.end(),0.0); + std::fill(sigmay.begin(), sigmay.end(), 0.0); - fsigmay(sigmay.data(),rdata->ts.at(it), unscaledParameters.data(),fixedParameters.data()); + fsigmay(sigmay.data(), rdata->ts.at(it), unscaledParameters.data(), + fixedParameters.data()); - if(edata){ + if (edata) { auto sigmay_edata = edata->getObservedDataStdDevPtr(it); /* extract the value for the standard deviation from ExpData, * if the data value is NaN, use the parameter value */ @@ -1229,8 +1215,8 @@ void Model::fsigmay(const int it, ReturnData *rdata, const ExpData *edata) { } } - for(int i = 0; i < nytrue; ++i) { - if(edata && !std::isnan(edata->getObservedData()[it * nytrue + i])) + for (int i = 0; i < nytrue; ++i) { + if (edata && !std::isnan(edata->getObservedData()[it * nytrue + i])) checkSigmaPositivity(sigmay[i], "sigmay"); } std::copy_n(sigmay.data(), nytrue, &rdata->sigmay[it * rdata->ny]); @@ -1242,16 +1228,15 @@ void Model::fdsigmaydp(const int it, ReturnData *rdata, const ExpData *edata) { std::fill(dsigmaydp.begin(), dsigmaydp.end(), 0.0); - for(int ip = 0; ip < nplist(); ip++) + for (int ip = 0; ip < nplist(); ip++) // get dsigmaydp slice (ny) for current timepoint and parameter - fdsigmaydp(&dsigmaydp.at(ip*ny), - rdata->ts.at(it), - unscaledParameters.data(), - fixedParameters.data(), - plist(ip)); - - // sigmas in edata override model-sigma -> for those sigmas, set dsigmaydp to zero - if(edata){ + fdsigmaydp(&dsigmaydp.at(ip * ny), rdata->ts.at(it), + unscaledParameters.data(), fixedParameters.data(), + plist(ip)); + + // sigmas in edata override model-sigma -> for those sigmas, set dsigmaydp + // to zero + if (edata) { for (int iy = 0; iy < nytrue; iy++) { if (!edata->isSetObservedDataStdDev(it, iy)) continue; @@ -1262,9 +1247,10 @@ void Model::fdsigmaydp(const int it, ReturnData *rdata, const ExpData *edata) { } // copy dsigmaydp slice for current timepoint - std::copy(dsigmaydp.begin(), dsigmaydp.end(), &rdata->ssigmay[it * nplist() * ny]); + std::copy(dsigmaydp.begin(), dsigmaydp.end(), + &rdata->ssigmay[it * nplist() * ny]); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dsigmaydp, "dsigmaydp"); } } @@ -1287,71 +1273,78 @@ void Model::fsigmaz(const realtype t, const int ie, const int *nroots, } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(sigmaz, "sigmaz"); } } -void Model::fdsigmazdp(const realtype t, const int ie, const int *nroots, ReturnData *rdata, const ExpData *edata) { - std::fill(dsigmazdp.begin(),dsigmazdp.end(),0.0); - for(int ip = 0; ip < nplist(); ip++) { +void Model::fdsigmazdp(const realtype t, const int ie, const int *nroots, + ReturnData *rdata, const ExpData *edata) { + std::fill(dsigmazdp.begin(), dsigmazdp.end(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { // get dsigmazdp slice (nz) for current event and parameter - fdsigmazdp(&dsigmazdp.at(ip*nz), - t, - unscaledParameters.data(), - fixedParameters.data(), - plist(ip)); + fdsigmazdp(&dsigmazdp.at(ip * nz), t, unscaledParameters.data(), + fixedParameters.data(), plist(ip)); } - // sigmas in edata override model-sigma -> for those sigmas, set dsigmazdp to zero - if(edata) { + // sigmas in edata override model-sigma -> for those sigmas, set dsigmazdp + // to zero + if (edata) { for (int iz = 0; iz < nztrue; iz++) { - if (z2event.at(iz) - 1 == ie && !edata->isSetObservedEventsStdDev(nroots[ie],iz)) { - for(int ip = 0; ip < nplist(); ip++) + if (z2event.at(iz) - 1 == ie && + !edata->isSetObservedEventsStdDev(nroots[ie], iz)) { + for (int ip = 0; ip < nplist(); ip++) dsigmazdp.at(iz + nz * ip) = 0; } } } // copy dsigmazdp slice for current event - std::copy(dsigmazdp.begin(), dsigmazdp.end(), &rdata->ssigmaz[nroots[ie] * nplist() * nz]); + std::copy(dsigmazdp.begin(), dsigmazdp.end(), + &rdata->ssigmaz[nroots[ie] * nplist() * nz]); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dsigmazdp, "dsigmazdp"); } } void Model::fJy(const int it, ReturnData *rdata, const ExpData *edata) { - std::vector nllh(nJ,0.0); - getmy(it,edata); - for(int iytrue = 0; iytrue < nytrue; iytrue++){ - if(!isNaN(my.at(iytrue))){ - std::fill(nllh.begin(),nllh.end(),0.0); - fJy(nllh.data(),iytrue, unscaledParameters.data(),fixedParameters.data(),gety(it,rdata),sigmay.data(),my.data()); + std::vector nllh(nJ, 0.0); + getmy(it, edata); + for (int iytrue = 0; iytrue < nytrue; iytrue++) { + if (!isNaN(my.at(iytrue))) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJy(nllh.data(), iytrue, unscaledParameters.data(), + fixedParameters.data(), gety(it, rdata), sigmay.data(), + my.data()); rdata->llh -= nllh.at(0); } } } void Model::fJz(const int nroots, ReturnData *rdata, const ExpData *edata) { - std::vector nllh(nJ,0.0); - getmz(nroots,edata); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - std::fill(nllh.begin(),nllh.end(),0.0); - fJz(nllh.data(),iztrue, unscaledParameters.data(),fixedParameters.data(),getz(nroots,rdata),sigmaz.data(),mz.data()); + std::vector nllh(nJ, 0.0); + getmz(nroots, edata); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJz(nllh.data(), iztrue, unscaledParameters.data(), + fixedParameters.data(), getz(nroots, rdata), sigmaz.data(), + mz.data()); rdata->llh -= nllh.at(0); } } } -void Model::fJrz(const int nroots, ReturnData *rdata, const ExpData * /*edata*/) { - std::vector nllh(nJ,0.0); - getrz(nroots,rdata); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - std::fill(nllh.begin(),nllh.end(),0.0); - fJrz(nllh.data(),iztrue, unscaledParameters.data(),fixedParameters.data(),getrz(nroots,rdata),sigmaz.data()); +void Model::fJrz(const int nroots, ReturnData *rdata, + const ExpData * /*edata*/) { + std::vector nllh(nJ, 0.0); + getrz(nroots, rdata); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJrz(nllh.data(), iztrue, unscaledParameters.data(), + fixedParameters.data(), getrz(nroots, rdata), sigmaz.data()); rdata->llh -= nllh.at(0); } } @@ -1362,44 +1355,37 @@ void Model::fdJydy(const int it, const ReturnData *rdata, // load measurements to my getmy(it, edata); - if(wasPythonGenerated()) { - for(int iytrue = 0; iytrue < nytrue; iytrue++) { + if (wasPythonGenerated()) { + for (int iytrue = 0; iytrue < nytrue; iytrue++) { dJydy[iytrue].zero(); fdJydy_colptrs(dJydy[iytrue].indexptrs(), iytrue); fdJydy_rowvals(dJydy[iytrue].indexvals(), iytrue); - if(isNaN(my.at(iytrue))) { + if (isNaN(my.at(iytrue))) { continue; } // get dJydy slice (ny) for current timepoint and observable - fdJydy(dJydy[iytrue].data(), - iytrue, - unscaledParameters.data(), - fixedParameters.data(), - gety(it,rdata), - sigmay.data(), + fdJydy(dJydy[iytrue].data(), iytrue, unscaledParameters.data(), + fixedParameters.data(), gety(it, rdata), sigmay.data(), my.data()); - if(alwaysCheckFinite) { - amici::checkFinite(ndJydy[iytrue], dJydy[iytrue].data(), "dJydy"); + if (alwaysCheckFinite) { + amici::checkFinite(gsl::make_span(dJydy[iytrue].get()), + "dJydy"); } } } else { std::fill(dJydy_matlab.begin(), dJydy_matlab.end(), 0.0); - for(int iytrue = 0; iytrue < nytrue; iytrue++) { - if(isNaN(my.at(iytrue))) { + for (int iytrue = 0; iytrue < nytrue; iytrue++) { + if (isNaN(my.at(iytrue))) { continue; } - fdJydy(&dJydy_matlab.at(iytrue*ny*nJ), - iytrue, - unscaledParameters.data(), - fixedParameters.data(), - gety(it,rdata), - sigmay.data(), - my.data()); + fdJydy(&dJydy_matlab.at(iytrue * ny * nJ), iytrue, + unscaledParameters.data(), fixedParameters.data(), + gety(it, rdata), sigmay.data(), my.data()); } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { // get dJydy slice (ny) for current timepoint and observable amici::checkFinite(dJydy_matlab, "dJydy"); } @@ -1410,90 +1396,95 @@ void Model::fdJydsigma(const int it, const ReturnData *rdata, const ExpData *edata) { // load measurements to my getmy(it, edata); - std::fill(dJydsigma.begin(),dJydsigma.end(),0.0); + std::fill(dJydsigma.begin(), dJydsigma.end(), 0.0); - for(int iytrue = 0; iytrue < nytrue; iytrue++){ - if(!isNaN(my.at(iytrue))){ + for (int iytrue = 0; iytrue < nytrue; iytrue++) { + if (!isNaN(my.at(iytrue))) { // get dJydsigma slice (ny) for current timepoint and observable - fdJydsigma(&dJydsigma.at(iytrue*ny*nJ), - iytrue, - unscaledParameters.data(), - fixedParameters.data(), - gety(it,rdata), - sigmay.data(), - my.data()); + fdJydsigma(&dJydsigma.at(iytrue * ny * nJ), iytrue, + unscaledParameters.data(), fixedParameters.data(), + gety(it, rdata), sigmay.data(), my.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJydsigma, "dJydsigma"); } } void Model::fdJzdz(const int nroots, const ReturnData *rdata, const ExpData *edata) { - getmz(nroots,edata); - std::fill(dJzdz.begin(),dJzdz.end(),0.0); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - fdJzdz(&dJzdz.at(iztrue*nz*nJ),iztrue, unscaledParameters.data(),fixedParameters.data(),getz(nroots,rdata),sigmaz.data(),mz.data()); + getmz(nroots, edata); + std::fill(dJzdz.begin(), dJzdz.end(), 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + fdJzdz(&dJzdz.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), + getz(nroots, rdata), sigmaz.data(), mz.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJzdz, "dJzdz"); } } void Model::fdJzdsigma(const int nroots, const ReturnData *rdata, const ExpData *edata) { - getmz(nroots,edata); - std::fill(dJzdsigma.begin(),dJzdsigma.end(),0.0); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - fdJzdsigma(&dJzdsigma.at(iztrue*nz*nJ),iztrue, unscaledParameters.data(),fixedParameters.data(),getz(nroots,rdata),sigmaz.data(),mz.data()); + getmz(nroots, edata); + std::fill(dJzdsigma.begin(), dJzdsigma.end(), 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + fdJzdsigma(&dJzdsigma.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), + getz(nroots, rdata), sigmaz.data(), mz.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJzdsigma, "dJzdsigma"); } } void Model::fdJrzdz(const int nroots, const ReturnData *rdata, const ExpData *edata) { - getmz(nroots,edata); - std::fill(dJrzdz.begin(),dJrzdz.end(),0.0); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - fdJrzdz(&dJrzdz.at(iztrue*nz*nJ),iztrue, unscaledParameters.data(),fixedParameters.data(),getrz(nroots,rdata),sigmaz.data()); + getmz(nroots, edata); + std::fill(dJrzdz.begin(), dJrzdz.end(), 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + fdJrzdz(&dJrzdz.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), + getrz(nroots, rdata), sigmaz.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJrzdz, "dJrzdz"); } } -void Model::fdJrzdsigma(const int nroots,const ReturnData *rdata, +void Model::fdJrzdsigma(const int nroots, const ReturnData *rdata, const ExpData * /*edata*/) { - std::fill(dJrzdsigma.begin(),dJrzdsigma.end(),0.0); - for(int iztrue = 0; iztrue < nztrue; iztrue++){ - if(!isNaN(mz.at(iztrue))){ - fdJrzdsigma(&dJrzdsigma.at(iztrue*nz*nJ),iztrue, unscaledParameters.data(),fixedParameters.data(),getrz(nroots,rdata),sigmaz.data()); + std::fill(dJrzdsigma.begin(), dJrzdsigma.end(), 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (!isNaN(mz.at(iztrue))) { + fdJrzdsigma(&dJrzdsigma.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), + getrz(nroots, rdata), sigmaz.data()); } } - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(dJrzdsigma, "dJrzdsigma"); } } void Model::fw(const realtype t, const realtype *x) { - std::fill(w.begin(),w.end(),0.0); - fw(w.data(), t, x, unscaledParameters.data(), fixedParameters.data(), h.data(), total_cl.data()); + std::fill(w.begin(), w.end(), 0.0); + fw(w.data(), t, x, unscaledParameters.data(), fixedParameters.data(), + h.data(), total_cl.data()); - if(alwaysCheckFinite) { + if (alwaysCheckFinite) { amici::checkFinite(w, "w"); } } @@ -1528,15 +1519,15 @@ void Model::fdwdp(const realtype t, const realtype *x) { } void Model::fdwdx(const realtype t, const realtype *x) { - fw(t,x); + fw(t, x); dwdx.reset(); fdwdx(dwdx.data(), t, x, unscaledParameters.data(), fixedParameters.data(), h.data(), w.data(), total_cl.data()); fdwdx_colptrs(dwdx.indexptrs()); fdwdx_rowvals(dwdx.indexptrs()); - if(alwaysCheckFinite) { - amici::checkFinite(ndwdx, dwdx.data(), "dwdx"); + if (alwaysCheckFinite) { + amici::checkFinite(gsl::make_span(dwdx.get()), "dwdx"); } } @@ -1550,7 +1541,8 @@ void Model::fres(const int it, ReturnData *rdata, const ExpData *edata) { int iyt = iy + it * rdata->ny; if (!edata->isSetObservedData(it, iy)) continue; - rdata->res.at(iyt_true) = (rdata->y.at(iyt) - observedData[iy])/rdata->sigmay.at(iyt); + rdata->res.at(iyt_true) = + (rdata->y.at(iyt) - observedData[iy]) / rdata->sigmay.at(iyt); } } @@ -1571,11 +1563,12 @@ void Model::fsres(const int it, ReturnData *rdata, const ExpData *edata) { for (int iy = 0; iy < nytrue; ++iy) { int iyt_true = iy + it * edata->nytrue(); int iyt = iy + it * rdata->ny; - if (!edata->isSetObservedData(it,iy)) + if (!edata->isSetObservedData(it, iy)) continue; for (int ip = 0; ip < nplist(); ++ip) { rdata->sres.at(iyt_true * nplist() + ip) = - rdata->sy.at(iy + rdata->ny*(ip + it*nplist()))/rdata->sigmay.at(iyt); + rdata->sy.at(iy + rdata->ny * (ip + it * nplist())) / + rdata->sigmay.at(iyt); } } } @@ -1589,14 +1582,14 @@ void Model::fFIM(const int it, ReturnData *rdata) { for (int ip = 0; ip < nplist(); ++ip) { for (int jp = 0; jp < nplist(); ++jp) { rdata->FIM.at(ip + nplist() * jp) += - rdata->sres.at(iyt_true * nplist() + ip) - * rdata->sres.at(iyt_true * nplist() + jp); + rdata->sres.at(iyt_true * nplist() + ip) * + rdata->sres.at(iyt_true * nplist() + jp); } } } } -void Model::updateHeaviside(const std::vector& rootsfound) { +void Model::updateHeaviside(const std::vector &rootsfound) { for (int ie = 0; ie < ne; ie++) { h.at(ie) += rootsfound.at(ie); } @@ -1608,9 +1601,8 @@ void Model::updateHeavisideB(const int *rootsfound) { } } - -void Model::getmy(const int it, const ExpData *edata){ - if(edata) { +void Model::getmy(const int it, const ExpData *edata) { + if (edata) { std::copy_n(edata->getObservedDataPtr(it), nytrue, my.begin()); } else { std::fill(my.begin(), my.end(), getNaN()); @@ -1618,7 +1610,7 @@ void Model::getmy(const int it, const ExpData *edata){ } const realtype *Model::gety(const int it, const ReturnData *rdata) const { - return &rdata->y.at(it*ny); + return &rdata->y.at(it * ny); } realtype Model::gett(const int it, const ReturnData *rdata) const { @@ -1626,7 +1618,7 @@ realtype Model::gett(const int it, const ReturnData *rdata) const { } void Model::getmz(const int nroots, const ExpData *edata) { - if(edata){ + if (edata) { std::copy_n(edata->getObservedEventsPtr(nroots), nztrue, mz.begin()); } else { std::fill(mz.begin(), mz.end(), getNaN()); @@ -1634,41 +1626,37 @@ void Model::getmz(const int nroots, const ExpData *edata) { } const realtype *Model::getz(const int nroots, const ReturnData *rdata) const { - return(&rdata->z.at(nroots*nz)); + return (&rdata->z.at(nroots * nz)); } const realtype *Model::getrz(const int nroots, const ReturnData *rdata) const { - return(&rdata->rz.at(nroots*nz)); + return (&rdata->rz.at(nroots * nz)); } -const realtype *Model::getsz(const int nroots, const int ip, const ReturnData *rdata) const { - return(&rdata->sz.at((nroots*nplist()+ip)*nz)); +const realtype *Model::getsz(const int nroots, const int ip, + const ReturnData *rdata) const { + return (&rdata->sz.at((nroots * nplist() + ip) * nz)); } -const realtype *Model::getsrz(const int nroots, const int ip, const ReturnData *rdata) const { - return(&rdata->srz.at((nroots*nplist()+ip)*nz)); +const realtype *Model::getsrz(const int nroots, const int ip, + const ReturnData *rdata) const { + return (&rdata->srz.at((nroots * nplist() + ip) * nz)); } -void Model::setAlwaysCheckFinite(bool alwaysCheck) -{ +void Model::setAlwaysCheckFinite(bool alwaysCheck) { alwaysCheckFinite = alwaysCheck; } -bool Model::getAlwaysCheckFinite() const -{ - return alwaysCheckFinite; -} +bool Model::getAlwaysCheckFinite() const { return alwaysCheckFinite; } -int Model::checkFinite(const int N, const realtype *array, - const char *fun) const { - auto result = amici::checkFinite(N, array, fun); +int Model::checkFinite(gsl::span array, const char *fun) const { + auto result = amici::checkFinite(array, fun); if (result != AMICI_SUCCESS) { - amici::checkFinite(ts.size(), ts.data(), "ts"); - amici::checkFinite(fixedParameters.size(), fixedParameters.data(), "k"); - amici::checkFinite(unscaledParameters.size(), unscaledParameters.data(), - "p"); - amici::checkFinite(w.size(), w.data(), "w"); + amici::checkFinite(ts, "ts"); + amici::checkFinite(fixedParameters, "k"); + amici::checkFinite(unscaledParameters, "p"); + amici::checkFinite(w, "w"); } return result; @@ -1680,49 +1668,36 @@ void Model::requireSensitivitiesForAllParameters() { initializeVectors(); } -bool operator ==(const Model &a, const Model &b) -{ +bool operator==(const Model &a, const Model &b) { if (typeid(a) != typeid(b)) return false; - return (a.nx_rdata == b.nx_rdata) - && (a.nxtrue_rdata == b.nxtrue_rdata) - && (a.nx_solver == b.nx_solver) - && (a.nxtrue_solver == b.nxtrue_solver) - && (a.ny == b.ny) - && (a.nytrue == b.nytrue) - && (a.nz == b.nz) - && (a.nztrue == b.nztrue) - && (a.ne == b.ne) - && (a.nw == b.nw) - && (a.ndwdx == b.ndwdx) - && (a.ndwdp == b.ndwdp) - && (a.ndxdotdw == b.ndxdotdw) - && (a.nnz == b.nnz) - && (a.nJ == b.nJ) - && (a.ubw == b.ubw) - && (a.lbw == b.lbw) - && (a.o2mode == b.o2mode) - && (a.z2event == b.z2event) - && (a.idlist == b.idlist) - && (a.h == b.h) - && (a.unscaledParameters == b.unscaledParameters) - && (a.originalParameters == b.originalParameters) - && (a.fixedParameters == b.fixedParameters) - && (a.plist_ == b.plist_) - && (a.x0data == b.x0data) - && (a.sx0data == b.sx0data) - && (a.ts == b.ts) - && (a.nmaxevent == b.nmaxevent) - && (a.pscale == b.pscale) - && (a.stateIsNonNegative == b.stateIsNonNegative) - && (a.tstart == b.tstart); -} - -N_Vector Model::computeX_pos(N_Vector x) { - if (anyStateNonNegative){ + return (a.nx_rdata == b.nx_rdata) && (a.nxtrue_rdata == b.nxtrue_rdata) && + (a.nx_solver == b.nx_solver) && + (a.nxtrue_solver == b.nxtrue_solver) && (a.ny == b.ny) && + (a.nytrue == b.nytrue) && (a.nz == b.nz) && (a.nztrue == b.nztrue) && + (a.ne == b.ne) && (a.nw == b.nw) && (a.ndwdx == b.ndwdx) && + (a.ndwdp == b.ndwdp) && (a.ndxdotdw == b.ndxdotdw) && + (a.nnz == b.nnz) && (a.nJ == b.nJ) && (a.ubw == b.ubw) && + (a.lbw == b.lbw) && (a.o2mode == b.o2mode) && + (a.z2event == b.z2event) && (a.idlist == b.idlist) && (a.h == b.h) && + (a.unscaledParameters == b.unscaledParameters) && + (a.originalParameters == b.originalParameters) && + (a.fixedParameters == b.fixedParameters) && (a.plist_ == b.plist_) && + (a.x0data == b.x0data) && (a.sx0data == b.sx0data) && + (a.ts == b.ts) && (a.nmaxevent == b.nmaxevent) && + (a.pscale == b.pscale) && + (a.stateIsNonNegative == b.stateIsNonNegative) && + (a.tstart == b.tstart); +} + +N_Vector Model::computeX_pos(const_N_Vector x) { + if (anyStateNonNegative) { for (int ix = 0; ix < x_pos_tmp.getLength(); ++ix) { - x_pos_tmp.at(ix) = (stateIsNonNegative.at(ix) && NV_Ith_S(x, ix) < 0) ? 0 : NV_Ith_S(x, ix); + x_pos_tmp.at(ix) = + (stateIsNonNegative.at(ix) && NV_Ith_S(x, ix) < 0) + ? 0 + : NV_Ith_S(x, ix); } return x_pos_tmp.getNVector(); } diff --git a/src/model_dae.cpp b/src/model_dae.cpp index 6d2546e21c..339a802b77 100644 --- a/src/model_dae.cpp +++ b/src/model_dae.cpp @@ -1,295 +1,253 @@ -#include "amici/solver_idas.h" #include "amici/model_dae.h" +#include "amici/solver_idas.h" namespace amici { - void Model_DAE::fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) { - fJ(t,cj, x->getNVector(), dx->getNVector(), xdot->getNVector(), J); - } +void Model_DAE::fJ(const realtype t, const realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, SUNMatrix J) { + fJ(t, cj, x.getNVector(), dx.getNVector(), xdot.getNVector(), J); +} - /** Jacobian of xdot with respect to states x - * @param t timepoint - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - * @param J Matrix to which the Jacobian will be written +void Model_DAE::fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector /*xdot*/, SUNMatrix J) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(J); + fJ(SM_DATA_D(J), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(dx), w.data(), + dwdx.data()); +} - **/ - void Model_DAE::fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector /*xdot*/, SUNMatrix J) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(J); - fJ(SM_DATA_D(J), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), h.data(), cj, - N_VGetArrayPointer(dx), w.data(), dwdx.data()); - } +void Model_DAE::fJSparse(const realtype t, const realtype cj, + const AmiVector &x, const AmiVector &dx, + const AmiVector & /*xdot*/, SUNMatrix J) { + fJSparse(t, cj, x.getNVector(), dx.getNVector(), J); +} - void Model_DAE::fJSparse(realtype t, realtype cj, AmiVector *x, - AmiVector *dx, AmiVector * /*xdot*/, SUNMatrix J) { - fJSparse(t, cj, x->getNVector(), dx->getNVector(), J); - } +void Model_DAE::fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, + SUNMatrix J) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(J); + fJSparse(static_cast(SM_CONTENT_S(J)), t, + N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(dx), + w.data(), dwdx.data()); +} + +void Model_DAE::fJSparseB(SUNMatrixContent_Sparse /*JSparseB*/, + const realtype /*t*/, const realtype * /*x*/, + const double * /*p*/, const double * /*k*/, + const realtype * /*h*/, const realtype /*cj*/, + const realtype * /*xB*/, const realtype * /*dx*/, + const realtype * /*dxB*/, const realtype * /*w*/, + const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); +} - /** J in sparse form (for sparse solvers from the SuiteSparse Package) - * @param t timepoint - * @param cj scalar in Jacobian (inverse stepsize) - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param J Matrix to which the Jacobian will be written - */ - void Model_DAE::fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, - SUNMatrix J) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(J); - fJSparse(static_cast(SM_CONTENT_S(J)), t, - N_VGetArrayPointer(x_pos), unscaledParameters.data(), - fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(dx), - w.data(), dwdx.data()); - } +void Model_DAE::fJv(const realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector & /*xdot*/, const AmiVector &v, + AmiVector &Jv, const realtype cj) { + fJv(t, x.getNVector(), dx.getNVector(), v.getNVector(), Jv.getNVector(), + cj); +} - void Model_DAE::fJv(realtype t, AmiVector *x, AmiVector *dx, AmiVector * /*xdot*/, - AmiVector *v, AmiVector *Jv, realtype cj){ - fJv(t,x->getNVector(),dx->getNVector(),v->getNVector(), - Jv->getNVector(),cj); - } +void Model_DAE::fJv(realtype t, N_Vector x, N_Vector dx, N_Vector v, + N_Vector Jv, realtype cj) { + N_VConst(0.0, Jv); + fJSparse(t, cj, x, dx, J.get()); + J.multiply(Jv, v); +} - /** Matrix vector product of J with a vector v (for iterative solvers) - * @param t timepoint @type realtype - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param v Vector with which the Jacobian is multiplied - * @param Jv Vector to which the Jacobian vector product will be - *written - **/ - void Model_DAE::fJv(realtype t, N_Vector x, N_Vector dx, N_Vector v, N_Vector Jv, - realtype cj) { - N_VConst(0.0, Jv); - fJSparse(t, cj, x, dx, J.get()); - J.multiply(Jv, v); - } +void Model_DAE::froot(const realtype t, const AmiVector &x, const AmiVector &dx, + gsl::span root) { + froot(t, x.getNVector(), dx.getNVector(), root); +} - void Model_DAE::froot(realtype t, AmiVector *x, AmiVector *dx, realtype *root){ - froot(t,x->getNVector(),dx->getNVector(),root); - } +void Model_DAE::froot(realtype t, N_Vector x, N_Vector dx, + gsl::span root) { + std::fill(root.begin(), root.end(), 0.0); + auto x_pos = computeX_pos(x); + froot(root.data(), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), N_VGetArrayPointer(dx)); +} - /** Event trigger function for events - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param root array with root function values - */ - void Model_DAE::froot(realtype t, N_Vector x, N_Vector dx, realtype *root) { - memset(root, 0,sizeof(realtype)*ne); - auto x_pos = computeX_pos(x); - froot(root,t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - N_VGetArrayPointer(dx)); - } +void Model_DAE::fxdot(const realtype t, const AmiVector &x, const AmiVector &dx, + AmiVector &xdot) { + fxdot(t, x.getNVector(), dx.getNVector(), xdot.getNVector()); +} - void Model_DAE::fxdot(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot){ - fxdot(t,x->getNVector(),dx->getNVector(),xdot->getNVector()); - } +void Model_DAE::fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot) { + auto x_pos = computeX_pos(x); + fw(t, N_VGetArrayPointer(x)); + N_VConst(0.0, xdot); + fxdot(N_VGetArrayPointer(xdot), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + N_VGetArrayPointer(dx), w.data()); +} - /** residual function of the DAE - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - */ - void Model_DAE::fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot) { - auto x_pos = computeX_pos(x); - fw(t,N_VGetArrayPointer(x)); - N_VConst(0.0,xdot); - fxdot(N_VGetArrayPointer(xdot),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - N_VGetArrayPointer(dx),w.data()); - } +void Model_DAE::fJDiag(const realtype t, AmiVector &JDiag, + const realtype /*cj*/, const AmiVector &x, + const AmiVector &dx) { + auto x_pos = computeX_pos(x.getNVector()); + fdwdx(t, N_VGetArrayPointer(x_pos)); + JDiag.set(0.0); + fJDiag(JDiag.data(), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), 0.0, + dx.data(), w.data(), dwdx.data()); + if (!checkFinite(JDiag.getVector(), "Jacobian")) + throw AmiException("Evaluation of fJDiag failed!"); +} - /** diagonalized Jacobian (for preconditioning) - * @param t timepoint - * @param JDiag Vector to which the Jacobian diagonal will be written - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @return status flag indicating successful execution - **/ - void Model_DAE::fJDiag(realtype t, AmiVector *JDiag, realtype /*cj*/, AmiVector *x, - AmiVector *dx) { - auto x_pos = computeX_pos(x->getNVector()); - fdwdx(t,N_VGetArrayPointer(x_pos)); - JDiag->set(0.0); - fJDiag(JDiag->data(),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - 0.0,dx->data(),w.data(),dwdx.data()); - if(!checkFinite(nx_solver,JDiag->data(),"Jacobian")) - throw AmiException("Evaluation of fJDiag failed!"); +void Model_DAE::fdxdotdp(const realtype t, const N_Vector x, + const N_Vector dx) { + auto x_pos = computeX_pos(x); + fdwdp(t, N_VGetArrayPointer(x_pos)); + for (int ip = 0; ip < nplist(); ip++) { + N_VConst(0.0, dxdotdp.getNVector(ip)); + fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + plist_[ip], N_VGetArrayPointer(dx), w.data(), dwdp.data()); } +} - /** Sensitivity of dx/dt wrt model parameters p - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @return status flag indicating successful execution - */ - void Model_DAE::fdxdotdp(const realtype t, const N_Vector x, const N_Vector dx) { - auto x_pos = computeX_pos(x); - fdwdp(t,N_VGetArrayPointer(x_pos)); - for(int ip = 0; ip < nplist(); ip++){ - N_VConst(0.0, dxdotdp.getNVector(ip)); - fdxdotdp(dxdotdp.data(ip),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - plist_[ip],N_VGetArrayPointer(dx),w.data(),dwdp.data()); - } - - - } +void Model_DAE::fM(realtype t, const N_Vector x) { + SUNMatZero(M.get()); + auto x_pos = computeX_pos(x); + fM(M.data(), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data()); +} - /** - * @brief Mass matrix for DAE systems - * @param t timepoint - * @param x Vector with the states - */ - void Model_DAE::fM(realtype t, const N_Vector x) { - SUNMatZero(M.get()); - auto x_pos = computeX_pos(x); - fM(M.data(),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data()); - } +std::unique_ptr Model_DAE::getSolver() { + return std::unique_ptr(new amici::IDASolver()); +} - std::unique_ptr Model_DAE::getSolver() { - return std::unique_ptr(new amici::IDASolver()); - } +void Model_DAE::fJB(realtype * /*JB*/, const realtype /*t*/, + const realtype * /*x*/, const double * /*p*/, + const double * /*k*/, const realtype * /*h*/, + const realtype /*cj*/, const realtype * /*xB*/, + const realtype * /*dx*/, const realtype * /*dxB*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** Jacobian of xBdot with respect to adjoint state xB - * @param t timepoint - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param JB Matrix to which the Jacobian will be written - **/ - void Model_DAE::fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, SUNMatrix JB) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(JB); - fJB(SM_DATA_D(JB), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), h.data(), cj, - N_VGetArrayPointer(xB), N_VGetArrayPointer(dx), - N_VGetArrayPointer(dxB), w.data(), dwdx.data()); - } +void Model_DAE::fJDiag(realtype * /*JDiag*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype /*cj*/, const realtype * /*dx*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** JB in sparse form (for sparse solvers from the SuiteSparse Package) - * @param t timepoint - * @param cj scalar in Jacobian - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param JB Matrix to which the Jacobian will be written - */ - void Model_DAE::fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, SUNMatrix JB) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(JB); - fJSparseB(static_cast(SM_CONTENT_S(JB)), t, - N_VGetArrayPointer(x_pos), unscaledParameters.data(), - fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(xB), - N_VGetArrayPointer(dx), N_VGetArrayPointer(dxB), w.data(), - dwdx.data()); - } +void Model_DAE::fJvB(realtype * /*JvB*/, const realtype /*t*/, const realtype * /*x*/, + const double * /*p*/, const double * /*k*/, + const realtype * /*h*/, const realtype /*cj*/, + const realtype * /*xB*/, const realtype * /*dx*/, + const realtype * /*dxB*/, const realtype * /*vB*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); // not implemented +} - /** Matrix vector product of JB with a vector v (for iterative solvers) - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param vB Vector with which the Jacobian is multiplied - * @param JvB Vector to which the Jacobian vector product will be - *written - * @param cj scalar in Jacobian (inverse stepsize) - **/ - void Model_DAE::fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector vB, N_Vector JvB, realtype cj) { - N_VConst(0.0, JvB); - fJSparseB(t, cj, x, dx, xB, dxB, J.get()); - J.multiply(JvB, vB); - } +void Model_DAE::froot(realtype * /*root*/, const realtype /*t*/, + const realtype * /*x*/, const double * /*p*/, const double * /*k*/, + const realtype * /*h*/, const realtype * /*dx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); // not implemented +} - /** Right hand side of differential equation for adjoint state xB - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param xBdot Vector with the adjoint right hand side - */ - void Model_DAE::fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector xBdot) { - N_VConst(0.0, xBdot); - fJSparseB(t, 1.0, x, dx, xB, dxB, J.get()); - fM(t, x); - J.multiply(xBdot, xB); - } +void Model_DAE::fdxdotdp(realtype * /*dxdotdp*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const int /*ip*/, const realtype * /*dx*/, + const realtype * /*w*/, const realtype * /*dwdp*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** Right hand side of integral equation for quadrature states qB - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param qBdot Vector with the adjoint quadrature right hand side - */ - void Model_DAE::fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, - N_Vector dxB, N_Vector qBdot) { - N_VConst(0.0, qBdot); - fdxdotdp(t, x, dx); - for (int ip = 0; ip < nplist(); ip++) { +void Model_DAE::fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, SUNMatrix JB) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(JB); + fJB(SM_DATA_D(JB), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(xB), + N_VGetArrayPointer(dx), N_VGetArrayPointer(dxB), w.data(), dwdx.data()); +} + +void Model_DAE::fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, SUNMatrix JB) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(JB); + fJSparseB(static_cast(SM_CONTENT_S(JB)), t, + N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), cj, N_VGetArrayPointer(xB), + N_VGetArrayPointer(dx), N_VGetArrayPointer(dxB), w.data(), + dwdx.data()); +} + +void Model_DAE::fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector vB, N_Vector JvB, realtype cj) { + N_VConst(0.0, JvB); + fJSparseB(t, cj, x, dx, xB, dxB, J.get()); + J.multiply(JvB, vB); +} + +void Model_DAE::fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector xBdot) { + N_VConst(0.0, xBdot); + fJSparseB(t, 1.0, x, dx, xB, dxB, J.get()); + fM(t, x); + J.multiply(xBdot, xB); +} + +void Model_DAE::fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector /*dxB*/, N_Vector qBdot) { + N_VConst(0.0, qBdot); + fdxdotdp(t, x, dx); + for (int ip = 0; ip < nplist(); ip++) { + for (int ix = 0; ix < nxtrue_solver; ix++) + NV_Ith_S(qBdot, ip * nJ) -= NV_Ith_S(xB, ix) * dxdotdp.at(ix, ip); + // second order part + for (int iJ = 1; iJ < nJ; iJ++) for (int ix = 0; ix < nxtrue_solver; ix++) - NV_Ith_S(qBdot, ip * nJ) -= - NV_Ith_S(xB, ix) * dxdotdp.at(ix, ip); - // second order part - for (int iJ = 1; iJ < nJ; iJ++) - for (int ix = 0; ix < nxtrue_solver; ix++) - NV_Ith_S(qBdot, ip * nJ + iJ) -= - NV_Ith_S(xB, ix) * - dxdotdp.at(ix + iJ * nxtrue_solver, ip) + - NV_Ith_S(xB, ix + iJ * nxtrue_solver) * - dxdotdp.at(ix, ip); - } + NV_Ith_S(qBdot, ip * nJ + iJ) -= + NV_Ith_S(xB, ix) * dxdotdp.at(ix + iJ * nxtrue_solver, ip) + + NV_Ith_S(xB, ix + iJ * nxtrue_solver) * dxdotdp.at(ix, ip); } +} - void Model_DAE::fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, - AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) { - fsxdot(t,x->getNVector(),dx->getNVector(), ip, - sx->getNVector(),sdx->getNVector(), - sxdot->getNVector()); - } +void Model_DAE::fsxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, const int ip, const AmiVector &sx, + const AmiVector &sdx, AmiVector &sxdot) { + fsxdot(t, x.getNVector(), dx.getNVector(), ip, sx.getNVector(), + sdx.getNVector(), sxdot.getNVector()); +} - /** Right hand side of differential equation for state sensitivities sx - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param ip parameter index - * @param sx Vector with the state sensitivities - * @param sdx Vector with the derivative state sensitivities - * @param sxdot Vector with the sensitivity right hand side - */ - void Model_DAE::fsxdot(realtype t, N_Vector x, N_Vector dx, int ip, - N_Vector sx, N_Vector sdx, N_Vector sxdot) { - if(ip == 0) { - // we only need to call this for the first parameter index will be - // the same for all remaining - fM(t, x); - fdxdotdp(t, x, dx); - fJSparse(t, 0.0, x, dx, J.get()); - } - N_VScale(1.0, dxdotdp.getNVector(ip), sxdot); - J.multiply(sxdot, sx); - N_VScale(-1.0, sdx, sdx); - M.multiply(sxdot, sdx); - N_VScale(-1.0, sdx, sdx); +void Model_DAE::fsxdot(realtype t, N_Vector x, N_Vector dx, int ip, N_Vector sx, + N_Vector sdx, N_Vector sxdot) { + if (ip == 0) { + // we only need to call this for the first parameter index will be + // the same for all remaining + fM(t, x); + fdxdotdp(t, x, dx); + fJSparse(t, 0.0, x, dx, J.get()); } + N_VScale(1.0, dxdotdp.getNVector(ip), sxdot); + J.multiply(sxdot, sx); + N_VScale(-1.0, sdx, sdx); + M.multiply(sxdot, sdx); + N_VScale(-1.0, sdx, sdx); } + +} // namespace amici diff --git a/src/model_ode.cpp b/src/model_ode.cpp index 9f9f93c72e..4284c801d3 100644 --- a/src/model_ode.cpp +++ b/src/model_ode.cpp @@ -1,314 +1,350 @@ -#include "amici/solver_cvodes.h" #include "amici/model_ode.h" +#include "amici/solver_cvodes.h" namespace amici { - void Model_ODE::fJ(realtype t, realtype /*cj*/, AmiVector *x, AmiVector * /*dx*/, - AmiVector *xdot, SUNMatrix J) { - fJ(t, x->getNVector(), xdot->getNVector(), J); +void Model_ODE::fJ(const realtype t, const realtype /*cj*/, const AmiVector &x, + const AmiVector & /*dx*/, const AmiVector &xdot, + SUNMatrix J) { + fJ(t, x.getNVector(), xdot.getNVector(), J); +} - } +void Model_ODE::fJ(realtype t, N_Vector x, N_Vector /*xdot*/, SUNMatrix J) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(J); + fJ(SM_DATA_D(J), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data(), dwdx.data()); +} - /** implementation of fJ at the N_Vector level, this function provides an - *interface to the model specific routines for the solver implementation - *aswell as the AmiVector level implementation - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - * @param J Matrix to which the Jacobian will be written - **/ - void Model_ODE::fJ(realtype t, N_Vector x, N_Vector /*xdot*/, SUNMatrix J) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(J); - fJ(SM_DATA_D(J), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), h.data(), - w.data(), dwdx.data()); - } +void Model_ODE::fJSparse(const realtype t, const realtype /*cj*/, + const AmiVector &x, const AmiVector & /*dx*/, + const AmiVector & /*xdot*/, SUNMatrix J) { + fJSparse(t, x.getNVector(), J); +} - void Model_ODE::fJSparse(realtype t, realtype /*cj*/, AmiVector *x, - AmiVector * /*dx*/, AmiVector * /*xdot*/, SUNMatrix J) { - fJSparse(t, x->getNVector(), J); +void Model_ODE::fJSparse(realtype t, N_Vector x, SUNMatrix J) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(J); + if (wasPythonGenerated()) { + fJSparse(SM_DATA_S(J), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data(), dwdx.data()); + fJSparse_colptrs(SM_INDEXPTRS_S(J)); + fJSparse_rowvals(SM_INDEXVALS_S(J)); + } else { + fJSparse(static_cast(SM_CONTENT_S(J)), t, + N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data(), dwdx.data()); } +} + +void Model_ODE::fJv(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/, const AmiVector & /*xdot*/, + const AmiVector &v, AmiVector &Jv, const realtype /*cj*/) { + fJv(v.getNVector(), Jv.getNVector(), t, x.getNVector()); +} + +void Model_ODE::fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x) { + N_VConst(0.0, Jv); + fJSparse(t, x, J.get()); + J.multiply(Jv, v); +} + +void Model_ODE::froot(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/, gsl::span root) { + froot(t, x.getNVector(), root); +} + +void Model_ODE::froot(realtype t, N_Vector x, gsl::span root) { + auto x_pos = computeX_pos(x); + std::fill(root.begin(), root.end(), 0.0); + froot(root.data(), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data()); +} + +void Model_ODE::fxdot(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/, AmiVector &xdot) { + fxdot(t, x.getNVector(), xdot.getNVector()); +} + +void Model_ODE::fxdot(realtype t, N_Vector x, N_Vector xdot) { + auto x_pos = computeX_pos(x); + fw(t, N_VGetArrayPointer(x_pos)); + N_VConst(0.0, xdot); + fxdot(N_VGetArrayPointer(xdot), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data()); +} + +void Model_ODE::fJDiag(const realtype t, AmiVector &JDiag, + const realtype /*cj*/, const AmiVector &x, + const AmiVector & /*dx*/) { + fJDiag(t, JDiag.getNVector(), x.getNVector()); + if (checkFinite(JDiag.getVector(), "Jacobian") != AMICI_SUCCESS) + throw AmiException("Evaluation of fJDiag failed!"); +} + +void Model_ODE::fdxdotdw(const realtype t, const N_Vector x) { + dxdotdw.reset(); + auto x_pos = computeX_pos(x); + fdxdotdw(dxdotdw.data(), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data()); + fdxdotdw_colptrs(dxdotdw.indexptrs()); + fdxdotdw_rowvals(dxdotdw.indexvals()); +} - /** implementation of fJSparse at the N_Vector level, this function provides - * an interface to the model specific routines for the solver implementation - * aswell as the AmiVector level implementation - * @param t timepoint - * @param x Vector with the states - * @param J Matrix to which the Jacobian will be written - */ - void Model_ODE::fJSparse(realtype t, N_Vector x, SUNMatrix J) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(J); - if (wasPythonGenerated()) { - fJSparse(SM_DATA_S(J), t, N_VGetArrayPointer(x_pos), +void Model_ODE::fdxdotdp(const realtype t, const N_Vector x) { + auto x_pos = computeX_pos(x); + fdwdp(t, N_VGetArrayPointer(x)); + if (wasPythonGenerated()) { + // python generated + fdxdotdw(t, x); + for (int ip = 0; ip < nplist(); ip++) { + N_VConst(0.0, dxdotdp.getNVector(ip)); + fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), + h.data(), plist_[ip], w.data()); + if (nw > 0) + dxdotdw.multiply( + gsl::span(dxdotdp.data(ip), nx_solver), + gsl::span(&dwdp.at(nw * ip), nw)); + } + } else { + // matlab generated + for (int ip = 0; ip < nplist(); ip++) { + N_VConst(0.0, dxdotdp.getNVector(ip)); + fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), fixedParameters.data(), - h.data(), w.data(), dwdx.data()); - fJSparse_colptrs(SM_INDEXPTRS_S(J)); - fJSparse_rowvals(SM_INDEXVALS_S(J)); - } else { - fJSparse(static_cast(SM_CONTENT_S(J)), t, - N_VGetArrayPointer(x_pos), unscaledParameters.data(), - fixedParameters.data(), h.data(), w.data(), dwdx.data()); + h.data(), plist_[ip], w.data(), dwdp.data()); } } +} - void Model_ODE::fJv(realtype t, AmiVector *x, AmiVector * /*dx*/, - AmiVector * /*xdot*/, AmiVector *v, AmiVector *Jv, - realtype /*cj*/) { - fJv(v->getNVector(), Jv->getNVector(), t, x->getNVector()); - } +void Model_ODE::fdxdotdp(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/) { + fdxdotdp(t, x.getNVector()); +} - /** implementation of fJv at the N_Vector level. - * @param t timepoint - * @param x Vector with the states - * @param v Vector with which the Jacobian is multiplied - * @param Jv Vector to which the Jacobian vector product will be - * written - **/ - void Model_ODE::fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x) { - N_VConst(0.0, Jv); - fJSparse(t, x, J.get()); - J.multiply(Jv, v); - } +std::unique_ptr Model_ODE::getSolver() { + return std::unique_ptr(new amici::CVodeSolver()); +} - void Model_ODE::froot(realtype t, AmiVector *x, AmiVector * /*dx*/, realtype *root){ - froot(t,x->getNVector(),root); - } +void Model_ODE::fJB(realtype * /*JB*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*xB*/, const realtype * /*w*/, + const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** implementation of froot at the N_Vector level, this function provides an interface - * to the model specific routines for the solver implementation aswell as the AmiVector - * level implementation - * @param t timepoint - * @param x Vector with the states - * @param root array with root function values - */ - void Model_ODE::froot(realtype t, N_Vector x, realtype *root) { - auto x_pos = computeX_pos(x); - memset(root,0,sizeof(realtype)*ne); - froot(root,t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data()); - } +void Model_ODE::fJSparse(SUNMatrixContent_Sparse /*JSparse*/, + const realtype /*t*/, const realtype * /*x*/, + const realtype * /*p*/, const realtype * /*k*/, + const realtype * /*h*/, const realtype * /*w*/, + const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - void Model_ODE::fxdot(realtype t, AmiVector *x, AmiVector * /*dx*/, AmiVector *xdot) { - fxdot(t,x->getNVector(),xdot->getNVector()); - } +void Model_ODE::fJSparse(realtype * /*JSparse*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** implementation of fxdot at the N_Vector level, this function provides an interface - * to the model specific routines for the solver implementation aswell as the AmiVector - * level implementation - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - */ - void Model_ODE::fxdot(realtype t, N_Vector x, N_Vector xdot) { - auto x_pos = computeX_pos(x); - fw(t,N_VGetArrayPointer(x_pos)); - N_VConst(0.0,xdot); - fxdot(N_VGetArrayPointer(xdot),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - w.data()); - } +void Model_ODE::fJSparse_colptrs(sunindextype * /*indexptrs*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** diagonalized Jacobian (for preconditioning) - * @param t timepoint - * @param JDiag Vector to which the Jacobian diagonal will be written - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @return status flag indicating successful execution - **/ - void Model_ODE::fJDiag(realtype t, AmiVector *JDiag, realtype /*cj*/, AmiVector *x, - AmiVector * /*dx*/) { - fJDiag(t, JDiag->getNVector(), x->getNVector()); - if(checkFinite(nx_solver,JDiag->data(),"Jacobian") != AMICI_SUCCESS) - throw AmiException("Evaluation of fJDiag failed!"); - } +void Model_ODE::fJSparse_rowvals(sunindextype * /*indexvals*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** Sensitivity of dx/dt wrt model parameters w - * @param t timepoint - * @param x Vector with the states - * @return status flag indicating successful execution - */ - void Model_ODE::fdxdotdw(const realtype t, const N_Vector x) { - dxdotdw.reset(); - auto x_pos = computeX_pos(x); - fdxdotdw(dxdotdw.data(), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), - h.data(), w.data()); - fdxdotdw_colptrs(dxdotdw.indexptrs()); - fdxdotdw_rowvals(dxdotdw.indexvals()); - } +void Model_ODE::fJSparseB(SUNMatrixContent_Sparse /*JSparseB*/, + const realtype /*t*/, const realtype * /*x*/, + const realtype * /*p*/, const realtype * /*k*/, + const realtype * /*h*/, const realtype * /*xB*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** Sensitivity of dx/dt wrt model parameters p - * @param t timepoint - * @param x Vector with the states - * @return status flag indicating successful execution - */ - void Model_ODE::fdxdotdp(const realtype t, const N_Vector x) { - auto x_pos = computeX_pos(x); - fdwdp(t, N_VGetArrayPointer(x)); - if (wasPythonGenerated()) { - // python generated - fdxdotdw(t, x); - for (int ip = 0; ip < nplist(); ip++) { - N_VConst(0.0, dxdotdp.getNVector(ip)); - fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), - h.data(), plist_[ip], w.data()); - if (nw > 0) - dxdotdw.multiply(gsl::span(dxdotdp.data(ip), nx_solver), - gsl::span(&dwdp.at(nw * ip), nw)); - } - } else { - // matlab generated - for (int ip = 0; ip < nplist(); ip++) { - N_VConst(0.0, dxdotdp.getNVector(ip)); - fdxdotdp(dxdotdp.data(ip), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), - h.data(), plist_[ip], w.data(), dwdp.data()); - } - } - } +void Model_ODE::fJSparseB(realtype * /*JSparseB*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*xB*/, const realtype * /*w*/, + const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - std::unique_ptr Model_ODE::getSolver() { - return std::unique_ptr(new amici::CVodeSolver()); - } +void Model_ODE::fJSparseB_colptrs(sunindextype * /*indexptrs*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** implementation of fJB at the N_Vector level, this function provides an - *interface to the model specific routines for the solver implementation - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - * @param JB Matrix to which the Jacobian will be written - **/ - void Model_ODE::fJB(realtype t, N_Vector x, N_Vector xB, N_Vector /*xBdot*/, - SUNMatrix JB) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(JB); - fJB(SM_DATA_D(JB), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), h.data(), - N_VGetArrayPointer(xB), w.data(), dwdx.data()); - } +void Model_ODE::fJSparseB_rowvals(sunindextype * /*indexvals*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** implementation of fJSparseB at the N_Vector level, this function - * provides an interface to the model specific routines for the solver - * implementation - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - * @param JB Matrix to which the Jacobian will be written - */ - void Model_ODE::fJSparseB(realtype t, N_Vector x, N_Vector xB, - N_Vector /*xBdot*/, SUNMatrix JB) { - auto x_pos = computeX_pos(x); - fdwdx(t, N_VGetArrayPointer(x_pos)); - SUNMatZero(JB); - if (wasPythonGenerated()) { - fJSparseB(SM_DATA_S(JB), t, N_VGetArrayPointer(x_pos), - unscaledParameters.data(), fixedParameters.data(), - h.data(), N_VGetArrayPointer(xB), w.data(), dwdx.data()); - fJSparseB_colptrs(SM_INDEXPTRS_S(JB)); - fJSparseB_rowvals(SM_INDEXVALS_S(JB)); - } else { - fJSparseB(static_cast(SM_CONTENT_S(JB)), t, - N_VGetArrayPointer(x_pos), unscaledParameters.data(), - fixedParameters.data(), h.data(), N_VGetArrayPointer(xB), - w.data(), dwdx.data()); - } - } +void Model_ODE::fJDiag(realtype * /*JDiag*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*w*/, const realtype * /*dwdx*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); +} - /** implementation of fJDiag at the N_Vector level, this function provides an interface - * to the model specific routines for the solver implementation - * @param t timepoint - * @param JDiag Vector to which the Jacobian diagonal will be written - * @param x Vector with the states - **/ - void Model_ODE::fJDiag(realtype t, N_Vector JDiag, N_Vector x) { - auto x_pos = computeX_pos(x); - fdwdx(t,N_VGetArrayPointer(x_pos)); - N_VConst(0.0,JDiag); - fJDiag(N_VGetArrayPointer(JDiag),t,N_VGetArrayPointer(x_pos),unscaledParameters.data(),fixedParameters.data(),h.data(), - w.data(),dwdx.data()); - } +void Model_ODE::froot(realtype * /*root*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/) { + throw AmiException("Requested functionality is not supported as %s is not " + "implemented for this model!", + __func__); // not implemented +} - /** implementation of fJvB at the N_Vector level - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param vB Vector with which the Jacobian is multiplied - * @param JvB Vector to which the Jacobian vector product will be - *written - **/ - void Model_ODE::fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, - N_Vector xB) { - N_VConst(0.0, JvB); - fJSparseB(t, x, xB, nullptr, J.get()); - J.multiply(JvB, vB); - } +void Model_ODE::fdxdotdp(realtype * /*dxdotdp*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const int /*ip*/, const realtype * /*w*/, + const realtype * /*dwdp*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model_ODE::fdxdotdp(realtype * /*dxdotdp*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const int /*ip*/, const realtype * /*w*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} - /** implementation of fxBdot at the N_Vector level - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - */ - void Model_ODE::fxBdot(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot) { - N_VConst(0.0, xBdot); - fJSparseB(t, x, xB, nullptr, J.get()); - J.multiply(xBdot, xB); +void Model_ODE::fdxdotdw(realtype * /*dxdotdw*/, const realtype /*t*/, + const realtype * /*x*/, const realtype * /*p*/, + const realtype * /*k*/, const realtype * /*h*/, + const realtype * /*w*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model_ODE::fdxdotdw_colptrs(sunindextype * /*indexptrs*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model_ODE::fdxdotdw_rowvals(sunindextype * /*indexvals*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); // not implemented +} + +void Model_ODE::fJB(realtype t, N_Vector x, N_Vector xB, N_Vector /*xBdot*/, + SUNMatrix JB) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(JB); + fJB(SM_DATA_D(JB), t, N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), N_VGetArrayPointer(xB), w.data(), + dwdx.data()); +} + +void Model_ODE::fJSparseB(realtype t, N_Vector x, N_Vector xB, + N_Vector /*xBdot*/, SUNMatrix JB) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + SUNMatZero(JB); + if (wasPythonGenerated()) { + fJSparseB(SM_DATA_S(JB), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + N_VGetArrayPointer(xB), w.data(), dwdx.data()); + fJSparseB_colptrs(SM_INDEXPTRS_S(JB)); + fJSparseB_rowvals(SM_INDEXVALS_S(JB)); + } else { + fJSparseB(static_cast(SM_CONTENT_S(JB)), t, + N_VGetArrayPointer(x_pos), unscaledParameters.data(), + fixedParameters.data(), h.data(), N_VGetArrayPointer(xB), + w.data(), dwdx.data()); } +} - /** implementation of fqBdot at the N_Vector level - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param qBdot Vector with the adjoint quadrature right hand side - */ - void Model_ODE::fqBdot(realtype t, N_Vector x, N_Vector xB, - N_Vector qBdot) { - N_VConst(0.0, qBdot); - fdxdotdp(t, x); - for (int ip = 0; ip < nplist(); ip++) { +void Model_ODE::fJDiag(realtype t, N_Vector JDiag, N_Vector x) { + auto x_pos = computeX_pos(x); + fdwdx(t, N_VGetArrayPointer(x_pos)); + N_VConst(0.0, JDiag); + fJDiag(N_VGetArrayPointer(JDiag), t, N_VGetArrayPointer(x_pos), + unscaledParameters.data(), fixedParameters.data(), h.data(), + w.data(), dwdx.data()); +} + +void Model_ODE::fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, + N_Vector xB) { + N_VConst(0.0, JvB); + fJSparseB(t, x, xB, nullptr, J.get()); + J.multiply(JvB, vB); +} + +void Model_ODE::fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot) { + N_VConst(0.0, xBdot); + fJSparseB(t, x, xB, nullptr, J.get()); + J.multiply(xBdot, xB); +} + +void Model_ODE::fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot) { + N_VConst(0.0, qBdot); + fdxdotdp(t, x); + for (int ip = 0; ip < nplist(); ip++) { + for (int ix = 0; ix < nxtrue_solver; ix++) + NV_Ith_S(qBdot, ip * nJ) -= NV_Ith_S(xB, ix) * dxdotdp.at(ix, ip); + // second order part + for (int iJ = 1; iJ < nJ; iJ++) for (int ix = 0; ix < nxtrue_solver; ix++) - NV_Ith_S(qBdot, ip * nJ) -= - NV_Ith_S(xB, ix) * dxdotdp.at(ix, ip); - // second order part - for (int iJ = 1; iJ < nJ; iJ++) - for (int ix = 0; ix < nxtrue_solver; ix++) - NV_Ith_S(qBdot, ip * nJ + iJ) -= - NV_Ith_S(xB, ix) * - dxdotdp.at(ix + iJ * nxtrue_solver, ip) + - NV_Ith_S(xB, ix + iJ * nxtrue_solver) * - dxdotdp.at(ix, ip); - } + NV_Ith_S(qBdot, ip * nJ + iJ) -= + NV_Ith_S(xB, ix) * dxdotdp.at(ix + iJ * nxtrue_solver, ip) + + NV_Ith_S(xB, ix + iJ * nxtrue_solver) * dxdotdp.at(ix, ip); } +} - void Model_ODE::fsxdot(realtype t, AmiVector *x, AmiVector * /*dx*/, int ip, - AmiVector *sx, AmiVector * /*sdx*/, AmiVector *sxdot) { - fsxdot(t,x->getNVector(), ip, sx->getNVector(), sxdot->getNVector()); - } +void Model_ODE::fsxdot(const realtype t, const AmiVector &x, + const AmiVector & /*dx*/, const int ip, + const AmiVector &sx, const AmiVector & /*sdx*/, + AmiVector &sxdot) { + fsxdot(t, x.getNVector(), ip, sx.getNVector(), sxdot.getNVector()); +} - /** implementation of fsxdot at the N_Vector level - * @param t timepoint - * @param x Vector with the states - * @param ip parameter index - * @param sx Vector with the state sensitivities - * @param sxdot Vector with the sensitivity right hand side - */ - void Model_ODE::fsxdot(realtype t, N_Vector x, int ip, N_Vector sx, - N_Vector sxdot) { - if (ip == 0) { - // we only need to call this for the first parameter index will be - // the same for all remaining - fdxdotdp(t, x); - fJSparse(t, x, J.get()); - } - N_VScale(1.0, dxdotdp.getNVector(ip), sxdot); - J.multiply(sxdot, sx); +void Model_ODE::fsxdot(realtype t, N_Vector x, int ip, N_Vector sx, + N_Vector sxdot) { + if (ip == 0) { + // we only need to call this for the first parameter index will be + // the same for all remaining + fdxdotdp(t, x); + fJSparse(t, x, J.get()); } + N_VScale(1.0, dxdotdp.getNVector(ip), sxdot); + J.multiply(sxdot, sx); } + +} // namespace amici diff --git a/src/newton_solver.cpp b/src/newton_solver.cpp index fa62a0252f..c651ea0094 100644 --- a/src/newton_solver.cpp +++ b/src/newton_solver.cpp @@ -16,8 +16,9 @@ namespace amici { -NewtonSolver::NewtonSolver(realtype *t, AmiVector *x, Model *model, ReturnData *rdata) - : model(model), rdata(rdata), xdot(x->getLength()), dx(x->getLength()) +NewtonSolver::NewtonSolver(realtype *t, AmiVector *x, Model *model, + ReturnData *rdata) + : model(model), rdata(rdata), xdot(model->nx_solver), dx(model->nx_solver) { this->t = t; this->x = x; @@ -81,33 +82,34 @@ std::unique_ptr NewtonSolver::getSolver( /* ------------------------------------------------------------------------- */ -void NewtonSolver::getStep(int ntry, int nnewt, AmiVector *delta) { - +void NewtonSolver::getStep(int ntry, int nnewt, AmiVector &delta) { this->prepareLinearSystem(ntry, nnewt); - delta->minus(); + delta.minus(); this->solveLinearSystem(delta); } /* ------------------------------------------------------------------------- */ -void NewtonSolver::computeNewtonSensis(AmiVectorArray *sx) { +void NewtonSolver::computeNewtonSensis(AmiVectorArray &sx) { prepareLinearSystem(0, -1); - model->fdxdotdp(*t, x, &dx); + model->fdxdotdp(*t, *x, dx); for (int ip = 0; ip < model->nplist(); ip++) { for (int ix = 0; ix < model->nx_solver; ix++) { - sx->at(ix,ip) = -model->dxdotdp.at(ix, ip); + sx.at(ix,ip) = -model->dxdotdp.at(ix, ip); } - solveLinearSystem(&((*sx)[ip])); + solveLinearSystem(sx[ip]); } } /* ------------------------------------------------------------------------- */ /* - Dense linear solver --------------------------------------------------- */ /* ------------------------------------------------------------------------- */ -NewtonSolverDense::NewtonSolverDense(realtype *t, AmiVector *x, Model *model, ReturnData *rdata) +/* Derived class for dense linear solver */ +NewtonSolverDense::NewtonSolverDense(realtype *t, AmiVector *x, Model *model, + ReturnData *rdata) : NewtonSolver(t, x, model, rdata), Jtmp(model->nx_solver, model->nx_solver), linsol(SUNLinSol_Dense(x->getNVector(), Jtmp.get())) @@ -120,7 +122,7 @@ NewtonSolverDense::NewtonSolverDense(realtype *t, AmiVector *x, Model *model, Re /* ------------------------------------------------------------------------- */ void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { - model->fJ(*t, 0.0, x, &dx, &xdot, Jtmp.get()); + model->fJ(*t, 0.0, *x, dx, xdot, Jtmp.get()); int status = SUNLinSolSetup_Dense(linsol, Jtmp.get()); if(status != AMICI_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_Dense"); @@ -128,9 +130,9 @@ void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { /* ------------------------------------------------------------------------- */ -void NewtonSolverDense::solveLinearSystem(AmiVector *rhs) { +void NewtonSolverDense::solveLinearSystem(AmiVector &rhs) { int status = SUNLinSolSolve_Dense(linsol, Jtmp.get(), - rhs->getNVector(), rhs->getNVector(), + rhs.getNVector(), rhs.getNVector(), 0.0); // last argument is tolerance and does not have any influence on result @@ -150,7 +152,8 @@ NewtonSolverDense::~NewtonSolverDense() { /* ------------------------------------------------------------------------- */ /* Derived class for sparse linear solver */ -NewtonSolverSparse::NewtonSolverSparse(realtype *t, AmiVector *x, Model *model, ReturnData *rdata) +NewtonSolverSparse::NewtonSolverSparse(realtype *t, AmiVector *x, Model *model, + ReturnData *rdata) : NewtonSolver(t, x, model, rdata), Jtmp(model->nx_solver, model->nx_solver, model->nnz, CSC_MAT), linsol(SUNKLU(x->getNVector(), Jtmp.get())) @@ -164,7 +167,7 @@ NewtonSolverSparse::NewtonSolverSparse(realtype *t, AmiVector *x, Model *model, void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { /* Get sparse Jacobian */ - model->fJSparse(*t, 0.0, x, &dx, &xdot, Jtmp.get()); + model->fJSparse(*t, 0.0, *x, dx, xdot, Jtmp.get()); int status = SUNLinSolSetup_KLU(linsol, Jtmp.get()); if(status != AMICI_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_KLU"); @@ -172,11 +175,10 @@ void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { /* ------------------------------------------------------------------------- */ -void NewtonSolverSparse::solveLinearSystem(AmiVector *rhs) { +void NewtonSolverSparse::solveLinearSystem(AmiVector &rhs) { /* Pass pointer to the linear solver */ int status = SUNLinSolSolve_KLU(linsol, Jtmp.get(), - rhs->getNVector(), rhs->getNVector(), - 0.0); + rhs.getNVector(), rhs.getNVector(), 0.0); // last argument is tolerance and does not have any influence on result if(status != AMICI_SUCCESS) @@ -194,10 +196,13 @@ NewtonSolverSparse::~NewtonSolverSparse() { /* - Iterative linear solver------------------------------------------------ */ /* ------------------------------------------------------------------------- */ -NewtonSolverIterative::NewtonSolverIterative(realtype *t, AmiVector *x, Model *model, ReturnData *rdata) - : NewtonSolver(t, x, model, rdata), ns_p(model->nx_solver), ns_h(model->nx_solver), - ns_t(model->nx_solver), ns_s(model->nx_solver), ns_r(model->nx_solver), ns_rt(model->nx_solver), ns_v(model->nx_solver), - ns_Jv(model->nx_solver), ns_tmp(model->nx_solver), ns_Jdiag(model->nx_solver) +NewtonSolverIterative::NewtonSolverIterative(realtype *t, AmiVector *x, + Model *model, ReturnData *rdata) + : NewtonSolver(t, x, model, rdata), ns_p(model->nx_solver), + ns_h(model->nx_solver), ns_t(model->nx_solver), ns_s(model->nx_solver), + ns_r(model->nx_solver), ns_rt(model->nx_solver), ns_v(model->nx_solver), + ns_Jv(model->nx_solver), ns_tmp(model->nx_solver), + ns_Jdiag(model->nx_solver) { } @@ -213,18 +218,19 @@ void NewtonSolverIterative::prepareLinearSystem(int ntry, int nnewt) { /* ------------------------------------------------------------------------- */ -void NewtonSolverIterative::solveLinearSystem(AmiVector *rhs) { +void NewtonSolverIterative::solveLinearSystem(AmiVector &rhs) { linsolveSPBCG(newton_try, i_newton, rhs); - rhs->minus(); + rhs.minus(); } -void NewtonSolverIterative::linsolveSPBCG(int ntry, int nnewt, AmiVector *ns_delta) { - xdot = *ns_delta; +void NewtonSolverIterative::linsolveSPBCG(int ntry, int nnewt, + AmiVector &ns_delta) { + xdot = ns_delta; xdot.minus(); // Get the diagonal of the Jacobian for preconditioning - model->fJDiag(*t, &ns_Jdiag, 0.0, x, &dx); + model->fJDiag(*t, ns_Jdiag, 0.0, *x, dx); // Ensure positivity of entries in ns_Jdiag ns_p.set(1.0); @@ -236,14 +242,14 @@ void NewtonSolverIterative::linsolveSPBCG(int ntry, int nnewt, AmiVector *ns_del // Initialize for linear solve ns_p.reset(); ns_v.reset(); - ns_delta->reset(); + ns_delta.reset(); ns_tmp.reset(); double rho = 1.0; double omega = 1.0; double alpha = 1.0; // can be set to 0 at the moment - model->fJv(*t, x, &dx, &xdot, ns_delta, &ns_Jv, 0.0); + model->fJv(*t, *x, dx, xdot, ns_delta, ns_Jv, 0.0); // ns_r = xdot - ns_Jv; N_VLinearSum(-1.0, ns_Jv.getNVector(), 1.0, xdot.getNVector(), ns_r.getNVector()); @@ -263,26 +269,28 @@ void NewtonSolverIterative::linsolveSPBCG(int ntry, int nnewt, AmiVector *ns_del N_VLinearSum(1.0, ns_r.getNVector(), beta, ns_p.getNVector(), ns_p.getNVector()); // ns_v = J * ns_p - model->fJv(*t, x, &dx, &xdot, &ns_p, &ns_v, 0.0); + model->fJv(*t, *x, dx, xdot, ns_p, ns_v, 0.0); N_VDiv(ns_v.getNVector(), ns_Jdiag.getNVector(), ns_v.getNVector()); // Compute factor alpha = rho / N_VDotProd(ns_rt.getNVector(), ns_v.getNVector()); // ns_h = ns_delta + alpha * ns_p; - N_VLinearSum(1.0, ns_delta->getNVector(), alpha, ns_p.getNVector(), ns_h.getNVector()); + N_VLinearSum(1.0, ns_delta.getNVector(), alpha, ns_p.getNVector(), + ns_h.getNVector()); // ns_s = ns_r - alpha * ns_v; N_VLinearSum(1.0, ns_r.getNVector(), -alpha, ns_v.getNVector(), ns_s.getNVector()); // ns_t = J * ns_s - model->fJv(*t, x, &dx, &xdot, &ns_s, &ns_t, 0.0); + model->fJv(*t, *x, dx, xdot, ns_s, ns_t, 0.0); N_VDiv(ns_t.getNVector(), ns_Jdiag.getNVector(), ns_t.getNVector()); // Compute factor omega = N_VDotProd(ns_t.getNVector(), ns_s.getNVector()) / N_VDotProd(ns_t.getNVector(), ns_t.getNVector()); // ns_delta = ns_h + omega * ns_s; - N_VLinearSum(1.0, ns_h.getNVector(), omega, ns_s.getNVector(), ns_delta->getNVector()); + N_VLinearSum(1.0, ns_h.getNVector(), omega, ns_s.getNVector(), + ns_delta.getNVector()); // ns_r = ns_s - omega * ns_t; N_VLinearSum(1.0, ns_s.getNVector(), -omega, ns_t.getNVector(), ns_r.getNVector()); diff --git a/src/rdata.cpp b/src/rdata.cpp index 7352444096..dc3992984a 100644 --- a/src/rdata.cpp +++ b/src/rdata.cpp @@ -10,19 +10,13 @@ namespace amici { -ReturnData::ReturnData() - : np(0), nk(0), nx(0), nx_solver(0), nxtrue(0), ny(0), nytrue(0), nz(0), nztrue(0), ne(0), - nJ(0), nplist(0), nmaxevent(0), nt(0), newton_maxsteps(0), - pscale(std::vector(0, ParameterScaling::none)), o2mode(SecondOrderMode::none), - sensi(SensitivityOrder::none), sensi_meth(SensitivityMethod::none) {} - -ReturnData::ReturnData(Solver const& solver, const Model *model) - : ReturnData(model->getTimepoints(), model->np(), model->nk(), - model->nx_rdata, model->nx_solver, model->nxtrue_rdata, - model->ny, model->nytrue, model->nz, model->nztrue, model->ne, model->nJ, - model->nplist(), model->nMaxEvent(), model->nt(), - solver.getNewtonMaxSteps(), model->getParameterScale(), - model->o2mode, solver.getSensitivityOrder(), +ReturnData::ReturnData(Solver const& solver, const Model &model) + : ReturnData(model.getTimepoints(), model.np(), model.nk(), + model.nx_rdata, model.nx_solver, model.nxtrue_rdata, + model.ny, model.nytrue, model.nz, model.nztrue, model.ne, model.nJ, + model.nplist(), model.nMaxEvent(), model.nt(), + solver.getNewtonMaxSteps(), model.getParameterScale(), + model.o2mode, solver.getSensitivityOrder(), static_cast(solver.getSensitivityMethod())) { } diff --git a/src/solver.cpp b/src/solver.cpp index 72c927c95a..59f073e4cf 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -1,10 +1,9 @@ #include "amici/solver.h" + #include "amici/exception.h" -#include "amici/forwardproblem.h" -#include "amici/backwardproblem.h" +#include "amici/misc.h" #include "amici/model.h" #include "amici/rdata.h" -#include "amici/misc.h" #include #include @@ -14,9 +13,9 @@ namespace amici { extern msgIdAndTxtFp warnMsgIdAndTxt; - -Solver::Solver(const Solver &other) : Solver() -{ +Solver::Solver(const Solver &other) : Solver() { + t = nan(""); + ncheckPtr = 0; sensi = other.sensi; atol = other.atol; rtol = other.rtol; @@ -45,18 +44,47 @@ Solver::Solver(const Solver &other) : Solver() ordering = other.ordering; } -void Solver::setup(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, AmiVectorArray *sdx, Model *model) { - bool computeSensitivities = sensi >= SensitivityOrder::first - && model->nx_solver > 0; - model->initialize(x, dx, sx, sdx, computeSensitivities); +int Solver::run(const realtype tout) const { + setStopTime(tout); + int status; + if (getAdjInitDone()) { + status = solveF(tout, AMICI_NORMAL, &ncheckPtr); + } else { + status = solve(tout, AMICI_NORMAL); + } + return status; +} + +int Solver::step(const realtype tout) const { + int status; + if (getAdjInitDone()) { + status = solveF(tout, AMICI_ONE_STEP, &ncheckPtr); + } else { + status = solve(tout, AMICI_ONE_STEP); + } + return status; +} + +void Solver::runB(const realtype tout) const { + solveB(tout, AMICI_NORMAL); + t = tout; +} - /* Create solver memory object */ +void Solver::setup(const realtype t0, Model *model, const AmiVector &x0, + const AmiVector &dx0, const AmiVectorArray &sx0, + const AmiVectorArray &sdx0) const { + if (nx() != model->nx_solver || nplist() != model->nplist() || + nquad() != model->nJ * model->nplist()) { + resetMutableMemory(model->nx_solver, model->nplist(), + model->nJ * model->nplist()); + } + /* Create solver memory object if necessary */ allocateSolver(); if (!solverMemory) throw AmiException("Failed to allocated solver memory!"); /* Initialize CVodes/IDAs solver*/ - init(x, dx, model->t0()); + init(t0, x0, dx0); applyTolerances(); @@ -71,19 +99,18 @@ void Solver::setup(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, AmiVectorArr rootInit(model->ne); - initializeLinearSolver(model, x); - initializeNonLinearSolver(x); + initializeLinearSolver(model); + initializeNonLinearSolver(); - if (computeSensitivities) { + if (sensi >= SensitivityOrder::first && model->nx_solver > 0) { auto plist = model->getParameterList(); - if (sensi_meth == SensitivityMethod::forward && !plist.empty()) { /* Set sensitivity analysis optional inputs */ auto par = model->getUnscaledParameters(); /* Activate sensitivity calculations */ - sensInit1(sx, sdx, plist.size()); - initalizeNonLinearSolverSens(x, model); + sensInit1(sx0, sdx0); + initializeNonLinearSolverSens(model); setSensParams(par.data(), nullptr, plist.data()); applyTolerancesFSA(); @@ -96,88 +123,45 @@ void Solver::setup(AmiVector *x, AmiVector *dx, AmiVectorArray *sx, AmiVectorArr setId(model); setSuppressAlg(true); /* calculate consistent DAE initial conditions (no effect for ODE) */ - if(model->nt()>1) - calcIC(model->t(1), x, dx); + if (model->nt() > 1) + calcIC(model->t(1)); } -void Solver::setupB(BackwardProblem *bwd, Model *model) { +void Solver::setupB(int *which, const realtype tf, Model *model, + const AmiVector &xB0, const AmiVector &dxB0, + const AmiVector &xQB0) const { if (!solverMemory) - throw AmiException("Solver for the forward problem must be setup first"); - - /* write initial conditions */ - std::vector dJydx = bwd->getdJydx(); - AmiVector *xB = bwd->getxBptr(); - xB->reset(); - for (int ix = 0; ix < model->nxtrue_solver; ++ix) - for (int iJ = 0; iJ < model->nJ; ++iJ) - xB->at(ix + iJ * model->nxtrue_solver) += - dJydx.at(iJ + ( ix + (model->nt() - 1) * model->nx_solver ) * model->nJ); - bwd->getdxBptr()->reset(); - bwd->getxQBptr()->reset(); + throw AmiException( + "Solver for the forward problem must be setup first"); /* allocate memory for the backward problem */ - allocateSolverB(bwd->getwhichptr()); + allocateSolverB(which); /* initialise states */ - binit(bwd->getwhich(), bwd->getxBptr(), bwd->getdxBptr(), bwd->gett()); + binit(*which, tf, xB0, dxB0); /* Attach user data */ - setUserDataB(bwd->getwhich(), model); + setUserDataB(*which, model); /* Number of maximal internal steps */ - setMaxNumStepsB(bwd->getwhich(), (maxstepsB == 0) ? maxsteps * 100 : maxstepsB); + setMaxNumStepsB(*which, (maxstepsB == 0) ? maxsteps * 100 : maxstepsB); - initializeLinearSolverB(model, xB, bwd->getwhich()); - initializeNonLinearSolverB(xB, bwd->getwhich()); + initializeLinearSolverB(model, *which); + initializeNonLinearSolverB(*which); /* Initialise quadrature calculation */ - qbinit(bwd->getwhich(), bwd->getxQBptr()); + qbinit(*which, xQB0); - applyTolerancesASA(bwd->getwhich()); - applyQuadTolerancesASA(bwd->getwhich()); + applyTolerancesASA(*which); + applyQuadTolerancesASA(*which); - setStabLimDetB(bwd->getwhich(), stldet); -} - -void Solver::wrapErrHandlerFn(int error_code, const char *module, - const char *function, char *msg, void * /*eh_data*/) { - char buffer[250]; - char buffid[250]; - sprintf(buffer, "AMICI ERROR: in module %s in function %s : %s ", module, - function, msg); - switch (error_code) { - case 99: - sprintf(buffid, "AMICI:mex:%s:%s:WARNING", module, function); - break; - - case -1: - sprintf(buffid, "AMICI:mex:%s:%s:TOO_MUCH_WORK", module, function); - break; - - case -2: - sprintf(buffid, "AMICI:mex:%s:%s:TOO_MUCH_ACC", module, function); - break; - - case -3: - sprintf(buffid, "AMICI:mex:%s:%s:ERR_FAILURE", module, function); - break; - - case -4: - sprintf(buffid, "AMICI:mex:%s:%s:CONV_FAILURE", module, function); - break; - - default: - sprintf(buffid, "AMICI:mex:%s:%s:OTHER", module, function); - break; - } - - warnMsgIdAndTxt(buffid, buffer); + setStabLimDetB(*which, stldet); } void Solver::getDiagnosis(const int it, ReturnData *rdata) const { long int number; - if(solverWasCalled && solverMemory) { + if (solverWasCalledF && solverMemory) { getNumSteps(solverMemory.get(), &number); rdata->numsteps[it] = number; @@ -194,10 +178,11 @@ void Solver::getDiagnosis(const int it, ReturnData *rdata) const { } } -void Solver::getDiagnosisB(const int it, ReturnData *rdata, int which) const { +void Solver::getDiagnosisB(const int it, ReturnData *rdata, + const int which) const { long int number; - if(solverWasCalled && solverMemoryB.at(which)) { + if (solverWasCalledB && solverMemoryB.at(which)) { getNumSteps(solverMemoryB.at(which).get(), &number); rdata->numstepsB[it] = number; @@ -212,19 +197,20 @@ void Solver::getDiagnosisB(const int it, ReturnData *rdata, int which) const { } } -void Solver::initializeLinearSolver(const Model *model, AmiVector *x) { +void Solver::initializeLinearSolver(const Model *model) const { switch (linsol) { - /* DIRECT SOLVERS */ + /* DIRECT SOLVERS */ case LinearSolver::dense: - linearSolver = std::make_unique(*x); + linearSolver = std::make_unique(x); setLinearSolver(); setDenseJacFn(); break; case LinearSolver::band: - linearSolver = std::make_unique(*x, model->ubw, model->lbw); + linearSolver = + std::make_unique(x, model->ubw, model->lbw); setLinearSolver(); setBandJacFn(); break; @@ -243,19 +229,19 @@ void Solver::initializeLinearSolver(const Model *model, AmiVector *x) { /* ITERATIVE SOLVERS */ case LinearSolver::SPGMR: - linearSolver = std::make_unique(*x); + linearSolver = std::make_unique(x); setLinearSolver(); setJacTimesVecFn(); break; case LinearSolver::SPBCG: - linearSolver = std::make_unique(*x); + linearSolver = std::make_unique(x); setLinearSolver(); setJacTimesVecFn(); break; case LinearSolver::SPTFQMR: - linearSolver = std::make_unique(*x); + linearSolver = std::make_unique(x); setLinearSolver(); setJacTimesVecFn(); break; @@ -264,8 +250,8 @@ void Solver::initializeLinearSolver(const Model *model, AmiVector *x) { case LinearSolver::KLU: linearSolver = std::make_unique( - *x, model->nnz, CSC_MAT, - static_cast(getStateOrdering())); + x, model->nnz, CSC_MAT, + static_cast(getStateOrdering())); setLinearSolver(); setSparseJacFn(); break; @@ -274,45 +260,49 @@ void Solver::initializeLinearSolver(const Model *model, AmiVector *x) { case LinearSolver::SuperLUMT: // TODO state ordering linearSolver = std::make_unique( - *x, model->nnz, CSC_MAT, - static_cast(getStateOrdering())); + *x, model->nnz, CSC_MAT, + static_cast(getStateOrdering())); setLinearSolver(); setSparseJacFn(); break; #endif default: - throw AmiException("Invalid choice of solver: %d", static_cast(linsol)); + throw AmiException("Invalid choice of solver: %d", + static_cast(linsol)); } } -void Solver::initializeNonLinearSolver(AmiVector *x) -{ - switch(iter) { +void Solver::initializeNonLinearSolver() const { + switch (iter) { case NonlinearSolverIteration::newton: - nonLinearSolver = std::make_unique(x->getNVector()); + nonLinearSolver = std::make_unique(x.getNVector()); break; case NonlinearSolverIteration::fixedpoint: - nonLinearSolver = std::make_unique(x->getNVector()); + nonLinearSolver = + std::make_unique(x.getNVector()); break; default: - throw AmiException("Invalid non-linear solver specified (%d).", static_cast(iter)); + throw AmiException("Invalid non-linear solver specified (%d).", + static_cast(iter)); } setNonLinearSolver(); } -void Solver::initializeLinearSolverB(const Model *model, AmiVector *xB, const int which) { +void Solver::initializeLinearSolverB(const Model *model, + const int which) const { switch (linsol) { /* DIRECT SOLVERS */ case LinearSolver::dense: - linearSolverB = std::make_unique(*xB); + linearSolverB = std::make_unique(xB); setLinearSolverB(which); setDenseJacFnB(which); break; case LinearSolver::band: - linearSolverB = std::make_unique(*xB, model->ubw, model->lbw); + linearSolverB = + std::make_unique(xB, model->ubw, model->lbw); setLinearSolverB(which); setBandJacFnB(which); break; @@ -331,19 +321,19 @@ void Solver::initializeLinearSolverB(const Model *model, AmiVector *xB, const in /* ITERATIVE SOLVERS */ case LinearSolver::SPGMR: - linearSolverB = std::make_unique(*xB); + linearSolverB = std::make_unique(xB); setLinearSolverB(which); setJacTimesVecFnB(which); break; case LinearSolver::SPBCG: - linearSolverB = std::make_unique(*xB); + linearSolverB = std::make_unique(xB); setLinearSolverB(which); setJacTimesVecFnB(which); break; case LinearSolver::SPTFQMR: - linearSolverB = std::make_unique(*xB); + linearSolverB = std::make_unique(xB); setLinearSolverB(which); setJacTimesVecFnB(which); break; @@ -352,102 +342,101 @@ void Solver::initializeLinearSolverB(const Model *model, AmiVector *xB, const in case LinearSolver::KLU: linearSolverB = std::make_unique( - *xB, model->nnz, CSC_MAT, - static_cast(getStateOrdering())); + xB, model->nnz, CSC_MAT, + static_cast(getStateOrdering())); setLinearSolverB(which); setSparseJacFnB(which); break; #ifdef SUNDIALS_SUPERLUMT case LinearSolver::SuperLUMT: linearSolverB = std::make_unique( - *xB, model->nnz, CSC_MAT, - static_cast(getStateOrdering())); + *xB, model->nnz, CSC_MAT, + static_cast(getStateOrdering())); setLinearSolverB(which); setSparseJacFnB(which); break; #endif default: - throw AmiException("Invalid choice of solver: %d", static_cast(linsol)); + throw AmiException("Invalid choice of solver: %d", + static_cast(linsol)); } } -void Solver::initializeNonLinearSolverB(AmiVector *xB, const int which) -{ - switch(iter) { +void Solver::initializeNonLinearSolverB(const int which) const { + switch (iter) { case NonlinearSolverIteration::newton: - nonLinearSolverB = std::make_unique(xB->getNVector()); + nonLinearSolverB = + std::make_unique(xB.getNVector()); break; case NonlinearSolverIteration::fixedpoint: - nonLinearSolverB = std::make_unique(xB->getNVector()); + nonLinearSolverB = + std::make_unique(xB.getNVector()); break; default: - throw AmiException("Invalid non-linear solver specified (%d).", static_cast(iter)); + throw AmiException("Invalid non-linear solver specified (%d).", + static_cast(iter)); } setNonLinearSolverB(which); } -bool operator ==(const Solver &a, const Solver &b) -{ +bool operator==(const Solver &a, const Solver &b) { if (typeid(a) != typeid(b)) - return false; - - return (a.interpType == b.interpType) - && (a.lmm == b.lmm) - && (a.iter == b.iter) - && (a.stldet == b.stldet) - && (a.ordering == b.ordering) - && (a.newton_maxsteps == b.newton_maxsteps) - && (a.newton_maxlinsteps == b.newton_maxlinsteps) - && (a.newton_preeq == b.newton_preeq) - && (a.ism == b.ism) - && (a.linsol == b.linsol) - && (a.atol == b.atol) - && (a.rtol == b.rtol) - && (a.maxsteps == b.maxsteps) - && (a.maxstepsB == b.maxstepsB) - && (a.quad_atol == b.quad_atol) - && (a.quad_rtol == b.quad_rtol) - && (a.getAbsoluteToleranceSteadyState() == - b.getAbsoluteToleranceSteadyState()) - && (a.getRelativeToleranceSteadyState() == - b.getRelativeToleranceSteadyState()) - && (a.getAbsoluteToleranceSteadyStateSensi() == - b.getAbsoluteToleranceSteadyStateSensi()) - && (a.getRelativeToleranceSteadyStateSensi() == - b.getRelativeToleranceSteadyStateSensi()) - && (a.rtol_fsa == b.rtol_fsa || (isNaN(a.rtol_fsa) && isNaN(b.rtol_fsa))) - && (a.atol_fsa == b.atol_fsa || (isNaN(a.atol_fsa) && isNaN(b.atol_fsa))) - && (a.rtolB == b.rtolB || (isNaN(a.rtolB) && isNaN(b.rtolB))) - && (a.atolB == b.atolB || (isNaN(a.atolB) && isNaN(b.atolB))) - && (a.sensi == b.sensi) - && (a.sensi_meth == b.sensi_meth); -} - -void Solver::applyTolerances() { - if (!getMallocDone()) - throw AmiException(("Solver instance was not yet set up, the tolerances cannot be applied yet!")); + return false; + + return (a.interpType == b.interpType) && (a.lmm == b.lmm) && + (a.iter == b.iter) && (a.stldet == b.stldet) && + (a.ordering == b.ordering) && + (a.newton_maxsteps == b.newton_maxsteps) && + (a.newton_maxlinsteps == b.newton_maxlinsteps) && + (a.newton_preeq == b.newton_preeq) && (a.ism == b.ism) && + (a.linsol == b.linsol) && (a.atol == b.atol) && (a.rtol == b.rtol) && + (a.maxsteps == b.maxsteps) && (a.maxstepsB == b.maxstepsB) && + (a.quad_atol == b.quad_atol) && (a.quad_rtol == b.quad_rtol) && + (a.getAbsoluteToleranceSteadyState() == + b.getAbsoluteToleranceSteadyState()) && + (a.getRelativeToleranceSteadyState() == + b.getRelativeToleranceSteadyState()) && + (a.getAbsoluteToleranceSteadyStateSensi() == + b.getAbsoluteToleranceSteadyStateSensi()) && + (a.getRelativeToleranceSteadyStateSensi() == + b.getRelativeToleranceSteadyStateSensi()) && + (a.rtol_fsa == b.rtol_fsa || + (isNaN(a.rtol_fsa) && isNaN(b.rtol_fsa))) && + (a.atol_fsa == b.atol_fsa || + (isNaN(a.atol_fsa) && isNaN(b.atol_fsa))) && + (a.rtolB == b.rtolB || (isNaN(a.rtolB) && isNaN(b.rtolB))) && + (a.atolB == b.atolB || (isNaN(a.atolB) && isNaN(b.atolB))) && + (a.sensi == b.sensi) && (a.sensi_meth == b.sensi_meth); +} + +void Solver::applyTolerances() const { + if (!getInitDone()) + throw AmiException(("Solver instance was not yet set up, the " + "tolerances cannot be applied yet!")); setSStolerances(this->rtol, this->atol); } -void Solver::applyTolerancesFSA() { - if (!getMallocDone()) - throw AmiException(("Solver instance was not yet set up, the tolerances cannot be applied yet!")); +void Solver::applyTolerancesFSA() const { + if (!getInitDone()) + throw AmiException(("Solver instance was not yet set up, the " + "tolerances cannot be applied yet!")); if (sensi < SensitivityOrder::first) return; - if(nplist()) { + if (nplist()) { std::vector atols(nplist(), getAbsoluteToleranceFSA()); setSensSStolerances(getRelativeToleranceFSA(), atols.data()); setSensErrCon(true); } } -void Solver::applyTolerancesASA(int which) { - if (!getAdjMallocDone()) - throw AmiException(("Adjoint solver instance was not yet set up, the tolerances cannot be applied yet!")); +void Solver::applyTolerancesASA(const int which) const { + if (!getAdjInitDone()) + throw AmiException(("Adjoint solver instance was not yet set up, the " + "tolerances cannot be applied yet!")); if (sensi < SensitivityOrder::first) return; @@ -456,292 +445,276 @@ void Solver::applyTolerancesASA(int which) { setSStolerancesB(which, getRelativeToleranceB(), getAbsoluteToleranceB()); } -void Solver::applyQuadTolerancesASA(int which) { - if (!getAdjMallocDone()) - throw AmiException(("Adjoint solver instance was not yet set up, the tolerances cannot be applied yet!")); +void Solver::applyQuadTolerancesASA(const int which) const { + if (!getAdjInitDone()) + throw AmiException(("Adjoint solver instance was not yet set up, the " + "tolerances cannot be applied yet!")); if (sensi < SensitivityOrder::first) return; - double quad_rtol = isNaN(this->quad_rtol) ? rtol : this->quad_rtol; - double quad_atol = isNaN(this->quad_atol) ? atol : this->quad_atol; + realtype quad_rtol = isNaN(this->quad_rtol) ? rtol : this->quad_rtol; + realtype quad_atol = isNaN(this->quad_atol) ? atol : this->quad_atol; /* Enable Quadrature Error Control */ - setQuadErrConB(which, - !std::isinf(quad_atol) && !std::isinf(quad_rtol)); + setQuadErrConB(which, !std::isinf(quad_atol) && !std::isinf(quad_rtol)); quadSStolerancesB(which, quad_rtol, quad_atol); } -void Solver::applySensitivityTolerances() { +void Solver::applySensitivityTolerances() const { if (sensi < SensitivityOrder::first) return; if (sensi_meth == SensitivityMethod::forward) applyTolerancesFSA(); - else if (sensi_meth == SensitivityMethod::adjoint && getAdjMallocDone()) { - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) + else if (sensi_meth == SensitivityMethod::adjoint && getAdjInitDone()) { + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) applyTolerancesASA(iMem); } } -SensitivityMethod Solver::getSensitivityMethod() const{ - return sensi_meth; -} +SensitivityMethod Solver::getSensitivityMethod() const { return sensi_meth; } -void Solver::setSensitivityMethod(SensitivityMethod sensi_meth) { +void Solver::setSensitivityMethod(const SensitivityMethod sensi_meth) { + if (sensi_meth != this->sensi_meth) + resetMutableMemory(nx(), nplist(), nquad()); this->sensi_meth = sensi_meth; } -int Solver::getNewtonMaxSteps() const { - return newton_maxsteps; -} +int Solver::getNewtonMaxSteps() const { return newton_maxsteps; } -void Solver::setNewtonMaxSteps(int newton_maxsteps) { - if(newton_maxsteps < 0) +void Solver::setNewtonMaxSteps(const int newton_maxsteps) { + if (newton_maxsteps < 0) throw AmiException("newton_maxsteps must be a non-negative number"); this->newton_maxsteps = newton_maxsteps; } -bool Solver::getNewtonPreequilibration() const { - return newton_preeq; -} +bool Solver::getNewtonPreequilibration() const { return newton_preeq; } -void Solver::setNewtonPreequilibration(bool newton_preeq) { +void Solver::setNewtonPreequilibration(const bool newton_preeq) { this->newton_preeq = newton_preeq; } -int Solver::getNewtonMaxLinearSteps() const { - return newton_maxlinsteps; -} +int Solver::getNewtonMaxLinearSteps() const { return newton_maxlinsteps; } -void Solver::setNewtonMaxLinearSteps(int newton_maxlinsteps) { - if(newton_maxlinsteps < 0) +void Solver::setNewtonMaxLinearSteps(const int newton_maxlinsteps) { + if (newton_maxlinsteps < 0) throw AmiException("newton_maxlinsteps must be a non-negative number"); this->newton_maxlinsteps = newton_maxlinsteps; } -SensitivityOrder Solver::getSensitivityOrder() const { - return sensi; -} +SensitivityOrder Solver::getSensitivityOrder() const { return sensi; } -void Solver::setSensitivityOrder(SensitivityOrder sensi) { +void Solver::setSensitivityOrder(const SensitivityOrder sensi) { + if (this->sensi != sensi) + resetMutableMemory(nx(), nplist(), nquad()); this->sensi = sensi; - if(getMallocDone()) + if (getInitDone()) applySensitivityTolerances(); } double Solver::getRelativeTolerance() const { - return rtol; + return static_cast(rtol); } -void Solver::setRelativeTolerance(double rtol) { - if(rtol < 0) +void Solver::setRelativeTolerance(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - this->rtol = rtol; + this->rtol = static_cast(rtol); - if(getMallocDone()) { + if (getInitDone()) { applyTolerances(); applySensitivityTolerances(); } } double Solver::getAbsoluteTolerance() const { - return atol; + return static_cast(atol); } void Solver::setAbsoluteTolerance(double atol) { - if(atol < 0) + if (atol < 0) throw AmiException("atol must be a non-negative number"); - this->atol = atol; + this->atol = static_cast(atol); - if(getMallocDone()) { + if (getInitDone()) { applyTolerances(); applySensitivityTolerances(); } } double Solver::getRelativeToleranceFSA() const { - return isNaN(rtol_fsa) ? rtol : rtol_fsa; + return static_cast(isNaN(rtol_fsa) ? rtol : rtol_fsa); } -void Solver::setRelativeToleranceFSA(double rtol) { - if(rtol < 0) +void Solver::setRelativeToleranceFSA(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - rtol_fsa = rtol; + rtol_fsa = static_cast(rtol); - if(getMallocDone()) { + if (getInitDone()) { applySensitivityTolerances(); } } double Solver::getAbsoluteToleranceFSA() const { - return isNaN(atol_fsa) ? atol : atol_fsa; + return static_cast(isNaN(atol_fsa) ? atol : atol_fsa); } -void Solver::setAbsoluteToleranceFSA(double atol) { - if(atol < 0) +void Solver::setAbsoluteToleranceFSA(const double atol) { + if (atol < 0) throw AmiException("atol must be a non-negative number"); - atol_fsa = atol; + atol_fsa = static_cast(atol); - if(getMallocDone()) { + if (getInitDone()) { applySensitivityTolerances(); } } -double Solver::getRelativeToleranceB() const -{ - return isNaN(rtolB) ? rtol : rtolB; +double Solver::getRelativeToleranceB() const { + return static_cast(isNaN(rtolB) ? rtol : rtolB); } -void Solver::setRelativeToleranceB(double rtol) -{ - if(rtol < 0) +void Solver::setRelativeToleranceB(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - rtolB = rtol; + rtolB = static_cast(rtol); - if(getMallocDone()) { + if (getInitDone()) { applySensitivityTolerances(); } } -double Solver::getAbsoluteToleranceB() const -{ - return isNaN(atolB) ? atol : atolB; +double Solver::getAbsoluteToleranceB() const { + return static_cast(isNaN(atolB) ? atol : atolB); } -void Solver::setAbsoluteToleranceB(double atol) -{ - if(atol < 0) +void Solver::setAbsoluteToleranceB(const double atol) { + if (atol < 0) throw AmiException("atol must be a non-negative number"); - atolB = atol; + atolB = static_cast(atol); - if(getMallocDone()) { + if (getInitDone()) { applySensitivityTolerances(); } } double Solver::getRelativeToleranceQuadratures() const { - return quad_rtol; + return static_cast(quad_rtol); } -void Solver::setRelativeToleranceQuadratures(double rtol) { - if(rtol < 0) +void Solver::setRelativeToleranceQuadratures(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - this->quad_rtol = rtol; + this->quad_rtol = static_cast(rtol); if (sensi_meth != SensitivityMethod::adjoint) return; - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) - if(solverMemoryB.at(iMem)) + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) + if (solverMemoryB.at(iMem)) applyQuadTolerancesASA(iMem); } double Solver::getAbsoluteToleranceQuadratures() const { - return quad_atol; + return static_cast(quad_atol); } -void Solver::setAbsoluteToleranceQuadratures(double atol) { +void Solver::setAbsoluteToleranceQuadratures(const double atol) { if (atol < 0) throw AmiException("atol must be a non-negative number"); - this->quad_atol = atol; + this->quad_atol = static_cast(atol); if (sensi_meth != SensitivityMethod::adjoint) return; - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) - if(solverMemoryB.at(iMem)) + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) + if (solverMemoryB.at(iMem)) applyTolerancesASA(iMem); } double Solver::getRelativeToleranceSteadyState() const { - return isNaN(ss_rtol) ? rtol : ss_rtol; + return static_cast(isNaN(ss_rtol) ? rtol : ss_rtol); } -void Solver::setRelativeToleranceSteadyState(double rtol) { - if(rtol < 0) +void Solver::setRelativeToleranceSteadyState(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - this->ss_rtol = rtol; + this->ss_rtol = static_cast(rtol); } double Solver::getAbsoluteToleranceSteadyState() const { - return isNaN(ss_atol) ? atol : ss_atol; + return static_cast(isNaN(ss_atol) ? atol : ss_atol); } -void Solver::setAbsoluteToleranceSteadyState(double atol) { +void Solver::setAbsoluteToleranceSteadyState(const double atol) { if (atol < 0) throw AmiException("atol must be a non-negative number"); - this->ss_atol = atol; + this->ss_atol = static_cast(atol); } double Solver::getRelativeToleranceSteadyStateSensi() const { - return isNaN(ss_rtol_sensi) ? rtol : ss_rtol_sensi; + return static_cast(isNaN(ss_rtol_sensi) ? rtol : ss_rtol_sensi); } -void Solver::setRelativeToleranceSteadyStateSensi(double rtol) { - if(rtol < 0) +void Solver::setRelativeToleranceSteadyStateSensi(const double rtol) { + if (rtol < 0) throw AmiException("rtol must be a non-negative number"); - this->ss_rtol_sensi = rtol; + this->ss_rtol_sensi = static_cast(rtol); } double Solver::getAbsoluteToleranceSteadyStateSensi() const { - return isNaN(ss_atol_sensi) ? atol : ss_atol_sensi; + return static_cast(isNaN(ss_atol_sensi) ? atol : ss_atol_sensi); } -void Solver::setAbsoluteToleranceSteadyStateSensi(double atol) { +void Solver::setAbsoluteToleranceSteadyStateSensi(const double atol) { if (atol < 0) throw AmiException("atol must be a non-negative number"); - this->ss_atol_sensi = atol; + this->ss_atol_sensi = static_cast(atol); } -int Solver::getMaxSteps() const { - return maxsteps; -} +long int Solver::getMaxSteps() const { return maxsteps; } -void Solver::setMaxSteps(int maxsteps) { +void Solver::setMaxSteps(const long int maxsteps) { if (maxsteps < 0) throw AmiException("maxsteps must be a non-negative number"); this->maxsteps = maxsteps; - if(solverMemory) - setMaxNumSteps(this->maxsteps); + if (getAdjInitDone()) + resetMutableMemory(nx(), nplist(), nquad()); } -int Solver::getMaxStepsBackwardProblem() const { - return maxstepsB; -} +long int Solver::getMaxStepsBackwardProblem() const { return maxstepsB; } -void Solver::setMaxStepsBackwardProblem(int maxsteps) { +void Solver::setMaxStepsBackwardProblem(const long int maxsteps) { if (maxsteps < 0) throw AmiException("maxsteps must be a non-negative number"); this->maxstepsB = maxsteps; - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) - if(solverMemoryB.at(iMem)) + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) + if (solverMemoryB.at(iMem)) setMaxNumStepsB(iMem, this->maxstepsB); } -LinearMultistepMethod Solver::getLinearMultistepMethod() const { - return lmm; -} +LinearMultistepMethod Solver::getLinearMultistepMethod() const { return lmm; } -void Solver::setLinearMultistepMethod(LinearMultistepMethod lmm) { - if(solverMemory) - throw AmiException("Solver instance was already set up, the linear system multistep method can no longer be changed!"); +void Solver::setLinearMultistepMethod(const LinearMultistepMethod lmm) { + if (solverMemory) + resetMutableMemory(nx(), nplist(), nquad()); this->lmm = lmm; } @@ -749,68 +722,63 @@ NonlinearSolverIteration Solver::getNonlinearSolverIteration() const { return iter; } -void Solver::setNonlinearSolverIteration(NonlinearSolverIteration iter) { - if(solverMemory) - throw AmiException("Solver instance was already set up, the nonlinear system solution method can no longer be changed!"); +void Solver::setNonlinearSolverIteration(const NonlinearSolverIteration iter) { + if (solverMemory) + resetMutableMemory(nx(), nplist(), nquad()); this->iter = iter; } -InterpolationType Solver::getInterpolationType() const { - return interpType; -} +InterpolationType Solver::getInterpolationType() const { return interpType; } -void Solver::setInterpolationType(InterpolationType interpType) { - if(!solverMemoryB.empty()) - throw AmiException("Adjoint solver object was already set up, the interpolation type can no longer be changed!"); +void Solver::setInterpolationType(const InterpolationType interpType) { + if (!solverMemoryB.empty()) + resetMutableMemory(nx(), nplist(), nquad()); this->interpType = interpType; } -int Solver::getStateOrdering() const { - return ordering; -} +int Solver::getStateOrdering() const { return ordering; } void Solver::setStateOrdering(int ordering) { this->ordering = ordering; if (solverMemory && linsol == LinearSolver::KLU) { - auto klu = dynamic_cast(linearSolver.get()); + auto klu = dynamic_cast(linearSolver.get()); klu->setOrdering(static_cast(ordering)); - klu = dynamic_cast(linearSolverB.get()); + klu = dynamic_cast(linearSolverB.get()); klu->setOrdering(static_cast(ordering)); } #ifdef SUNDIALS_SUPERLUMT if (solverMemory && linsol == LinearSolver::SuperLUMT) { - auto klu = dynamic_cast(linearSolver.get()); - klu->setOrdering(static_cast(ordering)); - klu = dynamic_cast(linearSolverB.get()); - klu->setOrdering(static_cast(ordering)); + auto klu = dynamic_cast(linearSolver.get()); + klu->setOrdering( + static_cast(ordering)); + klu = dynamic_cast(linearSolverB.get()); + klu->setOrdering( + static_cast(ordering)); } #endif } -int Solver::getStabilityLimitFlag() const { - return stldet; -} +int Solver::getStabilityLimitFlag() const { return stldet; } -void Solver::setStabilityLimitFlag(int stldet) { +void Solver::setStabilityLimitFlag(const int stldet) { if (stldet != TRUE && stldet != FALSE) - throw AmiException("Invalid stldet flag, valid values are %i or %i",TRUE,FALSE); + throw AmiException("Invalid stldet flag, valid values are %i or %i", + TRUE, FALSE); this->stldet = stldet; if (solverMemory) { setStabLimDet(stldet); - for (int iMem = 0; iMem < (int) solverMemoryB.size(); ++iMem) - if(solverMemoryB.at(iMem)) - setStabLimDetB(iMem,stldet); + for (int iMem = 0; iMem < (int)solverMemoryB.size(); ++iMem) + if (solverMemoryB.at(iMem)) + setStabLimDetB(iMem, stldet); } } -LinearSolver Solver::getLinearSolver() const { - return linsol; -} +LinearSolver Solver::getLinearSolver() const { return linsol; } void Solver::setLinearSolver(LinearSolver linsol) { - if(solverMemory) - throw AmiException("Solver object was already set up, the linear solver can no longer be changed!"); + if (solverMemory) + resetMutableMemory(nx(), nplist(), nquad()); this->linsol = linsol; /*if(solverMemory) initializeLinearSolver(getModel());*/ @@ -820,47 +788,253 @@ InternalSensitivityMethod Solver::getInternalSensitivityMethod() const { return ism; } -void Solver::setInternalSensitivityMethod(InternalSensitivityMethod ism) { - if(solverMemory) - throw AmiException("Solver object was already set up, the sensitivity method can no longer be changed!"); +void Solver::setInternalSensitivityMethod(const InternalSensitivityMethod ism) { + if (solverMemory) + resetMutableMemory(nx(), nplist(), nquad()); this->ism = ism; } -void Solver::initalizeNonLinearSolverSens(AmiVector *x, Model *model) -{ - switch(iter) { +void Solver::initializeNonLinearSolverSens(const Model *model) const { + switch (iter) { case NonlinearSolverIteration::newton: - switch(ism) { + switch (ism) { case InternalSensitivityMethod::staggered: case InternalSensitivityMethod::simultaneous: - nonLinearSolverSens = std::make_unique(1 + model->nplist(), x->getNVector()); + nonLinearSolverSens = std::make_unique( + 1 + model->nplist(), x.getNVector()); break; case InternalSensitivityMethod::staggered1: - nonLinearSolverSens = std::make_unique(x->getNVector()); + nonLinearSolverSens = + std::make_unique(x.getNVector()); break; default: - throw AmiException("Unsupported internal sensitivity method selected: %d", ism); + throw AmiException( + "Unsupported internal sensitivity method selected: %d", ism); } break; case NonlinearSolverIteration::fixedpoint: - switch(ism) { + switch (ism) { case InternalSensitivityMethod::staggered: case InternalSensitivityMethod::simultaneous: - nonLinearSolverSens = std::make_unique(1 + model->nplist(), x->getNVector()); + nonLinearSolverSens = std::make_unique( + 1 + model->nplist(), x.getNVector()); break; case InternalSensitivityMethod::staggered1: - nonLinearSolverSens = std::make_unique(x->getNVector()); + nonLinearSolverSens = + std::make_unique(x.getNVector()); break; default: - throw AmiException("Unsupported internal sensitivity method selected: %d", ism); - + throw AmiException( + "Unsupported internal sensitivity method selected: %d", ism); } break; default: - throw AmiException("Invalid non-linear solver specified (%d).", static_cast(iter)); + throw AmiException("Invalid non-linear solver specified (%d).", + static_cast(iter)); } setNonLinearSolverSens(); } +int Solver::nplist() const { return sx.getLength(); } + +int Solver::nx() const { return x.getLength(); } + +int Solver::nquad() const { return xQB.getLength(); } + +bool Solver::getInitDone() const { return initialized; }; + +bool Solver::getSensInitDone() const { return sensInitialized; } + +bool Solver::getAdjInitDone() const { return adjInitialized; } + +bool Solver::getInitDoneB(const int which) const { + return static_cast(initializedB.size()) > which && + initializedB.at(which); +} + +bool Solver::getQuadInitDoneB(const int which) const { + return static_cast(initializedQB.size()) > which && + initializedQB.at(which); +} + +void Solver::setInitDone() const { initialized = true; }; + +void Solver::setSensInitDone() const { sensInitialized = true; } + +void Solver::setAdjInitDone() const { adjInitialized = true; } + +void Solver::setInitDoneB(const int which) const { + if (which >= static_cast(initializedB.size())) + initializedB.resize(which + 1, false); + initializedB.at(which) = true; +} + +void Solver::setQuadInitDoneB(const int which) const { + if (which >= static_cast(initializedQB.size())) + initializedQB.resize(which + 1, false); + initializedQB.at(which) = true; +} + +void Solver::resetMutableMemory(const int nx, const int nplist, + const int nquad) const { + solverMemory = nullptr; + initialized = false; + adjInitialized = false; + sensInitialized = false; + solverWasCalledF = false; + solverWasCalledB = false; + + x = AmiVector(nx); + dx = AmiVector(nx); + sx = AmiVectorArray(nx, nplist); + sdx = AmiVectorArray(nx, nplist); + + xB = AmiVector(nx); + dxB = AmiVector(nx); + xQB = AmiVector(nquad); + + solverMemoryB.clear(); + initializedB.clear(); + initializedQB.clear(); +} + +void Solver::writeSolution(realtype *t, AmiVector &x, AmiVector &dx, + AmiVectorArray &sx) const { + *t = gett(); + x.copy(getState(*t)); + dx.copy(getDerivativeState(*t)); + if (sensInitialized) { + sx.copy(getStateSensitivity(*t)); + } +} + +void Solver::writeSolutionB(realtype *t, AmiVector &xB, AmiVector &dxB, + AmiVector &xQB, const int which) const { + *t = gett(); + xB.copy(getAdjointState(which, *t)); + dxB.copy(getAdjointDerivativeState(which, *t)); + xQB.copy(getAdjointQuadrature(which, *t)); +} + +const AmiVector &Solver::getState(const realtype t) const { + if (t == this->t) + return x; + + if (solverWasCalledF) + getDky(t, 0); + + return dky; +} + +const AmiVector &Solver::getDerivativeState(const realtype t) const { + if (t == this->t) + return dx; + + if (solverWasCalledF) + getDky(t, 1); + + return dky; +} + +const AmiVectorArray &Solver::getStateSensitivity(const realtype t) const { + if (sensInitialized) { + if (solverWasCalledF) { + if (t == this->t) { + getSens(); + } else { + getSensDky(t, 0); + } + } + } else { + sx.reset(); + } + return sx; +} + +const AmiVector &Solver::getAdjointState(const int which, + const realtype t) const { + if (adjInitialized) { + if (solverWasCalledB) { + if (t == this->t) { + getB(which); + return xB; + } + getDkyB(t, 0, which); + } + } else { + dky.reset(); + } + return dky; +} + +const AmiVector &Solver::getAdjointDerivativeState(const int which, + const realtype t) const { + if (adjInitialized) { + if (solverWasCalledB) { + if (t == this->t) { + getB(which); + return dxB; + } + getDkyB(t, 1, which); + } + } else { + dky.reset(); + } + return dky; +} + +const AmiVector &Solver::getAdjointQuadrature(const int which, + const realtype t) const { + if (adjInitialized) { + if (solverWasCalledB) { + if (t == this->t) { + getQuadB(which); + return xQB; + } + getQuadDkyB(t, 0, which); + } + } else { + xQB.reset(); + } + return xQB; +} + +realtype Solver::gett() const { return t; } + +void wrapErrHandlerFn(int error_code, const char *module, + const char *function, char *msg, void * /*eh_data*/) { + char buffer[250]; + char buffid[250]; + sprintf(buffer, "AMICI ERROR: in module %s in function %s : %s ", module, + function, msg); + switch (error_code) { + case 99: + sprintf(buffid, "AMICI:mex:%s:%s:WARNING", module, function); + break; + + case -1: + sprintf(buffid, "AMICI:mex:%s:%s:TOO_MUCH_WORK", module, function); + break; + + case -2: + sprintf(buffid, "AMICI:mex:%s:%s:TOO_MUCH_ACC", module, function); + break; + + case -3: + sprintf(buffid, "AMICI:mex:%s:%s:ERR_FAILURE", module, function); + break; + + case -4: + sprintf(buffid, "AMICI:mex:%s:%s:CONV_FAILURE", module, function); + break; + + default: + sprintf(buffid, "AMICI:mex:%s:%s:OTHER", module, function); + break; + } + + warnMsgIdAndTxt(buffid, buffer); +} + } // namespace amici diff --git a/src/solver_cvodes.cpp b/src/solver_cvodes.cpp index b017b0f1d5..4267cee5d3 100644 --- a/src/solver_cvodes.cpp +++ b/src/solver_cvodes.cpp @@ -1,33 +1,28 @@ -#include "amici/misc.h" -#include "amici/model_ode.h" #include "amici/solver_cvodes.h" + #include "amici/exception.h" +#include "amici/misc.h" +#include "amici/model_ode.h" +#include "amici/sundials_linsol_wrapper.h" #include -#include #include - -#include "amici/sundials_linsol_wrapper.h" - +#include #include #include #include #include -#define ZERO RCONST(0.0) -#define ONE RCONST(1.0) -#define FOUR RCONST(4.0) - -/** - * @ brief extract information from a property of a matlab class (matrix) - * @ param OPTION name of the property - */ +#define ZERO RCONST(0.0) +#define ONE RCONST(1.0) +#define FOUR RCONST(4.0) namespace amici { // Ensure AMICI options are in sync with Sundials options -static_assert((int)InternalSensitivityMethod::simultaneous == CV_SIMULTANEOUS, ""); +static_assert((int)InternalSensitivityMethod::simultaneous == CV_SIMULTANEOUS, + ""); static_assert((int)InternalSensitivityMethod::staggered == CV_STAGGERED, ""); static_assert((int)InternalSensitivityMethod::staggered1 == CV_STAGGERED1, ""); @@ -40,222 +35,321 @@ static_assert((int)LinearMultistepMethod::BDF == CV_BDF, ""); static_assert(AMICI_ROOT_RETURN == CV_ROOT_RETURN, ""); -void CVodeSolver::init(AmiVector *x, AmiVector * /*dx*/, realtype t) { - int status = CVodeInit(solverMemory.get(), fxdot, t, x->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeInit"); -} +/* + * The following static members are callback function to CVODES. + * Their signatures must not be changes. + */ +static int fxdot(realtype t, N_Vector x, N_Vector xdot, void *user_data); -void CVodeSolver::binit(int which, AmiVector *xB, AmiVector * /*dxB*/, realtype t) { - int status = CVodeInitB(solverMemory.get(), which, fxBdot, t, xB->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeInitB"); -} +static int fJSparse(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector tmp1, N_Vector tmp2, + N_Vector tmp3); -void CVodeSolver::qbinit(int which, AmiVector *qBdot) { - int status = CVodeQuadInitB(solverMemory.get(), which, fqBdot, qBdot->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeQuadInitB"); -} +static int fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); -void CVodeSolver::rootInit(int ne) { - int status = CVodeRootInit(solverMemory.get(), ne, froot); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeRootInit"); +static int fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector tmp1B, + N_Vector tmp2B, N_Vector tmp3B); + +static int fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector tmp1B, + N_Vector tmp2B, N_Vector tmp3B); + +static int fJBand(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector tmp1, N_Vector tmp2, + N_Vector tmp3); + +static int fJBandB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector tmp1B, + N_Vector tmp2B, N_Vector tmp3B); + +static int fJDiag(realtype t, N_Vector JDiag, N_Vector x, void *user_data) +__attribute__((unused)); + +static int fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, + N_Vector xdot, void *user_data, N_Vector tmp); + +static int fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, + N_Vector xB, N_Vector xBdot, void *user_data, + N_Vector tmpB); + +static int froot(realtype t, N_Vector x, realtype *root, void *user_data); + +static int fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + void *user_data); + +static int fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot, + void *user_data); + +static int fsxdot(int Ns, realtype t, N_Vector x, N_Vector xdot, int ip, + N_Vector sx, N_Vector sxdot, void *user_data, + N_Vector tmp1, N_Vector tmp2); + + +/* Function implementations */ + +void CVodeSolver::init(const realtype t0, const AmiVector &x0, + const AmiVector & /*dx0*/) const { + solverWasCalledF = false; + t = t0; + x.copy(x0); + int status; + if (getInitDone()) { + status = CVodeReInit(solverMemory.get(), t0, x.getNVector()); + } else { + status = CVodeInit(solverMemory.get(), fxdot, t0, x.getNVector()); + setInitDone(); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeInit"); +} + +void CVodeSolver::sensInit1(const AmiVectorArray &sx0, + const AmiVectorArray & /*sdx0*/) const { + int status; + sx.copy(sx0); + if (getSensInitDone()) { + status = CVodeSensReInit(solverMemory.get(), + static_cast(getSensitivityMethod()), + sx.getNVectorArray()); + } else { + status = CVodeSensInit1(solverMemory.get(), nplist(), + static_cast(getSensitivityMethod()), + fsxdot, sx.getNVectorArray()); + setSensInitDone(); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSensInit1"); +} + +void CVodeSolver::binit(const int which, const realtype tf, + const AmiVector &xB0, + const AmiVector & /*dxB0*/) const { + solverWasCalledB = false; + xB.copy(xB0); + int status; + if (getInitDoneB(which)) { + status = CVodeReInitB(solverMemory.get(), which, tf, xB.getNVector()); + } else { + status = + CVodeInitB(solverMemory.get(), which, fxBdot, tf, xB.getNVector()); + setInitDoneB(which); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeInitB"); +} + +void CVodeSolver::qbinit(const int which, const AmiVector &xQB0) const { + xQB.copy(xQB0); + int status; + if (getQuadInitDoneB(which)) { + status = CVodeQuadReInitB(solverMemory.get(), which, xQB.getNVector()); + } else { + status = + CVodeQuadInitB(solverMemory.get(), which, fqBdot, xQB.getNVector()); + setQuadInitDoneB(which); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeQuadInitB"); } -void CVodeSolver::sensInit1(AmiVectorArray *sx, AmiVectorArray * /*sdx*/, int nplist) { - int status = CVodeSensInit1(solverMemory.get(), nplist, static_cast(getSensitivityMethod()), fsxdot, - sx->getNVectorArray()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSensInit1"); +void CVodeSolver::rootInit(int ne) const { + int status = CVodeRootInit(solverMemory.get(), ne, froot); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeRootInit"); } -void CVodeSolver::setDenseJacFn() { +void CVodeSolver::setDenseJacFn() const { int status = CVodeSetJacFn(solverMemory.get(), fJ); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFn"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFn"); } -void CVodeSolver::setSparseJacFn() { +void CVodeSolver::setSparseJacFn() const { int status = CVodeSetJacFn(solverMemory.get(), fJSparse); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFn"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFn"); } -void CVodeSolver::setBandJacFn() { +void CVodeSolver::setBandJacFn() const { int status = CVodeSetJacFn(solverMemory.get(), fJBand); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFn"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFn"); } -void CVodeSolver::setJacTimesVecFn() { +void CVodeSolver::setJacTimesVecFn() const { int status = CVodeSetJacTimes(solverMemory.get(), nullptr, fJv); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacTimes"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacTimes"); } -void CVodeSolver::setDenseJacFnB(int which) { +void CVodeSolver::setDenseJacFnB(int which) const { int status = CVodeSetJacFnB(solverMemory.get(), which, fJB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFnB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFnB"); } -void CVodeSolver::setSparseJacFnB(int which) { +void CVodeSolver::setSparseJacFnB(int which) const { int status = CVodeSetJacFnB(solverMemory.get(), which, fJSparseB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFnB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFnB"); } -void CVodeSolver::setBandJacFnB(int which) { +void CVodeSolver::setBandJacFnB(int which) const { int status = CVodeSetJacFnB(solverMemory.get(), which, fJBandB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacFnB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacFnB"); } -void CVodeSolver::setJacTimesVecFnB(int which) { +void CVodeSolver::setJacTimesVecFnB(int which) const { int status = CVodeSetJacTimesB(solverMemory.get(), which, nullptr, fJvB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetJacTimesB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetJacTimesB"); } -Solver *CVodeSolver::clone() const { - return new CVodeSolver(*this); -} +Solver *CVodeSolver::clone() const { return new CVodeSolver(*this); } -void CVodeSolver::allocateSolver() { - solverMemory = std::unique_ptr> - (CVodeCreate(static_cast(lmm)), - [](void *ptr) { CVodeFree(&ptr); }); +void CVodeSolver::allocateSolver() const { + if (!solverMemory) + solverMemory = std::unique_ptr>( + CVodeCreate(static_cast(lmm)), + [](void *ptr) { CVodeFree(&ptr); }); } -void CVodeSolver::setSStolerances(double rtol, double atol) { +void CVodeSolver::setSStolerances(const double rtol, const double atol) const { int status = CVodeSStolerances(solverMemory.get(), rtol, atol); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSStolerances"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSStolerances"); } -void CVodeSolver::setSensSStolerances(double rtol, double *atol) { - int status = CVodeSensSStolerances(solverMemory.get(), rtol, atol); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSensEEtolerances"); +void CVodeSolver::setSensSStolerances(const double rtol, + const double *atol) const { + int status = CVodeSensSStolerances(solverMemory.get(), rtol, + const_cast(atol)); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSensEEtolerances"); } -void CVodeSolver::setSensErrCon(bool error_corr) { +void CVodeSolver::setSensErrCon(const bool error_corr) const { int status = CVodeSetSensErrCon(solverMemory.get(), error_corr); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetSensErrCon"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetSensErrCon"); } -void CVodeSolver::setQuadErrConB(int which, bool flag) { +void CVodeSolver::setQuadErrConB(const int which, const bool flag) const { int status = CVodeSetQuadErrConB(solverMemory.get(), which, flag); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetQuadErrConB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetQuadErrConB"); } void CVodeSolver::getRootInfo(int *rootsfound) const { int status = CVodeGetRootInfo(solverMemory.get(), rootsfound); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetRootInfo"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetRootInfo"); } -void CVodeSolver::setLinearSolver() -{ - int status = CVodeSetLinearSolver(solverMemory.get(), linearSolver->get(), linearSolver->getMatrix()); - if(status != CV_SUCCESS) - throw CvodeException(status,"setLinearSolver"); +void CVodeSolver::setLinearSolver() const { + int status = CVodeSetLinearSolver(solverMemory.get(), linearSolver->get(), + linearSolver->getMatrix()); + if (status != CV_SUCCESS) + throw CvodeException(status, "setLinearSolver"); } -void CVodeSolver::setLinearSolverB(int which) -{ - int status = CVodeSetLinearSolverB(solverMemory.get(), which, linearSolverB->get(), linearSolverB->getMatrix()); - if(status != CV_SUCCESS) - throw CvodeException(status,"setLinearSolverB"); +void CVodeSolver::setLinearSolverB(int which) const { + int status = + CVodeSetLinearSolverB(solverMemory.get(), which, linearSolverB->get(), + linearSolverB->getMatrix()); + if (status != CV_SUCCESS) + throw CvodeException(status, "setLinearSolverB"); } -void CVodeSolver::setNonLinearSolver() -{ - int status = CVodeSetNonlinearSolver(solverMemory.get(), nonLinearSolver->get()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetNonlinearSolver"); +void CVodeSolver::setNonLinearSolver() const { + int status = + CVodeSetNonlinearSolver(solverMemory.get(), nonLinearSolver->get()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetNonlinearSolver"); } -void CVodeSolver::setNonLinearSolverSens() -{ - if(getSensitivityOrder() < SensitivityOrder::first) +void CVodeSolver::setNonLinearSolverSens() const { + if (getSensitivityOrder() < SensitivityOrder::first) return; - if(getSensitivityMethod() != SensitivityMethod::forward) + if (getSensitivityMethod() != SensitivityMethod::forward) return; int status = CV_SUCCESS; switch (ism) { case InternalSensitivityMethod::staggered: - status = CVodeSetNonlinearSolverSensStg(solverMemory.get(), nonLinearSolverSens->get()); + status = CVodeSetNonlinearSolverSensStg(solverMemory.get(), + nonLinearSolverSens->get()); break; case InternalSensitivityMethod::simultaneous: - status = CVodeSetNonlinearSolverSensSim(solverMemory.get(), nonLinearSolverSens->get()); + status = CVodeSetNonlinearSolverSensSim(solverMemory.get(), + nonLinearSolverSens->get()); break; case InternalSensitivityMethod::staggered1: - status = CVodeSetNonlinearSolverSensStg1(solverMemory.get(), nonLinearSolverSens->get()); + status = CVodeSetNonlinearSolverSensStg1(solverMemory.get(), + nonLinearSolverSens->get()); break; default: - throw AmiException("Unsupported internal sensitivity method selected: %d", ism); + throw AmiException( + "Unsupported internal sensitivity method selected: %d", ism); } - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSolver::setNonLinearSolverSens"); - + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSolver::setNonLinearSolverSens"); } -void CVodeSolver::setNonLinearSolverB(int which) -{ - int status = CVodeSetNonlinearSolverB(solverMemory.get(), which, nonLinearSolverB->get()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetNonlinearSolverB"); +void CVodeSolver::setNonLinearSolverB(const int which) const { + int status = CVodeSetNonlinearSolverB(solverMemory.get(), which, + nonLinearSolverB->get()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetNonlinearSolverB"); } -void CVodeSolver::setErrHandlerFn() { - int status = CVodeSetErrHandlerFn(solverMemory.get(), wrapErrHandlerFn, nullptr); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetErrHandlerFn"); +void CVodeSolver::setErrHandlerFn() const { + int status = + CVodeSetErrHandlerFn(solverMemory.get(), wrapErrHandlerFn, nullptr); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetErrHandlerFn"); } -void CVodeSolver::setUserData(Model *model) { +void CVodeSolver::setUserData(Model *model) const { int status = CVodeSetUserData(solverMemory.get(), model); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetUserData"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetUserData"); } -void CVodeSolver::setUserDataB(int which, Model *model) { +void CVodeSolver::setUserDataB(const int which, Model *model) const { int status = CVodeSetUserDataB(solverMemory.get(), which, model); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetUserDataB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetUserDataB"); } -void CVodeSolver::setMaxNumSteps(long mxsteps) { +void CVodeSolver::setMaxNumSteps(const long mxsteps) const { int status = CVodeSetMaxNumSteps(solverMemory.get(), mxsteps); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetMaxNumSteps"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetMaxNumSteps"); } -void CVodeSolver::setStabLimDet(int stldet) { +void CVodeSolver::setStabLimDet(const int stldet) const { int status = CVodeSetStabLimDet(solverMemory.get(), stldet); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetStabLimDet"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetStabLimDet"); } -void CVodeSolver::setStabLimDetB(int which, int stldet) { +void CVodeSolver::setStabLimDetB(const int which, const int stldet) const { int status = CVodeSetStabLimDetB(solverMemory.get(), which, stldet); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetStabLimDetB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetStabLimDetB"); } -void CVodeSolver::setId(Model *model) { } +void CVodeSolver::setId(const Model *model) const {} -void CVodeSolver::setSuppressAlg(bool flag) { } +void CVodeSolver::setSuppressAlg(const bool flag) const {} -void CVodeSolver::resetState(void *ami_mem, N_Vector y0) { +void CVodeSolver::resetState(void *ami_mem, const_N_Vector y0) const { auto cv_mem = static_cast(ami_mem); /* here we force the order in the next step to zero, and update the @@ -266,60 +360,70 @@ void CVodeSolver::resetState(void *ami_mem, N_Vector y0) { /* Set step parameters */ /* current order */ - cv_mem->cv_q = 1; + cv_mem->cv_q = 1; /* L = q + 1 */ - cv_mem->cv_L = 2; + cv_mem->cv_L = 2; /* number of steps to wait before updating in q */ - cv_mem->cv_qwait = cv_mem->cv_L; + cv_mem->cv_qwait = cv_mem->cv_L; /* last successful q value used */ - cv_mem->cv_qu = 0; + cv_mem->cv_qu = 0; /* last successful h value used */ - cv_mem->cv_hu = ZERO; + cv_mem->cv_hu = ZERO; /* tolerance scale factor */ - cv_mem->cv_tolsf = ONE; + cv_mem->cv_tolsf = ONE; /* Initialize other integrator optional outputs */ /* actual initial stepsize */ - cv_mem->cv_h0u = ZERO; + cv_mem->cv_h0u = ZERO; /* step size to be used on the next step */ - cv_mem->cv_next_h = ZERO; + cv_mem->cv_next_h = ZERO; /* order to be used on the next step */ - cv_mem->cv_next_q = 0; + cv_mem->cv_next_q = 0; /* write updated state to Nordsieck history array */ N_VScale(ONE, y0, cv_mem->cv_zn[0]); } - -void CVodeSolver::reInitPostProcessF(realtype *t, AmiVector *yout, - AmiVector * /*ypout*/, realtype tnext) { - reInitPostProcess(solverMemory.get(), t, yout, tnext); +void CVodeSolver::reInitPostProcessF(const realtype tnext) const { + reInitPostProcess(solverMemory.get(), &t, &x, tnext); + forceReInitPostProcessF = false; } -void CVodeSolver::reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector * /*ypBout*/, realtype tnext) { - reInitPostProcess(CVodeGetAdjCVodeBmem(solverMemory.get(), which), - t, yBout, tnext); +void CVodeSolver::reInitPostProcessB(const realtype tnext) const { + realtype tBret; + auto cv_mem = static_cast(solverMemory.get()); + auto ca_mem = cv_mem->cv_adj_mem; + auto cvB_mem = ca_mem->cvB_mem; + // loop over all backward problems + while (cvB_mem != nullptr) { + // store current backward problem in ca_mem to make it accessible in + // adjoint rhs wrapper functions + ca_mem->ca_bckpbCrt = cvB_mem; + reInitPostProcess(static_cast(cvB_mem->cv_mem), &tBret, &xB, + tnext); + cvB_mem->cv_tout = tBret; + cvB_mem = cvB_mem->cv_next; + } + forceReInitPostProcessB = false; } -void CVodeSolver::reInitPostProcess(void *ami_mem, realtype *t, - AmiVector *yout, realtype tout) { +void CVodeSolver::reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, + const realtype tout) const { auto cv_mem = static_cast(ami_mem); auto nst_tmp = cv_mem->cv_nst; cv_mem->cv_nst = 0; auto status = CVodeSetStopTime(cv_mem, tout); - if(status != CV_SUCCESS) + if (status != CV_SUCCESS) throw CvodeException(status, "CVodeSetStopTime"); - status = CVode(ami_mem, tout, yout->getNVector(), - t, CV_ONE_STEP); + status = CVode(ami_mem, tout, yout->getNVector(), t, CV_ONE_STEP); - if(status != CV_SUCCESS) + if (status != CV_SUCCESS) throw CvodeException(status, "reInitPostProcess"); - cv_mem->cv_nst = nst_tmp+1; + cv_mem->cv_nst = nst_tmp + 1; if (cv_mem->cv_adjMallocDone == SUNTRUE) { /* add new step to history array, this is copied from CVodeF */ auto ca_mem = cv_mem->cv_adj_mem; @@ -346,238 +450,271 @@ void CVodeSolver::reInitPostProcess(void *ami_mem, realtype *t, } } -void CVodeSolver::reInit(realtype t0, AmiVector *yy0, AmiVector * /*yp0*/) { +void CVodeSolver::reInit(const realtype t0, const AmiVector &yy0, + const AmiVector & /*yp0*/) const { auto cv_mem = static_cast(solverMemory.get()); - /* set time */ cv_mem->cv_tn = t0; - resetState(cv_mem, yy0->getNVector()); + if (solverWasCalledF) + forceReInitPostProcessF = true; + x.copy(yy0); + resetState(cv_mem, x.getNVector()); } -void CVodeSolver::sensReInit(AmiVectorArray *yS0, AmiVectorArray * /*ypS0*/) { +void CVodeSolver::sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray & /*ypS0*/) const { auto cv_mem = static_cast(solverMemory.get()); /* Initialize znS[0] in the history array */ - - for (int is=0; iscv_Ns; is++) + for (int is = 0; is < nplist(); is++) cv_mem->cv_cvals[is] = ONE; - - int status = N_VScaleVectorArray(cv_mem->cv_Ns, cv_mem->cv_cvals, - yS0->getNVectorArray(), cv_mem->cv_znS[0]); - if(status != CV_SUCCESS) - throw CvodeException(CV_VECTOROP_ERR,"CVodeSensReInit"); + if (solverWasCalledF) + forceReInitPostProcessF = true; + sx.copy(yyS0); + int status = N_VScaleVectorArray(nplist(), cv_mem->cv_cvals, + sx.getNVectorArray(), cv_mem->cv_znS[0]); + if (status != CV_SUCCESS) + throw CvodeException(CV_VECTOROP_ERR, "CVodeSensReInit"); +} + +void CVodeSolver::reInitB(const int which, const realtype tB0, + const AmiVector &yyB0, + const AmiVector & /*ypB0*/) const { + auto cv_memB = + static_cast(CVodeGetAdjCVodeBmem(solverMemory.get(), which)); + if (solverWasCalledB) + forceReInitPostProcessB = true; + cv_memB->cv_tn = tB0; + xB.copy(yyB0); + resetState(cv_memB, xB.getNVector()); } -void CVodeSolver::setSensParams(realtype *p, realtype *pbar, int *plist) { - int status = CVodeSetSensParams(solverMemory.get(), p, pbar, plist); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetSensParams"); +void CVodeSolver::quadReInitB(int which, const AmiVector &yQB0) const { + auto cv_memB = + static_cast(CVodeGetAdjCVodeBmem(solverMemory.get(), which)); + if (solverWasCalledB) + forceReInitPostProcessB = true; + xQB.copy(yQB0); + N_VScale(ONE, xQB.getNVector(), cv_memB->cv_znQ[0]); } -void CVodeSolver::getDky(realtype t, int k, AmiVector *dky) const { - int status = CVodeGetDky(solverMemory.get(), t, k, dky->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetDky"); +void CVodeSolver::setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const { + int status = CVodeSetSensParams( + solverMemory.get(), const_cast(p), + const_cast(pbar), const_cast(plist)); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetSensParams"); } -void CVodeSolver::getSens(realtype *tret, AmiVectorArray *yySout) const { - int status = CVodeGetSens(solverMemory.get(), tret, yySout->getNVectorArray()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetSens"); +void CVodeSolver::getDky(realtype t, int k) const { + int status = CVodeGetDky(solverMemory.get(), t, k, dky.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetDky"); } -void CVodeSolver::adjInit() { - int status = CVodeAdjInit(solverMemory.get(), static_cast(maxsteps), static_cast(interpType)); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeAdjInit"); +void CVodeSolver::getSens() const { + realtype tDummy = 0; + int status = + CVodeGetSens(solverMemory.get(), &tDummy, sx.getNVectorArray()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetSens"); } -void CVodeSolver::allocateSolverB(int *which) { - int status = CVodeCreateB(solverMemory.get(), static_cast(lmm), which); - - if (*which + 1 > static_cast(solverMemoryB.size())) - solverMemoryB.resize(*which + 1); - solverMemoryB.at(*which) = std::unique_ptr> - (getAdjBmem(solverMemory.get(), *which), [](void *ptr){}); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeCreateB"); +void CVodeSolver::getSensDky(const realtype t, const int k) const { + int status = + CVodeGetSensDky(solverMemory.get(), t, k, sx.getNVectorArray()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetSens"); } -void CVodeSolver::reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector * /*ypB0*/) { - auto cv_memB = static_cast(CVodeGetAdjCVodeBmem(solverMemory.get(), which)); - cv_memB->cv_tn = tB0; - resetState(cv_memB, yyB0->getNVector()); +void CVodeSolver::getDkyB(const realtype t, const int k, + const int which) const { + int status = CVodeGetDky(CVodeGetAdjCVodeBmem(solverMemory.get(), which), t, + k, dky.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetDkyB"); } -void CVodeSolver::setSStolerancesB(int which, realtype relTolB, - realtype absTolB) { - int status = CVodeSStolerancesB(solverMemory.get(), which, relTolB, absTolB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSStolerancesB"); +void CVodeSolver::getQuadB(int which) const { + realtype tDummy = 0; + int status = + CVodeGetQuadB(solverMemory.get(), which, &tDummy, xQB.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetQuadB"); } -void CVodeSolver::quadReInitB(int which, AmiVector *yQB0) { - auto cv_memB = static_cast(CVodeGetAdjCVodeBmem(solverMemory.get(), which)); - N_VScale(ONE, yQB0->getNVector(), cv_memB->cv_znQ[0]); +void CVodeSolver::getQuadDkyB(const realtype t, const int k, int which) const { + int status = + CVodeGetQuadDky(CVodeGetAdjCVodeBmem(solverMemory.get(), which), t, k, + xQB.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetQuadDkyB"); } -void CVodeSolver::quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) { - int status = CVodeQuadSStolerancesB(solverMemory.get(), which, reltolQB, abstolQB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeQuadSStolerancesB"); -} - -void CVodeSolver::getQuadB(int which, realtype *tret, AmiVector *qB) const { - int status = CVodeGetQuadB(solverMemory.get(), which, tret, qB->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetQuadB"); +void CVodeSolver::adjInit() const { + int status; + if (getAdjInitDone()) { + status = CVodeAdjReInit(solverMemory.get()); + } else { + status = CVodeAdjInit(solverMemory.get(), static_cast(maxsteps), + static_cast(interpType)); + setAdjInitDone(); + } + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeAdjInit"); } - -int CVodeSolver::solve(realtype tout, AmiVector *yret, AmiVector * /*ypret*/, - realtype *tret, int itask) { - int status = CVode(solverMemory.get(), tout, yret->getNVector(), tret, itask); - if(status<0) { - throw IntegrationFailure(status,*tret); +void CVodeSolver::allocateSolverB(int *which) const { + if (!solverMemoryB.empty()) { + *which = 0; + return; } - - solverWasCalled = true; + int status = CVodeCreateB(solverMemory.get(), static_cast(lmm), which); + if (*which + 1 > static_cast(solverMemoryB.size())) + solverMemoryB.resize(*which + 1); + solverMemoryB.at(*which) = + std::unique_ptr>( + getAdjBmem(solverMemory.get(), *which), [](void *ptr) {}); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeCreateB"); +} + +void CVodeSolver::setSStolerancesB(const int which, const realtype relTolB, + const realtype absTolB) const { + int status = + CVodeSStolerancesB(solverMemory.get(), which, relTolB, absTolB); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSStolerancesB"); +} + +void CVodeSolver::quadSStolerancesB(const int which, const realtype reltolQB, + const realtype abstolQB) const { + int status = + CVodeQuadSStolerancesB(solverMemory.get(), which, reltolQB, abstolQB); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeQuadSStolerancesB"); +} + +void CVodeSolver::getB(const int which) const { + realtype tDummy = 0; + int status = CVodeGetB(solverMemory.get(), which, &tDummy, xB.getNVector()); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetB"); +} + +int CVodeSolver::solve(const realtype tout, const int itask) const { + if (forceReInitPostProcessF) + reInitPostProcessF(tout); + int status = CVode(solverMemory.get(), tout, x.getNVector(), &t, itask); + if (status < 0) // status > 0 is okay and is used for e.g. root return + throw IntegrationFailure(status, t); + solverWasCalledF = true; return status; } -int CVodeSolver::solveF(realtype tout, AmiVector *yret, AmiVector * /*ypret*/, - realtype *tret, int itask, int *ncheckPtr) { - int status = CVodeF(solverMemory.get(), tout, yret->getNVector(), tret, itask, ncheckPtr); - if(status<0) { - throw IntegrationFailure(status,*tret); - } - - solverWasCalled = true; +int CVodeSolver::solveF(const realtype tout, const int itask, + int *ncheckPtr) const { + if (forceReInitPostProcessF) + reInitPostProcessF(tout); + int status = + CVodeF(solverMemory.get(), tout, x.getNVector(), &t, itask, ncheckPtr); + if (status < 0) // status > 0 is okay and is used for e.g. root return + throw IntegrationFailure(status, t); + solverWasCalledF = true; return status; } -void CVodeSolver::solveB(realtype tBout, int itaskB) { +void CVodeSolver::solveB(const realtype tBout, const int itaskB) const { + if (forceReInitPostProcessB) + reInitPostProcessB(tBout); int status = CVodeB(solverMemory.get(), tBout, itaskB); - if(status != CV_SUCCESS) - throw IntegrationFailureB(status,tBout); + if (status != CV_SUCCESS) + throw IntegrationFailureB(status, tBout); + solverWasCalledB = true; } -void CVodeSolver::setMaxNumStepsB(int which, long mxstepsB) { +void CVodeSolver::setMaxNumStepsB(const int which, const long mxstepsB) const { int status = CVodeSetMaxNumStepsB(solverMemory.get(), which, mxstepsB); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetMaxNumStepsB"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetMaxNumStepsB"); } -void CVodeSolver::diag() -{ +void CVodeSolver::diag() const { int status = CVDiag(solverMemory.get()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVDiag"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVDiag"); } -void CVodeSolver::diagB(int which) -{ +void CVodeSolver::diagB(const int which) const { int status = CVDiagB(solverMemory.get(), which); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVDiagB"); - + if (status != CV_SUCCESS) + throw CvodeException(status, "CVDiagB"); } -void CVodeSolver::getB(int which, realtype *tret, AmiVector *yy, AmiVector * /*yp*/) const { - int status = CVodeGetB(solverMemory.get(), which, tret, yy->getNVector()); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetB"); +void CVodeSolver::getNumSteps(const void *ami_mem, long int *numsteps) const { + int status = CVodeGetNumSteps(const_cast(ami_mem), numsteps); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetNumSteps"); } - -void CVodeSolver::getNumSteps(void *ami_mem, long *numsteps) const { - int status = CVodeGetNumSteps(ami_mem, numsteps); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetNumSteps"); +void CVodeSolver::getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const { + int status = CVodeGetNumRhsEvals(const_cast(ami_mem), numrhsevals); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetNumRhsEvals"); } -void CVodeSolver::getNumRhsEvals(void *ami_mem, long *numrhsevals) const { - int status = CVodeGetNumRhsEvals(ami_mem, numrhsevals); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetNumRhsEvals"); +void CVodeSolver::getNumErrTestFails(const void *ami_mem, + long int *numerrtestfails) const { + int status = + CVodeGetNumErrTestFails(const_cast(ami_mem), numerrtestfails); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetNumErrTestFails"); } -void CVodeSolver::getNumErrTestFails(void *ami_mem, long *numerrtestfails) const { - int status = CVodeGetNumErrTestFails(ami_mem, numerrtestfails); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetNumErrTestFails"); +void CVodeSolver::getNumNonlinSolvConvFails( + const void *ami_mem, long int *numnonlinsolvconvfails) const { + int status = CVodeGetNumNonlinSolvConvFails(const_cast(ami_mem), + numnonlinsolvconvfails); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetNumNonlinSolvConvFails"); } -void CVodeSolver::getNumNonlinSolvConvFails(void *ami_mem, - long *numnonlinsolvconvfails) const { - int status = CVodeGetNumNonlinSolvConvFails(ami_mem, numnonlinsolvconvfails); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetNumNonlinSolvConvFails"); +void CVodeSolver::getLastOrder(const void *ami_mem, int *order) const { + int status = CVodeGetLastOrder(const_cast(ami_mem), order); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeGetLastOrder"); } -void CVodeSolver::getLastOrder(void *ami_mem, int *order) const { - int status = CVodeGetLastOrder(ami_mem, order); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeGetLastOrder"); -} - -void *CVodeSolver::getAdjBmem(void *ami_mem, int which) { +void *CVodeSolver::getAdjBmem(void *ami_mem, int which) const { return CVodeGetAdjCVodeBmem(ami_mem, which); } -void CVodeSolver::calcIC(realtype tout1, AmiVector *x, AmiVector *dx) { }; +void CVodeSolver::calcIC(const realtype tout1) const {}; -void CVodeSolver::calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) {}; +void CVodeSolver::calcICB(const int which, const realtype tout1) const {}; -void CVodeSolver::setStopTime(realtype tstop) { +void CVodeSolver::setStopTime(const realtype tstop) const { int status = CVodeSetStopTime(solverMemory.get(), tstop); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeSetStopTime"); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeSetStopTime"); } -void CVodeSolver::turnOffRootFinding() { +void CVodeSolver::turnOffRootFinding() const { int status = CVodeRootInit(solverMemory.get(), 0, nullptr); - if(status != CV_SUCCESS) - throw CvodeException(status,"CVodeRootInit"); -} - -int CVodeSolver::nplist() const { - if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto cv_mem = static_cast(solverMemory.get()); - return cv_mem->cv_Ns; -} - -int CVodeSolver::nx() const { - if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto cv_mem = static_cast(solverMemory.get()); - return NV_LENGTH_S(cv_mem->cv_zn[0]); + if (status != CV_SUCCESS) + throw CvodeException(status, "CVodeRootInit"); } const Model *CVodeSolver::getModel() const { if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); + throw AmiException("Solver has not been allocated, information is not " + "available"); auto cv_mem = static_cast(solverMemory.get()); return static_cast(cv_mem->cv_user_data); } -bool CVodeSolver::getMallocDone() const { - if (!solverMemory) - return false; - auto cv_mem = static_cast(solverMemory.get()); - return cv_mem->cv_MallocDone; -} - -bool CVodeSolver::getAdjMallocDone() const { - if (!solverMemory) - return false; - auto cv_mem = static_cast(solverMemory.get()); - return cv_mem->cv_adjMallocDone; -} - -/** Jacobian of xdot with respect to states x +/** + * @brief Jacobian of xdot with respect to states x * @param N number of state variables * @param t timepoint * @param x Vector with the states @@ -589,15 +726,16 @@ bool CVodeSolver::getAdjMallocDone() const { * @param tmp3 temporary storage vector * @return status flag indicating successful execution **/ -int CVodeSolver::fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, - void *user_data, N_Vector /*tmp1*/, N_Vector /*tmp2*/, - N_Vector /*tmp3*/) { +int fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector /*tmp1*/, N_Vector /*tmp2*/, + N_Vector /*tmp3*/) { auto model = static_cast(user_data); model->fJ(t, x, xdot, J); - return model->checkFinite(SM_ROWS_D(J), SM_DATA_D(J), "Jacobian"); + return model->checkFinite(gsl::make_span(J), "Jacobian"); } -/** Jacobian of xBdot with respect to adjoint state xB +/** + * @brief Jacobian of xBdot with respect to adjoint state xB * @param NeqBdot number of adjoint state variables * @param t timepoint * @param x Vector with the states @@ -610,15 +748,16 @@ int CVodeSolver::fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, * @param tmp3B temporary storage vector * @return status flag indicating successful execution **/ -int CVodeSolver::fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, - N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { +int fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, + N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { auto model = static_cast(user_data); model->fJB(t, x, xB, xBdot, JB); - return model->checkFinite(SM_ROWS_D(JB), SM_DATA_D(JB), "Jacobian"); + return model->checkFinite(gsl::make_span(JB), "Jacobian"); } -/** J in sparse form (for sparse solvers from the SuiteSparse Package) +/** + * @brief J in sparse form (for sparse solvers from the SuiteSparse Package) * @param t timepoint * @param x Vector with the states * @param xdot Vector with the right hand side @@ -629,15 +768,16 @@ int CVodeSolver::fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, * @param tmp3 temporary storage vector * @return status flag indicating successful execution */ -int CVodeSolver::fJSparse(realtype t, N_Vector x, N_Vector /*xdot*/, SUNMatrix J, - void *user_data, N_Vector /*tmp1*/, N_Vector /*tmp2*/, - N_Vector /*tmp3*/) { +int fJSparse(realtype t, N_Vector x, N_Vector /*xdot*/, + SUNMatrix J, void *user_data, N_Vector /*tmp1*/, + N_Vector /*tmp2*/, N_Vector /*tmp3*/) { auto model = static_cast(user_data); model->fJSparse(t, x, J); - return model->checkFinite(SM_NNZ_S(J), SM_DATA_S(J), "Jacobian"); + return model->checkFinite(gsl::make_span(J), "Jacobian"); } -/** JB in sparse form (for sparse solvers from the SuiteSparse Package) +/** + * @brief JB in sparse form (for sparse solvers from the SuiteSparse Package) * @param t timepoint * @param x Vector with the states * @param xB Vector with the adjoint states @@ -649,15 +789,16 @@ int CVodeSolver::fJSparse(realtype t, N_Vector x, N_Vector /*xdot*/, SUNMatrix * @param tmp3B temporary storage vector * @return status flag indicating successful execution */ -int CVodeSolver::fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, - SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, - N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { +int fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, + N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { auto model = static_cast(user_data); model->fJSparseB(t, x, xB, xBdot, JB); - return model->checkFinite(SM_NNZ_S(JB), SM_DATA_S(JB), "Jacobian"); + return model->checkFinite(gsl::make_span(JB), "Jacobian"); } -/** J in banded form (for banded solvers) +/** + * @brief J in banded form (for banded solvers) * @param N number of states * @param mupper upper matrix bandwidth * @param mlower lower matrix bandwidth @@ -671,13 +812,13 @@ int CVodeSolver::fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, * @param tmp3 temporary storage vector * @return status flag indicating successful execution */ -int CVodeSolver::fJBand(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, - void *user_data, N_Vector tmp1, N_Vector tmp2, - N_Vector tmp3) { +int fJBand(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, + void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) { return fJ(t, x, xdot, J, user_data, tmp1, tmp2, tmp3); } -/** JB in banded form (for banded solvers) +/** + * @brief JB in banded form (for banded solvers) * @param NeqBdot number of states * @param mupper upper matrix bandwidth * @param mlower lower matrix bandwidth @@ -692,155 +833,161 @@ int CVodeSolver::fJBand(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J, * @param tmp3B temporary storage vector * @return status flag indicating successful execution */ -int CVodeSolver::fJBandB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, +int fJBandB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, N_Vector tmp2B, N_Vector tmp3B) { return fJB(t, x, xB, xBdot, JB, user_data, tmp1B, tmp2B, tmp3B); } -/** diagonalized Jacobian (for preconditioning) +/** + * @brief Diagonalized Jacobian (for preconditioning) * @param t timepoint * @param JDiag Vector to which the Jacobian diagonal will be written * @param x Vector with the states * @param user_data object with user input @type Model_ODE **/ -int CVodeSolver::fJDiag(realtype t, N_Vector JDiag, N_Vector x, - void *user_data) { +int fJDiag(realtype t, N_Vector JDiag, N_Vector x, void *user_data) { auto model = static_cast(user_data); model->fJDiag(t, JDiag, x); - return model->checkFinite(model->nx_solver, N_VGetArrayPointer(JDiag), - "Jacobian"); -} - - /** Matrix vector product of J with a vector v (for iterative solvers) - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - * @param v Vector with which the Jacobian is multiplied - * @param Jv Vector to which the Jacobian vector product will be - *written - * @param user_data object with user input @type Model_ODE - * @param tmp temporary storage vector - * @return status flag indicating successful execution - **/ - int CVodeSolver::fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, N_Vector /*xdot*/, - void *user_data, N_Vector /*tmp*/) { - auto model = static_cast(user_data); - model->fJv(v,Jv,t,x); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(Jv),"Jacobian"); - } + return model->checkFinite(gsl::make_span(JDiag), "Jacobian"); +} - /** Matrix vector product of JB with a vector v (for iterative solvers) - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - * @param vB Vector with which the Jacobian is multiplied - * @param JvB Vector to which the Jacobian vector product will be - *written - * @param user_data object with user input @type Model_ODE - * @param tmpB temporary storage vector - * @return status flag indicating successful execution - **/ - int CVodeSolver::fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, N_Vector xB, N_Vector /*xBdot*/, - void *user_data, N_Vector /*tmpB*/) { - auto model = static_cast(user_data); - model->fJvB(vB, JvB, t, x, xB); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(JvB),"Jacobian"); - } +/** + * @brief Matrix vector product of J with a vector v (for iterative solvers) + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + * @param v Vector with which the Jacobian is multiplied + * @param Jv Vector to which the Jacobian vector product will be + *written + * @param user_data object with user input @type Model_ODE + * @param tmp temporary storage vector + * @return status flag indicating successful execution + **/ +int fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x, + N_Vector /*xdot*/, void *user_data, N_Vector /*tmp*/) { + auto model = static_cast(user_data); + model->fJv(v, Jv, t, x); + return model->checkFinite(gsl::make_span(Jv), "Jacobian"); +} - /** Event trigger function for events - * @param t timepoint - * @param x Vector with the states - * @param root array with root function values - * @param user_data object with user input @type Model_ODE - * @return status flag indicating successful execution - */ - int CVodeSolver::froot(realtype t, N_Vector x, realtype *root, - void *user_data) { - auto model = static_cast(user_data); - model->froot(t, x, root); - return model->checkFinite(model->ne,root,"root function"); - } +/** + * @brief Matrix vector product of JB with a vector v (for iterative solvers) + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + * @param vB Vector with which the Jacobian is multiplied + * @param JvB Vector to which the Jacobian vector product will be + *written + * @param user_data object with user input @type Model_ODE + * @param tmpB temporary storage vector + * @return status flag indicating successful execution + **/ +int fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x, + N_Vector xB, N_Vector /*xBdot*/, void *user_data, + N_Vector /*tmpB*/) { + auto model = static_cast(user_data); + model->fJvB(vB, JvB, t, x, xB); + return model->checkFinite(gsl::make_span(JvB), "Jacobian"); +} - /** residual function of the ODE - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - * @param user_data object with user input @type Model_ODE - * @return status flag indicating successful execution - */ - int CVodeSolver::fxdot(realtype t, N_Vector x, N_Vector xdot, - void *user_data) { - auto model = static_cast(user_data); - - if (t > 1e200 && !amici::checkFinite(model->nx_solver, - N_VGetArrayPointer(x), "fxdot")) - return AMICI_UNRECOVERABLE_ERROR; - /* when t is large (typically ~1e300), CVODES may pass all NaN x - to fxdot from which we typically cannot recover. To save time - on normal execution, we do not always want to check finiteness - of x, but only do so when t is large and we expect problems. */ - - model->fxdot(t, x, xdot); - return model->checkFinite(model->nx_solver, N_VGetArrayPointer(xdot), - "fxdot"); - } +/** + * @brief Event trigger function for events + * @param t timepoint + * @param x Vector with the states + * @param root array with root function values + * @param user_data object with user input @type Model_ODE + * @return status flag indicating successful execution + */ +int froot(realtype t, N_Vector x, realtype *root, + void *user_data) { + auto model = static_cast(user_data); + model->froot(t, x, gsl::make_span(root, model->ne)); + return model->checkFinite(gsl::make_span(root, model->ne), + "root function"); +} - /** Right hand side of differential equation for adjoint state xB - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param xBdot Vector with the adjoint right hand side - * @param user_data object with user input @type Model_ODE - * @return status flag indicating successful execution - */ - int CVodeSolver::fxBdot(realtype t, N_Vector x, N_Vector xB, - N_Vector xBdot, void *user_data) { - auto model = static_cast(user_data); - model->fxBdot(t, x, xB, xBdot); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(xBdot),"fxBdot"); - } +/** + * @brief residual function of the ODE + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + * @param user_data object with user input @type Model_ODE + * @return status flag indicating successful execution + */ +int fxdot(realtype t, N_Vector x, N_Vector xdot, void *user_data) { + auto model = static_cast(user_data); - /** Right hand side of integral equation for quadrature states qB - * @param t timepoint - * @param x Vector with the states - * @param xB Vector with the adjoint states - * @param qBdot Vector with the adjoint quadrature right hand side - * @param user_data pointer to temp data object - * @return status flag indicating successful execution - */ - int CVodeSolver::fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot, - void *user_data) { - auto model = static_cast(user_data); - model->fqBdot(t, x, xB, qBdot); - return model->checkFinite(model->nplist()*model->nJ,N_VGetArrayPointer(qBdot),"qBdot"); + if (t > 1e200 && !amici::checkFinite(gsl::make_span(x), "fxdot")) { + /* when t is large (typically ~1e300), CVODES may pass all NaN x + to fxdot from which we typically cannot recover. To save time + on normal execution, we do not always want to check finiteness + of x, but only do so when t is large and we expect problems. */ + return AMICI_UNRECOVERABLE_ERROR; } - /** Right hand side of differential equation for state sensitivities sx - * @param Ns number of parameters - * @param t timepoint - * @param x Vector with the states - * @param xdot Vector with the right hand side - * @param ip parameter index - * @param sx Vector with the state sensitivities - * @param sxdot Vector with the sensitivity right hand side - * @param user_data object with user input @type Model_ODE - * @param tmp1 temporary storage vector - * @param tmp2 temporary storage vector - * @param tmp3 temporary storage vector - * @return status flag indicating successful execution - */ - int CVodeSolver::fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector /*xdot*/, int ip, - N_Vector sx, N_Vector sxdot, void *user_data, - N_Vector /*tmp1*/, N_Vector /*tmp2*/) { - auto model = static_cast(user_data); - model->fsxdot(t, x, ip, sx, sxdot); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(sxdot),"sxdot"); - } + model->fxdot(t, x, xdot); + return model->checkFinite(gsl::make_span(xdot), "fxdot"); +} + +/** + * @brief Right hand side of differential equation for adjoint state xB + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param xBdot Vector with the adjoint right hand side + * @param user_data object with user input @type Model_ODE + * @return status flag indicating successful execution + */ +int fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot, + void *user_data) { + auto model = static_cast(user_data); + model->fxBdot(t, x, xB, xBdot); + return model->checkFinite(gsl::make_span(xBdot), "fxBdot"); +} + +/** + * @brief Right hand side of integral equation for quadrature states qB + * @param t timepoint + * @param x Vector with the states + * @param xB Vector with the adjoint states + * @param qBdot Vector with the adjoint quadrature right hand side + * @param user_data pointer to temp data object + * @return status flag indicating successful execution + */ +int fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot, + void *user_data) { + auto model = static_cast(user_data); + model->fqBdot(t, x, xB, qBdot); + return model->checkFinite(gsl::make_span(qBdot), "qBdot"); +} + +/** + * @brief Right hand side of differential equation for state sensitivities sx + * @param Ns number of parameters + * @param t timepoint + * @param x Vector with the states + * @param xdot Vector with the right hand side + * @param ip parameter index + * @param sx Vector with the state sensitivities + * @param sxdot Vector with the sensitivity right hand side + * @param user_data object with user input @type Model_ODE + * @param tmp1 temporary storage vector + * @param tmp2 temporary storage vector + * @param tmp3 temporary storage vector + * @return status flag indicating successful execution + */ +int fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector /*xdot*/, + int ip, N_Vector sx, N_Vector sxdot, void *user_data, + N_Vector /*tmp1*/, N_Vector /*tmp2*/) { + auto model = static_cast(user_data); + model->fsxdot(t, x, ip, sx, sxdot); + return model->checkFinite(gsl::make_span(sxdot), "sxdot"); +} + +bool operator==(const CVodeSolver &a, const CVodeSolver &b) { + return static_cast(a) == static_cast(b); +} - bool operator ==(const CVodeSolver &a, const CVodeSolver &b) - { - return static_cast(a) == static_cast(b); - } } // namespace amici diff --git a/src/solver_idas.cpp b/src/solver_idas.cpp index 5ba9b4b0d1..670b22805b 100644 --- a/src/solver_idas.cpp +++ b/src/solver_idas.cpp @@ -1,462 +1,717 @@ -#include "amici/misc.h" -#include "amici/model_dae.h" #include "amici/solver_idas.h" + #include "amici/exception.h" +#include "amici/misc.h" +#include "amici/model_dae.h" +#include "amici/sundials_linsol_wrapper.h" #include #include -#include "amici/sundials_linsol_wrapper.h" - #include #include #include #include -#define ONE RCONST(1.0) +#define ONE RCONST(1.0) namespace amici { -void IDASolver::init(AmiVector *x, AmiVector *dx, realtype t) { - int status = IDAInit(solverMemory.get(), fxdot, t, x->getNVector(), dx->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAInit"); +/* + * The following static members are callback function to CVODES. + * Their signatures must not be changes. + */ + +static int fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, + void *user_data); + +static int fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xdot, SUNMatrix J, void *user_data, N_Vector tmp1, + N_Vector tmp2, N_Vector tmp3); + +static int fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xdot, SUNMatrix J, void *user_data, + N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + +static int fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector xBdot, SUNMatrix JB, + void *user_data, N_Vector tmp1B, N_Vector tmp2B, + N_Vector tmp3B); + +static int fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector xBdot, + SUNMatrix JB, void *user_data, N_Vector tmp1B, + N_Vector tmp2B, N_Vector tmp3B); + +static int fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xdot, SUNMatrix J, void *user_data, + N_Vector tmp1, N_Vector tmp2, N_Vector tmp3); + +static int fJBandB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector xBdot, SUNMatrix JB, + void *user_data, N_Vector tmp1B, N_Vector tmp2B, + N_Vector tmp3B); + +static int fJv(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, + N_Vector v, N_Vector Jv, realtype cj, void *user_data, + N_Vector tmp1, N_Vector tmp2); + +static int fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector xBdot, N_Vector vB, N_Vector JvB, + realtype cj, void *user_data, N_Vector tmpB1, + N_Vector tmpB2); + +static int froot(realtype t, N_Vector x, N_Vector dx, realtype *root, + void *user_data); + +static int fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector xBdot, void *user_data); + +static int fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector qBdot, void *user_data); + +static int fsxdot(int Ns, realtype t, N_Vector x, N_Vector dx, + N_Vector xdot, N_Vector *sx, N_Vector *sdx, + N_Vector *sxdot, void *user_data, N_Vector tmp1, + N_Vector tmp2, N_Vector tmp3); + + +/* Function implementations */ + +void IDASolver::init(const realtype t0, const AmiVector &x0, + const AmiVector &dx0) const { + int status; + solverWasCalledF = false; + t = t0; + x.copy(x0); + dx.copy(dx0); + if (getInitDone()) { + status = + IDAReInit(solverMemory.get(), t, x.getNVector(), dx.getNVector()); + } else { + status = IDAInit(solverMemory.get(), fxdot, t, x.getNVector(), + dx.getNVector()); + setInitDone(); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAInit"); } -void IDASolver::binit(int which, AmiVector *xB, AmiVector *dxB, realtype t) { - int status = IDAInitB(solverMemory.get(), which, fxBdot, t, xB->getNVector(), dxB->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAInitB"); + +void IDASolver::sensInit1(const AmiVectorArray &sx0, + const AmiVectorArray &sdx0) const { + int status; + sx.copy(sx0); + sdx.copy(sdx0); + if (getSensInitDone()) { + status = IDASensReInit(solverMemory.get(), + static_cast(getSensitivityMethod()), + sx.getNVectorArray(), sdx.getNVectorArray()); + } else { + status = IDASensInit(solverMemory.get(), nplist(), + static_cast(getSensitivityMethod()), fsxdot, + sx.getNVectorArray(), sdx.getNVectorArray()); + setSensInitDone(); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASensInit"); } -void IDASolver::qbinit(int which, AmiVector *qBdot) { - int status = IDAQuadInitB(solverMemory.get(), which, fqBdot, qBdot->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAQuadInitB"); + +void IDASolver::binit(const int which, const realtype tf, const AmiVector &xB0, + const AmiVector &dxB0) const { + int status; + xB.copy(xB0); + dxB.copy(dxB0); + if (getInitDoneB(which)) + status = IDAReInitB(solverMemory.get(), which, tf, xB.getNVector(), + dxB.getNVector()); + else { + + status = IDAInitB(solverMemory.get(), which, fxBdot, tf, + xB.getNVector(), dxB.getNVector()); + setInitDoneB(which); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAInitB"); } -void IDASolver::rootInit(int ne) { - int status = IDARootInit(solverMemory.get(), ne, froot); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDARootInit"); + +void IDASolver::qbinit(const int which, const AmiVector &xQB0) const { + int status; + xQB.copy(xQB0); + if (getQuadInitDoneB(which)) + status = IDAQuadReInitB(solverMemory.get(), which, xQB.getNVector()); + else { + status = + IDAQuadInitB(solverMemory.get(), which, fqBdot, xQB.getNVector()); + setQuadInitDoneB(which); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAQuadInitB"); } -void IDASolver::sensInit1(AmiVectorArray *sx, AmiVectorArray *sdx, int nplist) { - int status = IDASensInit(solverMemory.get(), nplist, static_cast(getSensitivityMethod()), fsxdot, - sx->getNVectorArray(),sdx->getNVectorArray()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASensInit"); + +void IDASolver::rootInit(int ne) const { + int status = IDARootInit(solverMemory.get(), ne, froot); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDARootInit"); } -void IDASolver::setDenseJacFn() { + +void IDASolver::setDenseJacFn() const { int status = IDASetJacFn(solverMemory.get(), fJ); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDADlsSetDenseJacFn"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDADlsSetDenseJacFn"); } -void IDASolver::setSparseJacFn() { +void IDASolver::setSparseJacFn() const { int status = IDASetJacFn(solverMemory.get(), fJSparse); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASlsSetSparseJacFn"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASlsSetSparseJacFn"); } -void IDASolver::setBandJacFn() { + +void IDASolver::setBandJacFn() const { int status = IDASetJacFn(solverMemory.get(), fJBand); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDADlsSetBandJacFn"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDADlsSetBandJacFn"); } -void IDASolver::setJacTimesVecFn() { +void IDASolver::setJacTimesVecFn() const { int status = IDASetJacTimes(solverMemory.get(), nullptr, fJv); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASpilsSetJacTimesVecFn"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASpilsSetJacTimesVecFn"); } -void IDASolver::setDenseJacFnB(int which) { + +void IDASolver::setDenseJacFnB(const int which) const { int status = IDASetJacFnB(solverMemory.get(), which, fJB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDADlsSetDenseJacFnB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDADlsSetDenseJacFnB"); } -void IDASolver::setSparseJacFnB(int which) { + +void IDASolver::setSparseJacFnB(const int which) const { int status = IDASetJacFnB(solverMemory.get(), which, fJSparseB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASlsSetSparseJacFnB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASlsSetSparseJacFnB"); } -void IDASolver::setBandJacFnB(int which) { + +void IDASolver::setBandJacFnB(const int which) const { int status = IDASetJacFnB(solverMemory.get(), which, fJBandB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDADlsSetBandJacFnB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDADlsSetBandJacFnB"); } -void IDASolver::setJacTimesVecFnB(int which) { + +void IDASolver::setJacTimesVecFnB(const int which) const { int status = IDASetJacTimesB(solverMemory.get(), which, nullptr, fJvB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASpilsSetJacTimesVecFnB"); -} -Solver *IDASolver::clone() const { - return new IDASolver(*this); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASpilsSetJacTimesVecFnB"); } -void IDASolver::allocateSolver() { - solverMemory = std::unique_ptr>(IDACreate(), - [](void *ptr) { IDAFree(&ptr); }); +Solver *IDASolver::clone() const { return new IDASolver(*this); } + +void IDASolver::allocateSolver() const { + if (!solverMemory) + solverMemory = std::unique_ptr>( + IDACreate(), [](void *ptr) { IDAFree(&ptr); }); } -void IDASolver::setSStolerances(double rtol, double atol) { +void IDASolver::setSStolerances(const realtype rtol, + const realtype atol) const { int status = IDASStolerances(solverMemory.get(), rtol, atol); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASStolerances"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASStolerances"); } -void IDASolver::setSensSStolerances(double rtol, double *atol) { - int status = IDASensSStolerances(solverMemory.get(), rtol, atol); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASensEEtolerances"); +void IDASolver::setSensSStolerances(const realtype rtol, + const realtype *atol) const { + int status = IDASensSStolerances(solverMemory.get(), rtol, + const_cast(atol)); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASensEEtolerances"); } -void IDASolver::setSensErrCon(bool error_corr) { +void IDASolver::setSensErrCon(const bool error_corr) const { int status = IDASetSensErrCon(solverMemory.get(), error_corr); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetSensErrCon"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetSensErrCon"); } -void IDASolver::setQuadErrConB(int which, bool flag) { + +void IDASolver::setQuadErrConB(const int which, const bool flag) const { int status = IDASetQuadErrConB(solverMemory.get(), which, flag); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetQuadErrConB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetQuadErrConB"); } + void IDASolver::getRootInfo(int *rootsfound) const { int status = IDAGetRootInfo(solverMemory.get(), rootsfound); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetRootInfo"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetRootInfo"); } -void IDASolver::setErrHandlerFn() { - int status = IDASetErrHandlerFn(solverMemory.get(), wrapErrHandlerFn, nullptr); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetErrHandlerFn"); + +void IDASolver::setErrHandlerFn() const { + int status = + IDASetErrHandlerFn(solverMemory.get(), wrapErrHandlerFn, nullptr); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetErrHandlerFn"); } -void IDASolver::setUserData(Model *model) { + +void IDASolver::setUserData(Model *model) const { int status = IDASetUserData(solverMemory.get(), model); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetUserData"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetUserData"); } -void IDASolver::setUserDataB(int which, Model *model) { + +void IDASolver::setUserDataB(int which, Model *model) const { int status = IDASetUserDataB(solverMemory.get(), which, model); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetUserDataB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetUserDataB"); } -void IDASolver::setMaxNumSteps(long mxsteps) { + +void IDASolver::setMaxNumSteps(const long int mxsteps) const { int status = IDASetMaxNumSteps(solverMemory.get(), mxsteps); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetMaxNumSteps"); -} -void IDASolver::setStabLimDet(int stldet) { + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetMaxNumSteps"); } -void IDASolver::setStabLimDetB(int which, int stldet) { -} +void IDASolver::setStabLimDet(const int stldet) const {} -void IDASolver::setId(Model *model) { +void IDASolver::setStabLimDetB(const int which, const int stldet) const {} - N_Vector id = N_VMake_Serial(model->nx_solver,const_cast(model->idlist.data())); +void IDASolver::setId(const Model *model) const { + + N_Vector id = N_VMake_Serial(model->nx_solver, + const_cast(model->idlist.data())); int status = IDASetId(solverMemory.get(), id); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetMaxNumSteps"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetMaxNumSteps"); N_VDestroy_Serial(id); } -void IDASolver::setSuppressAlg(bool flag) { +void IDASolver::setSuppressAlg(const bool flag) const { int status = IDASetSuppressAlg(solverMemory.get(), flag); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetSuppressAlg"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetSuppressAlg"); } - -void IDASolver::resetState(void *ami_mem, N_Vector yy0, N_Vector yp0) { - + +void IDASolver::resetState(void *ami_mem, const_N_Vector yy0, + const_N_Vector yp0) const { + auto ida_mem = static_cast(ami_mem); /* here we force the order in the next step to zero, and update the phi arrays, this is largely copied from IDAReInit with explanations from idas_impl.h */ - + /* Initialize the phi array */ - + N_VScale(ONE, yy0, ida_mem->ida_phi[0]); N_VScale(ONE, yp0, ida_mem->ida_phi[1]); - + /* Set step parameters */ - + /* current order */ ida_mem->ida_kk = 0; } -void IDASolver::reInitPostProcessF(realtype *t, AmiVector *yout, - AmiVector *ypout, realtype tnext) { - reInitPostProcess(solverMemory.get(), t, yout, ypout, tnext); +void IDASolver::reInitPostProcessF(const realtype tnext) const { + reInitPostProcess(solverMemory.get(), &t, &x, &dx, tnext); } -void IDASolver::reInitPostProcessB(int which, realtype *t, AmiVector *yBout, - AmiVector *ypBout, realtype tnext) { - reInitPostProcess(IDAGetAdjIDABmem(solverMemory.get(), which), - t, yBout, ypBout, tnext); +void IDASolver::reInitPostProcessB(const realtype tnext) const { + realtype tBret; + auto ida_mem = static_cast(solverMemory.get()); + auto idaadj_mem = ida_mem->ida_adj_mem; + auto idaB_mem = idaadj_mem->IDAB_mem; + // loop over all backward problems + while (idaB_mem != nullptr) { + // store current backward problem in ca_mem to make it accessible in + // adjoint rhs wrapper functions + idaadj_mem->ia_bckpbCrt = idaB_mem; + reInitPostProcess(static_cast(idaB_mem->IDA_mem), &tBret, &xB, + &dxB, tnext); + // idaB_mem->ida_tout = tBret; + idaB_mem = idaB_mem->ida_next; + } + forceReInitPostProcessB = false; } void IDASolver::reInitPostProcess(void *ami_mem, realtype *t, AmiVector *yout, AmiVector *ypout, - realtype tout) { + realtype tout) const { auto ida_mem = static_cast(ami_mem); auto nst_tmp = ida_mem->ida_nst; ida_mem->ida_nst = 0; - + auto status = IDASetStopTime(ida_mem, tout); if(status != IDA_SUCCESS) throw IDAException(status, "CVodeSetStopTime"); - + status = IDASolve(ami_mem, tout, t, yout->getNVector(), ypout->getNVector(), IDA_ONE_STEP); - + if(status != IDA_SUCCESS) throw IDAException(status, "reInitPostProcess"); - + ida_mem->ida_nst = nst_tmp+1; if (ida_mem->ida_adjMallocDone == SUNTRUE) { /* add new step to history array, this is copied from CVodeF */ auto ia_mem = ida_mem->ida_adj_mem; auto dt_mem = ia_mem->dt_mem; - + if (ida_mem->ida_nst % ia_mem->ia_nsteps == 0) { /* currently not implemented, we should never get here as we limit cv_mem->cv_nst < ca_mem->ca_nsteps, keeping this for future regression */ throw IDAException(AMICI_ERROR, "reInitPostProcess"); } - + /* Load next point in dt_mem */ dt_mem[ida_mem->ida_nst % ia_mem->ia_nsteps]->t = *t; ia_mem->ia_storePnt(ida_mem, dt_mem[ida_mem->ida_nst % ia_mem->ia_nsteps]); - + /* Set t1 field of the current ckeck point structure for the case in which there will be no future check points */ ia_mem->ck_mem->ck_t1 = *t; - + /* tfinal is now set to *tret */ ia_mem->ia_tfinal = *t; } } -void IDASolver::reInit(realtype t0, AmiVector *yy0, AmiVector *yp0) { +void IDASolver::reInit(const realtype t0, const AmiVector &yy0, + const AmiVector &yp0) const { + auto ida_mem = static_cast(solverMemory.get()); - /* set time */ ida_mem->ida_tn = t0; - resetState(ida_mem, yy0->getNVector(), yp0->getNVector()); + if (solverWasCalledF) + forceReInitPostProcessF = true; + x.copy(yy0); + dx.copy(yp0); + resetState(ida_mem, x.getNVector(), xB.getNVector()); } -void IDASolver::sensReInit(AmiVectorArray *yS0, AmiVectorArray *ypS0) { + +void IDASolver::sensReInit(const AmiVectorArray &yyS0, + const AmiVectorArray &ypS0) const { auto ida_mem = static_cast(solverMemory.get()); /* Initialize znS[0] in the history array */ - - for (int is=0; isida_Ns; is++) + for (int is = 0; is < nplist(); is++) ida_mem->ida_cvals[is] = ONE; - - auto status = N_VScaleVectorArray(ida_mem->ida_Ns, ida_mem->ida_cvals, - yS0->getNVectorArray(), - ida_mem->ida_phiS[0]); - if(status != IDA_SUCCESS) - throw IDAException(IDA_VECTOROP_ERR,"IDASensReInit"); - - status = N_VScaleVectorArray(ida_mem->ida_Ns, ida_mem->ida_cvals, - ypS0->getNVectorArray(), - ida_mem->ida_phiS[1]); - if(status != IDA_SUCCESS) - throw IDAException(IDA_VECTOROP_ERR,"IDASensReInit"); + if (solverWasCalledF) + forceReInitPostProcessF = true; + sx.copy(yyS0); + sdx.copy(ypS0); + auto status = + N_VScaleVectorArray(nplist(), ida_mem->ida_cvals, sx.getNVectorArray(), + ida_mem->ida_phiS[0]); + if (status != IDA_SUCCESS) + throw IDAException(IDA_VECTOROP_ERR, "IDASensReInit"); + status = N_VScaleVectorArray(nplist(), ida_mem->ida_cvals, + sdx.getNVectorArray(), ida_mem->ida_phiS[1]); + if (status != IDA_SUCCESS) + throw IDAException(IDA_VECTOROP_ERR, "IDASensReInit"); } -void IDASolver::setSensParams(realtype *p, realtype *pbar, int *plist) { - int status = IDASetSensParams(solverMemory.get(), p, pbar, plist); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetSensParams"); + +void IDASolver::reInitB(const int which, const realtype tB0, + const AmiVector &yyB0, const AmiVector &ypB0) const { + + auto ida_memB = + static_cast(IDAGetAdjIDABmem(solverMemory.get(), which)); + if (solverWasCalledB) + forceReInitPostProcessB = true; + ida_memB->ida_tn = tB0; + xB.copy(yyB0); + dxB.copy(ypB0); + resetState(ida_memB, xB.getNVector(), dxB.getNVector()); } -void IDASolver::getDky(realtype t, int k, AmiVector *dky) const { - int status = IDAGetDky(solverMemory.get(), t, k, dky->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetDky"); + +void IDASolver::quadReInitB(const int which, const AmiVector &yQB0) const { + auto ida_memB = + static_cast(IDAGetAdjIDABmem(solverMemory.get(), which)); + if (solverWasCalledB) + forceReInitPostProcessB = true; + xQB.copy(yQB0); + N_VScale(ONE, xQB.getNVector(), ida_memB->ida_phiQ[0]); } -void IDASolver::getSens(realtype *tret, AmiVectorArray *yySout) const { - int status = IDAGetSens(solverMemory.get(), tret, yySout->getNVectorArray()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetSens"); + +void IDASolver::setSensParams(const realtype *p, const realtype *pbar, + const int *plist) const { + int status = IDASetSensParams(solverMemory.get(), const_cast(p), + const_cast(pbar), + const_cast(plist)); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetSensParams"); } -void IDASolver::adjInit() { - int status = IDAAdjInit(solverMemory.get(), static_cast(maxsteps), static_cast(interpType)); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAAdjInit"); +void IDASolver::getDky(const realtype t, const int k) const { + int status = IDAGetDky(solverMemory.get(), t, k, dky.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetDky"); +} + +void IDASolver::getSens() const { + realtype tDummy = 0; + int status = IDAGetSens(solverMemory.get(), &tDummy, sx.getNVectorArray()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetSens"); +} + +void IDASolver::getSensDky(const realtype t, const int k) const { + int status = IDAGetSensDky(solverMemory.get(), t, k, sx.getNVectorArray()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetSens"); +} + +void IDASolver::getB(const int which) const { + realtype tDummy = 0; + int status = IDAGetB(solverMemory.get(), which, &tDummy, xB.getNVector(), + dxB.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetB"); +} + +void IDASolver::getDkyB(const realtype t, int k, const int which) const { + int status = IDAGetDky(IDAGetAdjIDABmem(solverMemory.get(), which), t, k, + dky.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetB"); +} +void IDASolver::getQuadB(int which) const { + realtype tDummy = 0; + int status = + IDAGetQuadB(solverMemory.get(), which, &tDummy, xQB.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetQuadB"); +} + +void IDASolver::getQuadDkyB(const realtype t, int k, const int which) const { + int status = IDAGetQuadDky(IDAGetAdjIDABmem(solverMemory.get(), which), t, + k, xQB.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetB"); +} + +void IDASolver::adjInit() const { + int status; + if (getAdjInitDone()) { + status = IDAAdjReInit(solverMemory.get()); + } else { + status = IDAAdjInit(solverMemory.get(), static_cast(maxsteps), + static_cast(interpType)); + setAdjInitDone(); + } + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAAdjInit"); } -void IDASolver::allocateSolverB(int *which) { + +void IDASolver::allocateSolverB(int *which) const { + if (!solverMemoryB.empty()) { + *which = 0; + return; + } int status = IDACreateB(solverMemory.get(), which); if (*which + 1 > static_cast(solverMemoryB.size())) solverMemoryB.resize(*which + 1); - solverMemoryB.at(*which) = std::unique_ptr> - (getAdjBmem(solverMemory.get(), *which), [](void *ptr){}); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDACreateB"); -} -void IDASolver::reInitB(int which, realtype tB0, AmiVector *yyB0, - AmiVector *ypB0) { - auto ida_memB = static_cast(IDAGetAdjIDABmem(solverMemory.get(), which)); - ida_memB->ida_tn = tB0; - resetState(ida_memB, yyB0->getNVector(), ypB0->getNVector()); + solverMemoryB.at(*which) = + std::unique_ptr>( + getAdjBmem(solverMemory.get(), *which), [](void *ptr) {}); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDACreateB"); } -void IDASolver::setSStolerancesB(int which, realtype relTolB, realtype absTolB) { + +void IDASolver::setSStolerancesB(const int which, const realtype relTolB, + const realtype absTolB) const { int status = IDASStolerancesB(solverMemory.get(), which, relTolB, absTolB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASStolerancesB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASStolerancesB"); } -void IDASolver::quadReInitB(int which, AmiVector *yQB0) { - auto ida_memB = static_cast(IDAGetAdjIDABmem(solverMemory.get(), which)); - N_VScale(ONE, yQB0->getNVector(), ida_memB->ida_phiQ[0]); -} -void IDASolver::quadSStolerancesB(int which, realtype reltolQB, - realtype abstolQB) { - int status = IDAQuadSStolerancesB(solverMemory.get(), which, reltolQB, abstolQB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAQuadSStolerancesB"); + +void IDASolver::quadSStolerancesB(const int which, const realtype reltolQB, + const realtype abstolQB) const { + int status = + IDAQuadSStolerancesB(solverMemory.get(), which, reltolQB, abstolQB); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAQuadSStolerancesB"); } -int IDASolver::solve(realtype tout, AmiVector *yret, AmiVector *ypret, - realtype *tret, int itask) { - int status = IDASolve(solverMemory.get(), tout, tret, yret->getNVector(), ypret->getNVector(), itask); - if(status<0) { - throw IntegrationFailure(status,*tret); - } - solverWasCalled = true; +int IDASolver::solve(const realtype tout, const int itask) const { + if (forceReInitPostProcessF) + reInitPostProcessF(tout); + int status = IDASolve(solverMemory.get(), tout, &t, x.getNVector(), + dx.getNVector(), itask); + solverWasCalledF = true; + if (status < 0) // status > 0 is okay and is used for e.g. root return + throw IntegrationFailure(status, t); return status; } -int IDASolver::solveF(realtype tout, AmiVector *yret, AmiVector *ypret, - realtype *tret, int itask, int *ncheckPtr) { - int status = IDASolveF(solverMemory.get(), tout, tret, yret->getNVector(), ypret->getNVector(), itask, ncheckPtr); - if(status<0) { - throw IntegrationFailure(status,*tret); - } - solverWasCalled = true; +int IDASolver::solveF(const realtype tout, const int itask, + int *ncheckPtr) const { + if (forceReInitPostProcessF) + reInitPostProcessF(tout); + int status = IDASolveF(solverMemory.get(), tout, &t, x.getNVector(), + xB.getNVector(), itask, ncheckPtr); + solverWasCalledF = true; + if (status < 0) // status > 0 is okay and is used for e.g. root return + throw IntegrationFailure(status, t); return status; } -void IDASolver::solveB(realtype tBout, int itaskB) { + +void IDASolver::solveB(const realtype tBout, const int itaskB) const { + if (forceReInitPostProcessB) + reInitPostProcessB(tBout); int status = IDASolveB(solverMemory.get(), tBout, itaskB); - if(status != IDA_SUCCESS) - throw IntegrationFailure(status,tBout); + solverWasCalledB = true; + if (status != IDA_SUCCESS) + throw IntegrationFailure(status, tBout); } -void IDASolver::setMaxNumStepsB(int which, long mxstepsB) { + +void IDASolver::setMaxNumStepsB(const int which, + const long int mxstepsB) const { int status = IDASetMaxNumStepsB(solverMemory.get(), which, mxstepsB); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetMaxNumStepsB"); -} -void IDASolver::getB(int which, realtype *tret, AmiVector *yy, AmiVector *yp) const { - int status = IDAGetB(solverMemory.get(), which, tret, yy->getNVector(), yp->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetB"); -} -void IDASolver::getQuadB(int which, realtype *tret, AmiVector *qB) const { - int status = IDAGetQuadB(solverMemory.get(), which, tret, qB->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetQuadB"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetMaxNumStepsB"); } -void IDASolver::diag() { +void IDASolver::diag() const { throw AmiException("Diag Solver was not implemented for DAEs"); } -void IDASolver::diagB(int /*which*/) { +void IDASolver::diagB(const int /*which*/) const { throw AmiException("Diag Solver was not implemented for DAEs"); } -void IDASolver::getNumSteps(void *ami_mem, long *numsteps) const { - int status = IDAGetNumSteps(ami_mem, numsteps); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetNumSteps"); +void IDASolver::getNumSteps(const void *ami_mem, long int *numsteps) const { + int status = IDAGetNumSteps(const_cast(ami_mem), numsteps); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetNumSteps"); } -void IDASolver::getNumRhsEvals(void *ami_mem, long *numrhsevals) const { - int status = IDAGetNumResEvals(ami_mem, numrhsevals); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetNumResEvals"); + +void IDASolver::getNumRhsEvals(const void *ami_mem, + long int *numrhsevals) const { + int status = IDAGetNumResEvals(const_cast(ami_mem), numrhsevals); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetNumResEvals"); } -void IDASolver::getNumErrTestFails(void *ami_mem, long *numerrtestfails) const { - int status = IDAGetNumErrTestFails(ami_mem, numerrtestfails); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetNumErrTestFails"); + +void IDASolver::getNumErrTestFails(const void *ami_mem, + long int *numerrtestfails) const { + int status = + IDAGetNumErrTestFails(const_cast(ami_mem), numerrtestfails); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetNumErrTestFails"); } -void IDASolver::getNumNonlinSolvConvFails(void *ami_mem, - long *numnonlinsolvconvfails) const { - int status = IDAGetNumNonlinSolvConvFails(ami_mem, numnonlinsolvconvfails); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetNumNonlinSolvConvFails"); + +void IDASolver::getNumNonlinSolvConvFails( + const void *ami_mem, long int *numnonlinsolvconvfails) const { + int status = IDAGetNumNonlinSolvConvFails(const_cast(ami_mem), + numnonlinsolvconvfails); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetNumNonlinSolvConvFails"); } -void IDASolver::getLastOrder(void *ami_mem, int *order) const { - int status = IDAGetLastOrder(ami_mem, order); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDAGetLastOrder"); + +void IDASolver::getLastOrder(const void *ami_mem, int *order) const { + int status = IDAGetLastOrder(const_cast(ami_mem), order); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDAGetLastOrder"); } -void *IDASolver::getAdjBmem(void *ami_mem, int which) { + +void *IDASolver::getAdjBmem(void *ami_mem, int which) const { return IDAGetAdjIDABmem(ami_mem, which); } -void IDASolver::calcIC(realtype tout1, AmiVector *x, AmiVector *dx) { +void IDASolver::calcIC(realtype tout1) const { int status = IDACalcIC(solverMemory.get(), IDA_YA_YDP_INIT, tout1); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDACalcIC"); - status = IDAGetConsistentIC(solverMemory.get(), x->getNVector(), dx->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDACalcIC"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDACalcIC"); + status = + IDAGetConsistentIC(solverMemory.get(), x.getNVector(), dx.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDACalcIC"); } -void IDASolver::calcICB(int which, realtype tout1, AmiVector *xB, - AmiVector *dxB) { - int status = IDACalcICB(solverMemory.get(), which, tout1, xB->getNVector(), dxB->getNVector()); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDACalcICB"); +void IDASolver::calcICB(const int which, const realtype tout1) const { + int status = IDACalcICB(solverMemory.get(), which, tout1, xB.getNVector(), + dxB.getNVector()); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDACalcICB"); } -void IDASolver::setStopTime(realtype tstop) { +void IDASolver::setStopTime(const realtype tstop) const { int status = IDASetStopTime(solverMemory.get(), tstop); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDASetStopTime"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDASetStopTime"); } -void IDASolver::turnOffRootFinding() { +void IDASolver::turnOffRootFinding() const { int status = IDARootInit(solverMemory.get(), 0, nullptr); - if(status != IDA_SUCCESS) - throw IDAException(status,"IDARootInit"); + if (status != IDA_SUCCESS) + throw IDAException(status, "IDARootInit"); } -int IDASolver::nplist() const { +const Model *IDASolver::getModel() const { if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto IDA_mem = (IDAMem) solverMemory.get(); - return IDA_mem->ida_Ns; + throw AmiException( + "Solver has not been allocated, information is not available"); + auto ida_mem = static_cast(solverMemory.get()); + return static_cast(ida_mem->ida_user_data); } -int IDASolver::nx() const { - if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto IDA_mem = (IDAMem) solverMemory.get(); - return NV_LENGTH_S(IDA_mem->ida_yy0); +void IDASolver::setLinearSolver() const { + int status = IDASetLinearSolver(solverMemory.get(), linearSolver->get(), + linearSolver->getMatrix()); + if (status != IDA_SUCCESS) + throw IDAException(status, "setLinearSolver"); } -const Model *IDASolver::getModel() const { - if (!solverMemory) - throw AmiException("Solver has not been allocated, information is not available"); - auto ida_mem = (IDAMem) solverMemory.get(); - return static_cast(ida_mem->ida_user_data); +void IDASolver::setLinearSolverB(const int which) const { + int status = + IDASetLinearSolverB(solverMemoryB[which].get(), which, + linearSolverB->get(), linearSolverB->getMatrix()); + if (status != IDA_SUCCESS) + throw IDAException(status, "setLinearSolverB"); } -bool IDASolver::getMallocDone() const { - if (!solverMemory) - return false; - auto ida_mem = (IDAMem) solverMemory.get(); - return ida_mem->ida_MallocDone; +void IDASolver::setNonLinearSolver() const { + int status = + IDASetNonlinearSolver(solverMemory.get(), nonLinearSolver->get()); + if (status != IDA_SUCCESS) + throw CvodeException(status, "CVodeSetNonlinearSolver"); } -bool IDASolver::getAdjMallocDone() const { - if (!solverMemory) - return false; - auto ida_mem = (IDAMem) solverMemory.get(); - return ida_mem->ida_adjMallocDone; +void IDASolver::setNonLinearSolverSens() const { + if (getSensitivityOrder() < SensitivityOrder::first) + return; + if (getSensitivityMethod() != SensitivityMethod::forward) + return; + + int status = IDA_SUCCESS; + + switch (ism) { + case InternalSensitivityMethod::staggered: + status = IDASetNonlinearSolverSensStg(solverMemory.get(), + nonLinearSolverSens->get()); + break; + case InternalSensitivityMethod::simultaneous: + status = IDASetNonlinearSolverSensSim(solverMemory.get(), + nonLinearSolverSens->get()); + break; + case InternalSensitivityMethod::staggered1: + default: + throw AmiException( + "Unsupported internal sensitivity method selected: %d", ism); + } + + if (status != IDA_SUCCESS) + throw CvodeException(status, "CVodeSolver::setNonLinearSolverSens"); +} + +void IDASolver::setNonLinearSolverB(int which) const { + int status = IDASetNonlinearSolverB(solverMemory.get(), which, + nonLinearSolverB->get()); + if (status != IDA_SUCCESS) + throw CvodeException(status, "CVodeSetNonlinearSolverB"); } -/** Jacobian of xdot with respect to states x +/** + * @brief Jacobian of xdot with respect to states x * @param N number of state variables * @param t timepoint * @param cj scaling factor, inverse of the step size @@ -470,15 +725,17 @@ bool IDASolver::getAdjMallocDone() const { * @param tmp3 temporary storage vector * @return status flag indicating successful execution **/ -int IDASolver::fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xdot, SUNMatrix J, void *user_data, N_Vector /*tmp1*/, - N_Vector /*tmp2*/, N_Vector /*tmp3*/) { +int fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xdot, SUNMatrix J, void *user_data, + N_Vector /*tmp1*/, N_Vector /*tmp2*/, N_Vector /*tmp3*/) { + auto model = static_cast(user_data); model->fJ(t, cj, x, dx, xdot, J); - return model->checkFinite(SM_ROWS_D(J), SM_DATA_D(J), "Jacobian"); + return model->checkFinite(gsl::make_span(J), "Jacobian"); } -/** Jacobian of xBdot with respect to adjoint state xB +/** + * @brief Jacobian of xBdot with respect to adjoint state xB * @param NeqBdot number of adjoint state variables * @param t timepoint * @param cj scaling factor, inverse of the step size @@ -494,16 +751,18 @@ int IDASolver::fJ(realtype t, realtype cj, N_Vector x, N_Vector dx, * @param tmp3B temporary storage vector * @return status flag indicating successful execution **/ -int IDASolver::fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, SUNMatrix JB, - void *user_data, N_Vector /*tmp1B*/, N_Vector /*tmp2B*/, - N_Vector /*tmp3B*/) { +int fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, SUNMatrix JB, + void *user_data, N_Vector /*tmp1B*/, N_Vector /*tmp2B*/, + N_Vector /*tmp3B*/) { + auto model = static_cast(user_data); model->fJB(t, cj, x, dx, xB, dxB, JB); - return model->checkFinite(SM_ROWS_D(JB), SM_DATA_D(JB), "Jacobian"); + return model->checkFinite(gsl::make_span(JB), "Jacobian"); } -/** J in sparse form (for sparse solvers from the SuiteSparse Package) +/** + * @brief J in sparse form (for sparse solvers from the SuiteSparse Package) * @param t timepoint * @param cj scalar in Jacobian (inverse stepsize) * @param x Vector with the states @@ -516,70 +775,17 @@ int IDASolver::fJB(realtype t, realtype cj, N_Vector x, N_Vector dx, * @param tmp3 temporary storage vector * @return status flag indicating successful execution */ -int IDASolver::fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector /*xdot*/, SUNMatrix J, void *user_data, - N_Vector /*tmp1*/, N_Vector /*tmp2*/, N_Vector /*tmp3*/) { +int fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector /*xdot*/, SUNMatrix J, void *user_data, + N_Vector /*tmp1*/, N_Vector /*tmp2*/, + N_Vector /*tmp3*/) { auto model = static_cast(user_data); model->fJSparse(t, cj, x, dx, J); - return model->checkFinite(SM_NNZ_S(J), SM_DATA_S(J), "Jacobian"); -} - -void IDASolver::setLinearSolver() -{ - int status = IDASetLinearSolver(solverMemory.get(), linearSolver->get(), linearSolver->getMatrix()); - if(status != IDA_SUCCESS) - throw IDAException(status, "setLinearSolver"); - -} - -void IDASolver::setLinearSolverB(int which) -{ - int status = IDASetLinearSolverB(solverMemoryB[which].get(), which, linearSolverB->get(), linearSolverB->getMatrix()); - if(status != IDA_SUCCESS) - throw IDAException(status, "setLinearSolverB"); - -} - -void IDASolver::setNonLinearSolver() -{ - int status = IDASetNonlinearSolver(solverMemory.get(), nonLinearSolver->get()); - if(status != IDA_SUCCESS) - throw CvodeException(status,"CVodeSetNonlinearSolver"); -} - -void IDASolver::setNonLinearSolverSens() -{ - if(getSensitivityOrder() < SensitivityOrder::first) - return; - if(getSensitivityMethod() != SensitivityMethod::forward) - return; - - int status = IDA_SUCCESS; - - switch (ism) { - case InternalSensitivityMethod::staggered: - status = IDASetNonlinearSolverSensStg(solverMemory.get(), nonLinearSolverSens->get()); - break; - case InternalSensitivityMethod::simultaneous: - status = IDASetNonlinearSolverSensSim(solverMemory.get(), nonLinearSolverSens->get()); - break; - case InternalSensitivityMethod::staggered1: - default: - throw AmiException("Unsupported internal sensitivity method selected: %d", ism); - } - - if(status != IDA_SUCCESS) - throw CvodeException(status,"CVodeSolver::setNonLinearSolverSens"); + return model->checkFinite(gsl::make_span(J), "Jacobian"); } -void IDASolver::setNonLinearSolverB(int which) -{ - int status = IDASetNonlinearSolverB(solverMemory.get(), which, nonLinearSolverB->get()); - if(status != IDA_SUCCESS) - throw CvodeException(status,"CVodeSetNonlinearSolverB"); -} - -/** JB in sparse form (for sparse solvers from the SuiteSparse Package) +/** + * @brief JB in sparse form (for sparse solvers from the SuiteSparse Package) * @param t timepoint * @param cj scalar in Jacobian * @param x Vector with the states @@ -594,16 +800,17 @@ void IDASolver::setNonLinearSolverB(int which) * @param tmp3B temporary storage vector * @return status flag indicating successful execution */ -int IDASolver::fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, - N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, - SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, - N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { +int fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, + N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, + SUNMatrix JB, void *user_data, N_Vector /*tmp1B*/, + N_Vector /*tmp2B*/, N_Vector /*tmp3B*/) { auto model = static_cast(user_data); model->fJSparseB(t, cj, x, dx, xB, dxB, JB); - return model->checkFinite(SM_NNZ_S(JB), SM_DATA_S(JB), "Jacobian"); + return model->checkFinite(gsl::make_span(JB), "Jacobian"); } -/** J in banded form (for banded solvers) +/** + * @brief J in banded form (for banded solvers) * @param N number of states * @param mupper upper matrix bandwidth * @param mlower lower matrix bandwidth @@ -619,13 +826,14 @@ int IDASolver::fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx, * @param tmp3 temporary storage vector * @return status flag indicating successful execution */ -int IDASolver::fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, +int fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xdot, SUNMatrix J, void *user_data, N_Vector tmp1, N_Vector tmp2, N_Vector tmp3) { return fJ(t, cj, x, dx, xdot, J, user_data, tmp1, tmp2, tmp3); } -/** JB in banded form (for banded solvers) +/** + * @brief JB in banded form (for banded solvers) * @param NeqBdot number of states * @param mupper upper matrix bandwidth * @param mlower lower matrix bandwidth @@ -643,157 +851,175 @@ int IDASolver::fJBand(realtype t, realtype cj, N_Vector x, N_Vector dx, * @param tmp3B temporary storage vector * @return status flag indicating successful execution */ -int IDASolver::fJBandB(realtype t, realtype cj, N_Vector x, N_Vector dx, +int fJBandB(realtype t, realtype cj, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector xBdot, SUNMatrix JB, void *user_data, N_Vector tmp1B, N_Vector tmp2B, N_Vector tmp3B) { return fJB(t, cj, x, dx, xB, dxB, xBdot, JB, user_data, tmp1B, tmp2B, tmp3B); } - /** Matrix vector product of J with a vector v (for iterative solvers) - * @param t timepoint @type realtype - * @param cj scaling factor, inverse of the step size - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - * @param v Vector with which the Jacobian is multiplied - * @param Jv Vector to which the Jacobian vector product will be - *written - * @param user_data object with user input @type Model_DAE - * @param tmp1 temporary storage vector - * @param tmp2 temporary storage vector - * @return status flag indicating successful execution - **/ - int IDASolver::fJv(realtype t, N_Vector x, N_Vector dx, N_Vector /*xdot*/, N_Vector v, N_Vector Jv, - realtype cj, void *user_data, N_Vector /*tmp1*/, N_Vector /*tmp2*/) { - auto model = static_cast(user_data); - model->fJv(t, x, dx, v, Jv, cj); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(Jv),"Jacobian"); - } - /** Matrix vector product of JB with a vector v (for iterative solvers) - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param xBdot Vector with the adjoint right hand side - * @param vB Vector with which the Jacobian is multiplied - * @param JvB Vector to which the Jacobian vector product will be - *written - * @param cj scalar in Jacobian (inverse stepsize) - * @param user_data object with user input @type Model_DAE - * @param tmpB1 temporary storage vector - * @param tmpB2 temporary storage vector - * @return status flag indicating successful execution - **/ - int IDASolver::fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector /*xBdot*/, - N_Vector vB, N_Vector JvB, realtype cj, void *user_data, - N_Vector /*tmpB1*/, N_Vector /*tmpB2*/) { - auto model = static_cast(user_data); - model->fJvB(t, x, dx, xB, dxB, vB, JvB, cj); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(JvB),"Jacobian"); - } +/** + * @brief Matrix vector product of J with a vector v (for iterative solvers) + * @param t timepoint @type realtype + * @param cj scaling factor, inverse of the step size + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + * @param v Vector with which the Jacobian is multiplied + * @param Jv Vector to which the Jacobian vector product will be written + * @param user_data object with user input @type Model_DAE + * @param tmp1 temporary storage vector + * @param tmp2 temporary storage vector + * @return status flag indicating successful execution + **/ +int fJv(realtype t, N_Vector x, N_Vector dx, N_Vector /*xdot*/, + N_Vector v, N_Vector Jv, realtype cj, void *user_data, + N_Vector /*tmp1*/, N_Vector /*tmp2*/) { - /** Event trigger function for events - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param root array with root function values - * @param user_data object with user input @type Model_DAE - * @return status flag indicating successful execution - */ - int IDASolver::froot(realtype t, N_Vector x, N_Vector dx, realtype *root, + auto model = static_cast(user_data); + model->fJv(t, x, dx, v, Jv, cj); + return model->checkFinite(gsl::make_span(Jv), "Jacobian"); +} + +/** + * @brief Matrix vector product of JB with a vector v (for iterative solvers) + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param xBdot Vector with the adjoint right hand side + * @param vB Vector with which the Jacobian is multiplied + * @param JvB Vector to which the Jacobian vector product will be written + * @param cj scalar in Jacobian (inverse stepsize) + * @param user_data object with user input @type Model_DAE + * @param tmpB1 temporary storage vector + * @param tmpB2 temporary storage vector + * @return status flag indicating successful execution + **/ +int fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector /*xBdot*/, N_Vector vB, N_Vector JvB, + realtype cj, void *user_data, N_Vector /*tmpB1*/, + N_Vector /*tmpB2*/) { + + auto model = static_cast(user_data); + model->fJvB(t, x, dx, xB, dxB, vB, JvB, cj); + return model->checkFinite(gsl::make_span(JvB), "Jacobian"); +} + +/** + * @brief Event trigger function for events + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param root array with root function values + * @param user_data object with user input @type Model_DAE + * @return status flag indicating successful execution + */ +int froot(realtype t, N_Vector x, N_Vector dx, realtype *root, void *user_data) { - auto model = static_cast(user_data); - model->froot(t,x,dx,root); - return model->checkFinite(model->ne,root,"root function"); - } + auto model = static_cast(user_data); + model->froot(t, x, dx, gsl::make_span(root, model->ne)); + return model->checkFinite(gsl::make_span(root, model->ne), + "root function"); +} - /** residual function of the DAE - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - * @param user_data object with user input @type Model_DAE - * @return status flag indicating successful execution - */ - int IDASolver::fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, - void *user_data) { - auto model = static_cast(user_data); +/** + * @brief Residual function of the DAE + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + * @param user_data object with user input @type Model_DAE + * @return status flag indicating successful execution + */ +int fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot, + void *user_data) { + auto model = static_cast(user_data); - if (t > 1e200 && !amici::checkFinite(model->nx_solver, - N_VGetArrayPointer(x), "fxdot")) - return AMICI_UNRECOVERABLE_ERROR; + if (t > 1e200 && !amici::checkFinite(gsl::make_span(x), "fxdot")) { /* when t is large (typically ~1e300), CVODES may pass all NaN x to fxdot from which we typically cannot recover. To save time on normal execution, we do not always want to check finiteness of x, but only do so when t is large and we expect problems. */ - - model->fxdot(t, x, dx, xdot); - return model->checkFinite(model->nx_solver, N_VGetArrayPointer(xdot), - "fxdot"); + return AMICI_UNRECOVERABLE_ERROR; } - /** Right hand side of differential equation for adjoint state xB - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param xBdot Vector with the adjoint right hand side - * @param user_data object with user input @type Model_DAE - * @return status flag indicating successful execution - */ - int IDASolver::fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + model->fxdot(t, x, dx, xdot); + return model->checkFinite(gsl::make_span(xdot), "fxdot"); +} + +/** + * @brief Right hand side of differential equation for adjoint state xB + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param xBdot Vector with the adjoint right hand side + * @param user_data object with user input @type Model_DAE + * @return status flag indicating successful execution + */ +int fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector xBdot, void *user_data) { - auto model = static_cast(user_data); - model->fxBdot(t, x, dx, xB, dxB, xBdot); - return model->checkFinite(model->nx_solver,N_VGetArrayPointer(xBdot),"xBdot"); - } - /** Right hand side of integral equation for quadrature states qB - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xB Vector with the adjoint states - * @param dxB Vector with the adjoint derivative states - * @param qBdot Vector with the adjoint quadrature right hand side - * @param user_data pointer to temp data object @type Model_DAE - * @return status flag indicating successful execution - */ - int IDASolver::fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, N_Vector dxB, N_Vector qBdot, - void *user_data) { - auto model = static_cast(user_data); - model->fqBdot(t, x, dx, xB, dxB, qBdot); - return model->checkFinite(model->nJ*model->nplist(),N_VGetArrayPointer(qBdot),"qBdot"); - } + auto model = static_cast(user_data); + model->fxBdot(t, x, dx, xB, dxB, xBdot); + return model->checkFinite(gsl::make_span(xBdot), "xBdot"); +} - /** Right hand side of differential equation for state sensitivities sx - * @param Ns number of parameters - * @param t timepoint - * @param x Vector with the states - * @param dx Vector with the derivative states - * @param xdot Vector with the right hand side - * @param sx Vector with the state sensitivities - * @param sdx Vector with the derivative state sensitivities - * @param sxdot Vector with the sensitivity right hand side - * @param user_data object with user input @type Model_DAE - * @param tmp1 temporary storage vector - * @param tmp2 temporary storage vector - * @param tmp3 temporary storage vector - * @return status flag indicating successful execution - */ - int IDASolver::fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector dx, N_Vector /*xdot*/, - N_Vector *sx, N_Vector *sdx, N_Vector *sxdot, void *user_data, - N_Vector /*tmp1*/, N_Vector /*tmp2*/, N_Vector /*tmp3*/) { - auto model = static_cast(user_data); - for(int ip = 0; ip < model->nplist(); ip++){ - model->fsxdot(t, x, dx, ip, sx[ip], sdx[ip], sxdot[ip]); - if(model->checkFinite(model->nx_solver,N_VGetArrayPointer(sxdot[ip]),"sxdot") != AMICI_SUCCESS) - return AMICI_RECOVERABLE_ERROR; - } - return AMICI_SUCCESS; +/** + * @brief Right hand side of integral equation for quadrature states qB + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xB Vector with the adjoint states + * @param dxB Vector with the adjoint derivative states + * @param qBdot Vector with the adjoint quadrature right hand side + * @param user_data pointer to temp data object @type Model_DAE + * @return status flag indicating successful execution + */ +int fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB, + N_Vector dxB, N_Vector qBdot, void *user_data) { + + auto model = static_cast(user_data); + model->fqBdot(t, x, dx, xB, dxB, qBdot); + return model->checkFinite(gsl::make_span(qBdot), "qBdot"); + +} + +/** + * @brief Right hand side of differential equation for state sensitivities sx + * @param Ns number of parameters + * @param t timepoint + * @param x Vector with the states + * @param dx Vector with the derivative states + * @param xdot Vector with the right hand side + * @param sx Vector with the state sensitivities + * @param sdx Vector with the derivative state sensitivities + * @param sxdot Vector with the sensitivity right hand side + * @param user_data object with user input @type Model_DAE + * @param tmp1 temporary storage vector + * @param tmp2 temporary storage vector + * @param tmp3 temporary storage vector + * @return status flag indicating successful execution + */ +int fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector dx, + N_Vector /*xdot*/, N_Vector *sx, N_Vector *sdx, + N_Vector *sxdot, void *user_data, N_Vector /*tmp1*/, + N_Vector /*tmp2*/, N_Vector /*tmp3*/) { + + auto model = static_cast(user_data); + + for (int ip = 0; ip < model->nplist(); ip++) { + model->fsxdot(t, x, dx, ip, sx[ip], sdx[ip], sxdot[ip]); + if (model->checkFinite(gsl::make_span(sxdot[ip]), "sxdot") + != AMICI_SUCCESS) + return AMICI_RECOVERABLE_ERROR; } + return AMICI_SUCCESS; +} + } // namespace amici diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index ae006e60d2..335d26f0e5 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -16,6 +16,14 @@ #include namespace amici { + +SteadystateProblem::SteadystateProblem(const Solver *solver): + t(solver->gett()), delta(solver->nx()), ewt(solver->nx()), + rel_x_newton(solver->nx()), x_newton(solver->nx()), + x(solver->getState(solver->gett())), x_old(solver->nx()), + dx(solver->nx()), xdot(solver->nx()), xdot_old(solver->nx()), + sx(solver->getStateSensitivity(solver->gett())), + sdx(solver->nx(), solver->nplist()) {} void SteadystateProblem::workSteadyStateProblem(ReturnData *rdata, Solver *solver, Model *model, @@ -38,7 +46,7 @@ void SteadystateProblem::workSteadyStateProblem(ReturnData *rdata, starttime = clock(); auto newtonSolver = NewtonSolver::getSolver( - t, x, solver->getLinearSolver(), model, rdata, + &t, &x, solver->getLinearSolver(), model, rdata, solver->getNewtonMaxLinearSteps(), solver->getNewtonMaxSteps(), solver->getAbsoluteTolerance(), solver->getRelativeTolerance()); @@ -51,9 +59,9 @@ void SteadystateProblem::workSteadyStateProblem(ReturnData *rdata, try { /* Newton solver did not work, so try a simulation */ if (it < 1) /* No previous time point computed, set t = t0 */ - *t = model->t0(); + t = model->t0(); else /* Carry on simulating from last point */ - *t = model->t(it - 1); + t = model->t(it - 1); if (it < 0) { /* Preequilibration? -> Create a new CVode object for sim */ auto newtonSimSolver = @@ -111,16 +119,18 @@ bool SteadystateProblem::checkConvergence( const Solver *solver, Model *model ) { - model->fxdot(*t, x, &dx, &xdot); - wrms = getWrmsNorm(*x, xdot, solver->getAbsoluteToleranceSteadyState(), solver->getRelativeToleranceSteadyState()); + model->fxdot(t, x, dx, xdot); + wrms = getWrmsNorm(x, xdot, solver->getAbsoluteToleranceSteadyState(), solver->getRelativeToleranceSteadyState()); bool converged = wrms < RCONST(1.0); if (solver->getSensitivityOrder()>SensitivityOrder::none && solver->getSensitivityMethod() == SensitivityMethod::forward) { for (int ip = 0; ip < model->nplist(); ++ip) { if (converged) { - solver->getSens(t, sx); - model->fsxdot(*t, x, &dx, ip, &(*sx)[ip], &dx, &xdot); - wrms = getWrmsNorm(*x, xdot, solver->getAbsoluteToleranceSteadyStateSensi(), solver->getRelativeToleranceSteadyStateSensi()); + sx = solver->getStateSensitivity(t); + model->fsxdot(t, x, dx, ip, sx[ip], dx, xdot); + wrms = getWrmsNorm(x, xdot, + solver->getAbsoluteToleranceSteadyStateSensi(), + solver->getRelativeToleranceSteadyStateSensi()); converged = wrms < RCONST(1.0); } } @@ -143,12 +153,12 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, /* initialize output of linear solver for Newton step */ delta.reset(); - model->fxdot(*t, x, &dx, &xdot); + model->fxdot(t, x, dx,xdot); /* Check for relative error, but make sure not to divide by 0! Ensure positivity of the state */ - x_newton = *x; - x_old = *x; + x_newton = x; + x_old = x; xdot_old = xdot; //rdata->newton_numsteps[newton_try - 1] = 0.0; @@ -162,7 +172,7 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, delta = xdot; newtonSolver->getStep(steadystate_try == NewtonStatus::newt ? 1 : 2, - i_newtonstep, &delta); + i_newtonstep, delta); } catch (NewtonFailure const &ex) { rdata->newton_numsteps.at(steadystate_try == NewtonStatus::newt ? 0 @@ -178,17 +188,18 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, } /* Try a full, undamped Newton step */ - N_VLinearSum(1.0, x_old.getNVector(), gamma, delta.getNVector(), x->getNVector()); + N_VLinearSum(1.0, x_old.getNVector(), gamma, delta.getNVector(), + x.getNVector()); /* Compute new xdot and residuals */ - model->fxdot(*t, x, &dx, &xdot); + model->fxdot(t, x, dx, xdot); realtype wrms_tmp = getWrmsNorm(x_newton, xdot, newtonSolver->atol, newtonSolver->rtol); if (wrms_tmp < wrms) { /* If new residuals are smaller than old ones, update state */ wrms = wrms_tmp; - x_old = *x; + x_old = x; xdot_old = xdot; /* New linear solve due to new state */ compNewStep = TRUE; @@ -198,8 +209,8 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, if (converged) { /* Ensure positivity of the found state */ for (ix = 0; ix < model->nx_solver; ix++) { - if ((*x)[ix] < 0.0) { - (*x)[ix] = 0.0; + if (x[ix] < 0.0) { + x[ix] = 0.0; converged = FALSE; } } @@ -228,9 +239,10 @@ void SteadystateProblem::applyNewtonsMethod(ReturnData *rdata, Model *model, /* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */ -void SteadystateProblem::writeNewtonOutput(ReturnData *rdata,const Model *model, - NewtonStatus newton_status, - double run_time, int it) +void SteadystateProblem::writeNewtonOutput(ReturnData *rdata, + const Model *model, + const NewtonStatus newton_status, + const double run_time, const int it) { /* Get cpu time for Newton solve in seconds */ @@ -238,14 +250,14 @@ void SteadystateProblem::writeNewtonOutput(ReturnData *rdata,const Model *model, rdata->newton_status = static_cast(newton_status); rdata->wrms_steadystate = wrms; if (newton_status == NewtonStatus::newt_sim) { - rdata->t_steadystate = *t; + rdata->t_steadystate = t; } /* Steady state was found: set t to t0 if preeq, otherwise to inf */ if (it == AMICI_PREEQUILIBRATE) { - *t = model->t0(); + t = model->t0(); } else { - *t = INFINITY; + t = INFINITY; } } @@ -253,7 +265,8 @@ void SteadystateProblem::writeNewtonOutput(ReturnData *rdata,const Model *model, /* ------------------------------------------------------------------ */ /* ------------------------------------------------------------------ */ -void SteadystateProblem::getSteadystateSimulation(ReturnData *rdata, Solver *solver, +void SteadystateProblem::getSteadystateSimulation(ReturnData *rdata, + Solver *solver, Model *model) { /* Loop over steps and check for convergence */ @@ -267,7 +280,8 @@ void SteadystateProblem::getSteadystateSimulation(ReturnData *rdata, Solver *sol multiplication with 10 ensures nonzero difference and should ensure stable computation value is not important for AMICI_ONE_STEP mode, only direction w.r.t. current t */ - solver->solve(std::max(*t,1.0) * 10, x, &dx, t, AMICI_ONE_STEP); + solver->step(std::max(t, 1.0) * 10); + solver->writeSolution(&t, x, dx, sx); /* Check for convergence */ converged = checkConvergence(solver, model); @@ -282,11 +296,11 @@ void SteadystateProblem::getSteadystateSimulation(ReturnData *rdata, Solver *sol rdata->newton_numsteps.at(static_cast(NewtonStatus::newt_sim) - 1) = steps_newton; if (solver->getSensitivityOrder()>SensitivityOrder::none) - solver->getSens(t, sx); + sx = solver->getStateSensitivity(t); } std::unique_ptr SteadystateProblem::createSteadystateSimSolver( - Solver *solver, Model *model) + const Solver *solver, Model *model) const { /* Create new CVode solver object */ @@ -307,9 +321,16 @@ std::unique_ptr SteadystateProblem::createSteadystateSimSolver( newton_solver->setSensitivityMethod(SensitivityMethod::none); // use x and sx as dummies for dx and sdx (they wont get touched in a CVodeSolver) - newton_solver->setup(x,x,sx,sx,model); + newton_solver->setup(model->t0(), model, x, x, sx, sx); return newton_solver; } + +void SteadystateProblem::writeSolution(realtype *t, AmiVector &x, + AmiVectorArray &sx) const { + *t = this->t; + x.copy(this->x); + sx.copy(this->sx); +} } // namespace amici diff --git a/src/sundials_linsol_wrapper.cpp b/src/sundials_linsol_wrapper.cpp index ea0b3ac8a2..84ace7e0a9 100644 --- a/src/sundials_linsol_wrapper.cpp +++ b/src/sundials_linsol_wrapper.cpp @@ -356,11 +356,11 @@ int SUNNonLinSolNewton::getSysFn(SUNNonlinSolSysFn *SysFn) const { return SUNNonlinSolGetSysFn_Newton(solver, SysFn); } -SUNNonLinSolFixedPoint::SUNNonLinSolFixedPoint(const N_Vector x, int m) +SUNNonLinSolFixedPoint::SUNNonLinSolFixedPoint(const_N_Vector x, int m) : SUNNonLinSolWrapper(SUNNonlinSol_FixedPoint(x, m)) { } -SUNNonLinSolFixedPoint::SUNNonLinSolFixedPoint(int count, const N_Vector x, int m) +SUNNonLinSolFixedPoint::SUNNonLinSolFixedPoint(int count, const_N_Vector x, int m) : SUNNonLinSolWrapper(SUNNonlinSol_FixedPointSens(count, x, m)) { } diff --git a/src/sundials_matrix_wrapper.cpp b/src/sundials_matrix_wrapper.cpp index 1f3667c754..6f5f7cb0e7 100644 --- a/src/sundials_matrix_wrapper.cpp +++ b/src/sundials_matrix_wrapper.cpp @@ -88,6 +88,8 @@ SUNMatrixWrapper::SUNMatrixWrapper(SUNMatrixWrapper &&other) noexcept { } SUNMatrixWrapper &SUNMatrixWrapper::operator=(const SUNMatrixWrapper &other) { + if(&other == this) + return *this; return *this = SUNMatrixWrapper(other); } @@ -151,8 +153,7 @@ sunindextype *SUNMatrixWrapper::indexptrs() const { int SUNMatrixWrapper::sparsetype() const { if (SUNMatGetID(matrix) == SUNMATRIX_SPARSE) return SM_SPARSETYPE_S(matrix); - else - throw std::domain_error("Function only available for sparse matrices"); + throw std::domain_error("Function only available for sparse matrices"); } void SUNMatrixWrapper::reset() { @@ -160,7 +161,7 @@ void SUNMatrixWrapper::reset() { SUNMatZero(matrix); } -void SUNMatrixWrapper::multiply(N_Vector c, const N_Vector b) const { +void SUNMatrixWrapper::multiply(N_Vector c, const_N_Vector b) const { multiply(gsl::make_span(NV_DATA_S(c), NV_LENGTH_S(c)), gsl::make_span(NV_DATA_S(b), NV_LENGTH_S(b))); } diff --git a/src/vector.cpp b/src/vector.cpp new file mode 100644 index 0000000000..6462219db6 --- /dev/null +++ b/src/vector.cpp @@ -0,0 +1,149 @@ +#include "amici/vector.h" + +namespace amici { + +AmiVector &AmiVector::operator=(AmiVector const &other) { + vec = other.vec; + synchroniseNVector(); + return *this; +} + +realtype *AmiVector::data() { return vec.data(); } + +const realtype *AmiVector::data() const { return vec.data(); } + +N_Vector AmiVector::getNVector() { return nvec; } + +const_N_Vector AmiVector::getNVector() const { return nvec; } + +std::vector const &AmiVector::getVector() { return vec; } + +int AmiVector::getLength() const { return static_cast(vec.size()); } + +void AmiVector::reset() { set(0.0); } + +void AmiVector::minus() { + for (auto & it : vec) + it = -it; +} + +void AmiVector::set(realtype val) { std::fill(vec.begin(), vec.end(), val); } + +realtype &AmiVector::operator[](int pos) { + return vec.at(static_cast(pos)); +} + +realtype &AmiVector::at(int pos) { + return vec.at(static_cast(pos)); +} + +const realtype &AmiVector::at(int pos) const { + return vec.at(static_cast(pos)); +} + +void AmiVector::copy(const AmiVector &other) { + if(getLength() != other.getLength()) + throw AmiException("Dimension of AmiVector (%i) does not " + "match input dimension (%i)", + getLength(), other.getLength()); + std::copy(other.vec.begin(), other.vec.end(), vec.begin()); + synchroniseNVector(); +} + +void AmiVector::synchroniseNVector() { + if (nvec) + N_VDestroy_Serial(nvec); + nvec = N_VMake_Serial(static_cast(vec.size()), vec.data()); +} + +AmiVector::~AmiVector() { N_VDestroy_Serial(nvec); } + +AmiVectorArray::AmiVectorArray(long int length_inner, long int length_outer) + : vec_array(length_outer, AmiVector(length_inner)) { + nvec_array.resize(length_outer); + for (int idx = 0; idx < length_outer; idx++) { + nvec_array.at(idx) = vec_array.at(idx).getNVector(); + } +} + +AmiVectorArray &AmiVectorArray::operator=(AmiVectorArray const &other) { + vec_array = other.vec_array; + nvec_array.resize(other.getLength()); + for (int idx = 0; idx < other.getLength(); idx++) { + nvec_array.at(idx) = vec_array.at(idx).getNVector(); + } + return *this; +} + +AmiVectorArray::AmiVectorArray(const AmiVectorArray &vaold) + : vec_array(vaold.vec_array) { + nvec_array.resize(vaold.getLength()); + for (int idx = 0; idx < vaold.getLength(); idx++) { + nvec_array.at(idx) = vec_array.at(idx).getNVector(); + } +} + +realtype *AmiVectorArray::data(int pos) { return vec_array.at(pos).data(); } + +const realtype *AmiVectorArray::data(int pos) const { + return vec_array.at(pos).data(); +} + +realtype &AmiVectorArray::at(int ipos, int jpos) { + return vec_array.at(jpos).at(ipos); +} + +const realtype &AmiVectorArray::at(int ipos, int jpos) const { + return vec_array.at(jpos).at(ipos); +} + +N_Vector *AmiVectorArray::getNVectorArray() { return nvec_array.data(); } + +N_Vector AmiVectorArray::getNVector(int pos) { return nvec_array.at(pos); } + +AmiVector &AmiVectorArray::operator[](int pos) { return vec_array.at(pos); } + +const AmiVector &AmiVectorArray::operator[](int pos) const { + return vec_array.at(pos); +} + +int AmiVectorArray::getLength() const { + return static_cast(vec_array.size()); +} + +void AmiVectorArray::reset() { + for (auto &v : vec_array) + v.reset(); +} + +void AmiVectorArray::flatten_to_vector(std::vector &vec) const { + int n_outer = vec_array.size(); + if (n_outer == 0) + return; // nothing to do ... + int n_inner = vec_array.at(0).getLength(); + + if (static_cast(vec.size()) != n_inner * n_outer) { + throw AmiException("Dimension of AmiVectorArray (%ix%i) does not " + "match target vector dimension (%u)", + n_inner, n_outer, vec.size()); + } + + for (int outer = 0; outer < n_outer; ++outer) { + for (int inner = 0; inner < n_inner; ++inner) + vec.at(inner + outer * n_inner) = this->at(inner, outer); + } +} + +void AmiVectorArray::copy(const AmiVectorArray &other) { + if (getLength() != other.getLength()) + throw AmiException("Dimension of AmiVectorArray (%i) does not " + "match input dimension (%i)", + getLength(), other.getLength()); + + for (int iv = 0; iv < getLength(); ++iv) { + vec_array.at(iv).copy(other.vec_array.at(iv)); + nvec_array[iv] = vec_array.at(iv).getNVector(); + } +} + +} // namespace amici diff --git a/tests/cpputest/expectedResults.h5 b/tests/cpputest/expectedResults.h5 index 87cbc6e87a2c63e286ce624e32e0c9b5a00cffe2..673b46ab19c18edd13d98d82fb02568962a4bae9 100644 GIT binary patch delta 289293 zcmZtOcU(<>;6LytB_m2Gp-7pPRY_h-*;L5NOpy^GBhgVIWt2inL{drGd!5_fbsKlI zw1=jdhVeUmy#2nv-{brF$LHL0JLi4IJ@?-8dcN+Rkne)sr9ndOD#G3E>m%pY2?_DJ z(W8W?iFkc!5}qM05G*Y4`#+V)$X|k9Mz=?g7ZMlnQf(DJ;N=rBc9NO?pSc18g8%)y zZHuA4pn#CTecW@ zc!t-i5MguS@%E)&2NsWf?X^l=SWj3iTgdCsY|-ie-@~Dm|Lem>E&uDoUq1hjhiO;Q z`@&*U0$z3vqO_M@sIcOnWusYa*vstg->CyFVh$2{YyTT;$9h4pH>IO*+-F_)Iw`(V zEPB4Qw4lVXpAT3!gk$O1J5%DryM;L;yjZtKPZ>4xzs*`FE)XByDqJV*rPe;;n%C$~ zVKJ`{Z!}Fkou(??k5u!L6dXC((^}!67mFvZAQtBgcZ}sjENT>mc(d!U>|xZk6|x)E5^t_`Mu!N>p5M z?fuA8UJ(aG#YesQZ-MN$!tsS4#CHmM${PH)8%~RkeBt>)VS~_P2`}rf!ZUvF8&AVE z!@>=7g*`W~{c{|mPp=&#_-ChuOOCwnDY^dS?}>QT?PA%Y@kU`v4@9Y8+0(71-}(?Y zUnit{_d#4z^H$na--pZYF27nu{#X{EbTMsn zAI?d;i0pNHMd942FAAwk`{1&1Z+Axov-MHXvV`$?bI>m(w=ulc~CMZI{}7jM1md=jb$vUX_6^*X;mQKNdWI^>$?;wxELxs2m&^`#qLUldpmerCaG$xkz_ zciq@}=SH0#m5mWMWERWhb;BgJN=xr&HXI$t{Sc1o24`sM6fe6R2;6Ro7V_-|d&|;T zgKs%dAMn{Q;bu3Ey&k!8>zQ1zCh5g2vhIeMyc<=;dY_B8Ge0*U-qVfU9iLx5JeY^b z$^t*9d+dsULlZV&4#LWg4c0-nRP}FgKK1LdrKQf)t4c@0C0WXsCu{QPO{!-y? zoZE8FQDc4qj)~6<-PYTMPW{H)Yo8S0+^+TKI{0145u#sm#0v5CrcTh^%r5-+Nj1G} zwJC&;!cpzjVO@~lBe>75z7PrfG-ir>cVWwz^hZAHiqJlO&enRTECL+q*1KZt%$Z$VrP_tj znl|#et4c8Ulx~cGTo+DLGbIX7z9@n85@i>S5nUMLuX-s~pcMTs54YHV?8H`k;h=#1 zrMR+g+dyS)C*1su%XE@U(G{e2Sv#*2Qc*@r^d#8m(Hxl<8QqC{?X$XcG}*AMOXO(y zb%NH^3Wba~C>6(d9|z+mqSY3^P#`vre=;-*Ypg zm5r3caz5RgJ8@F`!2bK=%CNoUbregh6E|K5xnyXTVRE$c>HwuqbYItgvGY(FVl5Kj zHNF$}t=pDwbt!}6)?2>bKiSo_a+ zI&g-kF1ToMC7h0mH%-6XfsIS&^i&#FBJxyk?4F|?Sbu5W+wUhTF+=7}@5}8SIICOk z9O7JwudkLm4C{8Fc1t&EUswB zf`ixOs1+QX;vlt`)eal=Fhkev99U_WJcti#$HZ&yp0iJKko9Sk`90ru6j+!`nmBP# zmLsZg&b1x4j`?=W_;65~xPf9>Txv(queOa=5ga)GJg;%&NIOOiFEQMm!@;Oe@9k{1 zx8qD7EnQo~tYG4JCwDz&fHx!)`af`Rd*pt-B(-)tOcmK0BFaUP{l@T*)7lX_mpkxn z3Kt*s_R`D7w8KDV2CHEK7tOQfEM11%@YuIC!9te{S0PHy@BRBWOiu9n=)9c^|Iq&3 zJNa#JgxrO7N4fC(Vsx)Orwt>xC5FMaT-^1oR5y)n1H0J$M(}Me2DdI*{_$lShQ4RX zZFtH>ZRp&m4?Nl+d}ELFy)Z8BO}}hp;LwI&cgjwkr@2TR>RvBw-3FK5RVspPE_P66 zCREA(Haz8+YA?ia^a<+MU_PIHR484t@+t&8u* zwj!pNx6tM^4+@*Lf3*j+LRrC0<)JXkvMN<) zJ>bF2_Uwq&=UUPK%&b4%kB1SdtH-jSBJPLPT>qQ^i+KOu*=dYU-$HS;E z8C~P_S}|~OfMTh053a#jQd~1O@hX?T$0wJ5nx5CIM zWA&Lu53-ht(XF{)FdJ6Ent^>dS6_{hsov_$Awq7;ON1o)op9|_<7Yn z!*+2Cwzge=B(s^1&=1cEvt&+TT+d$e>%jRPMW!=6rmdZihx;QM>G-{3=CcJRp?t7hD<5Tb&- zZt)R5r~7X9{$_-_Hdeg4&Bu0~x80qF%^0C9aE-dhN7-51a5>#()Y^uvQM%7Zp5KGw zor{{$uroE?%ahq7o7S)No7oKIsg5qZhkQH=?-=+f&HVk$>LsmSd@Kwq_+mJs8TBg` z?oRjS!zFg}oWyTU_%xZi;Ck{2ACrc;cNe^G!p2~2({^7z?y8qhd0X9t`b*FDE_%jC zMPOu>Mo|;<)nn(hEq;6mY4+YIPHn=O2}ujJpYvh3X{5=i@FqMeO_m*Erlr}Ab7WsM zp>Ww}tJ^R5$WrYYVtX{9U-fBJA2a=ZYZc4mdJ{yc48JqV{(M~ZYB+n~LKBpFZ!v| zgqqV+qGOl=usltk392>0eeomRZf3wAlZWSwQ)~ij=JJ*yW`icTyf~^X-302I{>v$i zPx(+eE^WABL=!ekE;-=G?19DoO8kohjTm!4MtvExhwh#a9}4em#O^gKM+GvwP)x=> z>{D$crhYzY-pB0XX}WK_P}+!RR~9Esc*MsduTQVN(iR%t>`V>FHjD!;MJVYHB0l$cLZjT#+J^ zMoimlEZD=Gl&uvjTCc5Z#Jf)SpP_bq)UEjO4cb&A_$N2>H(p|1_ntmW#1}TgLOo5!RREZM$A)lPqJWMB__eeUN1&8LZDM=-t}Y507d66 z^c-wJVX^HECksCO;{$W=bvNKtmXoUaAwB{%9_@Wl-vBk^<@aXqpya6ho z9*7n)uUmueC3{k{8!*%E-hlCTKIWbdjBZG1fZPe?{A5EuR%{#BIVQ9LdMhmXVjKA= z*;fBm->(5tVTW&OG1m#v!P})bJQ~0rqqS&@E*}=2?x)!|8gRr~X8GC`d{}CnDw$>5 z0JqP}hi7TpRE0P7C}W$o&Gm#e#8!)2HR<1{mkKI;R#&#}ffJD>kpoKEbNs4tG z2ZaVqdFp>)rz9WKS7uJ1Ik5qWo8I^w9mU59pBGeHu^2OAQ?IuNMEG#Vas!dUdbFO) z>BdhU7VPPKOf}Ra(1G!syIe_Rj7KT*eH!#`;LdrH{28jkJf{0IAx+Aj|Y)il;x@JE%nfA z&9rZ1^Pu4qsNT7(9xFfp@E%*h!>JgB{X^60;WosJo}J0VV!7njsUz!QE$cgZZVC^P z8xABH_r1fMj#8;9u{@k}6)kzpdj|`>WgkAj{I3 z#)XH5!SiPHhIg1d+t}USfrrL(Nw2Rjeh05RKb$IUdHB|^oo+hy9az>!A51>W!v@Ou ziN5GNM5g44ZamIIg5%z~=&ggUP>9?~=IxzI>&QE}kynRug_g-z_V92+XpH(XRvq+u zUchcE4;;tWcOSp5!^qr2Wydx!1H3TjSd~W|+;zTg#~^4V4_{V!)EL&+qDap% zQiAzJ+1LpUHIC=I2doak11dPQQQ)fv+F!;tXox z_&GdwLpm4kE@M}Tsnx=5X8$Dp1TK_}_XZr7twoR|S4usci&fTgzI7tCSf@0zetaMo z8_FNb9_p)salJx#n=cmF^{9kztm zAWObTKT-xd|r*3;V~5#MYyO~ zru@A4b~QxLSyEZghB#=C5;Hh^u^M}KxK2-HKGz?gtaEer;c9&G9wS-N#=(t>!kwL4 zsxh)=VtPX@Gv7Y@ZR+xBoW5$judSSe-PNnFJfB^SdpSd6Tk<$?%R3hDF`*h>U-`eP z(m2?*`tY^~KdZ2Fx9;w|7!G=OSm?j%tip-I)MWke5Dv0s!|b!URrrxm-f{0a2m3;< z_kYQ#!qBP;l!HkYKjbr+4-SG&<*u`bE8&-0WYoaC4ZA$?Qh4#95`vcV ze@QTJnjFa z)0)Zqaw>6FCM;}&8nZlODXEO)O1w?`I3rD-PeJNY!A`}K`;>=cwWag-9;w8X!THN}+-5%iJKVfK z^|@&!l-;)|uVXHu;cD8VjYgGtBvkuh?NJ^kdwmj`zNQiqYc+0}FyD|qy}YBoN4pZv zmkOG!S2Gu=rT0QT)G9HFJ+j}Mx#FE@obQ&WREgKmj>WU5^I)1__+?nO5+!TSPMyen zWs?+j@1HMO3HN28vPY@!T;xeCk1!Rj1R7rl<#ci(p1WE5)=&j@B#I_3u42Bm`csin zeHGX=*GxYwmx~k2wC2~eRKUsO7?#Cx;W6x;CCJ=hP|_*!D-7TQqL(~pmsUV#?*`LT z%=INN`e5YxEarxT;Pa!?9J$Co>b>klVg+*LypyOJYc3Woo}qm^tO8e0jNA}-fC~|q zQ10uO6|i*3lCa;x#e}Z#8M%)ua4RnItC}w$JB6bK$M2KOHM@ z?{(gk?Q&eaG4B|jbg2Tok{TQw#YLw-uSLzO0xP*6Eo=ul5DP!ov(BsnPeLY8j(!~+ zJU4ng@8He~)YNQtE$4C2^|?Og!lnvL_D-^ykjsJb#(-ycbSqF4a#7gY}adLhC11K+XHt zhw(NXblU_)d>&PSL~Cl+o@dPK(MqPuZKOa2nxB0*sIiTMwgTDxa$n1F+Ae;$R*!=r zin~y?vmAT#D`vPY;@|;y`NY-rr`Hm{9z>U8Bc;fGmDN}Y8yTl#A;IPN%o;{| zStac4e(YpDEyq5Q9EF1PN|-N}t>t=@qemx8w?48GI_bJ2d!5S>@Xh0q0CTS=Zg*g( z=+$!g^>SnudNB8jj_Z3(J6n!hV>A|DE(;T)kE=PQdy5zoX%m_zaJnOoq99ri$y~|l$iKK=3BEj0s0=a9SuUJwE zg{hy@bJWTacWiJ|+N??#t#nRoQ!0lJty8mcB6B_1&^9zut{nG|4d}KqCpSMC59dmj zBmbM!CD;B6lo)wKQt$`B>H`ixCT1@zWmPnuF%2H*What=JHK-5*G zeQUGIU}P*4y5mj-;@e#cFC>;>bCAuH-B&B1zermuIE*TTWYcTWqo*p+WBo|C=4BZw zHnbkScAx?`+=~uNdY8eXXRpR9qYC8bG~HZ#uMB-2n@@0-BZC5PA!j(@v6(g9F_u5qz&BL-~KcurkD+3-BsCP zE6Xu<+sI7WOg0XvC69|_&XrN-d)_)Fu$eojJn1yWa-4s5n7YyTmJPoNrbQg-a+rz~ zZ{Pot4bjT+3kOCpXMdf%68kY56XuktD-ALumrVYpe~*px5tgNUyUMWia(hpT6B}OZ zSwElEm7!!^)_{gB8#&{W;=7o0f!bm`C+-v*7ff_N>1C9mbhp(l4d!n6S--V`0WoFR z!sRPU-L_ zmg3NX=bKyo*tqd!iO+e#Qp|j~GgQfwjr+@Agvj-j;M%C{veP%&h;bjfoX;=8%iG7| zGHuxu>}$49qOwY09Z;*9{JyRjT2aA^*NK(F z;+$Rbj-|}}6S<04K9qpnx8`l4LNSUo9vf#cyP6}Qy+&zlF><}9M#-j^;KvKod)`Au zc*;JwjJol<1gFN{xGU3M1l1BdwxCA|+~%L$=vz^QX{v5#-7lBm`oOc&xvU~YEy^9M zez*jgJ=Ya7LW`JNHha99nO*zj^nrC|-bLt_z5O$2NePP8Hf)r*S%fc#0+!j!mq5lx zTRrt`5kv%js2>?of*Ms(!Al2<;M@L{YC6!zjI4iKy7s0b7+f9FKEW@>U0%c`F|8uF zxLs4dm0pbJf({GbDHTD~P%I)Yq!?2+k7!GoPy~xLQzQC4i*f(Sf#85&g>c$xthnxK zG1N3&Es@zPM zXVJ_8q-;&JXBOi?f>~ws^Fl0hJ-57hbTMw*XKyIKTZp-9nst5qi*W4Dj8B7?3(VZBDm?_DxGUt2yefqPPzp}P~7&h-Q1)QgS}}l7sM2yLsWfLuwEhhc|uWhpE3F2 z%OAC0R0~l?C0f1Eyj6rF8NC@B+13dbR)cC}xGPhWl>rEkfuq(JK=M z3b0;g_tM-oMVJwix$}B!0d}m~sxn!<2qljEyT8f{An|i*imhA`R!OW?yv{1X=YfId zF3}=rJ^wm+LRbN~FH}8GeJF&>lCblynPik&Jx4lKu%;06UQAoD!KDBqmxN7TWf#Kj z3;*<&3kAqjHs7*4q7Vk@+oXfd3z#>fKwtSMg~&M3Ip1Py0nB&lFaC6c*>e}`Qs%8H zK&MW+D9^eOdn&f(4lgLckCh=Ki%bhKLurR0N3H<-h91r@TT=*6>8hOx;sq2AdCszI zQ!m8*htbUe-}2!T^6RYxvnL$Y1HSsS<|AN7^~Y6AW~*Uj$~Qk3pr6u@ zdzqdO;Z7I5ns)`@r+hgU7oLwT5?_|BDky+^e9GJMr}^lgeQO{lwg4yP8k`1h=VRVd z6+5jL1&FV3?Nz=^<)iiz9aVg%02W4NeFrV`5q?R}^US3JEGUR@dATDWmEXc7m6$DW zyfH4VPd}eIrV`UT3=5z&DfOPg;(W}%aV#oHy8!R}&;AHd$VZDpv!0xrAG#cwqAfvrs2xGuT=LDwiN`Jb zhacua{S5a-<*j@?aI|?6c|8v=zd63c`Fxxi^Vadqxjf{2)|j4jC?Bgo9h^vMo8{rG z&zW7Tx8x(z+;IC?<2;Q18e?CxJRdVQ&sx=mJj7JH-E>sWhlS&%sTrylL2CbU%LV$P29N&Yc;;I2~67Rk?r@jY&M#EU$5g!m|{CNR?#7v~q> z%R~GSt#vpk7mr3QRT{9%!`G$vhMzymMPR_Ed+NvX(0#V+WXFwM1U!1W-*!hHzW=k}tjc$FnC>JM2c(%k1=ED7w*r|hxxk&7rq`tj97kz3I1G>iN;=SI-N53j_ zae3V1o7V($v0BYgA~7Quw&Cs4(?90m-lF49*CKLJ6t;0oZbJ@2#GJmEJY)Wzs{O8R z%g({3@TP5AcXILUm=e8&m4oMdsvph0oQv)Wjb8@Cb0BP;88yc;7hksOl;`>7;4Ssb zciE0yjJT_{h26P1H>YI>9{Gw@}T3O1iUorBtieYwn+ILzKW znKnO=g9Yo~JA59S3u@N$%jJg+b5OaJICeI!O|n`Xb0!Cms|%-& zPS3`Qu{Bfe59Z)dgoMV4$ZXh6nxtjI3{>=GQd7l?Z06g9kiFK*929OFGi{AWHmU`0 zG|pX+1K%+V+S47gabMbf_nfIY;CE`QSbHuTlK16mmq_HGd}x&%_i#3Hb|@d%Jj7_0 z_9I4)r?zL~$cH5U+4tEn@H%{2(jXgycUxD!;xp4bts0V-W<%^(s&h+jHf#n>Dy(N` zBRTrxWCpq-<3-*;7M^A%vku+J zhOW5P&6n@9kPx%0xq-6D#&+qBM(64*xF{9AJ!zf|(Ukhg^MzU1|>gDkAj-oMu)BMXkwSJSeWWg&{sZVHUbLT7M)m#j(_ zw#K?V%k|HKzr)JYwo{k^E}nn$i$@lWyYCq^j>>{ctC{-(rz|X4D|>S5_e}h9sHdXO zp3B0XX%Y=ZJ(*bFwx>Pwa277CyZ+3mJ`*b?9x5m>1F;c)5m3j@#IT^rZdd&*uq4m- zoy*9?v|h^_zm{adsBH8VshCU{3k3RJQ(^{k$ZBoMt4wrQcRU;;lZAlj2U|{gG0^F< zlx5%uW`ja)@6B<^#KG;<_)#0bWx{XeXp!E_nTT0Xadx;plezP;G&K2SCLSvrd&l!L z5mm}_^4On=(MGN|RDLGB>Pwu@8f7BD_xkYGq)cSj_Qja$WkRiWlhUF$nfU1-F1BS! zCa%2e@0jYF3G+oQSJ%(Zgz&_tI#PEtVK+ffYRlwIq)_b#WhdHaVsJ)%mg%TW@Eng{ zP(00Sx#1bNv)?n|THUcu`yjI-@8HuOy&2%V8t-zzI1}fltC}S@WZ=MVg@ta+ij$YW zKHO8Dfz^tpss)QPQS23Kt&*LA?gp{zl1iDlwW;UE$@mO(&9s|oCX)%XzGlC~*BPK1 z6~#sAk<1Fil#|E#G7C(UoS{6Jfnjsq%2W3;z&_M}!iPa*MbR4{}xi$la z69qo4J;yAtc&v?oaR%(Z`1Yk+WH9%1=S|n3Gq57#SFQf83{*D_^0^Ti=o$4mj=doR zsz-i3cJOCbe4BmALMMZQRZi{>jRzTUUNdfnuv!Lu*R_fd++YUqI5zmTVg@XNKfKPo zn1R$SGsf+nn1Q)cE(?8*G4tbs49AOQV9d_*plFPqE$qXR7j=wM`9W$K* zHLf!&x^-xOhg@PhxaWPP0!L;*Fupf_d}uo2@BX}BKA4WAPuZ&_{nEjfeH9|!osN5F zC*7Llk&YQ*r%X1!W9IWFI4IvpM~3t&S1)!t1}3-6U3DoP8rwv4do$8;?m@J}A?idr z9z72|V;GwbM}?xa z6cf5#H661Beq9x7V8Jrn zZu*6=BnB`p<4Z+V53wLyuygC)FcvI2ER8rlESNNmqRdD5Gfz11v9pRGs+^$8DI zfQabj4ramM+v8d1Tv*Vv7wi{c7OW@vP*~$C3-Mbv?3~Fgc&F;90HHH1tSw&m$nY(* zf-%C+xaQ2`Gf%#`@tlRz%Z~L#?P9@o?HspkPZl&KX5I7J#BAW;kk&Y7X1a))eC7Ho z7Q9mq1(;oCfexMBg*LlLsDb1f$%66dbTWiHPMrXNq4BS z)i8|)dO$G1j%M!RZ1Hbvq7nST<)NNA4bM|gyUrHSICSUw&X1dDC{0@Ub$k?!6&8+& zSjzmn!Y?Azo5rX+vIf*N8gt5h#@%wDL75$GJg|Qx4ZlK;@PT7Ao)=g;Z~l-5t?`o7 zI%66RqgO?5s!GEwTj_K9S~SAtzf_xMq#-&jp=_%Hjk;&!`Y*ppgV^P|JLkk{Wc15^ zfBk@&Kk3w?z>jH2I_lffaWxH>*#{fCnB^ENO%BpHng*d4aeG&1q*1UqCaL|%C=CyF z?S6!Yq+yPW=wp%PY2eFe$EbLwp+x*btmDiy=4*7UO`KgCWMkjCN{&gxSLchWJIvGY zMq>nWe`Yxj7Aqj!xR94XJP!-?6`NX&TPxM~Y4^NQLF=pK;HpF)Q{;vS^7; z#iN1y70x3mW=nIVx3Qk4V$l)nz-u2;;mu#1{l+;Ji4`J>x2sa2;qMw9cqSDJce6Bu zGE(8dbDbDznu@|zs&$obQV}-&Ra1dMD!jgx2ueRpg^=?4313uG(KK4q+uS}C{D{^k z^JP;JJzM-y&e2pjzwWqTBa(`|1znw5lyNG4Y0y4}y(vgDxbK>_JQZ4{oS*6p9G;h* zaCG~uR2;`+pPUgFju;-MaRyiAa_E>8k;F8xS4kT#^Ze{Xqms<q1^EJ{HfhQ~gxPR8N{>36rLGAleG^Ey2{8TS%Jmko_b!842H z5gAjafALY;kvYj&IPd%I;j_u; z4H`OP9+?b|=#hj|`;%c~bG$>D`MaC^E{6~6lX0Y`JZku6GM3I6tiQH28DE>@66?<- zLum9+{&a<8w5z=QP_Z`|?rNqQ#iNt4wv1Y^k-s(>`h70bT?UdM?EvP{UOFcJQ=stG!ILqB%$<+o1*KNBzUntHi!i$K}mGu zg?IHy2;CSIHu^ylsy2RjsaKc;`+0?#)2=0|K|fh{75C&EDOKc&Ej_;~JcZqA#Ih3H(GA{_rQ;87T9qf4eX(H;r)R*O%Cc-&l zq8P>$K;eS4qT1XS+e&&IFj9S2vnFF%grNw&vd8 zCE!i96M6*`G1Or#%gahYmSS};t2Y5-s$)8qMkV0jo0hHqH3^W@T0hs{k4nH>`@IJ} z@)BU8mB*F4n}D=U!lUoUCSc^0l2-rA38*)Jx#5{Vvw*pSTQn>a@FQ&8_$0Rk=vBWL zuQEws?itVA-f|@YtYp2hw^k=$@1{&K`Qr(gayNLJzIp<-sn*Oi-elmKaygdKT9@vv@{>bT5FXA=`Sm;= z``>K9v&?wBcs7Qk?iP=UWBf;2M8?CoeRBD%GiFh<=D_@>^ zHy#6a$?g;P#N*6~-mzos;xQOxrE0e>9$Am)UzIo(53e<|p7bn>M?jBW(xjct^R?l!Hp*&~hTH>{@pm=4$ZQolT9y;|pWlyQ||c zX?ywC^`UVnn`WACksk+VH{IC_pTyzL@Q!oc@o~7eb!7Q8*Er0%ALVo{Fb*T?Mt+pD zjlGkdK`T5b7~ycSc+{={}hY8EB0R#A03BN)XvEh7c|A<)sx)^6^3JR<8bdl zKv^uVRqxhV(;15qmTuov(qkc$Z#8_eDi(q!!s3Y$vDn0W;~$q7i^#<$N+!=^@nh_w znL-J%keuhK|HCa7Q^(FewokN%nQ&=VK8zVe_hqhOsc6ttqZ;5sMWQj(;0i84KI5cPh%a$KuQEFQ#dy^P;FwJ!#LOIhLw&e`O$ralJox?xJ1q7>;Nk^T|s9g)5f=^2rJ5$P5E?Gyd0 z5s~^3sST035UB}~dJw4v{Z|KyrT*1`Nd1S@en{Pi)O<+2htzuh)OnaT4{7m`_6}+7 zkhTtK>5z5~Y32NG7PtS~jFzLs~UVo90iMhLmVXd4`l`NLhxI zWJozC=Wi+I$4{mULosC-Qi37n7gBm5WfxL%A>|fQYB~M4%R)LVq`N{oE2OJJIx3`_ zLOLnGt0>g(E(+h206%tY%A(atQ6(JQ7QVk)M5T*%2 z{i%YG3J9rwkje+CdXS0-sdkV`2WfJU1_x|vTDNK-}1Sv?6VgxBfOs4)8AxHs&6dy?8ffOA`!GRPT zNTGrB7)XDC^cF~8f%FteKY{cT{`C<^4}tU#Nbi934M@*`^b1I@;BTMcUyXp&2S{yz z)CEXQfYbv>E#SX80FU}t10ekWAN&8y{R#6Yyq~cCKb&8S|4;D~y-(CW(fUN`6P-^~ z{%?(MYe_NG`>(<$`kttJqV0*YC%T@fdPdX#!Sn>t6Fg7QJi+n=$rBt;Q2eLKjD@Ed z>P-+l!S4jU6YNfqJHhP)wTu3>?8LAWyH3nHvFgOA6Pr#<`frL({kG`Dpc8ve%sH{< z#F!IXPE0wWxus{V#0?B8zx+sFyVhZ_%93oj{|e5e+-!LU&4L~_a)4i@Ls}t|8QPL+Y!Y} z^e$1mMC%fzOLQ(#xxY28KDEvEjHm_%R_c}c`2k(NYQ5?M(^C4rO# zP!c#vz$Af^1V|DXNkF6(^%p`C07>8@0gnVa65vQ+BLR)XF%rK>+#>Oc#3>S=NL=DS zk4PLM@rT465^qSHA@PO875?>v{}>_RgMK5!m_G7f21xiHVSj}C z5#~pDA7OoeI3J_=h~gu9kElJO^@!3VI*+Kl-x{w$i1CO-;SqgD)E&`wMA;EtM^qi7 z>Hc6kg6IgIBWR9bIfCQ}jw2{;lPhE4C1u75tH_tqEWvs8Zl_Zo)L3KtQj$8#Fi0LMkpB}WQ2|pGDfHvA!3Av5fa8o zFzOElBLs}lFG9Wu^&-TJ&@Mu{h~y%Ii^we^wusasLW{^OBC<}6rT&vxL|_qlMZ^`6 zRzz44Sw%z@fm8%g5jaJ_6oFC%ND&xCKvWg=7eWyLMc@+wPXsy<;6z{(0Zqg)5x+#- z67fpJDG{GUT+%;}L>vSmB2#z5rM%;|CFcgDf2!bK_g`gLLT?ld^ zxP_pWzQ2}*7#3nzh*=?4g%}lLQ;13VO;M=d7KIoTVo!)UA=ZQ#6JkqK_9j@jr?EN!(9jeiHAKSpQF)&!qVz#V6@KN$p8mPf~i4&XZLB zcN*_6%Eb62g(vAdN!>}>PEvM~u9H-qNz?y?=_Ew|7oPtO%}H2JLUIz0lTcix3lT4XJ$s|H1(J_gPNmNWCViFCLNSH~2sXtLLiGWG;OCnzq^^%B}M7t!? zB}pzxa7l7Y5?hkgl7yBdvm}wdm`web#F7M-B(EfKB}pquSV^)<5>*nUk^q$irz9{X zK`9AHNia$RQGM!f5K0125`2=tlLVb4;3UB&2{cKLN%BjQTavtz{&Peh_k}Lkr6aU4CBt9guA&Cn~Oi1EE5)1wp2PRPeVn7oAk=T#KeI({1 z@g9lw{=|7qnnzMRlHQTjj-+)Yr6cJaN#%a0an@I<|C_>*^o^u$ByA%p8%ftls>Y;g zf5J2pqLJ{7gk~fxBOw_H$4Draw2{feC?*UeAs7k2Na#hvE)sH)aEpXmr~YPHB*P-v z70IkfRz)%@l1-6J>UR`{-&qvNph)&aGAEKXk&KCCOC(cbqNMogERoIA zdxkBb`$lg~ni3_*x>fHrH)LuMKD<};layaW(fz9Qg7Pn#G{?Mrq?er_ix;`;T58=} z-1r&fr+;rN-7Ww5XkLjPy=_UOxUYdA>#NGIIY0HIz#Ub$?9O{rx+~yCYU$w(wDJK7 z2W3e?*6t%K^@`nNF_AOj;>6pBX^$OZ&6|9T>2WUpixVR{F zoQ{uY?^k}dgWlY-xxOe+fTe$Njzpt)3dAI3ySZW3v|^pN$^`Ab^k~;gla{jrtY;-r zCw6JjSl;^drDUEB9l)Nof2YPFy5+sW!K)BpDOsjjon4U5v?8kv3pU!))8(QcZGB=+ zZ#uiT_|a$qmYI;Wn_*H09;xrT@`}>1r^jEuD!z4{CH-K6O5OgLUvx)`^E*SmEJQ3B zvpq1&fj;=caiyBhNjfBVquL##Uv%#iwiA`jNY~3I-dcXw=@d`fixw-a>EU(1bfbHI z(z^G@xOOs3`(?l<@8coPvB8Yn}U(o;^cym;WNBB+#?Kb@7&av{=3#PMcq(JxuqDcNhGiuR`|qh?j+s zJW_mV%O`ibZD`fYIioJqk8=GtG@tlEpA%L%#bGKn8Rr~qURHR}6)q{GuCuSua{h;Z z<&F74r=KdAEqkE^&*r&M1|9_u=%afieEQ=kTGmeJt53vtI&bU~jRt-x4ja9p9&sPh zizL5Zy1L(iZfO7TW5@RIv{}Qv^WIOG=38K5)Pir1>5V2FnGqiy>7dMzk3$2)w2b_O z_k5zSbiRK?==EpAwA5<$36sxd7_g-jKQYNPd1&4z=wMR~;|)qcx3H6tskI(SuJ83+r|d(RrN6iB@_QsLxiM z5H{@v{pRtUSH@*7^dgr)>(dX0=$#vGy~}H>z=;Wuz9;PWr<08fSA@U4O&2WNw9Zav zh`zqLGF`%@5}T+b0Slj?m-N^}i7ctdcj<}hvnjLoLHYwHR9t%w2WiiSk|qrX&^7iR zNxJTC^ylpvV`q8{(jpE)pG}$W@A1GTCf~LO(huL?Q2$xrP8*Lt@HI(ikeSn{dCQpBhEj#pmyvFR2ZK2nr@u;S@1!N2d&D> z=2?0T(5r3dEbx8EL-(2y$0FQA=+Q5KnU5d#q;I88Z@+9XKnL{S7#}~64~@pdQ#!>% z>8HC+biJGOkhZP6ZnO8xH@eN_h;$vz{IHD0s)nG@_iw|}E;w?ymgZ>oZU^7VB_MiI20pJCuA z&&TwYMeha+M8DAkYVYS}T32IK)l0iR*+}}ve9fTmx!&}2PYZiqd_OI)`$X27AJw=b z|0=q)Ba#mMP`WEh=m{OIykKji#Bp;mz;2p3*0UE~xpGf2EtQ6wFHWszu3xrAk5;F|;=o z{=&5K8Qs!3VPcH)S30c9GQDkT9g=PvkN&bUmQM8_;*=?Yn z*MaTV9T6@UM|bfa?zxfrobICqUMhb1LMyv3XzEaS2gBn(c266QqYcO3xg&Mr1wFbq zG56e?FSKDa%Qol5JIKt?4xLjMPs_ZSLo3Vq)43iZ*X<8~p_hCrPi~)HkDh1E!O7_f zwB(bVAF+D&|NbRO`?o_>zN<@yAhfeoJdz|ADbWH_>w-^^P(-0^O^P) zJbTGWq5=Am#a8kjN%Vw|$Fe1s2hhV}cU9WmKGVYMmz0fI*MMlwuJ0LFlj-$AljKr9 z2hhdSRkU(9e5Q{mR;I={HDI{Mnf5u6LaV6lTNWGsiVhtVvEDV}Go5!?k~-v;)_{hh zUFY3RQ)!cnM^|@Q1=90)cU8PIKG7A1kNA%V8qhSTtM0xzjoy7{)BUXrgXqOx-_xC` zPxQI7qjf6OnI9P%J;C&*E=@n6qVLy#2%=j)82c&ee4?EePWNARocU2AjrZJeITpQO z{8j$V*kJlb`X*(|;g9rV9(7x$`9&kDGIu<=?!}@ns#(eDTzO5qo;sAZBk3c(RC|6v z3cnHF)4JzqN~hDo>pAM*^h4+arjae(wjXKF0YRhEu}$zZqj#s@Pp3lf5%oL2zn|B2-gD0TS?6<}XEgfrEX?i_oWGe*L97{7mvvif zLAl65P9@+SNbBXlC=Q;5bbarszzGU^lFj`nI<^+JXlvX#V^sjxKYN?pHlBs7`KhiJ zeku}wJKtHwR|iKnzdUEUvkQ=p5RJ6@A>f@?lJ?#5p>n8191g@?3_D$~i4x#DMhebE4}Ma(0s1;udUdzN`c)eNN9JG=_lprP-v-wA?pjd0mXR=7{%J!r2@ zB#k=I(31b+I8R<9Sf88ycJlxH zizG4es@3Kn;0oC1)%f1{T7O2k_8QPHc6>Sama8v3jLWI#mK9`DJ;p zW!UiifvtC-E*<5{4fVY_+yZCMaYszom%xtSn?GMBluv<`mj&101v>g;zh+lWM+*d& zrMadWl)~hg@G(=XDX1^dFN%$%BlTauC3o1gLie?DSN6M7s5!Cbs}^k%mhzVsvWw^_ z+3@Gl!m(CJJ7lKbCsPI?Ebb7Uz)2vzcH!GGNJm4;haCM~+ki+b@;#GO29GCweiO`v zCgIbPHUI2iI+9!&lkk~ogEc1a*Ld)jgV4pW32^Iqf2byj$(3Z6Z zf`{*SK#F2<6xI79Y;UwC+}wPR2|3L_&uS+!5RZs|RUL09>|M636q^1BkL~CUM`f50 z_Q!H|{YM76CmUN~{j3wl|9L{a%O}{edo3fjh5>gsh^ZVJVW1r*e_?Se5qvuYbH0y# z0-uYyuH6?I;CwA4;rl8BRV6>`u(T#Zf>*-BSlbHNAVqMZ@N+T1K$}^(L7IvBAMuW- zB@rRc<4ICxR|P2LTm3zfO^4D@L9SS1CYm$%KJk5&2p!~aYh;dAf?)>V#b9$fyxOkL z6XDE6zXLue?%UY~YivR`ZfUH9t$V(d=S#r=Gp~fP9jT=CZFLR zp|GdqYdj4ElF7=QSxh7rV;WPP*9EimkQ{lK{*^%A< znCON=d$-c;^dCX!U~?MuBx-Wq)?su#mR?(bLn$J>YKp zZmQ@lz|qLxRX5Zr(4lVc`_GGop6-_LejVNe%P}&0#ssSg@N0W-$lwSWY~J&+-o;?L zRoUxy7R@~{6(Lu*&bk_Uud>8;Mv*~ouHwaL9t%AgE?j8i>4iud$&pOWCSec%`0MLYxk-?S3@dx7Mkz?wN(11@d>hJ$7#xMRXgEa9I-i7~am zV?_I)?U2U(Vr=tE%T>B-W+z}zenYvR@+5jw#%gvx-v_eM8bPsLHE=!gpuYc^33yqz z?Q@;UB;sjl8{P7v4<2okKXRYI{}qC46Z88j$Ki0xN$C{(Nklp{csYjL2lkV^-iar_ z!iks&pTPW)pk(20V`KB%0 zZvEgb6!~km>noVvPM1tr90mI~y&M-_Orq^V$xZ)q`(f|D5C0MN3Dm;VcD+xhT}R>a zJ}yhQ(n-WA*Kb}w*$?FMpTWteYQaxLVEwC}5f~RA5ngSXM5pwo7Hm}pAi685W_>~} z7<17NgqV)NhFMaQ9BC3w=So|YT^j%k$?X2SJ+;7lU88_jG7Qp6AODX1oJ5W(^&iLI z4S24G{vv8`Fwb#u3JgKwRjOL3#uO6ZjFDLHIS7yb62hnZ>fo2< zI(l%_AWlF^sven6p$Do?@#aN?5amU<@-bbw9{h?ngq-0Vgv)o)cTJlqRIZu4?bz%f z80Sll+uGIx@9e|W^uPgd!2Cu3c}yV)c8-)^Brcsu2y^eMs*l! z3Y%VNJ#T<6cMeN5ya$;ut z6mm#fuDy^q41{kX2YqVA8^Qa-xjURIJy4;sK~s4QQ)%xDSa*^+45|;rq9Yv|p)4x} zxrFw>Px5C8>**=v;!1nIEH?snH+Qow(i>sgAw+)1#vVw^kH0AWXA0Hpr39RF9szgm zWZ(4@jUXsqKPMg64TahSjyP^MYMBwt@Om*q0P)HjJF29b;BfkDFCWov_^7sV<)sK4 zG56PRMx!I}Vc|{nHK!)nOe6&fzUqPimHnq380TD_v9u>OXDq+uOdQx6?JVLVxtWOy?(=Sgi%o4vd8qW zOfxu@$YsYLA;P&2-Y30}ve9Nm|4gs8QMlN4E1c-u4Dt^h9{QGbf_}ovSnF9fTJz<~ z-O3GPu<^fL*;X%`;eM5ikjY>NoX;sp>u_Nse~yYnB_?A~8#R_)MQsMQd-PP`N;`Oj zT?q~JWTWmIpT*7-g2v#<%jOsP|XrOLO^lb&2lgp9E@ocnvFSnJO z$~ep)yr$2jwSZ)8yz!ar7GPYCUHYyRiL2dm$U~Di`Sn^^b$)S)WV1*{zVA zIkCxSUlUxqlrn7ciH$M}H+B#4P5`CfIBA~N3N3GzSDm~Y;X~<{!Yg0ds4~=(FgRg0 z0gOKj*J|b3pi@`9JSx8d%5C<3KGe!a-lY}=F~JjXzqEcskV_lb{ZfNTW<50S`Ze6! z!$tuIk5weYMpP zHayr>wqY8L6<$=Venx^D9uu{VjvY`ZF5s!jR}Bjr*r(U>PNQ8Hzv^(kC&9IKSv#Jj z;lHOuS1MP1ft9km`?m;Aqr+#iRJXU0fW|3t>ELJw1Ph$lx8zU-!;`nqc4LhI0#A(j z@LxI!qC6w$y*oPL=-5<99p`80sVH-^mYPP1hje+%|B)c`&fn=ncAYRQ%LRAeR=~?1 zE7e4~X>`+1BIST68OGg%8lU3O?23?v8>trn+zt0zNV;>iC|W3OrPJC4<<{Dn%jgU z)9CD1D=t4?3QRfg)6e=xgo|^(Uih}=LHB;eoGr(u5tLPw{+6dea^+34wUS)`Ua4bT zw{xNPrrgk#6VoWj{l3;ABMLkzyApV6Ul)8d__04?S2oDYPppqUHI1|*%3Lm4Q{cSe ze~+W>yC6wFKSc1io<=((xeZQUr9i0qU*q?FUEn2PRH^nN0~{RBEUVc} zqlxivX}ovw5AgIJs(p%sQ;Q3A7h6)n=i~6!eA{V6yFdtgo(gnu-a*=J9 zlAvb#6Z|+kjm*k@MivSv5TB~QH>bM`Ze9MK5V$E3EKU#!@3^qCMdY$VvTQvChMY8C z`_6X3iK>dNS%Iy%m4ykm5$cRv0qIMA6synrB8RNCvoZIiNrvf`&zSP5_8v?iAs*~G$8P-}5 z{<~s$ei{vCT>8ouqe3cwRzKOT8_qmfJj z1HZX_=EUz&!A`%N##roz?+=Spua_jEn`fIlU)$j&@bcPsF^LLy75-)R3iUv0SB1n% zSQ1JwQ~dTAFM;+AfkGb!;q&2CM7T_d3O$C`@bi(9hql#JNXZ_Y zbwAz%<>?PJ+Wx&j^3{iTjGmcBxh%;P?p`W{>7CxO(4}0{bvsE!+S{Y z9`n}&CKY_^cjhNY_Q0Pt=cjXWUZT`FYDabm=U@;_T>u!Wsq>QU1Ji@Fl=A9Qn3x*vD*N&FQkda*Rv(lij{5}C{! z>j4Sh?n1lYuaWruT{UZtP9wpR+fr||X#h8Jb-R~(KtzFaF0kkgnm<^`6=8ut^Nqw< zeKQ)==|BHV66%G>E;A_=S3*8Ab@vdVm`x+qsGiuTXK7$7VIbJA*$b^Q<+PiE1xN=q zhi=1iE5F2VsnuPff&XCXkz%V}FqhJ7b^KC@e1?}A#Ehp=!N2zbHGwoRxTfjvb-5S5 zIi5-<6c?d8!ULE87)_%jyOhY77#ckHF7Xi!?*)Bkfw2Z5v>3g6_ly1Ba2gG<_1sl5 zXy6pETPP=^7wD-9^ux#Aqx(kL?IzgL&2>IgPAaB>)y4lbwtwygi`Lh??b|<~j6?H} z-fB;y&6dFE{YnF+3Z;x21HI504SO0$AJCXo!J8TNX?*oQw}{+B1DXA^M)^N_VgHQ} zUkC;{_;S&ldj_}aGzzdaNdH2kLHk}cTKkqh5N#}2t9H2*y?=UV>ff$ul>XkV;p#6M z{Kv2KU$0^xq!*En=P8vT;d|@2x@4!3kX^)q$#r!2n2(cx(>}1Oc`;nfEW^?p%g#aA zmuR2_c|Bn|yyr_!k9F*WrykJ+qSecC^jJsa?mDq)WI8_nQNg^iChRaeg=! zcK#z$i(>a*7M@1uzZE|{s7r^#OKU1d6Z*g=<=v&T+weWkvC;lF{CEa_tDIeHMu+G> zpY!bsa0=#JzqP0F6PgoI{N2Pejr3!6zbBrd!_?yTj}=XQpfya`c4!G-Ahozh%dNxi zHX}6L@3#vbw(+m8`bX)5cLgt*M5RjfENZ=DI_EU{aW$S_=@uOtBn;COSNlN5Jih(H zKqY!>?;K^b%0{vt34^L(bg;UlwpT}_AGifn;O*Vd=+TRU zxaBhzMOP2lM9&3{o-(7{Wjc&+POtX=| z!TbKwg>=x1j=oyt(ht`kr;&7hzo6`{-dnQ_HhQnbr+cM}4kSTsmSu21{QG85S11Z7 z$ydN>1DTD+N}OuKTj}sdTs)=eSw9f2D;?`zQ-QC{@AEqLZQ-Rqu`-b z+z&@}eT+p_t5Nd~jl98LHgYtI>e6D;!R#*8;Cf3xC|-S%A&a>I-USB^CUmfou{~c} z-A_7Pv`$(`p!P$`mp7#{n4CbpGT2nJ2?w=rO6nCk8PIx6|3TVHKLI2}1C>O#)u4nB z+Y`k#Y{bVn>3Lx*1Fjahj6?|yz@5K$)44J4gQ=ygi(n-iB^*Ed)kB;CS#nqU&Z^;* zOnJHJcWw=eY+Rw(m*9|Vb2Is%5(D&F_Fvj`cmQTLP5-9MVt$DI|J1`V3%_}!)~|UT z1|*1tc&1+<41n7XV~#F;Oo=gN{w@JWz1;KpvbRkbz(?_%6!aT_<02h4wS=!o_VjAy z{TFQ1`>8*%^%w*0J1$gQj~f7cap&OAm`ud##o^D6No;getjqJKJp-x~pLf50Jph-V z_5S>V$xHaC=eLOCu-vq6A8FQ&0g@-eBpV3T1JEPAO{@J_E!yH9yal4z$k0!hr^br` zikELasvjJHD_ZKWC*y0;3HBdR^Sf-6)O=dfHJAaMuR3^hX9wT}T&)%E#q=yRo!D}J zoOq1BI4~B)fJWxS$5%EEf>gfcl#NgwQZ0Saw8fi^x^$D$l%6o41Rj@XdV`=i89FHXqz-jy?wh-DiH+Va<$pkL8Sv%5 z4~ZPd2SMmi+=M$J1@7hk%OBl(QZ8x0*Es84&p-~srbauYXb8-Ir9Y3x3bZmHymC$Tbb~j;QZ~= zQ>`J`(46_uU#Jo7=^acG*np+7rX^CEw=>~n@W|$3^C9q@N;WOEY(zdk62w*hPN5SH z@_w~DnJ~F&>xX>DA;?xN@{=QA1}upd8}(mPsMc_DZ=y02m*YrkIDmN~-LYYD`k>)y;dzr9tdSpa* z(=a5BX1C3+Z$?KYTVJHSnL=Ji-90W}Wdc`2+zkn-Vc1kvuK09MGpaP}jsKM~g~Shp zbP>G>OxPawOpdNS4BwbpeIc0mEwM;>Cwex8*gF?C^ZPU5CGpw9BlBUXHa%8a`luP{ zCoU)WKAJ))Dq@z(p-i~#tj7>>7=|x1(%HJ7no-trx%!K+DP(@iXJ5oE(N{H<-9?hy~>Cf8RO3ZwfsXt$7ev z!i2!3g15Y@!(fy>ey?Sv1yMZ@`lM;$D%pMI(bE-7$g3#1c9(AiTnFnVEo55Jc*IFl z1H~yc=~PJ2_yJ6Ca}hQlkR1WXgSsK|hg*>|cWFz5#1vvKNPWIq&jk0mPP9dD1eVX2 z+g`ldidMI~TbOObWwb1p_th;-P`_koBWF1Rm(7}v|BY!yn~znllyAf{$QyWgu9FF| zgiSHx4kO^LH_7|AxD^@icit)fcM{1zA>1ik?PbEX_~TQ*Zj8WW{VP4^-d6N^?bUg& zd0gpC4wGaIF@dD&^m44sLBDkbC3xxtqRi=&jn)0oSTY z^l69q(79PA=zY**-5(!;reJyhlTX{w(D|P;p6@5ovj5U&&!0^Ad~s`86qp)~!@#=5Kb|f9{o)hjeiHc4-pE7%f+?YZpF zdd!L%_UFQOy-D=GXM98Ib{53R*(jG6jKbsK1^@rX+tJb;z1~+UcmN%5SER&Pz|%Fe z#}Yxc^6z$J<@9=a7eRUwxkpuxg-NhLfcG5jOXnyQHO;(XZ0W$dZ)~nC!AbP2 zKW?H?iUlLE)kKIi3RO3ab(nIPqxXZD^fqoh0J#X=>76V{p9Wj2*-@CcBnMvG+kx`m zwBw6LEOgt2OJq)#1vxcN>h2t4kP-K!+xKJ#x-27noG>!SLIuCv(O#)+?9h$ zED-vom-kP73?xVVrCZ}Vu=G}yesc#4addF;b||ypdZ^r71{wpxBb8@=UUeXvof;u^ zV=W5>#c~zstFoZMGC)J__!#W5{v7i0V+VR|rFiB*ISX~R6!V4eW&yA3E3YWWF(_|4 zF2>c?f!v+jV?E!pkmCA{S3an*K-<@(mUw*(?kKO%8XfOIUVrL+ld*=NKAM@SRcAp` zucq2^;2205t+g_n>p%@W1QY$r1QwF!&>@y-us|d^0@g*3!D~1Flg3<~Xkh>HpS}nd z+Ic7aV6-L+%p2pv=2OPNCG;)1L$DKl+^RV<8Nfoz={>;*wODZaa@YOZyfN5WHm^D> z--(juKIkmmV4(>skM|Q8-%`TW@AGjC{7gD8toEg*-jG3^_4Qzto$vLwp=orq*6OlF^CIr5~%mx0QujKYso1GkyU{ z8oxeSX&j_IqiVX|brO*Dy~FAbTrBj3f86~Xen8}5Q``f+aqw2AiZ4`jqOq&z{WY-c zqS@Pmv;Ut$nd&SMJ~R%?$-4JGH+G`Ohf`YrPBGCx!%S~;j9*am>j^(K4vg=->t%Z} zmv>I~;ihqXa(a@B{S+@@I;Yjhz=d%zmvJdjArU%JeQ~A0qi!a;^nr7(1@D1Cm6Hsf z>*FvomvMi9-HDo1KAf;>U?TUj{Bj!JM1IdFxa9rEp`QHu$*u(~KI*nA$6dihLzy$4 zG`tB_9tD|7M~uS|-}f85YcRd~zTEfK@9^2MK-{qwyom#vhf4k?jDu)tg7$gBdLsI| z&R+ahCKJ`U?7RCEe+A_4t?tzsRKMg>h9QAtKVLy4Qa@5D!qBlkbuu z3qrb>Z|oYzVH@+yIl`zY5uHmq%_Zi^ME5Vfj%rb0!QxEmy1Z_@hPuUKCnbq!$Hj&Z zm6w=EH7xqLD7F*HTT|yp#>c@!+KcfVlZMN49NK7S%S4KKL647PLo=&?HvGrbI6SOO z+*74MM5`x^e47Mc!?#)4#_-kadp`RH{rwjhl2N9_(kL_QJKNys@S8 zR{T5A!Z`tX91rxuF-!T6XRpO2wU|iYnV(NDu&H+1jgEYlpMY!62|tt9 z84!_EcEZJG9wrjjeZ-L_%!2R&3o&KQ32-S(&d}ROL^t#EUYz1!qNdwTi>iXy7kpLR z7`Sf&zCW6?F-2GtpnJdH&^!Zuy0-CqHa`o5vtG--KRf|;RWDSPOo&KgCUK)9lYz_( zN(DA{Fh`#sF# zZt{>^>(s$OCoaprh~#F$A;S{m0*?u>H{dV(V?jit4kf!LY8a@*kL;R@y{XN&ZEZf@ z6QEnU=ftt2L@dwBS^9rz&Y7jP!bR8}x6AiW?8GN41k*ztib2PS$Z+yXME`3BdSxs< zp2fj}{j3HT${W1weEM5+^2`9@(3RR7B5$`46t{Kz~=TE+J^-429j zZ#v09ft$-kXIV_RvEUL{&`FqpsAy>!iE~7hrB>(=a1g7VmYfkaq%+}R#D;peA-slq zl3L>DiKtEL#UZvH1ASkk*LH)9XW)A1fwj~L$SQC1{DblI;z=KS6$ZMlt8^iLj0t=3ke=J;lkGUFK6mL$-9{3WU9zx?z<7z<(sb}N9eHybd+|3jq2OhhB;nu| z5=?K<@F>I&j6`MDvM1&(1U$=fa8C*aNTEu8uyE$ zZ@4+7Z#n#=pA8n6kbc|0s!m5L8=dP)LzrOqiGTQ=B?+>B-LdDvU)kE;HX|=tI$|%o zj@ky`-1C}YO1RT85_q5J<%q-r64^on^-N)`Sokt@#sUoN}uC_GPsSG$rcBeA7QWszUB&}b-PBt0SK z0u!$DvS(zSNT3rqI~8L|M6Qkk6J~=nL~#hmH5MlP7g2II`Vt9>Z7c(V&53AJ)PHa5 zT4`ur-Sj%AH4`5BIZ#<{`0ty~mD-wN#f)hK;e$X!%!k7I^2Z2FxU?L;!{7=D1eImU zJFqE!84#jT|DJ~QI5)TIn=@f`rCTfLItg@ZCl56l6Ooa;$Y%2#8rpbc>8Ozj6BKrQ z(=GKRK}^sgWhaEar9*02TQUtz&PLF*_A$ZCBV3d0O@dF6J9;Vmh^RB$&-C;o8X6`k zeH10=GNI1u$nG`1B>4I_HS&ZZ5#8B7Q8N-kL$6w;zRjuQ)XVz!9wC1ch}}CCou@}c zXV~q=ZntRY)-~V!N+l+|;4nTdfzOZ!soP|~?I9wm1>T+??lko2^=46DStd|3CLfE2 zkU;J=Z=59dmO6X&!$Z%}knW>uV`2o1(p;l$)Q1gk=UU3jAh( zP7i-bSpo?{wgvc~5+|bj-Dl@suf;R))+E5`I|FKn|Bju(EnWuuU8|A$dAcn;Y#N3=nPg835ua$AnWG|2$5upd!&A zjoXS13~=jVpOefcK}uc8DIP8&YFYb~`*;Br%d<@$mjniwD)?t!&&7=jGUF;||8}Bd zybSNPSyaqc7Pn*LM+PXVc9s_B;XZfc@sHx3?FNV8G9YY0c;!68Ek;@Ou zyHze!0(x)97-J4~>rkpdf!_VS3*mYa%Q8WWiPt8VNEW@1) z!bybGIxG~^=E#5YC>6zh*{i1z&VU=jTDQ-B#53^M&5>H!iMrn{5Ty=K5%GfcsgfWD z?ECQ`>R<&4{yLo5y5&PBDlY%YlTZ_ofW#7# zo_L1y20Ptffm^9ak#+rW=mmV@p=_-dRfB!O-!S>47<{?q@SeZt30zb(a&n8suq^{r zs#ag3T0DTWv{jw(PUN@kYU%;3!_0b~!lQJ80j~GWL)mpCxa?KJ^Cm z<_r{HH)p^Zkm`zVAVID0&61s7o#;$$M~wjP4Nv1cbN2NC27K7?U%G4)3BJ@M*?3}Q zoo&@74umy56eMcvB{ihafHn(>pM}jNIQsY9>1@YN6cra-Hs3%&FQ*(7I5ZiMmo7Y} z*^2EzmBHp3>rOPJ$^X(ZLcV<=ZClY-RUI3YrYtx5S$)w z6@FnxeM&*THCvR7_!*G9`KMSnkpva|k`acgohY&{j>dXGK_VY{_UUjlKuwVrDcy~o zRZFP-*v?M0xvkIdM=%AsF3ld2Tf=}ggF!7WJtWxrG>QMRNGA$4Aza|$@TMSgSE9?N zB|6wW%4DbalJM#D*>&SQooJ5vCzs!yf_Oc%N}02CP!&;I=;*_r5m(2feVm<$N;qr1 z>l_6IuT8Z1%%VeBB*)^(6pMizK`62GB%eBgzDibH2k!00}&s) zZOl?o8O9OKyZi%V{a9@1f}TyT3I(NW9Xe3ahz}NrH(pj6ApzUovx3;#fw#Bp#3?Q^Aw2$8|)jm9%T*nO6VOoDAgJWa=N5vonx zUFlXk8KKGR@_P5^aPH1Kxhjn3+KQ&li}q;!JCw= zL-^)G=s+T8Z(QD1Mn;=MTJkn~(_ta#u@xs3JBzITGFiJ0T1RV-w*KQWaEm;A~JJnW7 zSetA71jk$e8NGXw{9pEAIw(KBd+;H~Q&$3()Ube5o;|! zo0yPM`OVUX=VEj?A#+VfjfpoYrE2%viFRa^?-J3di^az4A|omU=un~9Hspr!>rR&l zUJ{P&-V zgBwr#x1;8`a|Nj*B$U^l6hmpD!KH{1r5TK`65xH*_3K!j%)HORibz6_VwUwfYiN+^ zzoxt&KajyRo$+_VGZ0YC#Z^y2S`?YavX3;-3nLbkV?24eN-gkoJ2DLIdR6ratE21g zKAl`hgFA}gM;{>i#%Bi?+RzMhbj9T= z3H^3@X<~MR29h`1Co4&P5BowFceXl>h(lO}z=EYM2wnQdTlO zt<`-&ZD=JmtbGU8i|>|COt~ONgC94eI18{PE$QErtnAr_wv~>m+bvI^bcYbxWq#Zz zl<#_;@CV!Cv`v1>zRqp9-oNi~1Z@Hp_+;&l-$(;F{Gofub;p><{ z>;7$Rm-~mi*;Xl;!PwTznA#gIn&DF|=M-VCPZP*Exm>997ZpxcCuWvlUvcNR{EpoQ zZ74qJ)9{Ix6KGQUqL|4v6$%`l5T+^E8%^e1y=AY`hMx82800;gK-FXK=2|IKc$GR4 z&5iLvOK-mri8j>pLD+!DZvyEQZ_ht9M1_O~k7z~g?RH*RbLPd?HpDJX9>3!}foc!_ zcIfY>AH+R!p(c4Y(lbkXWXzy$5f5$&ifyI4=+irja z3Kde_{R6G2_-F6A=R6ara^Q?rQyvxi&0FrA!tuZXd$lLM&8@gvZXdIMVH`QD7|t1G zP_Z*>{&4Rg4k%nN5AScQY(?JYKH<~kaWvn4YqjVp6&^VK)K(HqN22njf$ok{x=c@SKpB7XI=hrzG(n5l%H5$AR_O+snr+o=?vg62n2D5UsBhUW4Z z;td`0-MpJXg(i*D3zr-4cM5!ZP5iB>?k@i}XZjfGuW{*dk)?vU2+#3v2!!jD^|LYJ5XU+H&mCU<>j|Gs%d5Jce?l z=legcP#~}Cu>&8F;FoQLi7wXeJ66!ixfC>pZW-kq+dNN!+c~E`hvCGeq4tgSNM#EW z&-c7?_3{{cqtxnrZju5q3sYBTD{&EPmzZAAyB2gcEqjP%J%-AZb!7=}CMYn^Tkd01 zflDM!gFNCdTF?(d XS#?aKdlotzq6e$0`nOcfdD?QiX#qzioED~~XaINYXicpJW znzm6O>x6z4Z#mBYwN1oN-fcl!KPg^&DS`(u@3|@VD+Okr-rI7qlmv#ozmu8XE$DIT z6Fw`hF{E1Xk{~klkphZcT4VViNKkc{Cn3PK1qsMSG_LtRioCuZlawi>z=8L{IzNhW zWC4+f#u{&3*dvJuK1^wKV>7?X5iew1Iv#yk&>7PQ}@^?sy$k9{^&&te6g*&qyG|@YP+}wPf?rEyvClG zGIXdDj&n&g$4HlvL5cWFk>BZ%@+=cvsz8Sbv|3>|tvg0T~e9-2DM zX!}yB>FBW$l%8#$`IJHi-aEHqHsdPT<%{-8`3lXb`fy!y_}&rZQm`mFFh~aKsz!Wk zI0-DrXSsH5Z$@r&S+@-diX&*^TVR(+CmGh1GAF~saO%|ki$9CI8Hw!NW4SCig7`V~ z>(ADcL2SETreP>vW9FG_5`UTyb{_}p*NmVKypHQrD#_sVtqdlEafxPUu}U)L+FLAq z>hk>CFuJvVL*_^^#w{1E5(4oOzB=b2G~R^vZfJi)2pkzk{8eh160ga?85wS^=a2pQ zWkngi15^DSWdm$kMRIs;V@f>;q)fK8EJp5l!g3+qdHdmxoc@(Cqz^>tq-hm_y>FOfsZ&uv$lP(~sleOBb)FuL>QxwEl78D8#gzMAMpg2HIeqNH<8=&KaYx6Colp+jD6MU7qP%@2X7( z#er={4xY!}s^x660jmg29YzDd{=Go3<06#n(m$8q zjfk)mVZ#$pIE1YG5@nKj$WVDT(=Y2JE;JSpHY_l)BxV!qf@sJIGQ~g8vxhwUq7hyA=f*c+JcJsbw|GY2+RsRmI)|7s z2{d^vcf`dvqDyDp>&n&f3^b@?P8R&T+`Jv;`*HfMQ{A8$hFd20%N)D=Qep^|ee+tU zhu!SF%fX?WdvQc$adENlRwHUG(lSrkJcL~SuKy8-y;x=H2c@U_I5P_WTpj3!TQ2J) z1yh#>(Vt~A+i6@uo$JUR@mT0ytj}7N7eTEZ#6%(nU z45D(^r?1)quzfk&dxLiuo@ui5L&6KbMtopEqd$rnM42UOX_{Un$Z^mh2*}~7mcMbt zp0g1Rb2es-1`Z;**$Y-l9wc}uCd6MwQ%nca!-b zI(N!#&A<_C<2eSDev6Pm_}Qxkm*xi46vneATz3$iwf3MNG$G;p{vARzK@xB(j}EC< zHlV{|lb1KiV=@|J%eN(XXW(R1_t=M?bF=66re zzURde)n9@Er8B(&3Hq#=)8QOMHXTl@$?_yn^W^`Yuo?dYDQo%cxCW#-FwH$WJAkrc zKPGP#$2p%rIp1?5b|&(L%RzVX04qB=A59FPZB4rR5dzrDdA4X;tRq1)x4EbNtp>#R zoH1zLF@XGS%1IpDcn@vS4wvA>i3uTZD#6gL0ZkfTirQK=fVgVc=q~+_sXLE{^6LTs zE<%(dS`-Q`M5MB$audgso=}n&kyI*)Bq~v4ckR1kjAbk{_GRpZlIp$n zb7o$D^y!Rq&U5FP!8|kf-tTV~4tz3lxb_zd3v5|=Ugl6Px;T19tnqCXy7B#1{kJX_ zURCtn`ur#h56|$PFR+z}wIK^_Qv<8es)rl$zE!iZ&`Yrmd;=^z;=Vj^U~Mk?(0%{e zt6Np*<5qRvALLVUw|mt+-fZ&uf8}q5@dg6J&N`j@B*2;6!>c==Px{2dhIgvmgj-nn zteVMA`hr|EGYOkLI8}vgytaf-4`$)n^KT2g*R!yqN6y zxXF3#$1+9^dTz9VzDT?ZEq=9jkH0PpCj|fCJCVY|Vm3_?^+h=-{Ocn*Gu|rXG<&sd zstOCMo2ocQ#F5Yc_g<@M>q*T)ZYP44L=IG<^|vZB+4EU=Xo{dNJBo$V?4DiVjm|;L zVZr+GhDu~MH$+cbh=t1-{tohQSorZC$D{K@a*#s;wb7)s64mYQi{4LWw-klc&NX^T zKHe9(^Fhl$2f1jx*_Vkc(b9|kb9|f0^qs~sU5W}M4f3iSOgwXt7&Tu+{X-?Xl@W7; zR?5Wn>BoP*BvULTb}ajJ%{d1RZ?v)b7*vVaA0!1O)0lYCe2aalcUV}IQo8c?bPn3^ zZJ~|!y-H-Uw)2g}TP6;pA`h0`Aa^9iq@w0<4)SkJol@dbiJawAoZ=ocal88PEQ;w$ z=5XwoQ9OND4wAA?-f;SCC7P@%bZx396SwMqf7A1+Gx+h2QR&dzfP##=ZgZ)WpA%UnUuP1-f<>Au`*7bVa>wRODxiJ7w4c4 zqR8BBT_qYU-gajBP9_!`@~12c%viWQ+T*Us+#J-mvY}zKawS?;ww3;PEfYU3boTvd z%EGsQ+*dFb&Oz?M#clJXDv|o3Z!tn-64{Bjv;u$fTu9CR85l91jSOXXpBtT8iJEYm z=(X8QY=2a6zWojs7H<{l{oR|5!nS|7%KTY@0<@lw|Kwxhxn7j5pFUZDEV80|nQ>z_ zn)iAse`!|*isQGx=0cWM2fo|8ekNk!_k#f@3FXO;=h`1zE5pGQ=-O<^hEm>z{g_ zjfzgYyDoZGfs(Uou9-Zn$Go|5mLKPl=YW}PEXyYw+4C<5+kdA5nfi!c{q0_lukqy@ z9};I_wKc_Cj<{zd>4Uk;eXdp@qjNJ%-`JC>n9BwR>qe)t@U8+2k%kM|WbX$z3Hq4| zl%y!DaEMH`l*pbtet$BV#Uex(usW5Ev_QLb{4XYEK5tw< zY)saGBobzEM+H(y=ZPy=Q;)6GpX){sGx79QtCMbS&qi^jaup5hE6^1u>~(u-J%zm= zQf?Z(Oe{>>g%G0$!u7jEgyEgs$ycFirjAk zbF#^L;26`YKXf!@slIqAldS)5RarS}Ci!uDE?m4ORwx@?J9Fu!Z9g4}HY|yaChPx? z8xGW8$|uw1A`kbS9M3}Ar#)LJ+DJzc3q=DTC6W2Ue5SFr=}f$mtt+*lFAH5-)=5t; zqa&%QB44h*Av2z5mA%p=)A5$RU6NPVn1#eVm6Fb7(vk4dX~xb}KppmVc$0iLhKUFA z9*w-9XQ3~J3dL%l=*T3BspoLL4sSTD$j^*mVvm!PW32KB3?2SbU_6wLSiu^P931M% zWx3B?g%BoQW|%gvk(7njx9_nm^{1my`3TcX$H@iT!y(AZ^lI6O#j&O8q$wtOME@6C-Y^yHiFH_thXv4&BetY#gA0?i%+P9mDN_6yd>Lc2*=30D3CTRaBb0%&$ zl^=LvPZpAoe|&~1MMq)f%M72E)#5`lPpcK1GO=mm+G&pZSt!YE!%mUubTpDp@m#DY z6RUOaC1tbrkr65`*zBT79^)BT6b$~9lMjjSn=gr~#e2F+0P5pZBsXWZP6e z;?-Y{x18-ifdM=%XoC9dF6^-R6-bX#S*t-^Q$CtE!l3!>Q zX6t+6M6;0b(AW{>@^W;F+W&D}@lq`=oUuo0Y!wq9ky(9%&XzYxEZ9U{-y!n_|KVRBtN_-}IGrdv!#o2Nc@~URtrQbF9>*GVSPW`OM@1`-Y?T*Yu zYwsxs>K`ko(2%;x#_2sZxN=B4QD>+gm-MfW3VNQ2Slyw?L%Yk-B_wGQSzCka^*h4F zy6W+d9Sa%XQCNddD4y?THPz!7Ef47(*E7+LmrqvOttNM1 z^VjOpgc|&9Ws`42O+B_mD^9(4BJ{RA&2{Y(svMnj%h?tYUV~AuYN#!(9$$XL$`H39 z;x*6P%VKlM0EEXnP6X6oA@)`!)xvuG^}}klYezGY+q}!N148BK3r+R2ELpy@CT*5_ zS4KU4l9iG$xF-{J`uf?{{G_3#I-i)g2WRam>mTyvI*rzQO7%bYH*oJO8#Urx!m`15wD$c zCQ3T|c3No}4b6$0w$^$>4fa!Ax#>f2J-)6sHf?M{CZYm{vRGL(#Qc77+YZ$l{PMue zOGc0D@$R(^=(cz!ni=*>i24ynL&_W7-)YIzVE@(MZAX3Tv8VXmAUVNIwB1gmX8s!* z`aZhbTuGz`AFw{J_V6aThrHuT>0=pambZoK=0F;{(Zs&6c$|TcRS6cUQ1w{vfJB#h zZw4wd+{o{GhlU1>H?}S5VqlTC%MG#}>+zK*5loS$40J!QnL3c_N<+aHR;8%cFmUgQ zJ@1Wd$tfzjB9vA^$n4427YLlAq0Q>of1&~g7N?a9H(QeV`;RqkFBA~_yV=y_kU0&- zB(%Ian83hJYQolMj?`nd+m18TQi=7wDTFuHh=z8U_vJf=GqAC=4sBq6J>IYd_YFm7 zAWD?FBQCj_hODM59)BLdz~L|7?>s%G_RPIu;BZv|Bc&VP!quSEVyY=UN6bqdr^|nL3k(mbxEJao@+lB?tN0dzaN?zFLU` z3om4#NeiqSgC^6^HQAFLUp6vu?Wd^I8M5_QVKlIDid6MTPYfl?zbvJAXlRLSN6S>hqTGPkXJ7dZpfTl7W! z>dKJd+~=$YQ3eim|MGi8upV~=8(MVe5v5#SbY$z-GNfj?&2k$L120=&J?Y?|I=pe& zN)5JV2Kv2ig@j0E89HOd^iCx!C6%=$TtAJ};iQ}`M+cP%F#jaavO2a59XkADpC+ps zo2%=&OzW+~=NomIJPR`@6moyG^<8)wk{wgZPWV!d{XWeTGHb2FpL|o}XGvtB{?iRU z-hpLEFLaQpn^uiwg*Pe1*45#5ysLY~lnf-O@bc)nyJhH?_vdih`)Ztj@6ZJ)nPuDPiHRoQ|Uwo28wP3dJn-@Q7#%q8W$eo#7! z`>i^%M6wJ8H;0&PnqQ48rb~{i->Ab+8cmX@7k=r;dUT$G)wD9CaJ)89erh#Vsx;-d zyjq8!Jvya2>1H~5Ro=V#qI!kUw2g*!SSIb=+fv=*-x@jdk}*(LUM3bKPvJ z@XVVP(a+`UaDb>&DIy}{?>F~WWG<}Ke;in7$1p{qoyu=lR+IgFlK+}eh8Ccnh!tjk+( zzBQ$2C^O*8qhFP{P4?-YnwDDZFElyjr8h=Gty%$1f9HleRj~PbE3tXD?NUZ?+?%JkQvtWJ!Yty;WL=3*C< zor;X7bp)J$LKY70Zb=?4s=$Y=54>NhT8o#b#@%tLNJW->>k8f6$&9IYv%)8(R^ZUI zho^omuEk=-^1l_*Q&D{A3X5PHGUIA)&ZAjme~@$G)Pa*_Qe=dOOtg*WzU~fo-oJrXt?vkXO@nz99ef1-FgvSKz6+vAZq^*WxIH z8S>88Qqd2}XYaYWRggJgmRs~D)#9fCXR;62q@o)9L?})43sUlMeH`ad zfmcw=(!)f**WlMt>z8jlkcxijuMtxjD?u)Xo)H7)6qH4UbJFPb_x=FRNuivmhUo(=XE*$qEpy-x-3(l ztX{vWZhFh=LkhCpAG>zIumth(MX3+<((#j6@n4g}$OVL@VnA+S3X)MBwi!?>K^OV? zZ#p&5@iP@yGS#>Shn`=yU(GWGZT-TsnGR71``A7=#icKoo-CvC8BU4|lj-%s!H&qNGXR_()6y#8DJ&BcFjGR8& zUApm@j`>DZ(oUbP!3)H{it;Z^LD^Tr9 z3VJTKD(fwo4;1+8gT=@dI=)juKUQs0gU8a8g{jeGB=`CFHO1@2D2-pSA?Yj~Czp1) z-6hut%e55-b~Yy?n_Snd&$h+L@>TKCo5$!lQmgl_F4@4~^oL{iN?(&vb}P$$pK&ot zn#$WineL%u1N#@=-*m_o%DIB;63NNvqaY*pk6tl)5WB=&OONbSG?M80TD=CVkL^?v z4o@cA=4b3mRw*X$;oNJNQm5naGkx|4$!3(ZW|kbE_8=J@>>b?VDOrqc!#vIiEv1v! zk{zc6Q!Yc3tHe6Z>CXUQ6@k<@OZVM7|wLe;nS z6`?&BL@$mB)A3Q)=dKo_HMokGXYJ90$*8rjNnEd{2yN^Y8U0N*t-Ql>eJdtdgH=PO z-0{;(Ml!DKW!rL!P-vWmxELu=#z18o!8SM6driPdR=)rR#Z7HAVIERXgLK|m!~A7&)sPj zX`V&sqs-ab<2mJc`;GmTsg2|UWb3gP4d0T`nY%oHcpZw+_pT0uyYb~%>eF=A$!Z4n z$oDna*O-Kk%vuz3;7AejpuP-5W<-=@%6w?ajISi&LcR&DBnb^lJ=ag%T!a?=a1!|a zv>fXt?T`JO!@yag$EuGcC80@^o9dRWE<)x~=7%=kBl~%oT)lfFg+Z>O-y{#ePC|cv z*qnH~pa`vS@q2OaS~*^9_cnp=69d*5J~kc2b@;`6nq79q-cYJE+ULplB!J#Sq^ z1leXI!~E6OYe^_)#zrB=NFj1@78632<@l}bnDowI2A15YA0l`*3B78)C+XQ#i01DY zQ3yR)j@Je_SPYUYsFRv;Bc;Yk=&XTUzwVbpRPkrani*Tm@n~Ymtbn@=e6gGU{Ix+6 zlIZa8n3hyXp+w;azc%a-FEb2hfQoMYfE{GF!fM3T@W@9*2PT?)~W zUkTaMCCafSquq6Z6$4K;U$Mk_ED?PQle$n~O%uu*l1j_Nj<)?zv$yO@c-IuH0 zZcju};?0WnMun&#K}J~T2My~iirRg`h=GqbFKt#NJO1N>UXH0MFl7)Rc7YlXEc278vAXsI0Ik)@N0j~o(BoG&r;*eJP1{9tHt zx?MRDonNc)D#^D174TYm@Li-~r;0sI{-f16S+i~WwRxn!lr<%A#0nrXH6EL%=C6tq&}SQ`kvN$G#OLi;xp*lJ zyXg!+-H6F0SmY5c(f0{x^GeTa$x{o^n5=b^!8{ti-FhlY)3N*c}$t;Q}V zpT_)fOh6SXiN_a|=Ob}b`-JKtvUTFFnZ>bB$N?mF{}8fFK)3u}?KqsCk2-R;vgWpz z;lVm(qdoVl@pq$zGE4R)pzQ_@O^6wD~Y!Orj3FTGHW zd7oanogtlo_?uR&VO-2d8-5gyo+Nuerq|z}nti$&&roRiHjRK!*7Yy_P0aHV^ALkp z5?+SA#(hVRpQy&}Pxi}OjKx!E`z!zJoxAgqx1XNnx~FA0@Mp2W?}OD?Dr;0MuOl9{ zh)m+~BvUDtzG!*(@gDhp@By8tJFD@`*rsIls(AF{W798H<$QEl$S-__dl}wya9ya@ zrfR&oOhGC-Cm!t|)VkO%nU5@g&)@g?Vi}Itx~^SGuA(>fRV~+uof(gQeiJzIPLR-) zM|Zr}vo6DrO1Dm0@kn(0Bokh*{0G|&7xIa@etkr3o7BbEVaWSnhv9;MH|)AS#ZBCP z+a#X9UmO1Y%fllz8^yn1P41cWsDJyUx%>&+$+sWrviZ~>X>Xk*H<{1=>*0s}LZ=_C z6y%fp*9Dyt_T`&43;D)KKf3QDEHp{XH%8{siYa_kcsoS@{#SI?dZ9@ofB(1p>olQB z(|u!t9@*Eejf_uvula6T{O+j5_vifmH5vM0lAYPA^#(n-G6Ugqb?K?JnZ#29&i zc!C(Hr|Q}IGanbYV<4Y2{43p?{iG3&s*=u7jGRz zU_(Ix7*P;|6}5ZOv^|1{9&ya5<C# zGu^pBWe|Za1qEPCK@8T^>EWPlYwm||%&GOu7(-PH2NBp)kPikG#Q!WRieOZ2-K*UW zkA-nis)21w77olDM1WI40U%Wn16K9s*=aegV-Xn8s!au6!xH?12=FS%2gC~EznB#z z#D5x(k1sHwrM=^zR!tS&jkg*bU_SU zR|Vg|=$Y0;4%^jJoBHU(qX7hb7vuxu1u^y4^ZLtqJ%3iSHuzdHhxMwE?io;vd5Ciu$pAb1~GtTdn;>0+}CAs;4-IUJ|<1d0|)>!$Op*m zZ~XW8Jl`+<2b#^Ax!HI*Pc{cOt4cL~`gy?s0^sa#?%ywf&i;#dcmO0K?7ybbRLK-vxDGg!})53UP z#W=ePv#Z;C#*pO zkk%juac%U??it6^^EmR_RDaPN2WCG4ferFOVw<@CJUl?M9ek+ZRDLy|L$e9-FqJ&Y z`q7>Vq751V)dn$;ZIZb@4Rf>#ICR^%`!d((+2jmN5N?pqq1@mGAl-79r{1d!7I0{{ zcb`mRGvfOZ5O0tV)EmSc@~vyJjwDf$rcKaqhkjS*_C@w1uL%MU@+T-b3c(Fv!u?4v zAE-7XAkl;krx-jdYH3hE0wWIc!HR20swQoNHLk{x)v*aiS zX8>TzeLOZ>v;Ipl$CkTM6EAOy5-?T)ee`W;_5c-_!) zJ1a=2;_XK)( zE+o(7%%V~Z_#N}d!moH!KLP{~@(G4V{nPN^22kev#67H~!W7Cly58#U zj-O{1_9IaCpaE!m5QDl`UVAQTlWQ4A->a64nKwhCAA!Qh$)_ebKDYr`KG&!(6^F{p zI6U9)sx@&*g8c}XKF9~I4`N{Zj4g%Mu*kJ1@O>q9m%UgceFzvo$S0J=i8*_ou{dV{ zSid=ne_R+>XdK>e#D4VJVP+o!<_{VG_Xjbse~WK6ZrS{m#^L{_ZM?9lJEso;12~aS z@qh;i4ZsE5HAhodS+1Pp12(U{nRoAf9|9*3^1%y)7~H@w3+n_uFO+lqz$u5yzUw{g zL*NMhmrwEV5K!=+CkPF|6~sDxx(9O0Ilkc5hrbJ_y7VD%1|c83L5RT}{KKc=^L!4S z;}072<*2q<^dWEv|K*e8^Kd{y=r92jUJNqwKWs(k0ENGeI_H*e??V6;Lb}HUSO_t| zg=v?b4m?Ppa{xn|Fm-3z>OKUJ;otnf$LD!m^&e>X_y2#t05;sz_{30uN(BcvlzZ-u zTBQ0AK!^Wj^Y8$82r*dDCl0Ej=TJ|3UK--iG~B>Df2&jTVNG zI6U)p1xHByl=Z1Zr>7TzlnD7CCPEBy;<0y)#%cse0zonUv5WJ~ie3bgA{?LR;Do3M z4M0{LPrg=Eq+7`m7PI$=?`ul!MIbFgK8TADgS_~v_xH}GdzBo4@nfj_u3fKt5lD=j zzyCloLIa=~`Nl-uF4k6Zh{kop&Bwmn>qS5{LOzg<5Ch%#y)6INjiuzH;R(Vq|A2|( zF{fU{p&X$ANJof)b`&!RI4*I%ibFi=To%8zHC|*hk&e) zG<1#M+>0hCND4y%n2->I4JoW8toNR5NDfA%RL+|Hu4=sqtVqZQGZJF3BZC*(j3n%> z<`|MbDw;nI%SILIbcR<6{nv>OZUI7?TwxkB;f`^&+q)As@_1h{2vb zr6j}L(@L1I34`)FZ`!5x-96}^MM-Vz;S2y!N@xH$rO{5?jSb5g9HesD^V!i;CB%SN>J1ww_mg=>fLQ8R&3$k>tq1+ZEUB3`-JAgcY6%U<%ea5I<%}pJmPaai zlX?QV+_cTW>1lWm0_+m<0lkD6@XHx{*XRdMuHhhO~iL7-(qKB$=xgPti@Fm>@h-x`jh`E;gBLx6b?nm0lb zPLpVwPynhX#Gq@MO9dBqA$1#(9X$xNO~?mz6JpRemA$NZ7Lt#QLE&tB zyH4k!1~~)N1jh*lz;Z$iJg0%S#^JBWYdK73)P>#KRTlOj;5s26*iMLn@9bKsYsXLK zB>>}D5u@!@E!2akzn{x_Z}Wbz9zpo~QJ{^Rw8asm$&3cv-07<|wvn26k_m0OTQiuU4mcfF8B{Lg#v(0AqL=-QM<-^ zbhw@aosMfc>XzvdX=wsHg?xZdAqD_dvMlnBk2;eBp{l=nOI=!|LbR+25futRMuixJ z)U)(*+!uu0lS@s}O_0nxqx|=b|c$Be8Z-pLl1^sqR8R zvO)pStPr1^AX*nB_!N|%VR5L|HDjJ_DMZBsvK8`yZiN^K*T@GiWHKVi0Dy88ypuEU zJP}TSbcF(-T_FbI)ud_TO`SFthkBj!OEqN8<1Pf`E93+H3Na9{NmZ{mEg`GyCMZ}6 z%em{?i+Xe+FkztpY*>iFh^1%pGKN*in$QU=_G+iA^XSEt{2ITf)vS^c0 zJ=wHw0=vC%a&zLRe!_cBptq0@_$|bM;NClBm->b*`z07I^-sgS)PL-^a2*?giVFpx z<3bEdt_lBv^|`YeIa==Mj|KA`hz|m4F64ur3o$6VQeM5#3rO_w5^u2?4iW^2aR z`H_u4)rA7kbs+|2H-fILYPO@1qwSu#BmCAbhzKha>MrDiz6&uZymMkA`M!@ny0r0#K1Jirkq&QXSdLxJHeJf))<=sU#0=5_Of$xPF7~jc3Q5!bC zXvEZC&-X9qJH@Ot?8FJeb4;+lPyoCy#K8Por7Y`Ol+?)Kek<$V(3b3ABVd0aANXI0 zfdM}Gu5!Gb-bisgVEK_o_n%s91THWXfDa5YIKgs<`Qvu=HFCV*lWPMaS&P^R++fHD zKNwc>lFqIo^&FRk)Z&DWQajZ zE*_NDo|!@hFd-(ZQGplOZ1jWr#siHoHP)W);$jk~twNx5n!@J#^?qAS*)w z2+I(Iw0!Z`=+V7BO&oC<2W`j_JJyLnUWR-Sm>~vZ&ep~zO54h z%?t%VG(!wj^M?{<@>l6*3}o~D{8y_rwK@^d&5#d-GsGOq`Ede$I7hu11L^FyS4yv8 z5jlkuv@;YWO%Trz1ND4;U#r@k9nBo_dHcTAOCA&O9O!4r2Lc-62@0BQ<-F4Wt(+x6 zDkn_nStfUBM}K!9u%V#Szj51KiqbmF#XeLq(>@aQegdb40zj%E2CO>PNi1n+bu$O84tKP* zlX}sC0I!C8K&&DDi&;}O_lcfU=Xt59mpq0OsP(&V$J(^-bRfX3p#YF;hylBfy=p2t zkH3Y3Ue_(`tU7h60|9;w`G8H03{n@ z(6Sf5QjT1%*1}PZt$#y!+N&|Woxe{f(r27Pyoy~#K3(g3zyd@5oZ+GZ;yVd3*{~C2>5Tv2L^m1 zrg*>u&%U`#pf#t3Bk;wc0XX9jgEy`}>C6WqzE+Mq z?vN^7SNF6Xfj(^g2>Jmd{LP&=OAf-bLVmj3(X`AOvpwkIC{p{7=mZ>Y+5eVv#50W|@pGROq zRClCyeW2ZL<;d#qW&Nu3WZDr3>(IbsLRyCy#P!)nd*;OkwQ}Tjd+)(Bw+Imf0z2e` z#LjvDA4vA9y3SUcm{tzWu2UTtST{!ej|rk38UWP}F_7)A;&?ZWWwmnXc1tyx1M556 z5D@N=&!ODm1`g@ICiTp*O0r$=1npj!H0R;lsx}0~JLCiP4l$7LnqDV;pOQ_jfqwtu zyKKkO+%^ORJmgPM@Z8V;!GzzLC*B@4*2=NrRdOd?h>mSTV8lZMu;L*GGv3iST40*`^&ypwa|G^Dl%InQa_+~28#E4S>=o^!-$`(rx45^a2J~ADY+^;$;G< zhkU^5AqKSGihaWUhVine`FmL1F_M5Go zR{z=Fh5)&T27ui|4Cws_iSX;C7us-Pwo;wot5U3G`$MmFZ5vv-a7=c|m1qq4k1XV< zOtbcUQg_`=j;*wBe1LG6R(xOL6R$N72=iL{duEZ5>+rj#Iz!2i2D0xQ*N3 zQ05Lk=jlzjcK6D@s@H9pZMIG; z1DHnNloe2hHNA%Y*s-msf6KA#6NS&9o@}L&P&4BlvE<=N(Q=IPc>1^r_eT~@V-O8@ ziFLg(CUeAJ@OSHOdew?_D;Iecn?HsIJQ>}Ic<>On-XQ*B?PydJKJ9q@+kWDZ_&(EG z`Sjudh-`m9mqdl(n z#;Kpo0l#L`xX_~&?M_Xa5pvasGXQgQ%d4#at5@8AB}S&U3F`@H%&uSEjs@J-A73?{ zhR>|BN_*+hifX5Zh3x(9`CmObKF{^rD#f0r+yS`ex6SM9Z^EvMOQJjVh$(gNhde`x zhMN@IbybNIG^ch}g#RiR&HyfC?_D?cfAv}7W+m=qi`bE&0-v(I?YJvwah%l_8g?C6 zZC0?S6wooskIyrCi|uLc!`uN>2b*Vh&2Ppwa?e`)PqgD_Vj-1{Cuw-9 zSJ*6j-Bx7e{rwQH>P`d?na-+!LBs#64}4txOHP|gAb3)00T-C;Xj*N0`Cp)MW-L5>v`pE54=eI}I1FvuHij--7yX z$2>b5>yP00Jm(G-e0@Vcih)zw;^46E#AC9R^Jnq=RYbjVU-IWAeHK};v~}smqYW+S zSy__wxW{8o{sRMrUcdkQDOlC>IqSl^X6$#f@!U93xl(=Bm_@EE$A>+a_L-Kppbr<@ zs1(CzXyX3!@K{r~+;;xok73HT*xLuwn{oZh`}`rab{rU1-G6FVIo5wY)MT07g5ra` zR}}ww!5P5fbi;XdM%+6P-XRkxL~q6mZqlO{HMC<7&3*Gk&Xi+;=du!4qg#;L!)m2P zl`pw}|5GcC4A=eNFCxwB?Ze*f&G=TW+RN)b?bs}24dS!%4?zf=3yv`9<&rxBV z2Cv$+9ytEruVVk3$KrpAwBU-EodWfOgyHOZpl6j)j*qD6Bni5;pzj*ezMnM0IrTx# zLIr#N?{9&EL&u7)g~S!mUrhUx^LIo*#CG8tv>)YI-h8d;%M&eV&Jx~%tI?61`XT)fd^fjn z>vN}+`e>1hTHex0w@;{a;B2j@zZ+)JvHKi}>$#kZW3h~m_%qXIW^eQy1*)CHC7 z(=FJEaq!kjL*kTH$8C$*LB~$Djm*ZCEok!sHLB=n+6PX9i#0!T=j(DCME&`ACF5!f zHrAay_<3IkUUNq6vV%1p-%z^x&_cci^&Z_DM8!mN>MzZ%vi`ZATmRTN(Cgm4791*C zv7+r5fl$xKUX;8+$0zsKD-VgcAgy*PN_T}@45z{S_rqNuwYd!#U7Dwqg2|@#hpo?j zvmplLmF2Tu2h(xy_0FevC%2&1XG>ZQcYfs5d#(>F+4z4i=?1<&TK1S+6<;V{!D@8s zz&lRZu9%xb$C4el8LNhy(H5%c&GeGFpEwPIHx1o=yNWx2satUyJ+lS#^w@+&d30dK zB*kF6DmspO@wu+GwHbZAgIDJ^aO>+AL`C+fa_bjHg-17*w_x@h5kBN&H((>=eYCF=QhwAs=sHnq!kCm8Ff|WbzuG`{73In1>WwoNm=lAGdjupX{X2G z1Wvumt*%Pz+1&cWy)gm$8U#48vCGe*bzs9ElalHlRN$dvcAzkIwHX}8^E2h0G1-XVc*@J0l4qNdyjjkS@;q++z#N5< z{At{JEgy|OeQ+4=b zHfZB3rg)(hYpmP(0R19jv$XbegGY)~b`eCPWQ)JLRc0BcYi}_^o>iC;#q?s3$$6+RKZ-JwXhx@e4uts9xbw0!pi6u`rEh-J}Qc8oK9`UYjfomMW}Y- z28mT2TU{&h#QCs)oyrp>9I~+v{TFb`8ZXHv!rKMH4<=4 zTJox*q<9AB9z3{qaCb}ww}EKe9{Xc6h?q8>wP2T3C$@>QsJ%6-3Y*T|I_F++6T0-< zJxJ&%w_b0nyoE$ExBkJdLzz}mZFr{RsygFybbj2Y`r3#nsmf63_uL&JlHWob+ z&TZh8H{N6t%WdE~wV+X-h-vndUlbT#CQqV59dlvBDlFM6dqePg6Y3G}9%<_5*1y;J zqk4l(?SLP{&0Z^qht%8fJhztVk{+G-!JKRMRM4?1EO)o}CH-O(Iyq05r;O|&2L)m5 z&F>$)<~9%-dMEF=u?;_-7f&m>-HAn;>^mi#t8nw$MIrA_HKAQ|z8uJ+xb^b;ekFUp z;MUUtRg<;B3GPwi> zbZ&l`{Wp^~+{!<0*YvCtrz$+9y?H~<;2awzmu*eR`VswGns62;U$ptKh?Fn4UN3uH zL;uM(>|e_huraI?%j~8+WOCE1aIke<=*9I-sC=RL%95Sj1|p|iMer?d18rxGX&DY} zSbJGQx##;%tYCfNZca7X+DX`n>8#p>POnsb)9K5t-`(|7aqA^8=A~YJnI7I>g{OWSqj|~_^9|c%%_iG_#+e)BW8q%ef7%59j$^HRU$o+uxEU^tla(UORG;Sw|kj;cIudovg;|LsuR< zQ`d;hR_3X163gM7f#uV*kDS}btcrc=?$dQ9i`V%hcJd{C zX+%GlM9>6xavS^-yAi%~8@IuQIENysqBcCsIWn%Ks}oCHdJ^jCUyUu8dI~d%8HZ~& zo$0#8tzY1joF_r%2f;%s>AykhWJMc3@h9qtG&R(TN3FNNaeP~iZTC*jz3_>UZQTYN z2eY{iD*VO7v$eSm@=PM@uCv;3wNU5%yg*&oPI3se`B`0!B}Bh$Gk)BN?ya-F>7bR%8FBQM zS65hy+y*Ajn&x^#ZMZzDU(t378#6L$CGL}L{Mrp_%&Ml%swzh#tDRr_bK@tvdP>H-1`Y;iY!|0$P7B<;LTJ@5^;!O_L^OQCbQ4UU{7 ziZ6L@DB#JQm$I|j_)4i<&JigFHV<>G{AEYXIE9DW4lHhcoY^#&)*0OT)sZ|7uV)Yn z`YB}|L6>6Vr)Is00c0mc%abZEgUlL{V8P4VmuKa1&cJGV_2Zd>+y;}or2`A*wqvsd zO$l3hHs0e}J2hrA19v9;e7D7@5!th!=*4a2)?XZaAf@!1&FQ!M$YSR#*>-$}^6#G9 ztH{Qk!ZSX-Bm0P?`%W7CZP?PAk2<|>vg@?K3vevt+(6lHf!Cgc1*Q-JenM$!N&8O zcefaNGYHvoe2UldMl|lG68NT-+aPWcbJ2Jcw}G73X7%K?1hd9hj4Ri%@sufZPqe>a z;N#0=3QZR_B46iiff;l1Id`DIk+mm>!L5&Ut0@xGZ>O-zA?fsijcjb8>_68kj)5Of zsta2q-iUfc1ks!=+y-MFuTNbm@XT1?aZBQZ1r@ToJa!$D`K-d;y)VD zt|_rr*CMzLxi_!__X5Bu5J^yT4%<&zS=+Re_jEp96FSbue7X1Sq!-lS%=MeC7SkG#aB93lz-n%NvH{(9 zIG9^6wc%PBRqD`=>5`4)V>dRQ>+xt&>#7=@8?K~-avD%?dq~B#6Wj*=(aU7fBW{B( z+0m(Dm)r5_l?6GxHf$VP`e^hn*)vLD>E7>V2@NP+Z)aTGU2eUW|JVbkyWILfrTNs> zWp3^GR@A7ssXZGznrXJpIYRc7WiIx)|F!|8^(+k2NaQvM8H-na;lXXNd9PKPlvg`; zl%PL&c9D&@e!AJ~ey#?;$ejH%FQ@@s`Y_FvzlB>LesSTN&zHFM=4Af$(~f)Xm=zy9 zDfcoPQ%@()_LAf`=bVEFv@$JZbdl>%OJfaT_ex)|I&TkZ|3s%fnR^ z8^7)HGZ=qTgY#>ZSMR^kKwxyXmcJ&qzKv$~>XRk6zJxOJGZlH(jyHCi+^1b<;}~Je zUzlucmfN;-@Qrf=GPzRr`QUMGgQ`4@JAtO$1~CU8ygC^|ehSV>48`4IW4}jNzW>Om z!Amo4o)SFMfG*%NBYQ7yecZP9J?Hjt>#40xZgPQd+OdV>0Uh^yY#cW|*rBkZ2G3Y& zamVgh1M+&U#9)2kHjrda6V^85HfZumnery89S2=~>!frQhoyR|{7$iHxHhi`e`Wno}CoN`gu9C0C`M-Ir?3WYTQD5WQ zVnc2NGoiS`Txn>)!_z*xAjKx99jo4Wnynbd#-|3pA`#76Y<+6>ohh;nXzXzO!4o#z z`d@QTZ7~p^s3*tgF$uBwm|jTUVy(WcXdA)Cs^;1U7Lgqg`5hjtij-(T)&}{ze&6FZ zn7T80Jbnsy0L!OeH5~Zbj$4m@KKSfC8~aeg_oR;0;%Q#W2aO2rcwVz}`tKNSy{?X# ztkmz$e`n*JH zkcp0@^xyG$iaW~kBbn{E)b~qc(uIsn{(B#$^^@tWI zmjA8)@Ay2r23ElZ?L@u*G3B6k1{*7-Y!q9XREsTa|Gc~qz(Q^T^^5)-gI@@tL4&*v5E=;G~@_6n0|ql$XRC{d@NQe$_1WskIO9MI-ThW;tG;jP1WZirIo@Q3eQWYxeLmmsnRCuPbLPxE z&ojT2;QpUs4Uy+Nkl+yywU)zU)BqQp8n5EU6v$l19R?i~uV-p}^ zGVkN@_r4IxttK2tVj!QL4l>C zI#ctXGl=iJVz?K^dj5MpIGhIl`r>68GN7sCRg~?gfNTBgwALy^fXRhd|djXU0br2wY(x&8V8 z7PWS~aX(#wfbr*&BQO3yyrC^Vdu_K65;#Rx4BzA@gTY&(o^p#6z`{(}NiXTfR1`62LGW<$VcZW#NfKN4_R z9SQPYGza-_lK(Ww%bsS(f+!hmb63ZNS1I86C)<5axM8r+x=!WNGmNFh-S6-|iFo`) z5pLtb1tiG-Zc*GLP6qb`&eZF#Q^3#U-JN!@_B%(bQBWip#vUZB++=V<0;!A$pfHK} z?MWR+Qg*{RUcnLZ^P3b<%N%KVNN5a6$ly>5BS65an|?r0BnXV9$v?1{3=TB4 zYb(&qfEs;&IWgm5Fm~?Fx!3l?*nZOw!EfLG7T|E%hLUE7N)g}MP{8m7kmTM}oD~&Y;5zWZ?Z$+h&+)2AEuIzf}o0u{geY zqS}ivgb9~?+dD6g8j!f~Mrrvf;)#aNuZfViW_F{fvdjSHBATA-Z-+rgy}6@F-4N!Z z-Foi1@<05)$H(P44*lG`fCOLM`Z;VA$-q~%%R7{H2KepW5yVhC4Ajq_r_y>oghlQf zdAg{J8gQLA{@Tyeh~Fz4-3cJiVXrP+&Nc%cweE`!=^h4@A1;yD;~`9Tbycv(9P<@7}r}_T?Wm8E{aNI$a4z06Rn$WzsB%F#cUN2X};`28i&v{;b@M_#ut+NqZqb z8(zEkW%~@+d~vr$nS}s^U*02+93H~dUc^t?CA6aVf4}`YcNQNKxVg2fQA2@n2zyB@ z*9@4r(Dj^m7Xiq>DKg%_ZwT9#xsuw@AI*fX|k`90!?UC#Q+!HDVKkD+dE{+WM%goK-^)eJjlfJj9K;66eCTGbY8 zv0nzUwIt=x5BORnNI06@_>+PJztqw_^q@eO62qJY1?OaaI>Sy8KxX+;j%fcNHu;r> zRw?TXK>*S^+E&e2cApc;Cl%L0n#TYnOq6rVVM1np!`8>a3Sqh?+oJWq~Dw{ ztUqP_#Yf`Mh&nl z&X`Jaeg}E~`u?JycL~5pwww)jbr9nz#@|&uON#`bw_5JZ(L=v0wL08g!({eM^tVht01_X8ObUlVmURP;XLL->3&k4ck3{eXuCFW(G!Qekr0 z;U&x;Hff~WJUoaM;8n!8d$1wF{L)>f)(9ksH+fb#3g3pi?#7rZK*2BP#Ev<*FBlvB znOsh}K}=JEuve1{@k-^PJzfEb?^mQ|yC6vh^F{op)gT|-KJ~()25uR(@Xx;oF+Atg zN2?(oB(O>}q2Y5wf?F4|BO4^h;P&p1Xa8xy%3Sf*oDR6B)Q`4bZnT5ghH@d_Ndd(3 zp09aRb^-BwZ$=yQ!12UL_;4x<*_sK^hNFa_oLT7Or z2_$m@6O7@A#`g$M=OVlX^9Rm^Em8;|j(?MzWq1I)U3PDOxdh@jPYwuu(?|Tt5wV-D za72ZHV9zqhzw&5moP|3r2JB*X^{m4Wz@pNjfyH}}pq4?2Kk6V7G#6|+1T+`Lnlynh55{RAMP_2Cy7lluDQ^;ujxg?-EczJjX+yedpnrlI27C20!FAeYnb^ zS%`p3!}gjR{>cEw6;a>2R{;qIe#smx5Jv)*^rGV`a8&y|*Ov1wd<^Fvp1q|2x9?L? zaodcMO^wcW*wJ=&1BmBEgG5GR5+doCQo=Xwob zmme)9ZH3!({C@6jpWQuQ52Mz9|C?L+4lae83<6&IT7QHcQCR*lzCH3p(2^xDXlpls z*_R}xsc0bnT3*hRQ#i!edDX4#-A)GL4T=?eAkX?)k}g1l2#P!>@DXB01K8I)3+6f) z5~y|@^y&XL`KJNzF4KK|#t!3mY6Z9>U{}nYkuCcAC=r;2#d@u%4qz7LPxeY_Bc6Zm z9-Yk;;&oMvIp|o)fG7C5R}Xv_7zY>On@$nI#U=c)p+WHh%;TX_-1K22Am9^SNdrjm zc#Y|<2@@H-DdSt^f&%_oGucLKBCy*~$!}pF!18nUe+@l~cUV(uFUxBWguj`OZXe4L=`7YTN9`ELGV{){h~c+f*+?slD8|I z;p^1=3un`H4c^DJ_Mb2^K>Xx+>SC*6#Lum6;eG|bj>Ea(Pvyji@5 zC=7S;#7C$LsnGQFV>(B0l7U7@pf-IeyeAt8GPQ&V88|YK_}MQO0tXa5TYO?)M-Tyx z*P@Q%=YH(VTFdi@6NtaMtp48kG2$<@CC%bDr-0K#>RfL)peYbbnA?39Zsq#KOzwtz zem|D$v9kZ#DJ0mz;Ne^wiv&@dxmSB(f8K>CkABSY$0lZd8u9v>WwsKR z5WgovC|Cea@#KXbw5WuG+T0$s{5gCX5hO_xf@y90v1>;e7(bptf)@(fTav7i;D(`1 zT0ER&GE+Op>j8(=TKiRF2djx-F;<)|-k={_Og~K8v_QP$p|EERClMdOSjsa`o&t=V z49nXYX28{D`#5}lD-qy=z5Ms6_G6LJLL&TDNRT00VC{bx33e8l@u*EqfdUhgtvT?+ z;&%A9YR7&eu()@wwL`ogvrrLG*mV~1O@dF3%%~v#E0s#q)sZQX6RTvkZyWp#xQ5^l zZ5e}IsjSaRBu77%QQG9T{+A%NaQeEXG!nQfOPz}!o&u*U<5{w|&Vc>x$GF31iQw`z z+Zp3ueVD|-Ce1Rqq0#S-%e}atwq5}7mIIAHQ{hCV#g%y?e;+2( zoW3#q7hjO({33HJ;y*?ebK%>kK%jYx!YAAeu*b6$YaG}Dt5HalF|SqkVUibwbF|K( z21vyz%(Aae{5hkFdE<$b%~PP7g|vSQ{5p2Dy6~|I?kOGW++MNIsbdtmk1eD)oBzoj!~{gkJ9bU;OJ&^|#J+BL0@~C->78 zQ=l@?T`y>rf(HvxOc{LKBv8_^>x+qBAKY|m4@34}g4BsdgX$V2xTz$rNhq5FBLw@e z(aRLDgYQ!*yeQ^79!|5d z-`xS=#^Zgz=yQ8b`>?8hO>^151nDVms!v`b0eix*X3=XnSzW-Cck>$syh}dixG6#c zoRwrgf~I2CkSy{_>h}!7spj7&F@{3%6EZGA(Sq zx33R#+8o?^x?RQZ9unMAl-AOMLFGY0hr*A-SuSpscpDRG67Vm++)T&chb=N6 zvdH@DDX8zTK1mKoywfp`mdlw_fcw$bdw6a*%_hd*DsEnV%=Z^Rz}qHP za|Q9;w{_ZMlBYm3`&ZXriE_o_}(Km(UspMyFH2-e?>ckJxNd=iiLNm!z%$ms%a&T})wGg-!q zxWr9?+=KYl`kpZg&{x^{=k9F(y=S=P@KW&uUrDUaWg9?4*x5I)gBw+@8&SNFYsTS5+Jg*M3D;Rvh7EwZ%L3 zftUIua81W+-?OM*?Ado;OQyeGW?Q;Fq%%r9++D;`hR9C!=PlEA5BvU=0t5t;H|M}zldNjrW7zwiP1+_z`=%t zsN0A5QR^_BHoV0YSZ;LbyHZa9SHg>KL|=gSFeA9qiMbamG}reJ(nA7<$Sb*3jY#lq zpN=cF=@bB0{JuohP{17@>oKw;2}u9U)_uPYH$1PmDn6}?_;a(W+=l_;r*3l}z>6DA zfddC>UO%d&fD3D3ysDQ-Ffwu98D2^cwtbK22=8B`;-~(>a@DVqK!uvI@qiv&`;E0Z zkX}Inn;RVpfvzNQz31gzcwY~uTseKDP75_aQ+x-b-gCs$U0uZ+XlhS^5!^aggrhr_TR%g_E3ea^9lOORQ zf#{;${U85R^Z++&YV{!-a10=lETOQ>pKs}(wauwDAtTH?U!C)lFY!v*mux|=H)gLy~D}t+Dra$&``H_I=liQE05_&Mc z&{NSlDu_3mY1VS|M7$^dvUgsY6g-1~x)0oi6z~M6Wx^jw0^fs~q_2ncV0~kpqF?qS zf#5Pr=b{r5yvt_f@fL;ef{$Tl190tkENwc0Czu4jY-#?b;MRkQWwXQy{Pk_Aq<$bQ z#0v3vbkjvqR3i^t#nfQ0&^$Y(Hjm0S@ndh&5Xfoj|vSISc?@RhFSKHtA>m;aPuNnHZnBa?t-;CrF^Z5Tt+ zau4tJ2ws87@RBPv-B^&f@}~)Q)PQ{M&-M?9|L6aId|dVOYK6@K7!a88ng32W1!cBxd@}WeTulbn)7m4(MM;M-98DEKPh4byD=a<&tYU>a`V zxZ;KyU?ZSut@>yZhFt6^DYT~mcloW~PrW7qmtqrQzHm2Y*Y6%z+>LnqMg1G)_IMuZTyrBcY$@P~#$d_0A`;k=E$ZdR-i-;qR-`}Bj06rl*PmCjobheTb$h z1!ycrMIL@n0xBABpY9&%!hTwtu`<6wd|~OEulNN;B>3r=dq>4<5*)j!6-9r70_rBG zn#DhmfMJe)iCB9V7F0;L&hr8Zs8rwh)b2+7!ud7&S63#%W@%nz({T!jxvpfiQBDHI zj)H8mpSm!vn>>*W>4+zWe!+XC@gczxiTWvir%6zwyB>S%D14jo*mBhY5_rPYl%-SH zg^|cyLd1tiP-mh1J%APQl4RGTA?GH6ztNSKDmt($-(xBp`H2Lcy3Bbvq<3LHKW~aB z#vuN}QT+a&XK9dN@FerGfzy*1InL%Ohw?nO-R{1M-d=T{ln zJ%a?)&wC49_24BG+NQt_!*b1lV0&~83HT6`iaFi8uuAT0MZ)e#U{=}kiGzsv6gIOn z*@q^hLf=a&CApR-ES^_5z3m0rFt9vd9 z!|_tNhvgedAfizqSXQSCD=k-L{ABuv$K!_D^?Ik@BSF3)S2VxWB#@yQ*W(kTfG7oR z`kyJmRdm@cf*$G4RP zcJkjIDB9kIWwXU-1}py-;Bc(mPjtr~Al~~Evy>(~%yMznh|%SyfGox(zK%8$h|FLw zHrm>Sy=NB)SKIp^508(FsEQjWMHqJx=RDFRB_MPzK_M>FWv&l}3^+6*0 z#U20h{~R9|qJ54D=Ya$x?f2{%RwrOhs3%}I9sIbwlRlH$MFQ@FPuJ{vJ7MbdnXLpS z)PRMl^(=NL#Iv|%6i>}h0N&G<>2@>}uy=>-a!WS}h-2)7(siBKRhpJN8r1*i|9^bk z82*guZYv}R+H%kQ11uurmVyhr|8pALjKHld^}ugKUvEd;yH4!N4pHi`WfTEHlEU$7 z86*D0=o|Y85-da%kl|agIt@gGmR8vMNT7Rd>@F#%6T5Wz@!QhbfB1iokK6n1EY&$3 zBv4P=wRya60+^I&#*8gZgGjm2Eu#JK(~85zvMIR}tJ_SW?H)l5NMkJJ2vI>igW5Zd zZEX`k$9ET2-tNQ}U*;Ve?t%P&$H!HkQ!&qyMuJp@BZuDC z!ZJDq>!&zil1at@-9@QE62Loa#MT6KVl$R*@7o$t1L$8&bhZm3-lD`n%mKiB!IY0e z0+Z9=VfJ(n{}2hJ*|pDfxprdwUy5rADq7I{|I~EOUEFpgp!>LYD_!XXaB?a;CNeS& zg!gdy(+$Iykq!69FE*W+j?=YLzhcyYhkgbrigbwIufX{$BYy&v&D|RkfN45?r53i+ zkXOpl+T=XZiM<&G!}2d$(EGp5#cSo##t6J8|Nebt`6Z+Ld;)yo8=B(mod%QpZ^l;; zNFd25F72>ZC)S@l9oe0V8o=99nD=}h@hjmKFF&PBfXWS4KdO#tfUR2c#1LUm%C7q) zSfLZ+nYh5{co&ZZY}+nE(Q_XeYL9 zPv@(#aKt+b&uU8dBi@^B7t81936R4)a=E4!{s!^| zLgAWrIZ1goBLOz;Dy|Yb0lFTFQc^xo11sN`V&zcqYU_}*4{axwb($wN%MCSPMPoMV z6HGw*eFX(IwytOTPXPJ+l-*Zgu9y_{q`Unn{1HZ41dS|rU^O#Jc>EW8B-nB0>P5~H zBw!3~$YJ!D0HSPOiYjkmqEcXjA@vxXD`xp#X*S(~sb=()h*}`tMy$5+;tRyD9riJB zxikUxGJWP-eLW2dj=6vbkdIgAmG2(tz>dph<71B+A^}y{%ePgjNB}G!ZC0L#x!(z4 zU*5oUJDDu;PT_G9urO3FKiAZO?QM@YvD8BR=^mggb{Fx|D+#?^7BFd5_su(}>}f#E z@wodG@@_2c=f0PBVCw?-(&J~9kl>su-XJ0z36e(RmTXT8T}kM`@^I;z zICjKqzEiSlvqwB@^*V_cCZ9jklP*|Dm*3;ebS&QTTmm2Zr~SW$XO31qp@} zqgakwAi;=2Ad`U%%>Cv#E_(A0Ou1xYpGt>Yr4&o~o^l1f|!CYaa2z zifBFptMNCc!3mMeO0rWV@F6k_f5@@}TO2Ky$t5CQGtWM}RvGaERT1IuIVQmCt8cHf z22O+bl1=@Nke}a}c_w3k@4#GbFOoOloH)T@q-;0&uRV1tQy3ZgnuX|3jW0>BpFQNCcxSo*6trL@0VAu>0~+NABPp| zOXB4^u$Y~u8*J~9;PbX{T955W5XdqU@@;t>B-}IZu)PfX@|jSzK`1D{|K_cdNC#$h zzfkS+OT@38yuXi;9`QV9hgjBOVZ*i|nM+lU(_ruV4&!;qw_m91!y9hzz#$NyC}xZiE$wQ{&SA-`Drjyuq}B53am6LGTdl8u=dDG`L(-<_ulR$ zsK1DK$)d^F)gf40avOK5kJU7YQ|%DPkzob&5?4G+>PkEIsNL(LOgIu8cpN@IKZyj7 zi%Z|k!zvc;=LAjbVeU8U!-b7C*npwD9L^p}JNB+HsMf>}@%3?5`Hu$?pVOQeGuki? znxt#`I8RK23*m!ObN`%xCf?-5&f#`!6TFUebwz?My>4s6HYB(cv!>GeX&m^z9XW7D ze;TCwQI+;UK@~7=aA|l;$J~S12d?8cf|^$BDE!N$3eS}YRH4bFm?N8 zTYf3z4e*swF6E!vv81(f*#HwHPz!0#EPsatqRKKRNd@p8vW9+aIXDe`j~4R9L4i5_ zk1)ZacI*g7A9YO^@kMdZjs)f-K0k}$o7eMkz~ymq69@DEZN6=zvxPkVRd6JG^z(Mi zam_`|N)-vbeto8BJw*aBxf|uiDdV7jI#^#uaT@sS*&nnU3J8aEXlat#v8?a6{Z#iN zz9A?nkueeRXWQlKMemJ+dWlo7O=PBl>{?vJJK~)SDg^UG5P#!Q z&t`eZI9OI#q=^-t24By}-#k8vhdU7nJz;$A){Zfj+!=`9f&|<}AynaBNRS(yL>FnbT71H{;{n@k!{Du z*3%qbd_ue;pSmg=hWOa@t0kiPS}5{Y;Vm zD@*0rRgSOQ_9Ma9#D$r*L*sz;MItV58ODS?n!7Ir`PD$Fvjom|EVI-m_UUuf00n;Y z?k!S?kCXVS?xz9^6??kvNM0a=J_r1ZClBC|$T70Nv6{9W(~cZ<h)|Jrcl6I(H)2c8;@@`^THgi-yKPZ_`T$Fuu}5= zC&5OCi7?gTS|oKo9F!`bO4NKyY{QCr_9yLeLp*2k&_0Vb;vYWMI#_g#eH^^%+AquB zPX>6YJcqOEa9GUj-TI=oqYX=8@Y&;ShXhg?8b`y&ks$wihD-6Sl^!=g`Y-EN+L$iPuj5Q=PsU> z(n#>I>$z&q9VDn2e!0*%ItK0^6Mw|?9;RwMCPvdkUSod|Q%*t~)~D~`6U>kJ&Ntku zOg@NLD%_cOs(%bH>}TCli+@7~cWa!ZCE>SS`TXnnZ((hi@Hr~8C#*;?Q=CNbwL^l+ z!#}KhTF1bv>TMwbFUdeVMd&*QKQ4Q&KIuN{+lHNxICG$s3h}k+kM}JaAl~P|b>7o8 zW1#%@aell^7W@^STxc_fU$;-Le;5hjNgm_j-%)BBs?rC zMW(+*Z_x{<%j-|K1;IsUiT8pXJ-tX!xYT9!hzSYGdd&>$a>fAtC@dI!j|_VB8hlT| z`2h(fpXyb(;(5qY_I_7A;`{9_V-7A8{yYVC(rOEuX=A{a56}J_+<_t1^>0EC!x;+p zv*%5N;Ci3K#esuOWk}$mcRg%&1PQ)1dv3mZFb2$>9Kz0olR=d2lHxu%LqZBHxy}S* zK?5z_*UDcaUY)P%$g?KI)32C>3f~z6&+z+>)XWBxfsue|JO`Yk!37J6WD2)ociI{j zbDkixjQ@N%33qM*P;Rr#l-R z$MB$3|H3skCo(YDN_ub_&eIhD!Sj_cC|&6Dl)tDG5|nW4=n-BNjXP4HvDs_$LaHcK+t z@?(p1E1ap^z_YC_6trS@A6txn(no?zk)4Yp2asTCbJvZfV`D(ldrw7x2^oa3D_I3M zkbuznZ1ZPulA1y##}cK1__PPzcNatvuQS77Yl1&C2D~p{jh!=qefi0STYKPC>)Vzn zGZ&bca4Nm?y`d};NM2eIW@1GGjqVNeSIT4H_Jt)uOI44K82@2{J}!6 zfRr}GFF(idejj+o0PB@26}MofL)A0yx;vF5pljF{r6Jgg-Kr-=DtvGF(_$UrLE=~$ z5=6cr2XL^Bfg|tv%12?CeXt^%E7xZjj8^uVXtNFe1_s5bMkf)!hfns9QWoMfiTJV^ zN7^yKecv))4MwS#&mGf!PyuJDhCYVe{n~C z>!!vCkU>s+f;Ag}J!$_pyMf*o?EC3(ZRG~U8|AlBuLdIC6R-K)i1FJf@N56_fUtuM zyq=%>a1$<>xTcw0SA_F(HI*++2g{IPSDxD?my1a7LaNcuYI+n**_dYRff3R%%M_RS zG7@m^qr6DXZow*c^Qi>BM7&GNvwpHE;_)SWlto_*Nh2V0|Zf`RR^70^EylZVa)XM*+*voRj-ueDB~?+ppqB~*=6DYjtcN15*WJ0Jmv zSa>bf*P%Z@O7|~D0wM*Y;Mrs4Id+2qL(qbW;reoe#_h?rORyh(U$$T2|sX4@i zPXqt)@c6j>FM?$*JsSl}sihiSFg!LjIz><(u6%wuNpmf9YYUcgK*9g99uhbnbM}m` zK@He-Ny4=7(I_~bsBnmSa0<8@e$2cGmtMEL{cV*6)I z`OoojPTtJv%kiV&#*9+_URVc!#)rP;Q6350W_Z^X)!U5enLX_*l12iCmcn42RMY@F z`Y{pBh*5CTQzy;@2B%sJr)So_fVUvf)HA!H8Dk(XiX7xe{NVA({C!dX=l_3v9MOG0 zIV5NlnA~ss>QyrZ8fMbu=dwvaGTSw+H>Vkk$*|3-{;Ld!(({ii5?-hQw~Vj;s`eZO z)<<-E9sn4QLoE=*ltlvSzOl!5!ikAA*{Z1HIMje8hICsgyMOq9kB<{69Py%uRj)U9 zQ5n8@I|Wp@ci$C%1_KfmjGZq9G-KOmlO|dh>LKv&pA?_-l(G?OK&8R^vj*o!!H)|k zt(yv`z{k^7z6YL?z+GE|>1Ky!Y_^@I@6;ILzsc>7F;;{8f5*oSXVHNexR+D1fIr*l zizyIreOc{DI_!wr=oT zQb>SY6M4v1q#66A)MejQiUbi4G7N@(4*t0!3a3J>Ow>j}r3v+E)(2Ctj?m|2T^Iv0 zJR90f$JC6m!fFJkau6Rb^fN$sW)S~JP-EDqdQWZ?Fx}T(*bzGgYF!2c)RSO8aKkg; z^YH+Y{KZr zuADcFMtqdB-tp#2JQB2f0$nctQNZ3VmnH~{(mc_6^o-*^35+~?$#}882_uJOWz`2F zL8qoUMe!Bl3p+<*%{WJa>HMb}0lz8W7Bp}9JpnGe-EV0(d((vN-n;+Ok*kPLsy$Nh z4F4DjTwSw2-KQS~eCLw-cs*d%>V*yYjyMvi8%hW>Nom6LuI#I-utkEg(rd{gk%*63 zW}oiZ&N>o_#jrHJp7C6NMKw1i-Q}M z&v!a{b%fpl_GGV&oc*FnfYwmtM#!}$j4D7or$h${($3x(*R(_Y=u;&F^Vtzl>$f(& zVGWB`Z!q;AiGmvqHg7L1v}(eJPpml0DlwocW5Ir@ttKrVX~Hg^x0K40K!S>+RrO)&h_}yQ5$Nq30qXDjWe1F5Igd@b_{K02 zpkH6#cUZ0o(Jh+Er?;4^H3j%~s-&xhkbvS|Opa%36XrT) zcA0;@?$3`Nj(>FzS5qOu8x|YdT^2xn0GBP5}RK*Vl5~;?Ac2zZEA{TAc5?YZ!<5)2F#X7{B z@)vX1rjCGW_kyjm;;^WV{^(%0H|$5|IRqjL8ZmF2&pXyne|S8uIBkEHZ7~w8oV1~O zdT#{m4#}w8CNKp!{BO@*xlRJqA?u7r$&HxKQU1+4um1>e=XRbwdm#hyXXiV7CZk5c z%9_Ct0R|@S)?JRF_JE&ed#klX!x}L&A-`SBnZJ2F4rknHu5>LH2_!|+LidD>fVr84 z({EU(Kn?Hx#yB?;_}TvLHJw`{Cbq|2_Rjs^0+|1Q*h4-l0P$sADsor7N5Ed~`h#I~ zQ@}I3`327vxLqe{yU9<>M(mhJ`Z1R9|9E(O+*ijh#`zbKAg+J@R*~xn2p;&AXi7B& zdWWf%Qd~$N#0+q4(QU*I%2leD%PFv zA`EPPb@vO8d?R+zx@d{>(!cya$H%4JOP$|w7zunoKC;uY90Avqx7E!o!cvPe^)B}u zNg(eFoFv@Uh!sS;y-Kt~4LB;g;$|(6cuBM3&4?2tpsju_@68O1DKp)Wq_c;M&o^BP zuWxI_^saL^3mE>N|NrrEySi3c^97KA19!-+U3UaDo8J}p9iIgCYHqK+&chj!6T};3 ziw)RD{^Ysm2T=odxJ5V7Ga)|lF>^JqCfr_gXRWHnFbqcdVb4agA%V}S)~f7;2CT<| z5V%k7AO7Ft0-EumNi|O7?OdKYW%10=P@+yy_dU z!?mUA?}bqV3Z9oFjZPwdrc(LPE2$BnbNH!gS<57du`9Ur)Pe+}-hC3AEo#70glAsr zazg&UA3oIG1#vjFWIsX~rM@!n*-W&c|O zZ&DhZF-1KBw&xk_OD&v)b-&LC&KZ$FORiLg&4mWcoLtIryT2B_{~un;ul*c_1nPy! zh6h$jAXre!_T-C6a9SucR?&b2F1Quyi5NFv7+3#(;YQSe#~Ym5-ad#Y93#s_eT6X} z3LTfIpH2cNOV3izVu0cEcBpNWDeg5>O`H0^`Bk4?Kig=RG+hTb*FWApDC~b3Z65z}~ zIgxcp;2sxFvVj9OpmYDoJpKt1ocra-)~kgCwT~Ly!eQLc(v@Qibay7fVB$U;pB4!u zY+g@l{8^95v*`22#UuX08KfO#$|#UeRNvkLs~w;`k4i9mkNM_|-yjMK==M z*R6P!l?c;v_GMCtwv%8_#XGvGeK3H)-Z*o6L_HSiW@8khj`)w|IpJfKh<|@uwRBr7 zjQJ>M;|R2v1mZ6=thV>ZUXGG}s6E-9eTppje6N3clMY1XK?r^D8z%J{U&Lntl zEr;h2gX8)I_eOp-I1&=Sdfbv4@dMPgX+r^s7n?t4nCJ`_IPQ-=BdWu9$!sq>hcJw; zkEpS)mZ-;Ly@QJqJC|zyv^Xqt&k1E0BuKm7SdKeK0#w0$GamaVfkf$*ear$dI=-y0 z)1R{*TluCV(?5ZDXVI*MWDCUK=H2*l$qY_TXX^yh%1nY^1@S~0KKK-A9AnnP;p;I2 z>1x-MUL?3QQhS3z4+*SJIa2i=Cjnkz-b)X~;l5tDUa22DU{~x{v6XAC4xolsL z__~`+RAI`9KV#}VaafxK%#8G2NeE1W8{1mA#<#;0y7#qk8Q))rQSTY*-&ckNNBO2N zGKeFAik5zE5qxZN=3iM9aZiFw>t8RL*-0SRERJ@%rViVxsv!C^5Am!9HSd!-5&uZ< zpp^vt(Jt&)DZi0CH#+!rO-sVx+8$Gf5ycDE0&gHeU0Eyj9U>BN)+rwd=YRnB7U#j02|(}G zAb)Ht3Dk@!F(2};!|Lhv+$=p2&-Q4tPp<{>e&(el96dZ^!fmJX3os~+Suu>CngsM> z@Pb?Xoa?X?Ih4C_q90_PDsgzfL62Z%;p2dmj36PxhkAuQ`H3E=DW>sV;JWDi>NI=pux6)oCe{=Znbt)oF#Ew*-1!oKEv^`A4o5SZc6upbG&lOw;La3TV|UvhN)FJV;M zrKS0|^+d4Ju-l&Yb1mjEuC-J@h4?DT!GnvUh>zAjdc@6?2-ek@K4@!;Of~YJDhTM~!%I?)}2$vP7_1{qZpNZ~`b<$$H^)iitqtRIqNuZMb3- zJTN&|jszj+Qzc$3^!&LZnvp%9DSWWtKuGoJqSy)Wjc%J3C7%e^-fFV@`PO0|uRivq zeua3#_ie))B*fe8%+TSdCjwe4$}!1^3DDku5AQCTO$4(pp+&w zzCOPrevAMfx9uBe@|^$$BlvbLd@>OXSZZ2k=)eZl_PSf%LIQR?AG1;c5=4F~4C`nk z02%q)iGkN9z;K1_diq^>MapjPY)?9e4wB-pZ#KA7M-KwfF6Y>paeM*~)SNAsN8E_O&@bM|zP|=L z?faxOR1pbM#2#F$GC+dQc%>_~wgg~h{o)(b;R$eFp;)Q^A`wI@RgJ(>XV@VCx`Re8!=AB6J zRe?m(+lvHy+V#5<)d}G8ovYNO{SzRu!6*K;6%o*TQmWT8Yp~m^agr7ch`*86G|9UQ z@k(cw3h7}XGAbKQEj>I8tvsLGQGXA142ed5XH)OiVC6OI$vGQUe_DK0_{6nkMkE*o zlWf7<1YmcgAg@3iM#7| zz+IUTeLT0o1lSpICE>6x5y+fB;?8)j24j|+-D5F=1ia68Z*7}Fg5s@i!GR0IAnUM$ zPyjc48|IzTL z1m$ODumsH-EXOOgIP5LrEtq#Zjg=wZ^TJO?vG>D(R|QWco%3rPyvt!^jLqM+iV(q6%LBffd^K2p zhQ(p06vQ`Px)CJy81ZG51HqQ}hk*?~Q{?QGdALVgO|ipkUU&t1Q}|;UYA`lC;UZ!z z61Z&@d}bPr1Xi(Z0Y7gHgNTP_E3M>l5KL2NafXu!gdHmdvsbGzdDW(4);AE(f4W8{ zHURNv*I2Ex-G%}F;isf;awFql&%)x+G9wYVoMheELaxS)>+P9Io=89~7P7tTG7`L( z5|%szt6K6-+M2}okAv-1J2&FC5J4wr{06SC8k3;!qIGscyc*jnGf`{AQ&n7TGZ zgRJK5{0r^lAo3$imFOk`IMtr3JXBka5mH7pzgr+d=sTs^07E2@tiTo5s}6%vqaEfa zU~H$eQi|5A?*x!2m~3~aq#9#U-2Xn@0P)3bBHbiS#QS@Lh}*mI!{Em0&ol*ai?yix z_z>kU1mJbSv2F5MHOA0!@}?k$1d9p`OvbWEz$NeDZo)MTbiX@EGnb76&5QPNC6ffu z?X*a3c)uFM4y2`hkVkwnb#HX70OFb5tNexWH1G^;PIX**GY%T8=ltY|1P~SJF%Lqk zvBA9#Jmh10{|8@31!P_(tVsZSFY09V;-*>?MGtEQwgmvl@%-u34hp zf%xe2g;u@4y8iS7bh{68;@b#A;GX;PYq896Fd?hJ#R1=LA^a!08!uGDSg2(I#^z>T>YKBNj1j#;_O$)jmkdLTvcthwq0#a+6c*5DX6eL1R@kSOE(JHfYE*=u~406s=u?Uy;Cf&vaKA zOho)G*u}`)5}G{(OfP>sbl}c7Na1SFRDsp;9(;SL_(7o>+np;>J2ZlL_FK~LuYE>5 zgAJa^!so#d*l;17xEnSO;@z{$?i3P$fx$wFpGY;fQ2X%5lP)A^X^?D^e}e>;`2-#J z&>(qZ7c;Jm zgT3DL=UtNtp#A;K1a7ekJHWX{WO#}A=^OK7`C*91Q)j(Qx@9y35(k~dH(+onZU4L5 zc?kp{R83@BfbW2X7p=DAPmsXs!@i)0UPy4lJk=suV+brcHYA#z8wV@ZxX-}!JflE zK2>23Pk2nS!;m0pNo(oWnLh#?Cy_6>d*=|?we<4cwiDyPD(OY6o<9L3At3L(>}IPs4zvVfW3Ryx%B?&`ue}*n*m9=? z^-UKfIA#wrQ`LS8VD((a*R0at2SK&NhA<0 zO}tFlg&OdzbI;L7?Smjm>d57b@^HUUj!W`QRs^s)q5ZYby$Z8>r_SVc81e5z4jD23 zkG=Pbi(>iWILSc}Q9yzsNkjw*CQ!mrKqQE$fG9yQAYwoT1OqT2MlgV&BccL|B9cVO z2t`uKVe*i3PKuHYY~Aa-X}uqJ4;l$d8k z%A6{L7Lw{an^uB9G0n#sd_*PI|NFzgIrOY+Mqcgp>jLriSp5!eoX5Tm;h2%1-1^I4 zkK5drTMdN3oD{;Pjl^pGvI%D@i&V4=-v)hpw5ctZnT^o*{}Eq1IvH2#cz^Gs{Sxj!@C2ov`l=|e3{r&Kf_A?lc)h#o zk4;=iguvuR2P2WuCZu%!x@AjkH)2d~$upd6Me`>djh?+JgW8D=Y0sV#0_`I&3MR29 zM7%wB z$U;rCsufMtKZ#CqDTDg-4cb8m2|ifn5=r5UAR$mk(acuB>37H>nwv&qyV2$%iH*Z0 ztvInrfO*~SGKenwko$EHAut$m-?Qll<-beN)v`Emz`F^h7x5ID;OyBym$+9y&%<%Q z4~{$k-c|;c#p8}Aw-bCRjb!3nJ4y)Tdaj?&KHP*pl0Nu84ev&*M8R3Rj8;@SzOioe z`Z6fh&{{F1O9&iFZyTs=CwMI*QQzBpn$RPjTkcagyHTUVhYB7XxV+0MCL(BM8AM#F z<~pNJ@cAUCWZ{NJLO?Q0SkGf?6Y}`=AksP*=kL~Bw>>?s6^WRijipGG!By=!;W;@% zfb?*(ti6oj+h)#NT4^?+2T=m^ukrrg_vcew*#GcLUj7oC z%_0QUjjp8Vu4qECzVoSEUfn2a!;I(jy;ijCX|3O*nNqkecITNQKOwO5%)+K+uL*u; zdiN2vg-u9{c|XDF1l}R-;jR7PMl0&AmC_c*16VZIPqpH?l7BB5i9;gvsHWLtLf~50 zkdN|DHi{3R^<^LKMlMYLY7IP1kK1}+Nmz3!81EAwJ~H;707vx>Bi_K<1pnoLQQxXz zHuCsfe_=7s0lzSLt!cS$D@tzT5R<4ah0D=NZ40~pJ5rw`faW~WDJ^M-De7@xY^ zduJ<3x{_XY>RKtBD7&_}Fo7_j(+E4wZXtMujhx=q*KiHSS?toq`-|PThK)@aw<13C zn{~Q)06K9>&F}W3zxlt*=kS>qbDCEt1f&%xyqxFR=;4cyfCtLmNNe_2)(O2<^lJT+ z!bKjXFnVfU@$hZJfCW#Mga*kGe9zL(<%UPuNJ)Hb*`#bYdbV33qhnnw@?92j^NT|% z*f0$x4TJvT|CG;hIQ{%Wp(TWX%9Vp3uGp|q!lcvMJxjY$tv&D1-Zi)zZWB{!+gS?S z&EosyeFy_MCnBsyh* zJt6-+0c|%+H%ksSiqf9>s4;9+tBukpI#A;8!)mfU)S$ZY=Bx`L6-{ zon~!x+F7V6Q0~H)jxKa$qj7-nSPL>aP8qy1Tms&m+uAtfNrb@j@af)^L_$DePOUt* zj)kuKTzhT9?n3w9%LpFsZ9&n@FF(p#OW=J(VUC12!B3p%xEK1E;Ei8=ZQE9avuf5` zoX&3SLJAAEt7W#dAcdAn^5TXP=ttHgS|kBNfPZ!T!E<*AfnTB7iIh|p{zb!AC0*5p zDvli*<)XD9rJU_q$4g7V;=tvAy+4cpJ7XHz-s(^g!N;`MDx7@ELdSV6N(1l?X&ol_ zfnC)t$f9N6&Z^83;0}0yigam$5KwoQw|DR+1e_-??-shlLQdXs2fFg{-p<`weh*7q z(D=wV+f9ij(3a#_@VbZKd(mEWmX>-CInWyHa}JpVj<4B zL2)^}MO%&G$m{0cg3`~uqc6}efhS~lP0kwxpFkZmdpA!+I(wYom?ZdviNa|)?YDfp z(2ePP5)s~b!NRx5m#TsL|FC|^LXXRYfa(!s!#ha`+}b7)Iz7ZhtSb#CzkA{x(z1;k z^G~#(FMc*#UMZ9SIY@UO^9;c&HN+Yp>LYkt>6M0~O-yvEH0Z-2_bxP{sdc8~Pz!p- zN|p){$NgV%XSm?bqlCbxq~~Iv*n|Mr;gc0ZRZO&0Rg_-m(uGPK-dAxSXhAC`_!peu zF9H3LfYqP(6MWqmyJ&qa!T)mFp*E4vMEe_ybM^Okq1>hW_N?69f}Hu&_#8=d#UQvj z>Ajg1Arzx7QqA#nLVx5uwHOf=wHe?j6-E{Vl1n+O4eN5|e} zBoYEMA~OM+VN4W~+-e%OtqW0ZXly&bu?6+_c~5#!i@|rHThHVgf_Hx=9c>gt@JdV1 zAF#d5L_;t1{C;lkLeDI$YD3jq&>ImyQp1%`#W3HvaMe9ILg3V#jZw>8Lcm7BNhdfh~UxMFzIQ7^dY2l{U)c9E@!Y4!LYevGSdlRgujEBo44xvUG#6bYhF z0xiggR7f2%i!6q19>4i6_7DP;dpWX?|0jm?6f<{uh&mIUas3hi;$5g^%PRZ&`DV0z z_m=PXuNQ+9|7iDkEx~gNUo?05pYXflJ+dCm0;>Z z14Ev8f<1g9O_3O;vQq53t7nua(I*ak#bKJc5^dYSFd8V+_o5)5oR(weF$E{s!^v( zo-m-)PI}I-je(|1Zk~)D>qNRs8{+8{QZqWcC#3S>j$&wT<37NBkPz@_zH%a8oDeX4 zLisgW$3TU-=-`R{~;r|0mr zA7Y@XM{mc%+dI(-sg#$WNyW{`(9Y`CYmH(MYTe=YNShG&=0?$KT!p)jWz1$goofnqW2&$Qa-yNuwoS6)2h-%0S_y-XX)aSSB7eeIds z4V@^YJt>y-7O&gA$nNm%kSKcPxk9g+F?!D_oX1Sy)|KoV!uqwE)JERhU zWJ*BM?NmY_c*|?`mJ@`)kcRZqNgD<_8ZWOWmfVR}ck^x76Wok;$iyTlW#AZ7rq>&D z$OP{bI4EFeL-4&i9)sB?3}kfH^CS61Cu$>E6?E!cXh!4DYL+q{AsCFGMdQJQK&Zv_ zgf=}wKoWvKT-nG#?#u)?*(Z35b$92To|fA(pI(W`v?IsFT1Zw3kd;EEjzb$ zk_@!oBIm2xolf+hL-5M(}q0>&u?41YclK9p3((h%eN!8owL%3D+z}dCX2n4Wth*{qJO7pt@HQ`@e`%s4Syxu*zB8Rjsc=ejw{A1K z7ZN{GI8g-hE6xwM4Hf>^3l0bmJoJtx_@3Q81>dXbNIL85q8^faC))3^FI7de89iAw zKfsM!|JuT=+W~Y!z>QnKD)j2*8pSbT>4`KFe1R2%f9^ zd4h-`!ABL#h1rGE(F%)7(wMSECldOUY3;M188v7pZ)?0%1Zk#=lr97j0`GPGbPlQz z0%^4YSHLsZp_(??%QgmoI6qkF%M6b?>kBGO(2aCcTw`?PLZ{h1EY25^W|5{4PMmsv{CN15i zd|AB{^=D}&yy|U2C6a6?k}ZN7&m&f#P6)WRXG9y;5&|&?7j_1l($PEDqhVsooyfL8 z*zQbA6OwtfbipgWA~+=Vf@6;)!7E8k@U>?Xy#4t+eufSmk;)`p*Qd&MqRKMG>zk-e zX!Qb_(Ae=pI6tHAeTIt=XiYnR<`jpY865+89*`{u>={9Zx3& zJZ&@ezU&|j;O0)-I5|c`>w^SRif3>{v`n#IJTIFNORSl3J)jWk-BcfbdQ9*uG{2wx zqD}DUd3kU6kveEdW^uvd!(VVfFpWSQ)3^y`T+R;|JXQ!Vyw})jUnc}C>e|n6E++)` z3wO71Q)uX(!)=>QV9Kpv`W*I2(5xiF_hXLtP z84Za&on^f4!Y)>KcrF{;`u3nBUdiLLHT2;9ufuiiaD1Qwx93B_;7qM`Z< zS-oA&*zNv?hSvp3*2f zak>eudMnCePkjJ6v3gNnd4ey${PKZ8I>FoCAWe*H4y7ThHrh?oj~z&RrbS-yXcLZj zO#b+y;{!N$c@z~05du#auP;mfpZGwJf2ALtx=cf1!BZ|@u-pD^I?aXU(1gk}E)@h; zeSoQ%ykA`-7^NB(IVQZI3fewy8Z3L1uk@8AarWZ^d{Co(_4 z+AXWU?CK^2PJRk3%l0G$+!AUI-f*FzMbb7pmg(5nLwD@XHZ$CdIOK1;Kl=bD(p}D~ z)f0SqZ~GOS0|ZaX7vk!0+(Sdl*h6}A*k#)EY;5Q^!zL6%>DYPx<_GwBuJWpQ0U_{? zpPe~xObA3~t~#P?Ohe9V?2_KZ;Vsrvd4aDsHlhB!xz0$h53q4=m)e3vf}goqVqKu| zACKez``ztdzLAC$#Iu8)u)lBOsqwS+>P_f+%QeF?rw<^pq}Ek9k`S;T`)Yn&`ab~< z&zB*m1XXA_$>iG4i|%7b!?*!=ex)Y#W_yAi_l^$`#wo72j70F;u2x37^8Cw_I5MKX>@;AO5fMIqr39eUV)z{t)&P zRMj}aCypP%tPjdN-0`shC?6}2UlJhr?2)3QMc4o4|1O{7-HOxAt{*|-dSSh^L@^L@qfza2&wz>rX-1q4%sdI@N9PnA|07r zwWywrHk`YWr+ys^_*6JhY6*d_%b%L$nGpu8ob}`T9YaM4Pd{b3Vka}>eA~?Ik8GsA zLCIzDxdJGA;lgsxCwNYtu9h_$F#k{a97mQP*>7~0inme@uUlz^qu@CB-*NuHM)Cnx z8~B_GfHC>H;QK2=V1D-Gp=3G20Ih)gv3G;0$nnvsje}dT%hs9XT<-U5bf)2ny3mdS zXgN2`@i>y;H}U0EY!Rx4f6M2%Ak^B_W$4MTD}XH} zM-nW82!W4_j0D+JL`a2z3yTH8jf#ZCH7S1B!ztNwPIgxu8=cqLV7Fbi0Hlpfp7Wn3 z_=MiO($~AHNcbB3`CDc+wfcoU6%9ukoZY63V>dkXI{Fnqg!)m-I`voMIi`~})o_3i zIBlvpv8|3UfIe@&d65Mb{bp~A5yt*O(|OM&9^PT2aW_X_G?@?QM`%x9nG(GCKuCyi zK8X;>TTrGOqEAJ!EmC&ur5)&y^mCofSJ`LVxf8~~oh*ObA@bst| zCyuDb?hlkZ&PHONmn`#&&j+3(TB|P1<^5OV?_VzWAys-10+}9`-p~1{$mnbPkj}Su zWWUxuuJ|Au9d9vE;k%O$JFl$O4C^NZ(!{z|TkQzGGIzxS!&wTV#3ibB_O>I#m1(;V z*|E{Cx$`Ya7xLj?*GI26G=jJK+Dkezzl{*6(=WKuIZQ!MEvYegnC)mw((?zLJK5+| zRj!8f(R}FVXN=HG2!VUIN~N?k2;QnAe&}`^1qHPZm>6K!jJBJK=i~8XxF2c@m&otV zhmu(B=!NM7e|s-U8qUZN0@Yh~YHw|%pbay}R!`)&qofPm-?wgNqeS(POJDW!VRQTo z?>$cl0hQl(YCrH1{I-|s4nN8$=-jVz^H(X@y=H~jvJOo)isszlL{rHJm9t}h@wW)R zlyv!XzSJZUP~hyYv?E2?6!b&VJH_Q0-jpsSIU2Z%jodhll0S;&L(uQ)_+P$+z=!!W zzpk_s{6g!EJ8vaXP^w9j&GPVeq{UTxR#ujcL?5vnKh5Vs+UV$r+fjli4GM0&{iB)? z_*l4NiPIAb@^ExAZVAR2N`r=y3&hw+$U^-vV>k~krT?;R+CvD?Pkwg3l1uPnWyS9| z-J>8mDcitU?{>7$L^k2T0yeUKap~Q6W*(HhdO~-A|9BFIv~}}w$(Muxf9eTM=_?eJ zs3ak9%&i?&hzq$);|GbZ8y9R_S)K>!JC2^{SN~6d!&`54LVg6nS8}<9aG$53^yMSn zn)dC;kG`iXYMO;k-&@0V_-!5(==6yM$^6UX@sGaTgYQ%zAt3VTOX_!b3JQ)}A@R$s z9jV{Fv3}hc3mwqeFqDR`{r1x-MJomV72x35Y3_7g?Igk5##yG0J5W%!&G56@E!exp zYs>!n9v1rg{O0HaeC@|~Z9Y%`p8Ka8;_^9$ub0Wp+7kkcN)t(6tSE>!eCukwdOPy$ zpRhaC%tFH2_c?se=Rp*OU*mrFKLVJ)x%g;4&4l2esBTdDv6X`KH>p)#SkaE;@2aj7 zq_9weV!7nRp*$Fk3F~oa{KNlMK8I@@pHq=GAt3fd>Y?xk3VNqFd&Wkz9hK~G{Q9zj zg;qp8R=Kz<59VaFqcjm=K=w($sjL+Q&mHQ^P*$NJ$?B#4+Fb1@*zkLd5n>^4-^VK# z>*WFW8)se7)W7+^%jYm#cRi>;h!B`l@aQs?!DrwfH?ibf8wwjaxS>6Zh0fjF!=0;= z2M)<+YJNT@49GR)`%*VUc*Bn$=FL5`kb=$>B{xj>Vs9Cxnc~YSEHq@jFZZ-KZUe81 zEWh9Ui~mzT2lZ5);Y2SX5TM~;7so+COAhXk>%d#84=j=1A@zcVM8wQ=*Ky`Sz_FN& zSzp2cx<;O>9F^e39M9&mCL58o&*k>&$~NRulBZvCKxa z+%E6!{Q29N5OsBMHz5#u-`Gt*rV)ul?e$pq zHuU&M4c~8j7Jhoo>*wZ?3(L!<7k?fg_@0HF9M}IRD1}OFPs+QyjW}3+y+Psu%*X9B zjM>FP7v6QPNZpqUOES9(He&zp{|uOY*Y!(NoiM;j@P4A!l}6MX85;BsZ>Qa3um3`K zI}7!2l_+qVg8`g61?qK-Shp@1n>5HnN9ZjM)dvr$2}zmZ74_g>J|om7~Sso z%*S0j7q*Wx*RI@@NeKM-#^;>EPYAp^Xq9U0(TH-kr76cs=Il;W4*!4ncRPQM_%zQW zF5V7YmxE-hAs$V#_#BTEuLvgxhbTFf6R0_-hItZ%$WCPLtvQ)|yid5u4PJ|Q$i;Jl z0^}e;!wvucKmT9rKr6>2P5JeroSpUD30&l$9>LWQDctheRPGU>hg9wgGR=VJ68CXS z4)PvD9y#vCf%TDRc0@!@m~p1H+AKKGsb|K^jeNyH~Pi)w|nIU^^G<;g(} zyaL<}g4_`qvg8IcPC+h#zh4%ATp&n}H|LZi?+)P?ZYZmEZ9+-8vt3MaTY-jdIL=l=WrBCSnB$W`0|OZkHSE`%(a&VB#=SxXZ+ z@&*}U-JGq{Jg!`vjpUM9p7mtcAs#7m!*9deBizCfd7|XEJt8N_B1Sxa>Kfb^L~x%P`ny_0lX10- z4E+6COMqK|yQTl{*Z7hNaHsVA{W|Ec_doRF*AK`Y$=veft%BS}mWO<374P58jO_C7+hotJ|My?7sQR(^QYo}&T*5&9_TX38fyQ1yeOTU{clxl5E^obDZa*?g^u&*<)n{r zf95CuR_0w1DQa#()-d=}JNUvRk$)QS>?N0KGn`c);8b~%P1P+jMNg67xZ7l z`?u`sjEs3svQ{6jDECp|A#3(>yO5hW_0k?p2%P$>QSgX~a{OuACPqAe6UR9?$a*Qf z`?yqiBQl)H9s3QWgn0D+d{(oKhiG0S+j#zJUgWbKyaIv=y??&pN?x44#g1He2Imd( z+#&d!>#vS0FF=}FcBZ#}dK~gK7QY!e;0lskqLp{HkHb67yT5z32EpgAyVo^;8i!ly zFUEvSZ-a4$+AsgKaVRYOZM{k?3N+-Z{InmAgF%=6Y0fXvu-}0dITJ7r*E|=Nm4Ao@ z=cfyeDQ@F%$o5sru7c;VyLq)=k<~cFZ2$b-@!m@kES#HCDBL&>DUVj~k8^kpKZAB} ztdkvw>GsGRZP{edr!0N(g?AjppL^9RGgF~TM%P(oYzz{bNJ8c&Z{ho{WVL= zJlZ|0{vl!%>RFPDO!2O;YPB@mMVCh*kSon}6QcwU9HzE=9~}iN)s<-fp;Cx67cUvK z8ihgQ8lHD}Q}-L^mBVLsMxk%-u^3aYG60eC-GU0E5bvuzOOlx>0~P)cJ?|Eb0*BJO zu=W1frAetR`t-LEaDTojC+c@Ol)3O9+1xe)-N7emMx>9BeN}G#(uxt#w*0MS^!p>A z{ZhZU-;IEoT7W3|@+Z)+8-369WCY&NWTb4L#{RlxFD#Z^AAyPN`y2P3`wXu;Ei%`g z9f38@YNW>E!OyUw???9$hY`qdPmFosS^>tB?3$M*xPWbE5^3}b80KGcct&FcqI^t$ z1)Eob&{WeyN9hsB`*Gw+d_g7nI-NgQ&piT&_r@h1^(sh?Q%kWK8wTyWLRWXhRN(|5 zd%Oo&!{ASukD&`x1Fwi_;+?W#c%eet@}$?N8Xm`;(KAjP27R)p9sW%KxinA`j2;F@ zX_I@8&1&FFkoMm8kYQMM%|l?v+ZxD|s4V#CIShMj-uPZ!iesogjI#M~U>HbSxNq#c zTnobCY75IuhT&9fc5884EtE@W;wUo1Ak1)G`gv;|lw_G_ax5E$)ueU8<)`B7pv7DB zmJZ)AC=8mM%i^eq>5`|r0>2Ewl27V!2{@r&;RVMTTFVf0y}esyfU`_`H|So{s~Cc< zJhNIxLJd%MU{OWJ`yntKVDua4HbCT530)_42=v$i`H@E(pp&9ct_>Z6c8_az+rt|m z&iOotx9<>;s^Qil+kysovF!x!>cc~@&1PLlX>S9B98`KdVl@N-(pw!P7BxatNyfY4 z%|q}dI^ue%ZX+<3l-rw*^`V4)z*6u zjJa<+t;(c;ck+qb4K9N)@o>50G=l<_N=pnJcMd{tN>H!wo?a+&!7nD`vt`O_a~7?aF= zHaljo;{&LjX;+gl{EnNJ~v){D4;^46y5dX#{uAd8Dw;zi3+Fncdz^O4uAj1 z%!TtaRM6#@dq5|}4nT;v@Y5wrX>cwl<0DIRYS?UK7R0Ea#$57De? z(BJqy%fWpBe$(H-`FWBC$=>hAUf2%6>4UD<-rS%;(=YwtAKM0CL_)u){W%Rtno)-j zX%4`V-pU703uz#ti*C@D4?yYnF!Np}4Tj&=_LA%u41h@L0`c50G?;qGJU1}i55_Ug z>XJfqkTv-wf1?*4+rO^hUP*^9$K40kQ~P1ga-%d!pAJTRI||2&`+<4>xK*ev9SV=p zqjJ*vAy0I7#47BK{y@t1=-ns%aAA|Gh#k(etI{v5;2qizD?N`5EqzRfG}<0gvD<}y z&{oejKmC>tlVSln$By+wtg)cg0i2qcpXR8KwZ_5;q z-}=!9ana0dw`~mA{*|NV0XNp_ZTa$|4;J32leT!u0M#qnPowVlL14o1C3n*p z@N_(A_x9jE*rn5R>O2m0a_{4$IrrH<(3al6iW7%UnI@??k?Icifu675=5;t2%W}b} zzE@U#Q0YQ`COE->D5Ze&xAprVY;UIJ&3Oh`f8$Ujsr5lsy5EL$5hlFI6zvL>=>wTH z1{Ne)COFx?Ob!$5gUiZN{F;ObBe+Hs7Dn z3mw2-_4_gtKCw4`-xbvhqR+T)>fU6+E!kTyOGA3Wt7(*r=c zz9|~xEa`#J%uPH;gjw)>m+@=Kj2>WpylnnhoCQkD6(4Sn?*Vr$1KGRFSrGSWKFvL% z2QD$s0<2^~#r+$D@xeWyj&wZJ)L0-ndQyMjTo2USb-A^yV}bqAW6!o6?g7X5-#RKu zn^~Zmpu-ovw+AHa*F5su%7Tb9i6^*?dmtp)->A=&1=nM)mR?-n1F;trA1t+EL5Sc> zo}WrRke>3$SkQ(AE1&nOgo^jzOymjkMf+J$Y`sQlC2tQ{)9fzl9%KRktn+B)_ipg; zT;$?=gayf$y#g-xbwfnTb!XDWle< z)Ykhla=YQmz5GZOUluHzI9i^a)D4OkT_d+&Vgbkek2lXBcSFF&%CBmHEI7CBt-|9l zthe{OZF4XSE=IkOd+Of}!FINew%1s|{_#5|`E)m^ov0waB;RBKjqmH7j|aQqD9;

>s-VA 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�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 delta 285561 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 z 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%0D+I~Tpvx@+bVTS@-O#f-Mgnf73uA49P4GzL z9ie<(0X(;Ey=f>$M+1$sT-j?_?lZrJe!`>)=A(WTx_r-r<7zhY!IE?o+uIv@Twn+r zs-CP0!AmoR*Z~dM%em0U{Kh6whK|nFh8Tn^4Z-x^sEW#VjIVh0AZ=3)7!^Dso!dvp zQjw~{995@q*6sSd&l7A!s5o@Ln7c3wq-1kWH7Ln3xG!V~^z!p; zKi(w5(#?zy4b$l$u!AGC{uqId1cbuZeNG;Nnd8SUn!F)G?`hLNQWjDn+m(rdA>gQy%=uPIgcJ83AMk#g z4Ejd3uVVD*sEo5%v1Wb<;`9>3^ zU)PA>8RGT%NnHZW&1=MRouwn=cIV7_sbP4nbVX;qXfv!!Q0`@U90y;2sdw9)rz3r> zvq~q_hr#~lMW+jD&2a8dfSQMN4Cvna%j$oDj+R6SRyPcXA#s;+pwE?N=(Ul2J3aUn zL>KG23C|7bD6O;Qh=J`e6r7`Iq(5zjwiI&CzKBQ&lfS>X?-Ctxj^%vjcEM{vtHKjy zABwDB-!=T>F#KGoGy#0NFXrIuT7Tms$ee6}-`lm^Bt0(8k~P4bm-g32$zJ7U83urkbvw>kR zv_ug4nxKP!urzyfFz&$!Y=q%!L%l6vLdZEY+ZBK`e-{M%;~p?t)tM1?8-cWS-$u!Q zTVQ&jJMvyeFgkd1uqobzj()P7P`i%F@vOUOQEgjs!bD2<&Slq+cx)Z}6^I`}$PVu^ z#~&l`FJHqUDDQ7Qn zje_=iXRX^Mt+0W_6ZG>?93shy))?ToVApooPZJ*nEB*E33H`0W7G08NG9Qn=vZ<9Y zpQfYzlrv(92S%Y_H26@i>F7droc|G>QCQ#Wxk}pF2Fy}> zcgk54exMINPTPld=;&GNcmI&fqws*fo4-S`4PZwo`5}KY;ycvuyhWRiu1ufaS8g*3 zOl{vzrJZSm!V>NV^P&`V;u}|$kOqDWuK2ttaTi91W` zbfm57=;`N&%PYS=5_r=F^$ww$`w7lz=$jB5JzbTK3eWT1l8eNWPyd9r#zwY*6ZdAa z?%8yd_$sGC6VG&yv+ZR+rj3FNyV?F71#OVK+kbPzj-Tkt6;{9R2k0m=J&fG;AI&S@ z9gz<@+hC~Un{p-TCkozFk~pbAM~6dPQ-@kdq4DUT`j6jjkoMy}5L99_P-F7*NH%#o z@+sWvH(ugz$QJ86 zs$ArltTJECNk?Al+opnzusWa$TTWheJG{P3AfNd6EDs&}Jr`GwKexxeT0fn)8Ux9v z^95_ecsYL3kxG=wM`xP0n}1n{$H>Yi_gRpru0W+q;%R#6c$P%A?~o-P_yG#Z$08VaiL} zR4YvTS+p8D^rQ%dT^w_qCex62?{8(B!ZFCaDoNFU)dAr?Yvm~dfE-$PS!^7_Ysx0A zk~diCwbYXDzD1q~Sf_@eU0E`C{}) z?s!618x2YBzSpco83T%PyUM-V4#*9ei&Wm;PuBbMat8r?eR^aBwv?cyUANAsmC%sO;vu_h zSkQR5_EN9c)=sGZNczHNUV?(Em8|&lY3Nyndg*OpGTgp#wf*wGPH>;Rlr|rSX~VlX zN3LYhP~1@RkhTmN7G)K0un;Z^TG_aKSKnq(M!b7P$UX(zn2*kfEqC`G#3oCWR?G?bT67+iOj z3|P7+?_NMBEWgixRZvliX6{`oF#kwHe{Sa3{keku3Ap`Q;^RBviq_t_BK9)m;&00( zjF;t_Hjj1L!h(a?t{qMRl6WVr5s%eSJd6U+I! z$B(}&L#GVc1@&BLNM)y*sP!u{Y?~sM-frheEnmxQoL+(3=3*w8(aIaC9 zpz##vA5>8nTQ;7pK>I%i#+^MyL)%Tv>V)gaz!T#8%{{ma0!blvX{qR?byK~`gr`~?>K?t$Op~eM>V| zx5qy6ZNzWEEPGhZ4hr0QP2@<^!|8sf#;?`hdi3OMdr|MLgV z=Bw*ld@4z(LYz-KE-B1Sptk0YqrtKiAhYd^_j2rp9sRe|YSXJweU9v2;yQ*D0zA5`v5Sk;4WH^4Z@*5cMg`$p=!%uL@Jv|6$u_=)cXx*f)$ zFN-auu4L^cZ#U}!)rA`|hjMF>A9tBTgzE&lxO%xr*b_Uwh`svd@(|~)i=KP6VYCMQ zc_62f^=tx-NIVXw68tF8BDTccgawj3m3sIkxogqBOx-^P49C6s7Eb~7nGpNhc6?YQZQDJVQj3qz zwzI41PN37_$(!Pn@gTfw&f+xQ12*Qnnc_QYQ9143&RMky^zZ6V?|=*nG_%Ei=U(Z7 zb1WinYL{z~V$s=0pOq$1E0ajy#XRi%HuU|j3r{aF#j%K;+*OB+3r)3D_DrB1d|wIs ze**OW&Ed6_ZXG%*4Yidb6KHaPufVo~0zKMQi0g1KxbXZjzHVEG?u$J< zsk3DQy(uzIFRsPze)Wo%Br#p^?EUrUSN-aczIw%S_NEEcX#PNVrilXCtIU4FX1y@q zCb@Yvtq!?tySr0(bsYH%5bmX{v{7K^ZrzDF%(&F+89Z#+R)-E%I?me9;*-krPejSx z6qxF`b3fIk7fvO%-j<)QLznH&CKpi0QFTj2b>aX83NmFyPrvVl#Xb3I=G*I0LP?F! zzy5J_UORI5)Ce|C(~MfFj>D&6YmfE+9j-@GcLi%?TE~$RA?Rvk4TS=NF{-@MdA;Bq zxJ_!!xE`@Yc6~ZiJ&wBWn;nv*Q{e0C9J8DCSPG6xGJW`@9-VMrr(s<ORm5rH{;>8-&!OfzXvnyL5b3Z2YUh?hgeTJT_)|PWM7z=haP7*o@mr?_;lB z+&Chr%01q8<1Yma`WNbL*Y$yl6SMdAmU_f4&!J)cX&hy$C>$i`D}mR#TJs5 zXAiQP9qt2FQcSk-o(2^9dY#4E^Kk;&Fv`2Xla&fD6-HlI==A}?p^`eG)qtY!c$ZW? z7)PHC6eh&5hvlKfKI_d_`oN7Qe|H{JzHaZgi+y7?j`VL|HqzyyLcMu(fvRmE(5W9% zUp#BTuc3#gipeO&t`JrMN1 zGv9z}-={806U4{SuaNA1R}m_(%NmXrHT8j=pxAdZS0fVJ^@rsv|2VSZ@EoiXqk_le zCe5v+K3FlZSEfidqSIv(;#=6p5qkaV5Pc^VTIF($&tU1;tUTVAcaJurFLCX!UaU~j zG4kTJ83`(=@m*24#ng|bWGh;p7&M|TPSJCO-We)V_-v5bAw`9{W${)^tS59+a*rGP ztww~N2Y?cpikhrzPkfff|KV$wGsngIK~^iNSIVgoB`+Lbb?%{}$;%PT+A_ESPZbl^ z6#C(JV1jsEKqDH|s5ny7NJS|lOI(ezR3L8^ory&Km=k02VJW&1O)V)CK5Z(aB6{^w z(h+$ov`sngmpq39C{Mi3$!SE)0}4jRv+R7gJ0`-;5^tLqJL zME}-lLjAiwK3p}%0fOtc+L;vcwrE=MXe4Stb!Q}6(yYPx@C%ga6qao!22*(+Z|*VS;%Wb#5v37`xhw4eJ%d- zf4b28_Gj33VgMp%&D;vAn$YFa$hy@D3fliY+(8eQe;l=MeRE*|rhh53O0+eh5#ArV z)dLiy(!Vkhf*;|}O5NVhYXjg><((`?!j8;g+@*ZJEfjS2%F1*t9s{RVPLVn94S?{m zD)&xW6Y6@Fd;Ux%1&KY4%O1spi1fUNMe6wgw6{0DmtJVX2~aFjYQ?m*lgze{v`(y@kdy?NxY|^B5eotlh_bhy8Y~3Yn=Y8@#ZkU z0}nPvUkc@H@}!`e_mOGC_%$SW2enLM!Q{A;)AH(DvF~}NcgJHV3i|1{viZgyDs-<6 zCz@0aKsJeJO6U_HVn<~Y79l$d`rh%)t9Cd34`k9dB(w~G>c%o515qOSsb2px{{{tV zaeJNS>QQ5(FprqeDix^00dNH4;Jnw zqUT#k;cdsV+c4qHDz+b9ky&D^fjGATry6zx2v82k&XcEzkPS_@M3K`8urSWar zOocR*N~pgu2s;QlCj!Q`iAba2^F2}z8I?X^3%bZjg`O;feRXDoAQz)O<)(|R1g!^RJ0%C+mO-cRFT)Bc*Qfi z?r`#C{var@2yhf#CZZ%Z-SwenWc1a;H1Zu@G1c2B`}bC0?fRn^Nh>Bqw42jAhTVXS zSdB9Trl}M-c;tFOauZKFy=TUaV?b`OH2pS+Ur zRU$HK|8|0Qn2fw09ISsZM1jwxFORPu8HDSTJ$6gDT=D9!M_1&?C^=*IwRim#*s@<| zn+LvplDycXo{v?3WQ+z_YedP2R+xGm1D};=f9UsJaGAJ?+X1DeGD0O9<%4Ip@1gMS(Kp7 zMS?dk)o-WZA7uZDDWeUJA%z1f)WUKK^xovYm4$D|!aKL^&%@;>b~UQUn9ipnu$wqHVhAfEzZc5dAcg4nB0VC%VOxV*EqL{2Gh z3>lBR+h540K(g&S!gjp#1X`Q^rG5AZV}p(=5lLgHDxp_BBb@^0{+++MvWo(T!8Whrllh!AdaYDhVeTMCW_rnd(l+SVZmU zDDo=!9OQS608&2!cU)wKC^5a)ae>U{U_bDh$a+xDgV>O=LQL`m>a!# zrAI`?Qug1=>qb%TZq0k^46#}6&Z{HTnk+B51PU<6 zxe2P+kbr+}?~ov#iv2gb?XO55MSR@sbw~6lV3^db^b!-ORS17hRBPbZEMAa9FLo5| zee?IUx+Vo@K+q3m+F^#b_<30iL`2?Q5n1)2xSZExR7s5jf5hArha9j!;TEa3VKpKW z5pdGF=sSwGaLD8c5Drm*hfRMk^Al`d_b|@$yebjpxek}~z8*yhYofnq6e!TOW$xhC z=hz@|;Hpo;Vf?W<+pKZZaTEz9KK_v}Ljle*bWsG~A*=5emH2g#h+bUaYyEv|6!lKA z3p|maK-*1fpulVF%+|6aKooy1bN!CKaW)-AiT$o71W6HmhVwghl?{{J4NHkzk17xm z`jK*r@7ySwjJXt4wv7T7&E`?R-r!5CwW>fU0?%{lgEuY86Hph$MdBB7{ zlZ|&2U5x9v&Noj6Vk*z;tRQR!`^D|~1z{o*m@%D+Uq6bFislQ8DQuRTwtChGr+9Jx zyk9u6gNU?z50~rz9YI_kgdcuUR5G;myp5!O#1-`MZhMHAOgC2^ADyC&AQrVh&^|~2Es7zz z2}N8#Nj~&)1o86zd-Ccl8I*6k966dwf_Cw@3H$?1Xm3zu_+Psb^mo0wnMycz={&T4 z?gCC!&?CAAWVSaU(d;uWg;pa3MAaepQ-knXtaeY>)}Q&bN_qr6zQ4$pdLIw6w+GKI7h>nuU>Ps=s3!DIV&-wA&1Rj)8C{M) zPKNTm_r6F}W2@MIahL3FH=z>M6J`YF)?qa4Z7O5B?O%P*=0HU$G9`1*hn7lo&Rl`QxWL$Mc3!`P;2NBKXQh-a^AF0VkUT zJnic(({Dn_>^e0g$vC;)!TN#XPV94P;vT%M5j!@DZk0$rj^mq85#A4d9Yz7I1`7&$&G)I!9mn9t=sROOqHfJo3N z)q5CyX5&&*W+Q{|wGmJ87EF@o3%Jt1vk3`_bv^z4Y8d&Pn>iuLOon3Km$g={B&aWl z<-M~Vmj@88b22#$qj&P}t=RwJYqgq4+W*JiokztO{&C}PUsR+Jl2(#PNejiN9Z{%k z6(x}nm5`*-W{D)xRN5$7kW#5s?xKBPW^0{k+G(YfM9*cpzR&l0&Uv1Het-PV`JJD0 z-p&2@cVu@oe;3!LQb6s`w3)n7>qo!FpS|1`UQte1$|zkaHYigqM} zZNa)+^^p6)+Dg%e4YN)U4-^da5>PoeH9=z$L+z?ufm3yG(ur%i{vI}nn`SxQV)YWM zkAIyOWgzicEHyW0s1DAP#*cE^8L~k|SnB64MlaEDv5QB(g++Axzu=>G)xpbcd1Ag= zY>-IJ)8%jIC93v3S*_g4A}SWW)9awsK|=8614+tk$arq7_pZE`II1Avtc0rels&!n zuA#aPs+#(n8>QJ`qhh9Tg6JgzCI)Cy?Wn4c#K*GNMRjPZsL0DOjzw(f-MpSxAg32; z9gD6Cbg+o;Zaz6)*>#{BX&$34z=oC#@wumIy~I+1!}fz{xn6xuhgxbKcm;Y+pPuak zlLvF_HJ_v3{cie&7Im@+f9G$vsj+p?%+7rIe5?yb<`=&zj_D-|%&eE)W3q@<;jTg_ z!|F)Tz2t0hUr!e>bG}C!Kk6mQ#FDrySmVi9S3mQ|RePN*q4XC;qXG=;f)W*BFpS97%OhZsq1I{>tI2+mQ90W7nHlrvX-M?&%Z?VDw_AP z2$lPi&7@+UIxrQ!JAeD(E-+_b6W+bPm#Fm;?SF#q5bUTwcVW5~jF}fbKbUoa@?Ps7 zmlS%5b9b^H458)TuNwIzhH4>=+7xSG&;>_Vzb$(#*-P*}Jfoq~j}FJ*D%QVX*24SC zCvTQ(c7f^D+c)2d^%7g7t6NT@tZ+4t*I z4{^*fU`rcX9%)#xx&Y5#?bPsQKZqXJX8RU$7{W2 z@=q4Dt-2%hv$uy(SjQhVi|#<>t5cgnoyGFym!(a=ut5Dvf5`Xt9>Oc9aTUh^i#Vw1 z>%9JEEvoQ%du*(a1ts3OnPX^(#j_>jvoq-NCw|>3xAdxot2#&0qT5)&3Xi}6jI4_1EQ<}PKy zk=OF&Z*zNyJpP8X9JKr>M}>;FC2B#L=ETB07Hr~Gv5b7(Lxk6+OWsGzo7am6xtY{L zr*e|KE+S~% z*B~S|9r93%LCe>Q+zF*DME(6bAEeWKSzva7{{=52EB;tuWxiyrh7Gib% zId5KK!NQ~3mI0nU#E!M%w|;NMi>DgMMpHFFIomnwc!mXoI*A4*=X(f2i{qvtXgRNR z6UXpi4WRzG#XIa-FvA$=;y&3!T+Vi+D4^wYl<3`e+H1hn=)hjp{Va%*cwtg#+e1W` z?^k`(Lqbg~7Ly@eUjylWTIo`zEXb5{y5VlwLwug+dP)m@POpiio=7dOfuT^{&7!~p zBR|)-@}@n+I^KaBS!h2%MDxcgqn}iQ*y5x7n^-XI_2qMe9@+-fhaV}Sj~edT8s_7J+ePS}!upnWlS@b$Hq9csYd z9=6}?VM6`Ug!Q2UJw%%)sap>%_ugsrEZ?dI_Eo$06Lm~b9~7-snCm8(v1oz-+SlvF zI&7IXse$>6VwP~{F~Kz;cX9S)H(_)9;gc+MtkB%1vV8sK8gMr(Xx*E@gaslFk_Lz) z;^lfo8e*fP(S^1P9@fe=pl~H+b4oB1C{=0W)$DF0xH9hwqUAPcuX6423dhn&53 z4rdKysK;&ivYQFgYc4H6Sk_IjXGmiWd(h$5zD{p%#n)M9A z1w!c%I$m>p8LDjRML#^=R~+P$V}eWM{<%Gw-NgHBXTigCGxS%9>B*bb`7#S44<^H=*|J;q#G31gooWx40i#4Nh$ob*-nJ zpd;aD`U6oiq@M8$*FK_CD#t~I25$b<(B{1Uv}kZAP~Ea$Il6WeR(q4AL(wVOGvTMh zyDwHlMaGcV#N|#9J#nbH`eZlZ_Kq2T5S`QQ{4sJ>-l-a{IW0Kd?a&D=O5C-oN4g1} zq4J?6wJhRB+?pkmhpOS?gu70MStt0eT>CQ2vYUivz4Iv7qmxSOEc>2f(`s1XK>gCC z-3faqrTiv#q86y!WOKa=ef<{HnjfTF4O4V_*tmQraCQsXTk0W+MQHrST6EI7C!+P8 znMyUtKA({lUeF0vwLcG}YjzVK)oOBT%h7%$YTiN-nQB-y@FZSsrUR~CX}v78p43e^ z4_Qn(m!T7ea0#u7h1DRRB`)sJ+X46AooIHH?M5!!R8$u%yd{a(%zn0eiHP%!R(P36)b81^$I-Bi@=})s|lcP4C@LpK2-i2$K$vuDjQ8g73$7LU#%qG1Wf1LuL7>eCJdf+9niAocE4c_n^0e( zT{u67MSODGv0|TJ6>Q60dp1F$1Llpm%?m+B#knH^BFD1PD0uFlk^L8{U^bjAYR26G zkCGEf5-quGB0OSa$IDFA1>-ChcJ)LR6fe77&NnF`IZK z@jlTReR7^_{8#5bUgn!Ew@do3U05xleQtK9YkF;27G-GfRG!nHk`~NOkGc= zl%gRVr>+-GufEg{(K}t|NEHk*GOye27#0zf)#6CWsf4!&5_h-%Y6F8^em0g{*o0B+ zhba@(1Jd|?vdto;68zfdyk$SPL8M8`=7*|i8-O-%ok$kZXD;X65QRp$*rqkQ(c9qW zhxUq6d(gEc2oiXRE07AT)p zYr2nC@X4lKm}A&(-UeLJHc!IYsA#r@$2}p`3%2HjozlzIl@QG=tqjy@1Lwr=E9t1RyCg$3 zv^o$S%xII`ic2fOB{1>Q7SftF@a?(QC{d55NQCqc2Kuvz1H{b2r-GGWk)dHav#1Ts zCbdfsm39%tSIs9|d|8Bhh4opf9~EG;dxudeXB*t$w&RP+?;>)m-V%d8EW&3hTRC#5 z0Zpv*^3GdOTmctXY9`rdw*r^3 z9cKcnJ1%E^XXxBT7IA4z_Gj>$3b^&)i0$#GtuV}P_UsGlBDz*h-Q&8zB2wS`tPn}8 zfPtNdNE&V-t)RR3Nx)j)E<*9g!|MC4EF!z`l1lKS3V4^}p%&!b3VX`5RnK4UBCg)9 zOqofXWbeNoN)Tjky!zrz>FhPg2~Ngi$N_q(wXETXzwmqLpVJoNOoTfNM~s2r7Lw2{yScuPB_dWZXX(b zWg?DNu&p7KwoP^8kz30+|C89`qMJyjIs=+yY)6c^`K!=^}D& zT3xZQLSH;?{hV|BRt_W++Ckx~uUnw7=#gB6a2IjT@`>luedu^z;m<$SUk;|?pX`>! zw7|-zzEAtPx`;R5?XL}+qf@r&e6c$%y=2A4fsz)^j0eb6X6RWtO+@z{yBu{P%Qyn=EP1SWXaYMyL?{Za?bI?(z0 ziCD^Px(TW^DNho#N-KwPeWg>JRxKcTEH7IPoeO&E6*~|Uwf>O{R?PbNg_c5Gu2Zu3jZ z)ci^1Ab4nxUxHK%j8iV~YN5j{$3CMLHy!l#dg+O2sz*7*cdPG+5<<(n`M=ypM=`V8 znM&c>EJ9~@QIYq_a_E_mUKIVE0SxB$gF~nXm9I#nJX-_(r1NgA+Ui5)KM3`UR#`=6u~0&)A9uRH=ticRydgZ z>XgIs-qf4j9~p4%+?E{v5cF%=bF+f!bu2>QYW@oK^{9!pL=G-|!vLOErnNKL!F0$h zYv)~y-U8ljwEmUl;AX~_HH~M$yzS(~nycswi7w^*d_{Dy@=&-#iW4md`6Ye5!S@)T z^Jqhinmg)RF;gXpDX@skuibZ9bCrXfbVYo-Hv_mD?9C!hp}w0A`a&^T)DL*{+v=qg zWw7kM@?Om|3@Em$Ddez2U9idLHi)c5$9tFU>sxxuV9%E1@u5-VO%*+~CAX--YUwexWISlVA~D9LZCTm1S`9kqBp?CIdFB zIrkbFpg!X7wTpWep{@81vt>hm8SGs9$$8!?1{{#u^&$(sHD~wErPeM)wXYv&$p3m- z23HT2zl#!Qz&ClYS&dq@r*vrS&HUeQHEAomKDG=-eYnOoxX>CVANv`(3VrWCzHl^5 z2%RuEA6)h9ei`_84hy!A(ZSt0FLJ>O780GiUb+gPUFoSeGuQmkxZ$D(u3#n|q@HML z-xNc?12|Tx?c`+<`9`~3tv$-XKBk|sqmB;wD!D8UK^BqBr5-57h0X~{?ZaVPPL@Hj zzT@o$g>?Ap#;y7N7n9f#@XfaECzBZP(73SjP#LsMxGd~?L5HqqcRD6MGl|T%)6N&B zn8fK#!IFHYWw0hj_FQ2U9cJ_l$|o31;&AXnJ??QPu^?VGYFwub40=PO6K~UjJ)z0< z9sMdEbCoe1^a=g`pRj1%ENguknAn#`hI`UMd)3|;&P*nu;zjqJ*TW<@^p2QQq|2cH zRgpuO6CE5k>`Iy+&m>9&@67Y-U=m*=F2qzXECbP4_lc+jbePL1P>~5nLpL-W!pCW7 zfC6o7vW&Y7HdSrlTZr#&V8(aNyP8zP3fpEg{tfbIR`E}Bxh>K7OJ41?afs#rDUVu z|Cb!?=o>C9g>%Mnt5e5msFVuh!zW25L7(N*|CGigs!ZZMxYA1@iZ0MG%c6nFn*>8C zLDckh58dUHnZ$S5D@P@tA{IR2x`A~A4I~D~(yh?wmL(BwOZPuv5?kr_UTZ%@qu<3- zEI$x5$QAk`5!#J3Z+fFXi4?^oC_%06);CJwu=NTNjn_0de;_lksiBj&on!yw2^wNS z6iEv%WprxX_I z7U-RJp~0rYdDn$*bQ0P+(~r9^Gl^Yit~zhmFNNhrcNT~pp~0>n@^vRKbP^jKtf#tM znZ%tXPjdp)OCjKExLe6?8a$Y%b*|jLlbBtXtpYmwAT1v0)NE0by$n&ntyX z&b58ZSI}U*V=_Y!4I-l@HB~-D+rryjzoJrROTctsuiKOm4L%zVh+bdPN%-3EOS+md z2^mMxkzXSvkiRh9yYmMXhTg@_{^0H;iUUdK9vGns$hs-0icMDuY5z-l-9hh4J167Jn-T74b);TAn@UDY+LrPgVWue3* zI&w_Ed`&HZS4Ql@UoWVj+Qeg5kkmme7OwruD~IZh&e<3_#gssXg1?VKG!+_k+OJzb z>L4V_vroKP&LrgXTD&+HudH?e3AY^ze4j&RnpQ50;ok#pi;H*gb z`Vem_%=L=eB)fJHr2SvNv`+~!i8G6xL|7graF^k`h;o(+ucGDF9701a%gUC{-seH( zLif=4PC1nT_eYEU@<*t!Rn2u+WOoPgv3P!o{LfBe6SI5kz@_%`y7vlbyuk^P2B&?k)lT>+)3ARx0f5ExL6_v4ccZN0NLsM>>gG0V{uLKu^G$ zC%i|63hQG$+$|S(5S-^6@4xNqBx;439hCXgJ>*_XV36_AXxYv_&V-zqqUXzuM=Kn7>AMdzQ(Mjl(A00h8`w<>z zM9YYFQ9w0dVHEjYJE16Klhsy)ifM8gt&kr52+DPXl>9~t44s`{7m(aem`F7yYN6qH zj_=o$w|9MnwQJVBbNfJnRqsU(dyfiD7o$otF%;D{_z92Ln&{;R|_acGBl2wL3JM6zuG{{& z5n)`?r`2j~I*H7OTZJzfeT2M)`sO0@Dey{^#C5TQXd~Pozbj!{b`roLyW^VHM_|QV zU1KuW47r>Sj;}_T)(vC*9IwR4SH{w*S5<3>yvAmAO^*_=&D#3_> zbquRxP#r_+ZT|pu45wo-{Wp}ZvzpWl7)Zx3ItI}(gpL7p44-5096{%QoOA4(W7iye z=GZaEemQo_u~%MbikNZ|a>}t!j$Lx>k*oZ3$gw|;-Er)V+x}z5F)xl;am*+gRPk+BQbEF|LhKZH#GSL>uGT7|q65w!jtAUnCpj*cipe7&b<* zF@BBFYm8lE;~Lx6*tA~#&$7mbHMXm4;8)n9}$gdXiP$53K|p8n106O^FQhtu<(p!XDm8n$r%gI zSZ>B*^IxgCm;_j8#xgS&nX$x-1!gQSV{!S9w2Yu-3@l?<8H36gQpSKXhLbUv{2NO4 z8Y3Yh@*j|lVPp&%Ca*#ab^ydP#qjUaa$Cl^1KgSmDL`E>?H3wu_Nn zjO$`l7h}2@(ZzT!MsqQi8`@9$i{xS)7o)fs!^H?L#&0oti?Lg5++y1no3_}p#fB}m zYq432t=jXXe?~2~X|YL*Em~~QVtW>wv)G!M&|F)52FSxm@cIu?_$|Dj?r5sPV9 zOu}Lc789_Te#PYLKkAjh!Yh_tvFM5=S1h<; zImNyyc1^KoiXBtzmtwaRd!?7?h$$rnwGkWs`J~t-#U3ekNU=YP-BIj~YX4(KF)xZ) zQOt>AMild*m<`2T=x-&I^qUFAJSb*CF$annP|SZ~_7iiTSoy@dCssYN=7|+gtaoCy z6KkCa=_LJ8I7ilFOrFI zOpIb;3=<=m7{A2mCB`nXafxk9Y+7Q=5*wD-uEb^~_tw8wUXk^h87EHh$}5lf6%V8rqw78n0WiwIi8z#@hfF{p?kMGPon zI1z)1zoEp4k0ewm^dFFjVMGifVh9lfh!{S^;30wz|2T)(H*Edq8e-27JBHXV#BL$> z3gcA}Q%DM;{KqH6E+O^^u|tUcLF^7-f|5VL@o1H=p<=KnDJhq*tj{9)Y>tA1GX!-^l)`>@)FwLXOOk^U%sSm(nkAJ+J= z!iV)etnOiL4S0U|BYGIm!)PAH^182))czoO7{|jX9>(x6f`{=tjNUE% zhuvZ04%>Fvw8NGiHteunhs`={)n$?X8FkpE!zLZJ=&(VD?Ky1DVQUT(bC{OHq#UN? zFd>KOI84Up>fiw)G zVGs>NXe<8#Xc#`j;2DC>{y1mYH^Z(O_RO$jhW#?^mSL}K>Kt0y(!kQOWys+Md)h?`cA*74+N9n>k7go8j#)TCwtZ!j;3u{{#*}}LMMzt`e zg%K@`XJIr8V_Ah1(qAMC<5(EQ!Wb4turPjw(JPEyVdDzhR@k({mK8RvhX2`B*sQ`< zRTk-=QH5(lDZM}cb8rlGa_21g&OOXYc6}vunp|bdDfjoz$Av<< zUDLQjGo;9RqWpqfFaKP0z0WJa&3f_o=AkWuT*-egE)o_bA4}$z4lVK|vmAJ3!uARo zaOd9QCXchZrFqb++mefm^>6*pWwsIjwJb-r*&v@M=eqNf`x5`R|F3YrRpE``5^O{( z&p_!Qk2i8lkcQC3QIszzlPJ?DKTv+5aB*^Q@SzBz2&0IiEJBe$S&kxwB7-82vKnPA z$_5m56fKl3C|gnVPz+H_P)t$GQ1+r&aB^^QSmi8y#&ceX>_OquCIJt+H;O)r0g54t z5sER23Cebq9Vk0dOi^~B>_#y|F-O^hvKM6^iUo=#iWSO!lmjRSQ4XOTMzKb*L9s^1F4HqO3%bL;2mR$|xJijXQGRP{=qLwW;OBfkX65K{dXJ7SO6W~_Uit*iXQQUpNFic zA;d!-`oS+u9&6%pC8xLR9?D^T8{M^!HmOVO>$)x^0 zs?mi6$%C)BCCK(>0;y!LI^HEhwyeJgN^dqdS$ZqaDz1l<|&ydGznf3L6mcROB> zEFGTTryXOvI0d*T;Ll|l0d4{A=-=xhMgH9;+?1`qZ+{doD8OB!`{yp#lhIvzwxP?$ zVb?_kxOsGV$S=$VuH|^oa^KpvbRoyzr~j`%>d(rMi@JDo!&oM!06i2>o+=0}r^gubW9SJuLJ;&xqprN!%;X z_tsC1!lRR{l%d1+kgglKYBzHfI6r*;)uVSEe!btjv8`+rF27OvDr9mO?mH|0^iCUv z>t}f_SBpP{53I_|YT={s!kl@Yb377uxY5I=uZ@C1N>g=7K^(M3&NFN}F$!BQe0XP` z{}ei`R$YE?HVPX*Nb}i-pt9G`iLbJS8lxaEDBu)toebiO%rxqxMuGo1-)WQ%?~{}EpQnw0{nLH3r4bq6=Xc(x^8E<> zN}tkPH24OBHb+)!B#i)zJ+*n8Mi#v7(N)R{8G-#iHV)?3NZGJq!L<-0&k=avVlru7 z{1z5#+#Kn%AAy}m+E$u!=0ZPTnD*nHBapjPZ(`@xJb1XF+wR!L5#Sdrb31hL9bC9r za$vLM2*j(KUL&RCgKXHn1@b&2z{y(GQ_)g@s#PU;uN)o*N_XjzZG7(`{o?ui%Cupq z$!NPZNKzpXI_fR-g<<}DFGBE@R zrDh214%hi`u41vy?Dh-2QXguD!ML)UUpz8HsLKcy) zmcbFb!A!1bRBg@UM!NA-8K`l`n~L2Sf~3~IY^zE z?mmu+yKB!)#6GgGfF^HR%`;SKJZWWSDur4B@%QIDOsgPaU@nluZ)YXMlIY1%Lbu%vO?BT(3tpP_@tM7^a~IZ1FpUOk=oyURPc)@-t*5?R^lnqXrJHRIy8{C`FqtE7z-oMfN6H9Fm_=@hgqdva?U>;JwVc+gg06;O}Nxk8BQ9 z5x_0V+-Lj~(A>BZ5>W+$oVAChDea%2ulz{uR#ensGC;#irvjBOH9NA=0F_3o5f!e; z$ovE!C$$E*YNJwU>F=ppai3u9O=W>~gxZEKXhfBU)7}(q{sgQJ=FJb< zjbQ7v&MS~IRIK3+FH^I!be6!UBRK-%X@W|?{ zCOC4}>28A^s#-}mwVk3h!7}~@TWxj^!h-cL`*goI0pH&EmI-ZC_OgKPdU|?!GYCnh z9*9{r2Va#S`oIi*8l{)c=wsIhXThMzhqlG z4ZsVdq8GDgC}_v?dL+SW05;p6zL9#90>hkJe1GT+Z& zOd$m_^$KoMk!|y*EwngyoC2#8DQ*L(!k|oU%X$eRD%`i6 zk-6F155@|KmD~zcc;@9epx)dMm+DlOlXR(|vD~O|q^KX3WZIbrSy5rcyXj$0T0cyS z7(ZBzhE8&-?Q^^r-4DIfk_-0xQ=yh~p6rF7e(0V#@OeoT6@tgjNku0-`@!~BqNz(d z6$I72v`(Jv2Yru8v%_Unu#g|Q>S5In4i^66?P#>AkKtnf`v(2MQ%!sOZ48Yr-RNNO zPPHGpn6-OTd1-Lz`JUI~EBhf=?&;LjG8&w5vL`e}P|3uOk@u|iG*I0ked_j)K6tP$ zC+CDd4PMHSq~it$`@nAA*|D2eG>9&rIed`O2f1cnJa#zI;DYe}drf71&=Hwzo_vJ{ z+cFqxzpKab>$?Ob-=V>6N9`@+34L&MS>kfj7#f)8tHeBf&LZcDaa#JWp_q@8ovv1A3RDA}1Om?L~4rOsYGIUf{6tmW^mkb-rn6-*dA*D0es> zCpdF^XU*gE5i0!-UoAu+`C9pbhNQO zeQ`&y51Jd=y}v8dp^qr=csAV&5}zOEtlUBem6wj{HT|e^SxR&7=yp1gW-L-W=4ibj zXWBXHvLB6`H+bf(hf0?{EN16NI?!P(!J{@fyBAEDE$cnb)1gBZ=K2#+X*0(##a~zH zpt@0g(i~M9yRsnvmi8?)>iqJJXCM4~kz&!IqVSLo>QY@tObU189jQPr<;%-^7>l~?c(BtJvHCxyVPeWP_^w7)$;SPs}Rh+%Rxyn(GGC+rh zb5_>##(Th_CTL9N3mtYvAKLbV)dLxBX)6?ekmz7`|C{@C1FE4UX>_-rn*nlp1tXlr zJ#ge?E6;IZ2F!iXOP0t$F6XqAY1ASHcs!5}-~6Np*7bKt-CMzc?rlHQoI-nmxmZ*W z6c{io960#Iw+G0#sOQoU47|nJS$Dv#2V!3NoNV97fK%+_v0EH^fE^vuRY}^+fYk~b ze32GC@XV|v;<6qCu8b$1<~HnsqaPL-_L(r?ptRqIE9yPKr=0g_i5UZQL!R=?%J)Fa z&-F%vmJBFS>Q)R|gkFG8n}ayS0HYQ~c?DinIPC1e744%8I3Hj$Tshedth9K$OUD^7 zDd2kTYF{_lM=Uu?x^jvEYMMu@wCUY&UFndFfeQng%U4x%R&+zwmHIu6ZVU*uuf0Ue z?S|JOCJz-aF`y&Bq2z5!H&m*A4m0**K+L1>sZXQ2Aw0?AtFjLR4eov=8+E4}ihZx` zZ}Vk9g~}7@7;jWU$+*qNDiF0OdG_%Omu`@eGABJJ-(o<0;%IQ`(QY_?O{MwS9rS?g z^r26CP+2O%oJ2zNaiRyjUrXwD!->pa#;ZaZFeDaHdvIentlF@km>k9c<-*gIM+e9C~9fo^xZOKixj`l6vJQ|IA4dRl6bWxh1x&D~17fNfLWZ`Pjgjns_u0wZMp~ z665l87u?%*OH~uKP~EFHGp|2&0ogA@@HuM2#$91)U)sAs#JlVGC~9I?k95R{bpa9q za>T+JAX+h*LPphQ4p*J^7JbM7)n)s;zGrrUv-XKKgXk^HaVoog@_82tW@QppV?xk- z*uMR7M?@FcaxM{EAB^T$ee4jiyVC`vxd4U8AO`f@e9-dgS{J+ze>~B3lK~tVm%d#` z#a{Mp>mTGt8`YuKcw5=yU63x=c0tGwy#*QTd204|L5Be%?~N%_l8(- zwL)Zr<1zG}L!|9PCJPi?N`qG)K^tkGWp4thi3MKrvb@#kbITmnvMxlWP4*I!)BCK@ z3%aIcvT|AQjL5FY--9N7S%`fSdC7u|*eSZeE(VbHvL}t=SnzPgMBrw$pE&#VUVY#L zq)Z1?shR6DAmC2e`G!Cij1DJn-mc34z4dN&3RhTg@M7JJf)?7SU#9ht{LZpqxkqqg zzZz%-U8Sv)1o)X=J1)56+9UsV|J>T-I zCYvoR5VIDO9F}Cji~XeepVqU0w7cWE$`Uk>Z}l#!uPj=jAG&r2MbWG_*)nPQ#Vjz7 zF^O`T#{e)t>V7XYR zE`r_(d1D=;UtiH7<4m>~(zhZ0$(5;h&*|t(s;k$LG^d2pF`*J;$E-N)9# zNC$j~IxKk>9o|XzjO*-o(K}$$>)}#wYt#ZZVrtGs9k89(64g(n!+CmIa70Q6Y>XCf zbT*|!cA1KE3#!SmrY>dv4g)%>lcM-m*}DTa41@L(bb?_}tnCzXq63xP3kL zjRsO?8r4fP+u^wA36s=e8e|ohzFHE~4*n9;-90@t;B{zqU3Ie^HmG>;inWtyF!;-t z-`K4ko$uf2R%@m~|9-0DMVof`_SNRCQ56kR1gEaPHEsu&{Il7n#Wb*t64w(@Z3pMz zObMf08t@%GmUw!3J4`KqD6WQPt?6HpiSFQOhlska&V|p>EH--7@?)cIpt8CvtuLAe z2jmsRCtKPeLvUyAYf=~u(uieOB1_t!eWlR`mmnI%5^OMk-3G6hxo?;DrGYJT)LJ~M z4aNlr)ypo^Alo~?a3G)!;-6_MI-H|HK}e-d-FY;>{{At`ua4;c2}x^8tlMB|{HMVy zHZ+)DHzZMI+y;s(>mx_Tt+&zg9dtgwb${jXhZ50pLoMI8iekS z*4E{30}FP!mWl=qL?6p(`h95yx@FN@KV=$Riw#}M=xBw`FsJvmvS^yyvb^T4m90== zm+QS?DGiDg-EA_nT7lxACcR@m4VG%gC1}UD!Z&+b(`6nSESy!2uf5X>-;b)1N@8cI z;O!w|?0&fwM#?Rv3(f^2OIfTBTxsM7>!3_sLnYV(96|0EVPK6~JRj&%S zw8BHr6-WA-s7RCE;T^xa6?_fK=J!-kfyeV)&I7SlP##a5Yk7|*k&QZ8KK#`J4$UHm zE3&AdIp=Kj>{APrS(ADVU#C*xD)Gjrl-2@%>*5FQ#Zw_**X?f;A6nq%2BY&%;Z#^y zur*#CO%C4|;XJ2}W|eJ|QF8Tt+yZ)Ebc(qBs1R}Km38OM79g#@$|%1~g+0$TtV}&x zAn3aHxj+{x?Dy2zS89(QpRSy&dz=c7?<<+?F>3)HQkBq);X_o|;zU*)*oiTJHXR4`d5=Vv0`0*T+c+uAo%VfM?#9wCkvI8z(#erW?0R3tNa zSwjpkR+6|iZxx#M)*jIMp@jjaUoJu9aw;5>d~q=O0#=R_b7`~KzR`v;+1g2Z3Iwyn^Ajaj-+@9js2)wSWOlMNJ9Ftp}+uK@!dOD@PiQbvJo=ij86 zsxn~B@lRqO@+qLO=$p)YDFz9&gr6Ri%b-ArH7X4y#DG;Q2a|oCQ$U5*`n(?P^CL=| zXb?Z?6G78#(M= zP5jZX>A;LSv}~~(1qxovu5ph+*QZm~*`A~TY*N!aa0i|LALcl_BHo4q3o0xWcc5M9 zeEuc8U3)2Dpv4;7jwbr9s;uRbHlcuzr_QbY=o=DM^^}^m4h23uuVcF!qCMV<(=Q*W zq80if^eqzY@qABL;98kWPJ=WZs`sv&__3S8p$O)wx6q*Q;gO6tqs_3>;G|JnISrD$ zCDyg|G=puD3uwNjf%7BJ5>C{?&|%&bSDi?Mq45J@tLvK~{j|BwIkbPd{RJ|1mNdgj zkwmAJel%#A^3*(&*9^)EG4Duit~6*B=g_&6)(lmyLZ;8{XyBK3mzI*y4DtRILO$j+ zNIiXDraYn*Q4sv3>zvZPh9y<1)JCVhj%zMLtxYrl3&~a6)GQlDcc`xhN6mn zK}~e@X}J9%>(cIKnDKk>Ay!TWiM2Ol@9Lu!2)(Lh_ZD53VE=rg*$ni(uPbt&QGw?2 zs{gHWGpMEBTq*sC3iHkdxK_)c7Fw`n!-D`SXe};z$z0qFt39~K7kZ#cpSgF^zs_rh z{2in<*0JdG$Pr%{!q3qRUoTA9YgwYtYvkM!$;l=NG0XqaZa{^n5Aszud}@Mw5p1p*fg)WEBoln&4?beD_i@Dm-}SdG1VQ6TH5!cy5f73gP7E zeAnMM!AC}}Ed}+ooDlf(>tSZo@3I%o&q~l7O4n>>=hWmTc<(a@#f=m=W8HJGD5eQ^ z5|y%5#S}>0$JtJM&;&mx%k`NV6xhB}iGSo~6STez59dIAJ!2EWpLnmK^ zmf$xjlcN4n5b$fA{Y7?9m___PN z0@@$Ebn7DbQHbaf~`FG3e8{tWhbD7=aW;oC%dSrVEx=va4^1$6@ zm)Q9lq8K3EYa}$!VlUn6rDu>vXOe#xDBlwwCPZr2PM6}iVn zBOBqz345()dz;~zTK$df_Zp#1VBZ-k+8BSu#N}M~Z-iy4zh*?$n_*76g{#1`5oAQ) zbR1M@27eoli{D)u!R$`qyi~OD1&^O!zWI0~6tDBV!q49f&7TgUs_4`@VFzrU}m9{`Mns zeIt}_U9-mzZCu-`gX;U`8sT$fY?xen6BNvxS);uab-iYHb<{p-f(N@Z7v2$RgqRoC zRt1DLLCZbMa1x8F5zdP7Iv58wfz+L4#oE(|x%eh}XW?bEbvwO`4jXQO1e#vNz$vsf z^W9S)XE(r5^cLSL>n3>RBzb=yqX8I?Q@7^qZh}(o;DW4LRMh#FVQJd7CfKuX>cjGn z4WNGU{eld%tsHVPedV9q0GxHLi{8sM!P#m{QsC&T25`4HT0>pb1Z3+%tD^}GV7+j$ z$~0dSc-zij)ezADpN}`Ht@_>w&vRbP8Qp6D%D|a=+s}GLo%f19 z3iD}%R~fy#Mp2<~AL09|N^Xs)NNm`}fK}-ExbhZPqW9X))JZ@}vH>a{BpO>=&LrP6U%xS);L>!bWtPlswO=8npY2n-A^dS{Ebk%tyA&9vwHZYAv~-( z(*Pph7xNU|N4AUml)y&xR&tDN)mHPa2mkVH87)==koeb#HKjP!!)lYbbtcV-E+~{> zDp}UUQ0Kx`c4ZB)`*_URjBWMs#@3C?FRuY^e#^h9x3(Uv>=g^sUN!)I{=u#E#i-1+ zZGu^ETmy{Vl#05@Sr5-btY5AQYXF*XP^#o`9oi36G@ifF06I^)y($@X5bk^S?Z+z( zBzU8=O@dTX2P-4$EM#2}fAPiZj&6D#+za-KyJe4RtZVG-=8Ud``Sx6ozN5PQ!ZEAY z)}wN`2aA3xdms=*qRQ?};)Oae7nyft8of1K&7yC7?U2c|r%L9AN&~c%t`cu5FO+Gj!hzj%HS83b6pbjXdDe2lX z^{5D>ZHL*9T4NPfwEQ43Mx2C9dy*Mn=^hSULaEqHGC7f~gl^3$G2zEfoaYT@K9D)+74B!{?Z5EnMn&yIVrL9<&YIU#8mD!aKIzO zA{poW0~)ok_D7AJ_gEbW)o*&0CyPp*eu&xk0X4Opn3Bn=`Ka=!jNF^$#R!Ay*?BK= zrUo`zp421-rqsa&zYD=j`cdJ|-M$T+59(lRqxG)n<{B_edFX!Ms}3A%gX`B8)j(o$ z&3v`vsL=113$h+G%plh!$%GmKi$8;|!C zKy@-*1_C$SDz1gu?QZKkSJgm$f=K$?)LKX)yKWA-%{pPLti1s*bQ6s z1Ek^fRk7(RkWxOXQI0B%7N)#C7|>e<#`_MgS*lh8b4k^~b4^tsAtYCDMH0Dt`?tsi z6jgyV*RSr+yfr{tFt}7KtqN8rZ3;U#Rt@_qo?J{pzl@Ud)-EgLY^w&z!(zIox2quW zqQL%VrPZ*fq2~O=r78%Kbv8eoj@GNXJ+nU7sT(q z=l9{xJv(>qwELSg=lssB_S~?u#=@n=ovy1_b%Tx z7%G%-#9R+rJ}5$c>Pc5PPwxkF?SlSrwj%tFRJRtVVW%_s@bUK0vU@@Q?)E%QY9-$} zwY5GR9=YhW(cOHSL_}}$SyS;0ajib)MA*9=vh>S@ifjLaWApU;-Tt-0@pMZzP3pS+ z!D%>{S9V==HHR#F_ur{gzn><%R~_G8;Y<$sulbSh9;Kfqn-jIS4kYD}zpwu`rcvB! z(&Dz;xqka{NaY>rGeWnXCJ)oDj(h`$#of|g-SJEKX)-Ej;+FWuIpo5MW!K5f)1>p{ z-3zBq&mmJ5b*OzDe&6%ANdc9u?Z@PhEuVZF@L9jpB;%j%gWLzeFN@AQHvein_yN#= zYxq7OIpprB4NDi+KTVGG3Auc+Ne<~Y@%wQ;l};1azfN1yKFT3Y-%WS>*X1;+)@W;$ zWK#}V?`@x%`v?w&Gy9!Ld6P}ro^QVH@Rc0$oy}SN}wZd&U zJI$6g>-f&2IV5*S?M&Cx*(CJMl|ykma>&nh*Vk{FluasjoZI;PnjG?aWm4O*`{1`( zL)z9Wnv+9ZZY+Hh6O~Q&YG18sHXeGi#qHpNCE29M%N-+U4bCB&yL(o*otaHEaZQ(B z_$-I`oQoK-VO%!ZR(qS=vRMwf_l@<-Yek>KuffJVPup87hXhpHt^ejTIEn0ar{wz3 zl1~Px?_6)5O~!mdZzsOaCh@7N$$@p?x_!-NgWym&LxlV82j$^`EB}uA@0V;6usCvQ zm@1omIrDSlv9xURXWN^VYCp{)BWFxr6c(3FR`u)FFFQYrwC=I&#TVPM$pvfV%Vx83 z;oPkIcRz2tDw~XNQ9GrjJ&PPzcRi}%>}(SJ^va9ZhqB1U-Pwm51Z9&K-P&Br*`7r@ z$IIWh9-K`+-_T<4j&M1_=bqq4}eNskshcZXx|VqeahnfiLmuU-GmB8Q}XOM^)kG3X+bc0B+t zN8=$^<~Pj(569OQ^OY>JKwbab=Gs|sI?X9*MNSqOKK8&+d$}yK``~9^l(%J(is8YF zpTI8+KYza0ZSS5eva|Qy9$tl+q|cI_o_-s$$n>t^yC&pkl3;Vu(pL+!ti<{xVPj%$ zCULb)ZE2Kw3!&6NiJ5a(e?|dXkvQ8t^e)FBzX-q z^V@dHA~{QRwYsm%B&K)aHygtFUB~>r{z_e#3BTX9;mZa!p(i@E=+I+&Ch=NU`&vsQ z9I-a4Lyh0YW|CifHX7CUA7HZ#yViT*SF?WM=v}WJyv?ni< zOn(%Vf1*_;5R_7v-OkP=_U!}y7xGCaNnHQ+58k#+64LBdx29DyVQ2L3rsaDxiFxv; ztzT&~NulLv|0f$WN!JRecH|Ugko`MS?4y@tk_nH4mhQTjLC&uo{%5{*MkcwSyM1%n zl??L3zd!#NJT8;$Pq`XD^K1s0HDgQ_HB>D2{uf0jv_y-nLQ>BkJx z@e+C)>GpE;@O5b!WYfM`r!re-kQ))d+}e9MgRJTK{6TfE46TTIO;r^!W zr_8FBLB`*0y7a}m46=9M%5FCe(BchWM|`>{gZxykYVbffKq$A#pVlMOre~1fo@?{Z zJxV8j5hMRfADclgHe9gH|3*5Qd^py4=gZ%&X|OyUddynM(EC+7nfaaG_3`Xa~YCmvbN51t54C!3Q7ZL;O1 zlbV)a7p;Tyb_QNwJ;Qn$&fRH$ZrL>dbmHw5{$?PYG}OK8qoc#Rq!ZW4{`Hz4NGI1` zXmtHr!u{j^DX6&>e)2r-=DePtq?1X}F&!(ePA4a}kc$00;Ca@dan&o&PbVD{u4N1} zq>~#ilfSJqC7q0aJbCT3w`t_g7Z3f~2d0zg&-PAP_c)C#u2p&YC^*=s(l3q2oXAfj z<1T#MWGy`S;p@F)A6-f#F&jrVJ<}qcOdEY9#0!3lefI#^`aRTjeLa=z znOA;6{UxcSuis}YXl^RGopr0BZ)hs%w^-ZxaT?s$>+pin)`_X4{e?qQ`zNH5CO393 zSUED4sMoZpbYdUW^slR*oEnfy4)=?&G~1j?(nimm^tx**iBCVhCVo{aQN3O{prcPJ zsgZJTa0h6Jv1*$O%NwN<*CsAgFHTJ*`(LcMb-h+9$*frS*N}0kWa$3rWgRM{l95X$ zDnmE`(PK5pHqP9<5pU5Eb+#KQKeRlY0#+71*6w<)VSq>`_?Z9W$M&`$1cvK<`M zG?i@DzMH4Zx0B>Et=;O^NhQ{4UF{M7vy)+iGsES|sbr`3q`98icG7F;uB!L7spLWX znLix|0^{&IRSsYI$4;(496WINPj=Gs`9%L;AKA%Cy}>$8v(rxEzIk!~G87#8>!KS; z8|~z4gKl;K6g;Dr%|3m(om5@u`qBjo{-o~)xy?K~>EPdYNL?s+<23zlX{w#fJJ&m^ z|BrSOGg#X8+c-PvY?*RkNsOH|S{n4|aDbgO=+by?=6XBv7+rS_?PDiDJ^iBz%(T`-{Yh_9$blEHRJ-S-kbC6@?Yw&{g_yjTZ|pKIg;c1w>%p`$ zDTMsCXvot(DWuEk7JrvNoI++CTR`GkrI0uGcE_b}N+I*_SLZf+D|z^5X{W5@d@XbPDF0JrR7Tjp2lt;M2YaZaUE7@^L0iup z^;&<547fNmVPV}p`6Q8kT5)9W)RUy$-P1n%(oT{w4^~u; zA9fOcwz}mXzZ^J8R<_@B>rA(k#On8ps?x@jB)wP93NMk+%#_fN%GaTJ6jr8 zKS{ou;vSXt%}J7Tt>D`kt|!U-X@&ROS_hmY*QTXxKKnSCOjsJV;#9knWJ~$q-ZcZ_ zaIWgqH-kPtN!l)t-n=h8nJoIc$;>xyCrM|IX*bPv3rHrvq{rE< zowg^F7fqv{?)@y8tiCh;TJPn_r0U6^>MU)NOd|IGb8h_fWO8mpTK5^%lF5wUXD^8! z0r?WzeKFNFne4A(di{HkWb$Lpux|67oFLZhOrAi;V6oUZM80@nZ7!v76FL4K5mw>fV*LHZ?G zoA=J^ae}C_SJYl!be!}Eev|yL`3bVI#ox0&zkQtee0iX4`C2E)uxf@*jdPEa^0ogy z+D(6gBzUwPTQT`Kxqfa#ofR*RlNvug%h$yoC-W}%|MvQg<78&1*q=-r;JW7DySkh` z4it}b>9xWjf7bBriHXNay6s|B5`3IIvJMLx-frh{k~8Mhos$BN6Z3xQ!ui$5N$l=H zL-%(*PEMxReh@P2I4STv)#h*S;TW-g z9zFAqyq-05%tpbqq}W9MqYPm zC*93BMo6D`?(PeZk&16Ko79azM(#I?T6KH!F%tCr;KC{2A0yA3-@KSJ>=^mFhySxP z%a4)7O5gl;-0v7k{jb`zR^J{YKevdFJl^VBCc+i1fZ?fwgBfoh5J-tnT zc;GO*;m>l%NYMk$ji1{eBL^PD*o^-sk<;f%(2L+NUv9|{2Nw2Mq01E zo4w@sBvR|kaPnAnjNI%#t@`g7NhI_8>yPb^lgLl)6AM}$P9o3h+#9&_Y7zUL}HqEbr1h6i7Y#mt@~&~68S>A|MRZRl89yP zjk@E8B$4~4(l5HzP9pjv<2E1b3ULMD?ZR$bO-Uqu%b?L8HBTZQmyZq3`Nu|DwNIF| z7BUW*@}$qP0vq|{@rI-2+@PRQD~H;Cvylrk4UM)zzM55ize~%ok-(=dv)Vzvc|mEL zuN=0KeZRM#bLUqZ8L{Sc@_#Wl@_k`HvMt?43R;%?wb@!5aqquZ`&GP+%ns{+)fzI- zMw;|mc)IO&8(CYaXLj;L8yWmvquDiA+lamT$%x8BZDjARXYOuuY$UAbl%{k1Y$W-- z_dMfx8@Y79-j+wLZDdUC9>=Q=v5~8{emEOk&qjv*we3W+ZZJ-?~oMk0~s-HNXBE|KgU z^lklpKPQqoD{?M|JxnAu9$ogEb~2G@)|HQVcqNg1ZTjW!o(B_2wKk(pe|0*MH0<@+ zCr!2_k~s6Q=?`s*B)FXZQmqwFBCyiRAFS1#u&~Cz8e6etGr9=tN>`9oc(TE2z-0 z<&zuxClb#AbDL#NL{i(@pnBy`ZzYiHYeocmzDXce+Y}b0Ho~;9ph*IG z^!!ru@46I8G3b)PA`S;yXa^mr& zNrx&X0F~p|z?x5v5+%?~PsSeZx4ip}qr^6Kd=($H72}XF{s`laFy08`j4-|k*3)%5Y2;8JjKyF zO4JTU>tK`)M(1Eu4vWUAUSv%#j>5s{8;rWaXd8^O!RQ){s)1-49!!HlG#EUCK{FUE zgF!MF9D_kIH?BY|j1_`mFbD>NUohwegIzGl1%q2Ks3lH`Wx*I0j9tN)6^vEE7!{07 z!I%^liehE4C>Vo+u_qXFg0UtTV}h|I7*m3wBp5=1p(7YFf}tW9B7&hI7!m@JAXXj< zf*~Ln`hg)I80vu`9vIqzAsraWfe{=Sxq%TI7^#5~8W@>@5gGMsTSX)WMqps%1x8$8 zqy9uI2N-vN@dg-Yfbj(wSD?fb6o(OD_yC3tV7LH= z31D~th6RXl0N^8H7y#1$k@k;tf28>%y&q})oX+1iub9P;?0sbIBU>L?`pC{lRz73n z&kC~U6|?Y>eUGerWZNUl9@+KCss}bbC(|Q|9?A1ann$uclH`#bkEHk-U4RR3-2z;5 zg#?e}cO<-WLzWD8X4BetVTvP5~-0ujl^jrOe0Ym z3DQW6MnbfkRUtwn0UC+VNO(q~GZLJU*o=f`|dtG6se*}5k+b!QbK_V zYUNZ=q<|vz6Dgla^+bv%Qah2-iA+vpa3XUP8Joz|M203ZGm(*brJ_|ZF_D3Z%u8fk zBGVEXmdLC`MkNv{kwA&WNhC}nQ4$G~NQ^{6q>EJ{LLvbYiH}HlM4}@S9Ff?Fghu2s zB7YHii^x|*o+9!Sk(XG^M?@YX@(+=Bh$d&t^DwjQ$dke!FDJjTY$mw=Cm zEIef2A?ps=cF3|rb{(?nfKA8AbV#B@@*I-pkSvEJIV8s+DejM#z=g8{84gKsNPa`o z8!zTsGveA$JWqYsghYjv8{)kdwx!XjaBWLk=2p&yaJ5Tr=dDA-4=U zWk@AM3K>$zkTQl;F{FqgH4G_Xzyz~$Di~70kotv`FQj@Q#S5uj#gs0E$%PCqWNsm2 z3z=HT&_ZSwGP0&GvQAmgy zW>tt#NPt4(6B3?~=!66(BsL+T33*J&Uqapz@|BRMg#0ArB^C3LkcWi)Bjg<+-w1g| z$S*=(k-{e`rV$~12x&t|7ebm4(u0r|B$lo6zgAVmbJAxH@UCWw_&L68E1 z)DNV5Ak_mY9!Tv#N(VAIkimh>4P~>pNQjteRfrHsfI#8{5+0D~fCL95HXxw^c?`&3K;8oK z6_BTZ`~>7B6!Q^~hk*P8g4X#+?XK$-y31CSOV=m4qK zVj2Lt|IzJ_?tXOhqkA9S`rMrl-hA}pqxT-Y_UNrgFFkta(JRlq@jKPv#z!wadf(CO zj^1|kvZHq$z3Sji=gxF=qNDR1o#yB)M<+Qt$I&U?$P0YoR&a)+6C9o2==4TsH#)h| zxs6WkA&M^>ec0%`MxQnMs?kS{zG?JHGgq{g`J&MWjlO5}Iis%`eaz@vMxQdelF@~X zu48l=qpKKQ#ONAEmoRvOt=tuiE?{*1qRSUuz3Ada*Dktr(UXfFT=d+c#}+-c=%Gc= zEP7;LG_?v(EP7zk^NJo<^t7Uf6+NryQAI~8I#AJZiVjnBl%j(a9i!+FRaq5BC^|sV z@re#kbabMF6CIoA&_q8b`Y+LMiT+CTQ=)$o{gTE0Nc2OZ{}KI;=x;(EPw-Z}KjF>hSRJgdVChu$~zx}moXy=>@RL$4Zm)3`GYooMJhL#G)!%g{-N&M|a~ z)o=q}m=&C1=mbOO7dpMr*@aFnbZ((jtG43HLLV0TuFz+NzAE%lp>GO(Qp^=am@f)_ zQ0RL?pA-6;(8q+nCG;tQE6KKYfotE|H12?GskrK-598K(f4bBui z+7Qmi`-sM{Qvq8x;Awsu@yA{~Elvx(&5r`K!zsmd!);fK^Kry<@?BRQeR)f+?sP(F z@ma;@SFRsAtC+gKg0uTRaHAAZg%;kDEyZQt=0_BFOoK*b!`~VBI}d*s;Ex?!{6F~n z4gT2i#aH0(8vI>{zkK++1%J2U?=Ic5%eA-K4bDj{XZv@T>l~9flD9G|@ErW*!XGQ> zSNQv#-nl5ZpoQM*k3HFOyju8UhaDT?kCyUujOEoeG@36NkIfk@v(wh3nT=SYUKI&ykk9FTdvJ zC>~O^Um)N9lZ^{M*z%WKe2Dn*n{Vs!q2q@;?(XawA0Mw`2ZL^vXaPI*6HWqkrD019 zmDow3AB%%Q;jB+@I_{EdrT32cgaSNhQb)B<4Ik(h_AmOemVdJIo!P&jZwR zNc%NfSQ}0lEh6d)G_9AWs!r1IEFrYnMYTn55LYfLI^k#aCp6So?JfHTsN)Z)B^uX7 z{q4sZ7eA%Yq)&|ePUX@iU%7T^Kzn_r9&C-~-@3S1+Kb9yK0oC;sH2ekEBU;ei)UlL z0OdNgrI2Ieg8mezM`FGRPCX6kD((*Nu4YG+-cZqPpQv@VD+kow;k4G)CSMvybS-Ufr>J9P z&#G;1HT-cxMPJu`7shVZ*RgM|Uaw73OUd4_SH)_1erhcrtK%O_7{LEjQbbk>YyOKIhfjq>SG5+HCb2sY4$cJO@6<^`URs=g~fN)m*4W zSb^F+G5pJt{THWP=MEEcozj6WJxbLH%PL&@hmrc^%VI(!_|^*oI}tMSmsjS_ia zmUY*7Nh1_|i?o|*V6etRDjY`dhpBU1L}Gfo{w{d;M8tNOEk7NxGe zpV*BM0x5RRc;1<_>fSZj6UAa;DI86O=Nn^)%nnpTqQ_w#i*O)lsoyTUW{SU@xtX z27ary*ghVp?x3OvEyT|j^R3!TMZb7@uC!*pGu zw$W|3WOrIPTHQrTDxFW%A-_y9)P7=s+S94RmQfnBY`5~(S-ysDiDccvmo1mYT$;PS zyLa-U5C$@^>)NS?7t zAo;^h0?9jU7D#T7>1h5ee1m8Ou7pml;;)4`_ZfcrVPDC7KFW2#5Afi>*W2|Tve!X@ zdKL5ci@X-)#R@p3ru}co9r@y!`=bnt=Sej^R3v{yBYseKp=)l*wP@TNbuVeq<`N(n zsS2Rw4?%m`8`3EBhmuvuKP#?XeiomIg+4#=o{NBA*r3&R!rkV{0EIt_?qtVb`yJw) zWu9cz8ywAG_&3=0!%BlnCGFl^3jfZ50P!!yu6-jVZz)C|nqt#Dl;YL&$0QA(f0Uib zA8B8}2EC9@%~C(1y3OiBTg)7NkgI@{7zd`nVsCqB|!r%nN!jih!~ zA-+w?%jZ=utXfSk{lSxAzM$Vsy{fJqwZKg;T@x}mpI*8u8p#f*54^$CTtg218PfG= z;D4IfNV~5Mnh>a`=8>AQs;ttmnmblohLl6I{k_agOb=gII~0EfuFct{%H@bwa8n?f zPj)Fp3#YlO)Ov$sO0!MCJQ{IVH&pHlncv4YC!JMbTkdPFW3LZU!^F750j)9(yQ}u& z`jPB+mqXYny7{c8ir&0mXwD%t^)4XO#~ocQ`uvyTMRFX$?{QDL=gdpcZ}aYo?~4im zO+QgvZ4+LpJGnBf8)vRj1ok6Do|{A1L3l=K}tI}(y(6&;>v2DCyKHP zMXe{@cd*|skb|9s6g1f7MyK`*6?WAVfhMd5Tv?l2SS+xCDh7%aQ<18f^`fD^3X+Fc z64y@pt5y>zT6U$?M{QM49W8Sxu>nfAV>jo1Buar@WQlGLGVxY&CDzQ7Oo^rOI!$dV zbl3`9%5~WCkW_S7$7paJmij&hg!5Sq6F{_PG-Ua~!b1nd7mfmvH z)ZtfLlYRKi@ip(9YtKsw&b@F5xF(zYw*X95A_Gi0x3Y#lny9YLN)?)^?4sk9=gW9F z-BBJI#TIwTjf=T*T28}=!Bf>j?=7mK@w452p$=BrBC^#HVn`~6dWudf)><2Bn4zaw zYqe;E*4px=SYlX}99nBL{xGJss^aKVm8L!owP-Ag!rG93DHPUvJk1nV+2>Q9W(q6a zVS#FMD^hL)AqD)C!>ViOqyTkoS9qB6TU87f<=79%b3Y(2`heV0qjdR9Z8bQi5d+IF zbd?ThMo)gVpG3y{a-~xCd+$CD`O^DhhWTZPnI20ESr0KSR`TLaW@@a;h3-&6S8z4f z0%D=Y%J%n)X0R`rz37>W8oOKoUvotkJL-K!_620&U=LZG6j_HMLx6pB4-Ge{gn4Pp zOD#1_d#y};C$|aG$95GH#DU`8Z8YMCRJfI*P_c!W+i0Av6f5>>oU9ZG=&R2OKtKGG z0Q92o1fXBqAOJns*7q9#bQ<@U#>3{_L6f7R5%&PqGsYvTdvwq^p}I#`)`eWZ?It>t zy#n2N(yjua>j6mt>XJ_f+Tkzt8o3XYFli2e`-~2Bc!9d*_4Z#V5jh zc$<>>oLXK2cl?z5u)KiJ#eJc)*(ip!al!!M;V;CnWWq1E$e<|+3oBS4IO4|+M8pr! z(6e*F^tHUVrh(X0G3^$9Yehrv&4EJYgZo?^}s#Y+$LT-R+)1T*H zIt-0#sbNN0j{KE6if&~9Q;hsAorE0u4UVp%?dE9mQ(pO3q7`Vw@J2@oI(C;!t zv=*U1bd0!mK!3y+nssz@uC}d2oZ7`xa@J7;(2(vl5boFokU<;9$Cd_v>OBfDB(6U+Y&dTi{~4a3VJscx4~x2LtR{u+6py+J>@OIv@uM5SQfsq! z(FBxO0D`87{=)?ze^!YFplA{(q{T|L{1$5W(O?{Kv|gC4ycT>Nh@lt1gAO(`?P?kq zbMr-RC{J6yhdNK^Nxm3(&Ee^|k3iL)%9DIGuz$zXOre&7podB>1{_`A7xOj3{sDQx z2ju4Q?-lRc(?@S`%r3DQJYDqshp`){ITiz-_iyAC2>gIN{Czn$Ys7`{rm}qr4GD!| zrVid2d0G{K9CvFwMw#d#;N5 z&oFw(;R~G>0cts|(Wx9OfMtoY0)QT0)v!=ZAQup#8vjmSA86tBX$ARht5P%`DjIZ1 zlTU-IYdmPk3QZpMj?)yzx5BoRFUSW`mgh7?<}3-ZK#s z!QLg3Oa+qtHu6s5w&&0l0_j&l2^{H!3|Sjz}N6q-4H)M-xjR zeYkQTmKVK4+?TWqDx=t$1GWiS2TxXNEtjC$hu`WPZs_Tv*##J<{y9zM|hGiiva^BrLgyY@_SqSy)ul2G; z-hoX9SF(NIDe#*wYnhS_{?}K~8W{cN)Zk%iD-}2)6h~k0e|a5a2EpX>e66X?!58c+ zfWtJ-544+qc#^N&KCd9Xkt^5-p&)1QrMaG(-mG*K3e!r=3sF(~OAS3hw0SH?NmRIY z&mmF3w{WK3vx21f*A?tFSJ67KxA3Z_8f~D`juzM(5(SUG@6<33y=F7?m*8|X(!DC# zr_psq=}Gm(Cqi$bn%~2r*S``^=T`5imt1+WkoD03jg~{NxtCsVa14!r-lXzcfZm!9 z#B1hIuDdcx3Fv)RNy}|240>&5oi*H#a%pG-ZBv?ZPt(&@*gzYurT%|v z4zk{}{r9H^jO1(!0-(E4s;@2HQwq$@t*3QX=YyX(bqE7o+1!rTiJQqIe0bV?29}DJ zJQ>&lfVMSH`oA^mr4~Zk2*$a=-_Y*B+tAoHLde#dPax?)T&Oi7?qYawS%@3w2Ay3J zi7iWE6Y##61GJ0#d)c|%>peL)m&5W;&sNl=$o}wsNyufYK^+0NpViGvdN*QR z5^cKKT2RRyHt}jPe!_&Zb%`*cTzHblZQ|!_twOY~B+x|)sYkRnlEex^7x0 zn~AP3A0Pgn_Tx#hd+g8C9D9pcTCs(D!OhGTE_Z+g)28KdkYL8}iMazryAXMclxvSc zLSC#+`43qIj`#^C?_fvg6b<3& z!oA>o9xzu+Ii$OG7v0KADz=$?>?9PB?$j&XS}u2mEDmsEpwqI4L*k@RS1TMP8tbco zdeB61?J$~zjnuBAtYPxUep-h$$YCrGEY+uRGei-&--=H}a=)>nwaoh){tZiuUBwtK z<^Uf%UUUMu6=>=*j_REB;WSJf)m6XJW~IK}DlJ*u+eyL=Co}qJOW|f*umd-3Q~GJ@sOUYYx8QRGeI**q ze$+WWXpMig<1c@yo+z8&# zZ%cseL#SA>&E&>XaL|~Si(Xx(WtLyn*H9NV&fIRMfIS17D#R5>Ll`bBJSZ{Dl8et9jXX}X(P5Y`G))24v?i1DZ|A0L7 zeL1&=#LVK&=GKtH!SM6iT#L`2$EBcC>80-=sTf0I-YbNw)rIrviUO@&&VjE>fP91v zXdY)&(l66!Eti=uRoUzAVfDgHc)}wxzx2M@uOfgImc}#a`I-apujL~mK#kLZ*c-r zzEW%j2|kH?p5;|rOH1I@D9+>1-(d@>N`S_5e96h;t!GvfusBTJ2Xc-;c}L+bi5Uf( zIyM6p1wMkRb~C_coT)H6fTeTmfU!H3XW=UR-0w>-MS0ndrAVmodB1C`+L}gdzjw8* zI;ZU?v#pt#PS^&<(02R9UdJ|SzgE%aryS5Os2KCB7)f7cBqQ$gmq9Jb1;4?=M=&ms z`~sd`yxL3|Yg|XC-h?Vz#(}zCz^mxb8g!Q@LzwhWp5*FJ5d7>U8~iXD(Wg^^4eAKA z7QQf;wm~nJvF@0Y%a;3mKpyx3dH8#Bj^*Zi0?Xsz>k?QVPUG&h^P>3^w7sO{&A5Nc%vW z?;CB5OyhpjPM||8>RNA%&JJnpRo& zxJHgF>V{P-RIbe$xP?>tqf# z0gP&}Y@@r*;OSkpWa$CQb!asqH`9xHUDe1_X0fxb!fVX;-t6%}h5=iIREZ@w#^`l4 zqM{DyvEVpRnd!}nI+*falv^kqm1cl9*mg{%tIOf*h_zQ=we6R3y2TRhGl0bLmo4NU zrH#%qg`9Fmw_kM~y-*~7O6^)`dqhhe+or>N99>OE z&;F%7DVtm9=*j}^2wPlrom7sVk(ImI3oW(aEn;spjcll^FBf`q0aF`oHyi3aRZ?y_ zdhHLL#fd&jNGz zT~h3vy~}tNt4TgOrfZF$F?-dXIzOiwx87fGmh;-_#1?$^Gi?=PwDMjiwbQXJ`1dMX zNKc(zvY9{C&vnIjg~GdHX7Mksw)}3o4iLJML~HiYMbR}kfN=^o&M-Ny2OkP<*X${V ziP<%K@uaY8vb5M+(+T}_O{n*NFoUmKs>}M!yM4(y?o=rb5=fp6?Nc&ejB;)72Sp3= zY{UQ^4;iTF=P&YFcnP3QF4Z~gf}A{S9;*{uX8&6{!7PJOZcg{N(zd1RSL!^)y3U+4 z_7S`p+&PoquVfwKR*Gx;Sn-KEUCihF9=3smtdijIbe zw~y0j>6sDAJN0CQ@=n3an5m|}4Ate^#(k!XhKQEox~?>Alx{1pEih;w$nZre{h={f z)p2RG!P_eI63XAtfvuG{j9-mugHL4KSZdens;S1>uIP2w)oh=bUx5N6%fl8oR~M?U z_<6tnT~#hRmmsB!V^;9Cam^wuoTs@zCS(~;3)9+io)%W({vtnYy;b?N{A!)zJs-)% z+$B>O$kSZ82>nu5ywA1WXsfis_Fp@tZ*1i{Ao6gnA?d0Z5nrWx0e3mtJc8*v($-{+ zZgw9IWra{Ib`AxYiIn~B@Q3DJ2j5HNHK+(OIcDWW>{IMBX#Xsrr6xAwAgK4lI z7MH{u*?gThF7OB}8-MU5A5Y(#Ld;xkS0H(k#`tuoW^gRe0+9;dmPRCduG&M^-4~F= z7iO1##JChM%rxmgotXv`*MW5BU%D#B!22RY*h7v<82`2Zo^CRa2ne+0KGp5i3iK%) z;?tY9_)phL4tdS1$Y?J#{t2jloM|Ugu;9soZ+X3$r`$ICp{}3mv!aLMLAY`|J($lK z#2xQvfz|XI(3(f>T|4!nx2h|Zg6+l*YH8OAXj3)xGm24$R`^KC zz;-CKLah?nD}bhvUrWh;qgMM(U?g(7it-c}j$MgdSYFhY@y&C~iAHF_>fz3l96IvN zJk6n_ko7!c`a+ZSEXmkEl8y^GaKsvTngItrEMevPY9gucW>^42Wc^~^mJ6HPEvI3w z&Q}oGCBEY*5?e)dwy!dvMV@-0vrwPPdQK6gPy;c596bsw{K;%*D%BR1Xb7&`T0H3w z+8A9>6-JBs(WDw6+{k7@bK2w{@b?EA^>v&;#PP)#(+9qo=JtUv7P^1ni*@CkzEGlT zLoIq9T|2P6(|6(2zjpgBqGHo3h$ixFhSNM`P%@8#<}#P}Cev8hY6lJE+{CK9(J>35 z(al(+`PfBQ2C3Ll7@gF90U#0JNh%|-D7Kzn=rrELI(X_@i6QZdkN;1_bsAqUz0hem zez<(1yeD3IC%1yFdK>*@XT^psXn_(7mh+=)igew@Uf@|`?OI=2^-U28d6D z7MITa7Ouqv($SwN<4U;H!ZkcdP6Fa{N4>$(fD*_ujW+BpW{<6ZZ@t(XfF1+E7|`_| zB2F>h9tIIFqjaqtN@p5fj+h6aj%N(z(+`Ii({X=^8V?Ed5 z1jE(gWy@W9mMz~6V_8-? zA}iJL)CWbr~*C-_w3N#COB63?0%`AozDw2onBN@cbEMT(1 z3C$YzRJWzi>lvJOW(99~6ZpTURO_Zv(PwX!ml5`5K=*nlt^m`1RNJ<`)*l2YyQM!z z{od$@Q0r2IntC(?USiBB7@^#ELiGWo@Bn?&2=yT zsbV|<)a2YLJk0}6Ci5h>Tzk>fAVYoGCm2KuoO!%{4{kWj-Tr_)?w_*rd%)Dr1Z_38AxZ9=i%rT>d{m8?R=idF{b74Q?F^=I-RtW==>@k5io=3us z=SdiKINd-bS$y2rkW?(Z_UQ(1*>8oRlnz!n(=eaTZ|-BTdMlCO2nkM+;e52>lralZ zyn&d{$ven%gem!Q6z(48D|B%Ac#a??pCybFvnse?Mn55N38D)YGL9?Q#pi>$Ynhlj zUd1?DILqKB@Y_$ydxOF0BCLAOAM}P_wt~Hn+@L7 z(#P0G=!~1^8d}RaOL>p-Gcm$>(wq&QyPPL|`@-77l4t;%Z%}l`fsux+&-`YKYGLD? zs>!R1I%CAjlKGO9>%7%av@n3hMi_(vEO4#JYf*a8H?uU%dhHZ2OU-KxzHDW0&KVVS z;Btf0hBR+0ZvjB8zTxVUkIy+HuJd+^Peja9dw)Es|f%GJ5 z9s{&wM;nT5U&8WCIlS)A6txkiuZNoq`L?+W4Pg3Oyv4vxU$g0%ETzI+XM}G{>5N?$ z85}y}e>n!BGumpOF*sYQtDh64I74dDPA5psuV9NiVTgDiO_S)3tp){6qqBJ{Ihq2& zW(COoF9S0$hl@j4_}d!M56tWZ5lV1p)c4?p{+>%Y(JY21xt16aZ79|fZHKys9{tB z6!YsC*&4}}!(30E_FoJ}jEgml^<}@>AW&e`vdK^;{K1fwDNov5wtP5@R#`CWwYapL z!)Re$qkvJE4`r%_FW^2WwQ!-qF>8e-EaZV!BUKPyG7h3Gml{fesMklV;d~l<)Z~5O zy%k<=3r|MmL$Vqq6$R08`jSy7h#nn`B>^?+UWaN@1Ebi|vXhZ?ZN;#0+@djcAsDfd zQQ+2cjf@PoM!4D{yp5rgZY!NSg@_`n+F_?IlaP`NgLF63B=(9c-=qVB-k)StJM z>Fz2ERkbtL=1h;iOv?OVa(Ew}A)oQx`a!ZAEewZHSgbXxi{b?XeQ#g~BL_r)AaI2R z_cVel?5!1?_OJv@a0(LBwKNvv3_bL^!&|zjlTm1^Tzy+dL^;CpzSCE^;-0bm&yB(} z7SP#fmc1tP*A_fu-XQ0mvDjcf&qq-3j0MDBAE6AhnlFy!8;i}kvA;{ z0v&7v#uum^e$U&f%cX4W5M{3msguM7|ht-O$AzAinTZ zGWwcD^RU1m7E5 zvr=F+;YUy`<8zY-v56!HAir=BE8g{(!$ziD^jjKHo_wnZmn;G$P+?ub~brQtBaRRek58oX_b*4SY^tV zz2+N>_iOw}Gvmlx`hueO<_J}m2fUvFt4S^A>AqpVUbbhN8~s_}uGw8-dqtw*I9-$))? z>F{)=>HIXacSqG-Z24BBkE^ZlXQQi3x9&2|rG@Fn*-|tdu!G&i`^{sgL%{k?pLa?$ zF^I-5($=r5+b=G|a2BPD>k#ESESZnJ6RQIA#1+l|PN=?cV_Hj~9KF-cV25f-q_$Os3Sm##ALpXQi==T=-rw zOxL4@bB((75$5Bv_t8=-3#Yh-s;K#nZlK%&zD13Xu4P2m{LRLp^4tU?y|@w1HelUshHs{rF@f32DiLypuM#}i zEDVHvmGEUJNQC_^vN|@S9%r0UHZNOzUvwH#Hc+ko|3KN^($5Z%tt1!z#;Y7x5w@Cn zJjoTkfXgf$RSBdl^&ZN)T;M1xgRiz0>L>K9Qei|IPgzLQ~htT zG{ayzXeEKeMwm@L^g3@fhhe|m(lG339fx5lAlCsP98vNvIs{x%3%{r6u)YPN#!gtA zch?z<1ODfzEAqJ-<-ID|9WF)U+VYS1L@W-!FWSXoRA2HW4`Tg`C(S&z_olHn*JYz0 za3Qxb{0+%&BHS1InboN@5=$y6?|+VlaVQS>)mRFOlOFxQL$S{@M{&I4lAb#RyyFTb z6W4Gn#=a3v^;JN($lZi4)qjkEPB;rXg`-Box$0(OOA&Xjy7FWkbFRuf$(^gs-ohKq zovSV&RA9CM3Mva`y?!fO9`pfu><8pNf0rr;{i<#z=2w+N;rp@_VNj@wfeKu!v*PW( zaQJWuU@ZdOi#gXE(}Y3xJb`FQaB7@0qAieJ-m$sBov&|MiZbV`q~iS}ys!U&SlOr+ zD~AhY*3F+=vdWIQG9KwY+@T!R2co-t2I{J z3fh|d!6D&FlYLEL^!5W;sE8ibO>9@pRm4g(ppnVKt6i7Zl^^N!F;DXIZ0bRh`zHXZ zdziY&UU0M9kEYdfFeD!BGcJ)|K;p-6V~dG-pOjRAUFs`!Yal)mz@jUc%I9c92>^>| zS~6dfa-G)<>ZE9pE}x1#A#}kGh|X%rCp2@%nW^Qwj@48R=>Aov9Jyr;6IUR0umExN z3Twie$h^z2pD+ohc_ZFxF<1?FvpIGhts%NgW;gTW$ebERSH3g;E@wcN^Gl4iMHbK_ z<*pw&N%&hB+tMV?)D3JQs*G=8UUzZrh_NbcW>O9<;HqUtEl1@YaaKjmO7@O-khr$^ ziBHtSlUnm;aUVia7nT-A!B#@dOoiQel3T#KLQ*jgI(nPKn9ZZI2|{PP*5<=5no8<5 zd?23#Npa&jp!M-I3HV?=#&k;=W+EMtE;>k@Wl*!6pUtO($p<1yCY6Jum#UizBpTvt z>JypJlIxgqP$#Ey8M4U*#7VM!jtL|^P4uLsQ#yXR+YahK&ooqu>qigHGg;VhD2JE% zH|K2xtywSm@FZKKLGg(14x`V^fSo~lQp8kw`8r7K>|Cb}jSxGzDchcag~~2QK0j;ySE9 zz1YaagR}IKm5G^Ag>IS%G8UOd{VJJ47#Y+Ju4zQE}CrbZ}?8B?d@N|z8$BYp$3 zj$=-i)OQQt&(F*8p8A0rzAxv|ThSAF*D*Q(9dCtB;;0+Sh2ll_gEXKnujKoM7Z#7(CRlIa03DCbybt(U1tQ5euijhu(17b( zSz7$zcD}T1fCOJ!YDS8srDxvMQp(%+q6wDm;uDt^`$}<*IU4(6UN0@;6SJ`w}4&GRL`FH`6;ttq;#{CVgaL!bR_n0___-ot9L#9g} z)YqfhY+C2M@*TMCBZX051~rvhZG0{}RvXIuH0ckghj59{4TnTHo;DWh%S*R;G|V=g!&Xe7(Xj`pQDSt=r}gI+qzzh>-!8Xhy; z__&pK5nBwW{#HQI%jTVHg3>K)Q4C*SyeRr#W{aYon*1W;eW-b^X*N(tN-lVBB$vnZ z;a#!T(Q<#mni$s3js7^#Bqkk~xD0ZVJc%|ftT0Jb$yb?Baba%4S>?0`NXmC~D+HS_ zV0Hj0`hsnLfoZSG234J`V)lojvLoG2N_=cenyv8D)h^*m@iA66cw=Z>44m>jpC`Ff z3(hKM3mjW~L%5Xlor7%!_bzsz`gx|JZz?0nfdJ>kh^?aDt`nELo0+c3T?jQ z-HPC3GFa^>tadtE<5)Q1nJ;-V>!dNX;cinMS6nK@52DDvP+l3UMA&z3Vz;2I5)0(K zDZIhVWIzY3fl0#&A-d^Y%3wqlO(SnREfSW6%7`s?6FUY!>Q=mC2n3;H#}K$dWEI=v z{8z^E(o#3EVpPvGRy8aZU*|1#Q+5p8_`!D!)HKsQoQAYBdDwzhyTx&c-#X6SY44D` z(#?7Au*^+BeC$S6bAFU{lxP_{jgKXr_6}11N^1Jiv_}4Tl^e%*T8Fv~?v2>(zf4pN zu{+68Q9h3lm;82=%on0uN9=;fDcgdiZ6c3y)MemSWqz}L0sFBHxoESSFfBh^<;Hgg zdRV|2ywiecHw)zqg6*s&LRtQvH;OrtY57fVV(n(;<>1w#J7jh{-y0-FE8PnZ;Cq9) zkcHzL&%)H$w`-gvd~XoA*G+H-JobpYEy|l?KI7&}BPwdu^Sl&j?IQ6H^G6Co~F>fCi=Tq3t{s75I zG!UZl430h*n*+amH`pA!jo<@Yn|BA_94wA>YAZ+gg@3x$;ol2%548ClaJ%n8-xa`5 zH~61(A4;b!R5Caq-gUj3fOyu2d_&>!tDD$R6!&WMWv3@`i1)bakO+upXU?nq=ztrn zag6cqb)C14qkPO>=rVThmvn;*`D(7)&C_i61nz|etXFs>N+uVxgLI^9p5#zpG(~us z5bB-!x)|!s=iCtLJ=VGv?|*)E1E}|3?}ku6N4n!Cwl=(l%iZ3{`S+lA?*gzh2?zGT z*6sr8izWH}og|!?5IA1c$yb?D5p&(&!}{oJZpHn}upWL;3YNIDZngsjrajM&c)U%JO7N31m$ZT=J0AC86B`(nICW|%MKO=1^O zLQU@jgUvx78s)sJS@i*V;8gcA^T(7aA1jp>7h^u#>$uuJ{>uG@0DjxS!??{+Fn@E5 zTOC(NwM4MtG9KflnN$`3qIjO|AgtJ zn2DU6oC5T{*Si;kzHgYj6X^R!h*3tBAHoe6x(m=3;f9KzG-;u`g8DHF-2Y!q-ZW&K zPj6|Ny94>9xV56r5O-)0DR#`eYMs(7@a~KB_DDN8Us&*Nv3pUrLrGk{_X~HY=bN`| z8MHxE9+5vrxwc0MIj#`ii%RB;b~K7lZiF)L?@Q(jP_E&uoh|MT%XiFXkxiV@CPgdn zMe!PuALTCAi12Xt5^IEH+4_G7`x1Dng7*KzwJQ|r5?QmBJ+7thkbT!GZCZ(xB&pP; zC`u*CrBaeg3(=q#V_kj3?0yQApPNX}94VVA!hMZuB|HN;W)mZ)&+ zIGByxxpPjQ%tsb%P~p~b5YmXB-G0uL1Ggxs{JUeYWV>ZIS5G-`-%X@VthPsLm!zre zprnoMKro#^ok&M-CSR~bIK1VgY7Nx0`GKV>*lKSs$1&}9_RRj13Fj4LcW57Bj`+(} z{um<8&?j$EX*cx$$FwBx3oOm}y9<+dcBmm;Sb125W5c8Oa*d{j@xU2!1IS8?zwB1& z$c9IDJSmn9mo7WabqecXrOl3V8^px%k8m4oHe#u2uFR$&7QfxA0>g%6&cv0$5&xTs zd-gx-%~t&P{iFX!{g?88_LtD&g3~I578g{ga8$=XwoGuW-Io1-XmK`Oar>Rqffi4$ z`N!xMpm?S%qMj>i<-`{&VZy$jzx2<3?JfAr#)Urnd%tAKcEJltXIgX9d-yT8AB=>b%bH0){MX1a4^NAau|o}cHltfr*m1B`*H_$0>ysuk48MzN`!G0_XTcX&zEJ5%k*D8iKY6nH z;+UhZ5|6K}O5)CPRRxO!FaGYY)N)mF@W`&*C-oi4Z@4BRSX(GQtE$QoAiQu@_E5xk zN5AIwsD#vnVku=Uc&s6ngE}Oe{BsF7b0;?2rCP%A$i-V>^@2S@ zKBl#_&WBUaaL-J+)1(n-82*1VU7ge}JiOem&OWPQT zFI-a5C%gQvS9vLIlOS%c|EprzpNi@Y78al~M>+7CxU%kAKyI+h1GbaiVxLcOv8t-R zI8x48ffu=h)D}bXMX8ru!^u>FpfjoGmaKFlWfH#os&Mt}3pUx@WIb|pwMa+PF>!~E zCu3r^p*D&viJ&IC6@D)!?UlBv6z{wsIenn|3&jUcSqzdss4BjwrNuS-jh5DTp0BE! zxK&$H6LQAlm2|wO_-Buf6|UXeE39?3(u~68xid&9t@H5%L`=mh#}G zc2yO_FgT+++)yiZ3}j$f99pFcPy=)UbOoqahgNC$MvCDZxy5(Ww5so>X&-N3TNdAC zaf6v=b%R;A$UR~kJ%_GbqV33wD*imMLhToktY%^Kwb&b$oeAKz^(19BO`BMOy+$NS zn-ZyHrvi9@T9xJSE@B72WQ=7KPI z_}H}uiqdjWeN;?N^*;~7(lxvb&|<>w)0R4TeVQ{2AtJiqA&qNY)R==&*Mt zX(?AEuds@R+71j4FB1NmIKWaUuw#62Nvuvg#nUk2@70tY&Xzoj)Ckwj{CmGP$+lWD z{J)E0-wYKkg&Eu~@#3nF%4X8>I$TiuXYigpO@|x2PZ_I22Jd{GM%qM1?XSjioxx-On7B1ToTPu@@Pyv^0lN?rPIh74TFUqiHN>s8 z%+cZKor2liU+YUOPtJOszx^7orAUY4cliBuJMcR{CbTz#`Zs0i0&>!o;NC24lK<|p zFD|;|e>832>S@omrI5cNgh%uU9hiAx->urPj;m!eDbFQElD%j?6usCiMM{d?QK0|H zKt8`%C!FK+Qznw@tVeo?_%xYG?ai3w0pj4x3(hV*%VIgw?^DOh{i+57wb?|FAka z#X5YI>d-;Dz>34V$r4^QoPApw@nyU+xR&J?B&SWBS+bVaKwhP{F6YhI_7QJ}*XeTJ zjIB}aq4ko7e;RV$tG%YqxV?sxp5$@oha`7Z@m3S=VIcnN%LaH5SpS80m=r{@J_#$c zCmJoa7=8nK0`vg-00a2OuxZfZaCOt_aCNiFI*AX9Y%#8mZ0Q9)tWr-ahKJXhv|lT- zJ}7643EPFmHbDDsCdE`XMO`T=va%KF0#fA02!&9VcrLI$oX3F%cJy2)|3|${^nc&K zq1fcV9!G8CKlLru1va|>xUTA)=zeCfQYIAK8d>B3|AaV;_LCx9PbeIW_zCuEwEGF@ zSy$MZIH5bsWu z@2=Fo9-VUuk)O?3mms!z4KdIIbIv8ONj=4o)Dv=qJTR|hU;IvfidR;N^UHW@em0Vo z_4JDlEA7dSbj0%|?Qf2_!)ycD&rCf1ymEK@4ofgebvXGAuieP_dq%OtjP}Z^&ElME z@&*bYB73>foP7_IAk5*U-|~|H?dCO04tVyn~B+_R{>1i_#GL zp0I%Tzs-S%`=dE|pu|WLnLMS2BV-hMjhRQ~*+6 z&DmTsz_b4*C$IJumgk1Cm4p~X-$^@&UrKM~dVt6NIq^$nU%0cbgMUE$QXcF={1VxP z^-CIWI@A!q6bkQsVPEsDt{}^`MPG7384HWQ{L&mQiXp^OUR(Q_Q&*(K<{s(g01Oc8 zi;8)F_e}I@Zrj(A`=mV>sQ^8t%T2LHp0v=zp2k~pP4!665P!4|YW{QtnSt;(wJ;>#(-{aeWs4?>Pl_;; zd`EneLSGBc&as_Hd=lTqg7Zmi4e?3*r52nAYOmSSUenPhg`XmABU20~xFQ>CmGyGq zVl9}NNP|vfmt{+{HxalmHaWJw2`RISETwyqA~B*irld?fl#R6mxpLG)b?9;Gq4xK- z00%{)tZHZkbxr)U&_dQVv1i}zn)I(gGuc3ktp?ZhL)y%e^Gs}oo{rWl=`0)04gIMY zY}Z~v{1VdV&I(EHDm8nD4t^=x(t`6#Hck#L6Inl#+?kzS0KbIqS^z8oy#ajyR@L~f zwf?}xYDEs!YDImc&;4Jqm5r%G`;{XiEk?Xj(E=VYHPBVqIIz7-CZSubNtwj4Wk#e7 z-K!{V3`L2H0_(%M7HDcm7d7&K)UW#=^{BVQe?3m>zx6HE3P&vd4;R%8|Ae@x_LCwm z3d6yOi(;=!yNiNO=3Eq8O#3SbY+~AK%6I(vEW`~E|}^>?t$=lC(fIdr8^OC z7TF34D5xX>4y*7I&tK)L6r0>zK=!A$wOF!^t+;x9$sL6X{@yQIvYokxtEZ%zn&kh} z<8?S6HlENwDQBRN#+{?#fAkm4PKhc%04lQtkHE`h~m(jfH zV|oG$o)|B1sf1U$3-*hx`U{lA@nS&?&t|rh_>Nd$Bu0w_a-C%$X&?7vljg+Uf+XA9 zLQ-VuBx$Y)ZVSc@=}=Al);OL3jzU)KUKl2r%JYRS_9ZUjSACtQ@}k-A^8EC~Zi@sO zv27XLdD52h7fH@Lll!FqzcTTk{o30{PLMA-&$@!Y_iK}EtF48zlT5brv$=iX4GCci zX3|+s?M@Hbx$=48nNH#ncCPdCw>bIpGE=zIPge#A6pa zaVd{^W%;C*+sNj7>e9~wa zwEmI3{Qiyoku8JAOi(*p<9YNTxdPM zU?n8JU1=hKB~^R@#H(w$!_xaSUXy!bQ8`yn@4vRKA@8W&M$e)#t!Y;u+3?kxb2c) zpg3kL*Y701%$HS8WOT>g~u5 zSMa>pDHK!HZlzewc-sBwUwNl( z@U9+j*FH}V#HCNV95Sl3#tE)kjFy`x_T0E=(8I0P`0#b{@5MnO*yvkQgs7+wcDQ-8 za>FctJhE?;-_z!-aR-q&5Nt@t%w-r4uKo^$I zsa!YT&lp?RguU~13c?5c`r9|HF~&jLoQH>5_+z2zoXC>f#(2Xq?X6EIc;HeW_hD0h z8RMIR`3=mo5IolLXGFA>5ng*`;YvNrU|eguB;;e35#BR#Opg7J0Ni)iq*Re^lM(hT zZ@YX{*%zmH&fO8DZj29asTteL%Np~dQRg{5jqx`Vzde^CL-1%bd z8{+T$EXz-)eevD?r?>EL8RE)!4RE-AsMej)f%w;w8Fs6nK?M(&TEAKChYzj&>iE*q5a$}Hevhhl$MuoBRz40l z#Cxjt@7$*xisy;SFD5aC`uN z5c66&5WB?Ge&E*_VE=(DmM8u6#fPJA1!l+_;@FK@r$R>d$HPKbl)Ua^h;Iy@8gS%u z2);FM#)exd`Z%xoP2<#O!B|iuw|2%eeSAdA-eXgF5Egmue)(Aqx@={i-9D?n1mLzx zoiO=L2DsnsYp$*Le6f+)mSssV4e;-73VfL{{jf*)&U?yT46$Ou(fBPNL$FGfveu7r z`q;T&zsVO~1!I@i1%34D^>Lqml~S1p;UB!)-X+e?0EfQu-#)ZUAhy_y6$6$U;JXo~ zni@KOB5awbySC?b1I%b#mP)G?;+vaI9}fQoC$O_+J;Mvdkqv#Vm-_4Df$Gm!ERYVt zHy$2MTvx4+pJ**D-_{g_r2?aMfA=uJ)31!si1G}?b%uSqq9g-6{QNe}gIQol3cNnM zA2+~f7R9DtJn4>)xgI#+{nh|~HMx7_yP7BzOPxGZ@yrgo$b7us0mBfiz4T!Y`=hd%CU(r{VNF9cT=TKc$vYWki<@XR?PyI}n5 zePz+8clx+TY~Aa1=K^uz#j*18p$7QLM5*Onm-}PCp?=e>HyYroV8MKg;a>R4k>!oS z%?8-H|D9L297A#P{b2dUGWvM*h^!jD86i0C#N$orGxc$xkM5RJ6N0f`ebOw8yZZR; z7~^?1U4n34f$iDieg^p3r*-}!59I)y<1l{5h?NF-q}!Z5caC`DhS=>&$1WS-@)z0D z-@B+I=I)F$O4r~L!OvrGr;aiV^9?W@@-}^eO#G+8b01e?n31r8ej@NR{b3&T9iS&H z_BhamVN$y?Oo}?g1Zpx&Js`Im!>kAQtE$C_m}4+Ac2k>S@__$+2RH<*^68!ovsj;D zS^!A~3=?7q|ICdrW9lekm_$d0xd#{p!-7;8B9yx@%v*r1E5j@Y7`ib` zP(Ox=5i-n9Kr6r#G0X+PJ9mb$_Jp^b04f18UJSDkP~r{$&=o9g81GMD4_VL)8LvjbjZ4X?Md0(2yV1`WdHRZ`0`*<#;oP|GqeQmJBtYcL)PedMA5uNz6b3T47?vx9YAacG>& z9BKX1_J3Ft;{waYFaCQ@4IOrYLGx^J4c>QbpIr7o1ZPUwcj=DDL(Wob*9*lY}>anP&%4-&JE{eJ)>XE1?q8^BP z9_n$Zr=cE3;#vMU6zWK*1EG$CIt=P4&WI!)g7XZae;fjJ1k?dg!%q!9HT2ZL6T=R6 zo!WD1$Ep3McAMI3YNvImfo2UeYnQ1#X6tz&ADG>IMKE4q zc3FCSM`y4Tu+(G*nBC)Gc9((4jnDvV2PSu3cZN|1llv0j+Jj*#0P$dLw}Q1T(qk9} zu(Y=U@?dGZ8Z*pvfEHL;c@u_-0xSCvFx(u>wJVs}9bjhX^kJCm0DmyC#$aN%*)WU) znAQxy3&0*QvCn~=Qv<6y5^xOQ2-elZnPH^Bq7DLV0(1e(S_Ek94<`%ObOYciAjAW# zFu(z<>UyxI4*-@vB8FMv!!RabRwsi=l?nt?49Ew30HlIR#b8Yx!IDk~oB&vYB|Qdc z25TAt7PJKL2+(gB!yE;?086@I1jF117>{I_WWYPX(@}7OkzjQJ4r9Tr1AZ#Puj+B& zxhAfxvLhKWTNHQ^ywuH3gmxp*RtTGkJk?mBCdOx+AS-B{QzEHX?In0Aeqk&iv2!(S zL4rCqI@2=|Oy}9`B=(bC_$|KAhOk89bDXow?EGo`2`Ixm;!0)p#m`Iy+1#I&%BG3G zm`S$a#nqR^C<)#mRD8<`i4@n^CN!4T)?8b3!>wuIJz=i%z?{#%Ua0w?(o)$HPb^)g ze(P|+I6UZg=f^=8PuA%7o1iePXH$)mZQ)h@bOvb+-pN~E^rA*%Xw#DSm+uH)%8y%N zcXkwduF%E5VUj2AHB6rWb#^Sy9_-P)G3H&(y8FWmH%?N*;~l@gzOBQEQ2APgD-)XZ z@wBr-@$*?vgnJ@q<&8g-gGO#Idb7Xp09?M&N;+G94d%bq^Y?k#1v@ZK`yYuBeyvfJ z<+$pX@J8f_u+f#DYt}AE8ddW2nQ+aGRrUqrBGH%?o=9V=CqC(+IyEqQ2F~uhVvo*^ zwi;{mZMvtWw6W-dYQnn*B2~2M7eC!{zb5{1q2Q*5+H>K@yIIS=Uz&q%73t-?sThE- z{xT>p{hEbS+w2z|eyoCZ*A+UQeJQ|>`2z#9HmjhS6_1!D=YH3m`(eCZ*W|76&8(lk z1NVD&c$9sV-`f0E;T!w(69;CkLRJfIudtfzfj`~JH5@c*6aJ)f`9=8- z7^1puw{A2Yfpvp~$)b=%O_Y0eW1Uc{GhVwtq;bRix57d*!{%FZlTpggwR*d%JaPT! z^Qi{E=i^cHrpWdDc3HS4{Es?YrAAS{HxO*6(~;u2txoW4FrA zFd3cSRJ2d?-T-{o}onjCTnt`Tp6lOx_(MZ_~@zaW@_%{r=gh4mD%rmt~K|dckXyrc{fXlXj zwJ$YYhL6TYcr0J6h%faxe&uyT2%b}RGrHSX0~A%S7qmvh8^?4yRr+wlIt=&8F$H}9QAt$sZ8G7N@$A(NR z9chC4)|)*lc%_dMVtQ=HnfHZ?dJZbfWa!`9_~!sGHK=>=9DV*t`cn@AaLWNGca=(UjgOGAr=G489gacZH-5#4K zp!n`zK7Mrf#s!^jc((au;GI{zRfa}up*i~$vhKD*WcO2)yIw)Z0&Q6?mDD;(8}HvE z{5t-_WnoDW|9tSpB$Tu7cf_bbFB~&Oc5zD7V*JfO!_G!t3T>aX#AWG*FnoWcX>!~n zGZbHFzDoU-DZa7O*e5Q2R!zBD(cs_4i%{y}N45O~6 z`nIu4lV~iybD=?gf~zUI(KF(xW_}ZG|s~Vw=2IIA*G&FE`Igjg(iK)W$*iAz>=OrRu zzmZ=rZ}-O6F32`E-N?YquizUyi*(SV(%akWMcH9^{OZQ%K3{sHUc%{ecgJ$&GwebIP)GrzXxu!hjyK9%}~RDVvRLTjC3G_(a>)nV;0KRZMNI z$aifF&gTW-tCr?SH}gQ*tNZ{w-osVn$%q#lH+D*X)7scO+8c~^x*Qedk*?GwjJusVl=L|d@u@_1e{KSuI`1=Na5c?(}aaGp% zOM^!Ju&!tI&S_bT;?=>^Nc}OsPXn` zg;z$37$0XrT*A>#$oU6hxcMT z6MosT4Yt_ufI|G|f+cu{i`|MC8x!R7Ubf1m`BqJxe4n4+{6E#qeVXHNC1@6^AJ)8e z`5RySL9hPa=+Z@4TlaR0mYpSX5{->;n0h`O-)FY(Y5i!6qD~A}w4C`~7_jMf!QP=i zYQ|ny67_eUi)PQNI9FQbgA3=p-!iObF+Lb$n(#!)5H0z9xn-#FXj~mVuRjq_GFo1qOAW9HudA%%OR=B8$i0`#tJLDXfL0r*`1WdS)^CHVc( z_)&-Nbi>2c<*)e2z zt2w?pxgh%boXdF32BY&X`@BS0e#`Zud7|xj>8&1fuY_x%DPty1FTJRa26{CPGZ`U+ z-^~AIIb-x}6w^Ae=hmZsSk!#I%EEXNZbm!&9CG>~r)LL*NOm;VOgc~(B(O(J!=Zh} z_4+8}-m*P?U&~=Bk2~347l@GMP9?qGgMIO1aZt|i@0r+A^>XY{Cy_DQc67_FQnT?m z;Y8O@^~!c=_t^53_MqB?u8zB zCOU?cmA7DLt^2WyxweNH}GrNUYaJnfy{I(@>nczR+fbATlm7Kk8%`DL(Ofwy@9*ow>DPk=Z6Sy!7!5xvb7vsCa+YggJRWID3^> z!M=!{ z7ClQWKK@3=6YJeOG`+#J1lulKc)4<;G@kdlL=>vF0Cy25K8&Akg%tgF%v;rY7V6&R zP4EO2CG31GXS&6z?=;XcU^?uzT}NtH!lFEns>5RT(t;W ziF!EXxWBbQUbUa+jk`G!*BwyRdKB9ish!imeAmkZsck%It>&eP`;6ML!S!1P>K7{V zN-6Zib}do8SAWmOO?T^$E!b*`?w##g(rf;&?&73SDvaS zF1=s(>sZAqq+|X=Wn8s4R%x)|wVW=%qNd%$cb`z}if&%tIJa!>T&&V`Eb42rEsFG# zxi}@0}(s^Nm&CR<0*l;`# zyGc!Uj|nnI>IM^HQWXR-V}; zTzFyuvT}Fsvc@_PpV)TtDqlmCi5Ct!HTmZtTlDV2rL=*nvG}Xc_?z$T9Z_-V(Od2+ zq3CU~W%CMGZT!0HW|I-mmLjuB2l^P@hpD8&asKi5arnj@7ta%N-ncSd=;fN_| zS=4aeqU*28>3F|+%iZ2OwkUZvzMEv3hSW#-#&xdmim$xYlF3Osg3{yaBD~f);&hz_ zcxw#C%jeZQ?|lkYF(zThfkMG-g19Z4ocgxUE^YwKYkfIQCB`Q z7gz8Mc@Y(6Xh+)Ds7nROcun+Swds!>kl3_`%B#LH$W>O`Zdi;Cj&dG0$#uy_G|c&O zLsE+u{xwMyp7~3-33rR>@z$@ILHY+bTDgTU!Q0KHMHO1MXyy7&55nzpkXMguZePV+ z@L_d#Ro;SANa{5|;`{#IxMj}yLA^(w!4vC7Y*Z-iho|?B)mm&;i5DW9y1BvK(Jt9@ znMY>&p_}iwGW!gTu*ten9~+}mQRO4mi1!1+@F&rVJ~Ch9GVrgAsiQ4R`XPg}o{{&@ zN8?S;`#+U;c0)n_Cu6E-1fj9g{jA4zHNv^iR9=`d3sAp|)`wa{La}0NWoCMD2G&c- zevNp}sIaxa&HIE{oVPP^O<}wXO1CXbcu+YAZEQI*(n~E>bHO0B-MSAbk&SIVFkmWC> zg!tX*TQ{)O9xUZDMJA`HE86INMbVQ9MIRy#e%mq61V0V9j$4MOA>NIxeJ)vx24cC^ znvW^z88|BLp@!N8Au?UM%c}lr44&ny_eAB3M27&Q#hUxgEn$L_Sswnp75 zCeJKF^KkEgiH=8XoY0xgJ2$+Vl8DrHmUvq`8{(-m4i$^=CR7l-y{{Ww+>^epkqvycVoO3oqC%o(c)*(2KSXm0lEHOnqc!RI!+W$N4% zBC+hx>19WTpmNjh1Nu!h$EinN$R#x`L~6S;c7z!Y#D|+3Z??2#;I?HJ+F{fBp-%k5 zxQ!F0;gDH1HPM&)q2Hp0W4*LTA}RF?G1+BiICif6q6u#@(H$j`;D>HoDAv-v*e9|s z3oG9{B6go7KxKzk1h}1@f!!B(s|a4C?{+2IC4KCA4#Lv5Q*HuX#P~&@1Em9{ro=;j8?Gt7{GkkkmW7jqi<<(3y%aZ;Cb< z<14!Z#y{q5LIRO}spE}Ve)#9LO{3NYl;GV4JMEE_K2mgC)==ZT93SZOW1KB=K(#9B znKujak-@qb_aCS7apYV1d80y3p!u<*(q~`ni#r$bMrV#cj)%+=_jX)sk9l+Dmky|^ zz+IHo?kvBpj~(2C<+a{I;d=JAG|8j=IM~og-G;(>c+RSD5Z#}gyNO2(kI6YqR)IcbT* zuFso$ZAg{~ZT?|#px|j3RzqT)S2MEkIN9`dpBr6}X3G=lVd06m=!u2)=#yg2xUdonAE%AZ-tK_TG>#i=b$uRQ z{?bkmCaoiqqYz6cdkmr$!#L`r(eI?ybJ` zDRdp;W+D^yW z=0#u74TOdze7`$0YAiC{oqF-^Pb*x=INuV5uFgX5WA%J4w+_TA-onCGoh4CO z?wo!Aa`mKKb(m*5&-hZnS~PPj_Q+aV}WOQxr&;u zrWK>53cDk7j|SmcZx6n+lv|H0Oe?z0IolhVbxPDaaefK*f8qJ!93w!MI=9Pbq_0FZ zx213G*kXt;{N8tD#is+v@TmT3yJ`>Ib9!Axzo`BA^n{-7R?eFE{hqe1&)iF~sH!*L zbyXh}@AUPB^2pK1bb`*^u?coK+*`yHyj_)ne9bp)~ou2j~14Vu8nmu*U5S;C{ zKg(^&A_%iJL=#qfpxvFwERb8u*<(d$0T!|=QKZcQ*V55Fp1k~ykh ze>85{FYUIcN$7^%1Z%!qA1n&t6&);Fz8+1vDK-6=Tqu5U`o@SN>$O;Od(Xz@CANq^ zsN0*%zccYwz2Jg=YA$Ff_RgMJu^jO)8g5IpF~K);dK$kTwHH0GEPi-m608a+zkXKv zW)+TFqJL|0>(koM zgH1+?6{A8?XRi~svAbX4Qs)g8ed>nc&GARf);+j|yAQptdUK>JURk9tPJQpB|7Yfc z&REcy3OYkUXC~;31f7YXGZ1v*flfHki3U2sKqnUHgaVyNkW3(e2bD}5&5TFwQbOM0#{*>>hJU`|4DX&lYe9Gf<{Jm%dro27n>nTr9`FYCAQ$C*Z@RWb2 zv^%BSDa}slbxNyKI-Sz!ggzGme@MLrNJrvO<8Zsc2ky{vfGr^ zrffE4v03(7#M0N4wWe$}WvMAUO<8HmMpG7A!aie4I#aTllFXD`rlc|@lPQS||nD5XRx z6-p^kCkgXO8BfY|QihW!TKC_hJeIm*XT9***Fly;+Z8>QJOy+&y@N~cj8jnHQz;Lj*+M(Hw2lTmt% z(qfbjqcj+$zbNZP*)Ga*QFe>6T9nPAEEdaNiCFrIvR0I>qAV3}8-45B0uA%B4Tp_~updj4=c z5^jfbI+V+y98U6AQO|iScSAWF%GFSghH^8Mlc8J;C^bSU5lV$n3gnZ7`JjvkWjZLsL75H8Xiz4DG8liD3(8nf zrh+mQl$oH61Z5&913`%gN;puWff5XqSfGRgB@!eAf(TLKfD#6jD4+xZB?c%VK#2fK z0MNLf#``qRr|~_F>uEes<9IH97i~f`Zm01&jnip-PUCVKkJC7u#@{sTrr|aXvuSuu z!)h8%(=eKZ&mxGQY1mA|Wf~^a@R)|hG#sX3Fb#ic)JvmX8s*aHmPWNSnx#=J8@-Cy z@RdfbG+L!mDveHQR7#^!8ih)tPeg-G8f?-alLnVGsHDLp4I&|U6hZX35z(NL28%RE zq`@H#3TZG%gFq7eLF`9kJ{s%M7>~wwG^V4m9F5@$8IkiXHg=;i8;#Xyj7DQK8k5mj zjK*LPdy&wKhFmn%q9GOyt!PL^Ln#_Ug^Rqo(20gjG*qG?5)F-LNJK*+8UkrbA|D#@ z&`5_yI5e`M5e77OBaM$n!8AD$RKR+Aie@ovBFFuuSrL<)* zTK-J!#w9Ty?SJ-d+|^fY!gA*+g>@qXu+^zGkw3#&Gd~&#}#hB>r^rrg}?oZd#5?!>3It` z9kQB+edC%+yT6P_#&=#tjgA|Jeg#?tuS@HXw>zxp)6f==y#!tMubDj^sV1%P6WOXo zpq@?sv&Ovhz~ON>c3yW%!0Xm5$cXzn9n~jYe3!g*Bx+S`+PC(&H@^6FiHBDF41C|x z`ptoq1SBnG7Gd~!G*Z^MGE8=}KOTR2MRiC~B2GCp^yd3-2`I#<_72L8M2`DLE7z|K z#>R0LR%?#W#9LG9zn+>s1I6sxF5A%TTNss`aiGYQG z48S75Qou4mz66Evxeiba*e1aa_^bd_0S-%W1U`=fFyJEKQU}yCQ4Dh#D(*?p0-yH* z4*^dAA0+q)pPvC=0N(+fpi5Y&0lEOx0o?%>|G)~iZ2|7$syb=)z5!4i-T@I%W)TUP z28ahF0A>IZ0ZD)b;+3w>_f}$!KkF}J6?I@SEBs*T{1zUctj1&&>Eawsfr2t3Uoni0 zrGOKvaw1X;%S1`8es$!Vf*Az+uyH*A7DZw9Q+kQIze<%qkSgfiL+wa?kwVtC82 z*bhoYttPpz8lY)JE8c!SL@SOA1)NqK>LJ}D zr2W+rDDXgn$tDfB!LvEuLHo#R#^J5W8@1Twuk6cPwMgd>&3LK_DchWMGW+vZiw=@) zPV(L0E@;nbWhLm4)2gXIceRN08YIym3AYt=%xPuKeIITs;I!BKhX_D=9i1%*{_oOj zHM>qhsjSMgcgp(dtjd#UvBkUl3%ZHh#IW}FA2D`le{%k*!ys`#B1MwtiYt;lSME?N zd4XHxFu^TexTBz5f-RlwC@#+P)8`b}`~lqA!ktNekYqd3i>p_V)Hm4vy`P}Hm1GaT zWWTl{fA7a1%54k6;hZ=Hw)k0JXw)r^knAMv$xfipQ#@<5RH zLg0ajQ3r_Q!^#BI(dz z@$KtQH^eU{am%X3?FwvB;oM+crSMaz@GXM~tWPbUK?K&L{DXQaBCvKo@ZcsZ zuwL@KjtH#ZJWL`2>rV!KiNM1-L?W=>I^qfuSf?sF5rMUmO4>gH>oNtsqqSE&Y@LA%jpW4E z?~)`fW3@cII_Qd{JDv1M_mZ0AejRE^$`yt20+Mn?3lHZmBfT92v$*X)Z-L@vCJ8uQ zb$qyh(^ZSn62TOD@v_7@kR8M_JpAw5BQ=@Zj*j6zL2f{P6ehG^>F~*d?XBuGW$* zYtT&RSI8@p^tMWW z2Ub2U%a0=RX=9UfL_VGQn3GTMmRtFo5Ax{`SJH`mI>vh%kx$#GB@_Adhp;3fpWb_W z5|K}z9^PA?l}`u6j^O0e8IJZuKHYu4B9Tuk>?|G2%BPoya`I`W*lhqSpMLpeEs;+j z5Bza~l}`uv;}iMxoxERLS^4yQV@^JO`P>c-Rz5wx&j%u(u37tNC@Y`-tlUiG(!P{j*{?`ElvRz9t>>j9BZcRF9|&dR4R`X35TBAP!*o(-gtvxk} zeA-T=ZOY205A5LN(-$2^3R(GdVVF9RPrtqW+K`n`FUk1E$*1EyK2K)l(}NV&6Zy1p z^H~K}K0R)VGLcV*9PzYZ<l~2zReQ0In)3ZFXht83iF~^F@kb(`4yfJ7XXVr1twcmVeZp^u6)T@Ub?Y~g zPd{qt`iYfKpR3$LkWunm!iG13!dp}M-y>1PWPkTIl$YbTxxk(d=e0q9~ z36W3#aJfX})34ifma+2b?>}0Ie7f>i&uUgat?IRa$ftMLcO&v?4fCZ$K7H_EE|E{a zU*Chsr+wTHyA%2J!knK(KCM6Lqc)5;+7={=!0hjh%BL3;ce}&Nr~UdLs9@#OZeB}>e7eWYOb=E*J<_O< z$fsp{N(@hr+WV7<=@L$7;eA+oPa1JY(25c^6AbulZbr!LF1T8RzBV6 zv75-J)0MyO{Q&Z5A63C0`7{Jzcz4}yB66j^gZd2WE2xj4zJdA#>IFao4Hh+2)Id?g zL=6%(MAQI@sNtaohZ-7cV5niC289|DYCx#np!R~=32GmxU7+@W+JQgMKYi}$^G=_0 z`h3&pnm*6;Ip&_;y?&oHf8HHu0lLB5_QZg6rfhSZag7}*X&NwZ1pfRel` znLm+?E!osoQUdYH@ep4`K44#F4<$BeY*{R;pTD|8c>X2-ukc*ZVfY9y;mjhU^lGc+ z3Ozx03nSbpX~<3h*u`U*Pcon|l?5fLJj28R_%O)cqzIB#z&_w0B8ZHvkEw!BY@w4Mco;uj~2s(v1t;PLg4|R zRvYB0J%Inw0qredGhml4)c1t%0UCM?GZvtx4<}#%2Lv1jd#4ux^umW%oun<@#xjsNX0R{jP0mlF%tbnKj zA+Qd>127$M7~pBkFimz0^B$mK4^#{w74Q|%04&ujfT|;qRe&VG6Tl%DcHag31el7P z;RFCR02x;<+^B|+3vf9s;c^U+0fGrGM}Qo(0xrdNKs;OuJ7q|(0ASyiY6zDg2_S&W z@Lmmi0xkgt)TuL!G<5$MfB{^BFWn%g19W{3U>qMB13fMZgkNg_0_b`p=WR?RTaAG20?0uZPXJg!H#eAoq8QKJD}|t)c83-L zUITPIfHMP}1N8TV6N4YP0wlo{+}I5C9w8^8;S5}61}-TB|Bo2}@B;V&h62I?BLEWt zQGggg5+E5c2QUwiIR$=Y0hR(*0M-E30@edc02P2;fGWUlKsDe5;3VJ_;0)j*pb>Bb z&;+;*xCeMH!3+3&33w&mxY0>n^b?A|0hDI!ECo+0fTbH*s?k6KBlt7{m;x*TP7*l7 zrz^k>fB-=f1jA=2APg`VFs1`o4stADk`#0gB_UZNawdQ!B4+{8{y{oyX8@J~I{xT6 zd^D7#2GJ3F-sh?Do~`DB?_t2L<&h%EPsuDLWx~ZQCPnsjGSZn)Tut~v=}ghTq#sKg zO7qkHTeWzByWl_A#OUn*wyX4)W&c*QY@&F9kK_o|wYBbv(*Kpdy!*aiS6&sagLg~} zb4}o7uK1g1RPMl15Ux>d;V%j8Np4EKh;1qR{uS}tbnp~}Wo*mhny&!K5sT+QS@M>w z4oM$5rZK0yooOsv(~)T`-VP5J{In-|L`4#&k-SWOE%&v4k%VZh#+YRq|67vBIx%_e zOK7Wthcv%L+$eXaie+ zQannBmbCMWlbb5Ill*f-?UI~y7553omHZSG{j*Jfr?<8PHsO-SwL`*GwM_k{sE zyv4rz-G>etaD=V@-Ml?Lqv>t!ORqrl8ihCRJv4^dpY3zUedEqGQ`r5*^JY(G>&N>( zeV7{gqW1HVgz-;YZq^=;)$Yt5Ctlduj@`ef*QdU0{Utp{U*pS}uv*0Q_F*~eh~h&w zEN316*ed2gsiV8xX1(g1H-*UOhTRZvp1%9^T|Xz@bXtJyDn`|;SXhj8Pb}GNTO*CF zhp*_$OL~j__Qn|`H4nnvP_6i2`;{P+WHIz3At~98f|^&-+d5RR9A)>qOWdVqB)8b$ zfnMMfWW*az3bgG`aoF7gn(TY4&_i~Qo{ z!oA_Zw^6{o;Q-8)o!G)p)>(Ys#o2&d!CkKMSz;^;@1OZ{g}pKC%OuC=pZxo74fo;x zzgZ(!&zJ1i)Ie@6Hg6=aSk4&^!M)Cg?*QeZc3v}qR}D!y_^^69R2(@>UQvnO{U-!m zF38&(ruCQPg2YF-hmxEjn+sCBCdP%kO+E2zNcruUhef`0C+_3pnB-ulMnnmlH}<(h1@9g@9X)ehVBH|^u+ zW{e!0#UVi}CZcGK8)_FagGBgZs-gNO(dU|Xt3L5fQ$;n)Z|?W6ne(;A=8m!Hd963X z?)?{%WR)qexMYc5_B^f1A7 z#lJs>ZCPIvK0aS#6VC?UHijZwKs3FY^}%|=k6)qZKIBG)tmV}CO@%3 zzKWrifd`^cOwg^8&{aV=ZuqLbGKCE8K566R_ZL%Y9L%lSqO}yU#i5B(olpM~I>_mf zY?&vwaM?1S4LGkfZq_I~X$MZbEoF;dX?gDyM=eBUSMx*H&JV`S^`&9#a{ggwfOD*A>-Hf<& zojqI^{JP{i6xUCvxg_fL)E))<&N=^5Y8E!EjC}P8285Hp%hL9eMEVz83JuRyq zZL1iBb@#^2aX;pS-Y(j{bDC}f>J&2hiPcAFac#Q{NHACdnFXUt( zwkw%7C-Z_P{;2(WNtg2cn)_G2S*?BSfG7K#Ue?dtz$!BiSR0vW3V+*|JAOiV$fSk(Ayq zCDB4rZj_OhRVqqSzqjk^_4)q(_#Vgizx(d!Jdfw=KJM9cDFfv#5xb{m^Q;dwUZGavZ66ty0a55R}|ny zHf6~fRd&G28A)tR?Vo>4Y)tj(smk9!bO;tI26+wKRRUjQQs^~(6M!jp%0#x;4DQQ$ z(ZTnb7vdEnd#l|C3n(+vnBCaI(!Y~2)ea4Ct_6^EjilaDj_sDLZgu{S6gk>CfP2#eZ+Io#63n%?!24Gyim zTA2KshuuFB^D^h)P6*w4u8%V~#k)8n9^ z^@^pnpd~aHbYj!k!44fAgW|5XRbf8P?J7GMcfr1pv;9zo52U3;6V}Drvo>x zZ0TZPwX<^V#)@_*SnqFE}B6* z_X;|n=b}*Zdutlk_ae%oQKjug4J&wV_07kAe{SGY%9?3J)jn6;$gR1-zmhW-4(7p2 zOu=cqf$Y#ySUU4rUlR!S>O1!G9hKV3+T)cKmIm;g$qStVw+_mg&pQ>^3&UYXRQ+`P zL3SW|O@LUt8(CaWcq2=<6TYLE6Ey9+&Cy`ng$4f5_1^aQ1c=!?Rikmp5+2e!!(n=j z9hP+avYxwbdK6@Pq3rQHn*gnGX9YugH= z%@y~|?MwhU=ZhVlOIpAfp{L0emE6$cZc4$o@*Hg7;w|Ag9bG>c|IAT%elIUzX%hXtCcaYi=LqrjGg-g#aceFt^zh+ee5(vQ&i<0K zUbzFjLsjVB2Sizzbqs0;Xs9zKX(7bXM7L5ZR{p>S;GFZXLFJ_@{Svg>Upk4@*aXsdB z#xG#UpgyEseG+80P8irTIS`AB9dBt9=FP0IKj6?hbI32?m8E%B4OT=qEI9Bb0)~>^ zM&lxuaHiFmZqAn*_T38Z_)&HN`|DTq_v0}$nDgpSF0Ld>A|@pjeyA`bEMG>H-wV$ zQSZTR9r)uX$P?anm$Laq&_SEJDA=xN(k0`^1p+fq67I0q={K=@dEF+B0|J_2(1MSJ zd%w;eXm?j7Yv-FdkfnO#V(tcONbC5TS=EL5-EF6LzO3#|tY~T9fA8ju;Fm_8b76L( zVB};9;U3G6q*^TE)ZNXDAM>}E9?}G9)d&1PYwAG><>dbO8>hgv4O)fal2))Yz|T<4 zf)f@Ur`Q}Cy@n-bskSrm?1TFkSkw63MZk7XA7b~jD8EnK+Un<ZlT6<(i)I3CJ;9j+v($~s%16CERtv%^4-G$jptcAbZQI6N(qj)jIH$dL`D_Jw zH8juH?DL(~qH&CQBX zD)oG^Ugky0?eE)vRA2UkjXMg)uBY;YFuc`kO`cXs=n(Q$Y|Fad4*zoS&fyLIH$>79 z$}jwqn5yjqU(Q=C8+59`Z;C8jtM6Q3=C)AA`t0YFF?A{B_S|A9FShR0fn&_zh}J%0 zd9@EFPT1EJ-2*bN;|BoSli@MH3#PEVJ#zk~^GT2_nemxYZVjKTKkJ1~_8MNj>lHGYtH%VSF@^{|U}9LoioxZR0KO%+S{WAAZm^UuPtqI=@#i>XrV($JCE zpv|tZPT7@L;!3udC!1dHK_a^%XeXR zFElvF%g8yb0wo&ox=RPDDXCfNsZ~iK@cj)|;jl@5Fj`z4z2={BE^8%1KhdNJLb)-XcxRCzir-zf=Ovw0F!{J6 zXXnZ9*slm(V*j`3;0&?;`?SgD$?Lv>;Fh`=ua1f>RAzUndlYsGh_GB2eOYP)Z69mL zW?W{+;Te9`?!M`E%%|)b-L(u$c>49Yz#OLp2)yh>><_P7$RPyy9VxT*A8#B3ig$As z8L!wuI*IX5WA~Fm{aGI7@kREB@e0#Xz#?}-Av0T8unP1a!ixuQl2&-bfE(E1) zbqIni)JG7;aEE91HRg0{2+?&DRvZ zu-ZCe1G@HU)y!ZM3J{B9p&Y$t1zqlZy7lbsBQT&@h+p5K4&}M?OWUSxp-Q{wsbkNd zP^u$#=nM^>fsc&bb{X=l2QA0n5F-G+H3P&5!1~F(+`%DHAoFVIyVKJLVC3$^8~ARi z1n@!%6Y7`#oN~9vUpvFJozMNB^<{xgN)^(% z$<}c5_G1;R zba!-A1XwM`6FcO8(UCjkKU-b$-!Iz;CU4aq^^S6c?xJmUTaGt_E%lgGPpA<*?ke-q zO&^1w4j*I~E^4BL+pz|Svm`+6FJ;O5{dvJo`tQUL#f5ewVu(T>XUg8@ZGIBiwk&v< z@p@2)7xH^F9TS26)ps$fgZ9wr>yme!83$CnwfgaJR~PoxwOF;_yA2GV^VT;#Dh=Lm zFd~-NzhrMErZ5t$qSPNuCV&Ucp%R0o9CK5@L*_YU~}k1z$CrF0(luC|(i$d*HnXEE9gb z`dB6%xajIuzwohz-MbR>8qaXTDl3x%r!Tf+p$#Q}P757?zI=@lc}KSa2@?lmi@xcl z5Mm4@Y+pgS)uRH?`S4UR=Ti?Hc3Rp@=%Q$gRdEqRvhCgBDCoxCzx=d9eqP!ZkdpQm5ed7*`UvIUt*;ff9 zqmM2vOj^Kfuc4VdIyuPn-rA07eJ=KnugkxgHv93lLj-N7>itUe^1+Rbr-ei=AB2CiU96vUWP*O3rv+_db})T= z)x1F&7u>laDY!A2g2_Eq(o`*Xh5PUCy7=~>BsjqEA2EsZA|4WxIFb)*K6&Y10PT-> z4mx;S{JltQC$ce==0;#YNVwPx; zD}crN^L?G^-w+%$@IaEMXSIr!oOMWvF_!)BX65e5|Z|epl9543s+> zo2Sbp3cg%_P8_rN^n^TSalPN;TfY1oz^i>svi7P2l(^R67(CezMtZtF;D421_fjIPVR?c1NTNdOW7e- z+ckel@ge5zEi4+G>kKKeTI+tX$$}ig7Gj=k1ErRjCyThOZK>;a0jNFRwK9M3C~S)< z7QxeVfNvJCGTrR}ot;-NNbll?<_VIzHq-YoQ2W6fA29ZW412#X&C|(%7BO4mp2yZI z3Netj>a)suXt)-*wTQHA`R)%5BQ4s`vX%frf$!&M>Fi(}yUC6yClOfIEibwBs||aZ zE%F2GJ_7dz$C##iNP=Rw-D~$iVAZT3G0!${;p?8(dK>ubbI69?J_LpJ%5f&PcMdVq_ngL;80oWm`0VhjEf>MKyu_d4+d(i%+fE{ELoQg)|58#eaX&oRrmoVr zfg1)LNM~Al-HFv*Z(I!J^@6`Q?5o{UydBtfdJ`8_N?7HHkwCnc|F=cE{Z;U5Sjz9? z0t2pi7(IC{1?<%B z5GPr#z0fCS3ftYDr`_Io7w8JVPw%8V0-cs+ZE2G0fNI)czj~lGOr6=C&SR5b-u^Z#$fQ_ZEsyHKiv54a9 zv5rkHFd}C2*9?0l(6`|baZ{#b^&T-4IkvUpeIi{ssIFf=c>Yu<)ZqRZ?p_;_Bm*{YrPE&<>pb9=MvQeDO-Gdtuv38U z&Y$(SDgt3$lCP9-Pc2AOn@iB0v4Mt@5+Nr7$=0erPDn`vUM}U((Ph{q8Z+KOP*Y8G57jWC3@~AM>0Mc`Q`l4{s z7@i#JZ!&&ygYv3O-pY@^2tH|K6v=Vm24`EBfvo8u zF_>DsAVUnM23+xCWmUfjJ|qZhy1fdAa;FZ-oO_T7Tz@TIiqvy~`j56h_(RVDrMjnN z^WvUjd)$p80?$x+zkSc{9-su2_S%LYr=E}45^U{EfXh4kiNRRo zM0a8^HZ?UQl#aCl)N>^=`VCQkciYx+qj>LGK>5@Dn}*X73bJ(O4R8y=yFxm{0}J=D z?a7us!J$VW&T`N%Q^`UWFhp%34imXK8xxbYP25*Lj_##^g?NsNj0eH+V7|`!Q|~T; zI>mb&Zqq-U|CRe9F2mU-cDQWwo3uVQ-_JeMvNikVI!UE zyQQ~xy#mY`bgitGhoK>r`x8L1v*Sh)0G|E zVa7)r4IM8|Sg=IrXeIa@%MM7nc=m%kj5Mg{b?{dKuj(?XqrtcZ)cf<6IIR?Ru422s zZxvWqI&A-3D-sGG-e~LBodv2}+>GtVTp1)lU% z7_P+y4QgK~wmCn>R%|<79kla?x!P=AADNWE>1jFQw$scGC1N;v*AAmu<uzpEFGHbMbj%&it<|7pH20M44hMMtsF&J$AO)NF zBz->1+{IqI-aip85(ztLWn|o>WWWMr1#w$SZ@QKkj{f4BWU~o<=5?IW#ht>D4HfpdIL+vZf(2GLkG11e z19y)R)78AcH@v&|oJ~hw)x_Df!VgT?cUm2L`~!FySUgZ@Z~#_L@jCydMZy!(C2pPe zQxtB!X2FVyKJ>H%hBb@Hd{Q?Dx3pHV)fN&V)NhmVD>lNd3eUR9DNBNPe!{~}Nf zsJE4vMgOl^Yn1-aPo&NW1fPM|HCb5y&m_aYo6Y8Dp1suA`#-Udggz4aNZ=!JkAyuE z^+?boFOPiu|2+IZ|Bk#n^6kj8|MTnk|GYZ#>Byrae~!F4^5w{rBR`I`IMU%rgCqTo zv^Ub-NOO~V8@Go@Ya^YFG&a)LNLwRajWjjV)5uCA8;vY9vd_pmBioEDGhvr;s$ND` z8QElHk&!({))?7hWQma-Mp78bU?hQ&{6*3i$zCLR*W@m~W_OX)MKTvjTqJLiv_-NO zN!ovM79uB$Tr6_1$h{)xid-vltW>v(Q{}1-L{1gCROC>RJ4MbExl-gv2{%erqezJ& z6^axnQlCh9BGri$C&q_E@{6>8N)xF}q%e`XM9LDWN~9>MYLYOM$Veg+i3}t%kH|P8 z(})aX0`5whMPw9_Nkj$_nL}g@ktsxmF#MkwL_!dWKqLT>_(Q@Ei9RIw{wMa3&_f~* z2|Oh3kg!9d4hcHs<&cj<9uE08)6tR7g)DD}`(nvQWrAA?t)}6S7Q% zUBan)30Wm%laNJ1_6S)cWQ&j`LUssAAtZy41VZu$NgpJ8kmOmDJNTO2K~e|F93*j& zyg||i$r>bS{>d4LoD6a?$iX1@f}9I-Ey%G@-3m^XE2jW*D#)cEhl1P*awf=?AV)&D z5vm$NN(8A8q(G4RK*|HD4x~6DcjJjS*3<@48c1azg@M!sQWi*6AVon{6HIHLMw~~t0SI{I6C6z zh?^r`jyO5u;|Pl*9F8zJ!ruscBixNJH;K3LegDJS2xlXVjqo+X)(BT4OpWj~qSA;) zBMOb^GosFjHY3VR&}E#8ml0J)G#OE3M2`_QMzk1FVnl}#6h<%@L0|-b5%fi{7eU@N zxQnmRT?BOz%ta6v!CM4v5v)a!_8**uh{+-rix@0ouZX!K)`}P_m964bxM~LxQ$;Kl zF;v7(5i>=s6fshQjZ)DlLZS$TA_R)iCqkYGbt1$GYVi-=YiJW8O@uNL!bIp2AxnfR z5u&7`NrFrwB8f;OB9Pku$Ri?-h%_R?D2UULWDyZXL=q7}MC1?=LqrM@A?*AI1`!ZM zAP@mS1pW~4L!b`fXKuiX)7{p)@dqKLPaA8i69h$5C}pa2zemXfe?rH9=xS-4Q(K#flvlQ z7zkY;WPwlxLKIXqL68YVBoK)}1Okx%UlBZ$Q`e^w+n2+Ou&*=J0uGkr^O zqx0CfR@&Mg92|V!`pHc@bvrQs5V~b_$q-(2QS^QJeHe2qQ#izX;x|?{uw{5{QI3uB z^Jbn5>agW6jr&4{=Fsw&>E3%a6Ijc+lQ%i;Fads{_rxL{v+b>~JbtQysOnF9c0aR$ zv@(**nCyFOe}L2Ct7vwxVQ(?9XeTwGu_HAV*8mE4e$jS|I>KE{<22KY(^$TMe5ueO z0U#>qLTut`GBf$!FTWG4oHNL$PMJbXes!$y^JnaUmCBQ_WO2ZBOpVyo^N2s}KY8km zX~3gXc3FB}a3<}}*YgoyF+;g;o$7D4gUvUmiA_E%{lR_Kz8WwvAhmo8ZsZHU8SS~v zH})Mnd|o?OoO-RQt!|Q7^>a9Y>+EG{4SDGtkJXga!cp6XcF=+DQl!3^X-MfWMGNFYiiJOHwkB5V8PiMaYu8BWQTHkRzkW^L%zm#Cxj6?ip80cwLHZ_j5s@za z+TT(CLTv5MZ?4G!`ug_&eP%;NYQ<+#Of;?OnVU&i>L>G z9~F^bjNPRE9rfVv+Rq{m{_+kIXb+@pWT6`%{*u_0ltM>iPeyzy9-qFNicSh?$Ibz+qCC>G*2B47kVl+?b1t6+WyHJkY#J0D5yz zuQ&$WqgdpV7ltdFnTZR-efP-#8~z12`i`tOlq2ielpe<=>F(3 zx;sO8@AP(KA3X>7AbpWIaa^)Oo;VhHto&{r-74kA)5k+TJ)f~{HeD!^A!Q z0ZUk6!{rK_6)`ZT^if>}f4mN!{PE3tvsyQ1#34Mz`TmYMXG|~7Dc`CFYJK@KH70yO;>uag z*pFHu`5{-Ysze>u^~{nu(%cD%Bh7$rm3)B9zX!OQk^$Z>kpXJ{)sOzGZ&vwBe^x>W zex>U0WseMYQ0()*f9=S#x4SlR)J; z55#NR2`x^5w-@o^6$|PpHgp>`e&9Rz66-U6yIr zu1r6n*+g8KHvLWpsI4UfWc!l!ZXd{cIOzX?dTkq?jl`MiNpJE62O7v1upJ@;_;Qm0q&JZb-mD`VOx>AR(=IZC^jB94 zHs9I;K3EzPXRd3-BZ)KDrfh%QT-})A%HE3d9?CN2>#}I+E~N;^UODJ1gfC!O`@Z{E zkM07v#GlXdJ|Q&FZTau6YT7T@fRg%{&W@K9<0J9Joox1pdc>XVw^d|=z{`$A1Gi(d zsT7u6K*urrPM5uKIg@i1bmq<+2?9W0AjxUzO> zoSsRXxHKMinG7%yG(`ju$sy~_>Bforxc424|Mg9QpQmT;*1dB9@2+2#vp<&u7Pq_a zzBxuc?)vbkI=*(QJYCO*I8~lrOum32hkSvr5gC9^kqmIDnQUONlWgF?Yj{zAN)sl? zciVNp5(UniuMwxt{mLbYQ|A}Czlsd%Z-#WrM!%FV&;o`I|8q{O%FuZozfsikOUe;n z3|C^0Py>E3eH-a(SYSkjYrwdCMY&;L&U9goy!Ttt@OCxWP_#6%tV6^ zwM9KKEywamI5qqr0zAHzN;F_gBO7p@W8~jzNj+(_YftQ#R!g8-niA^~w;w2p z-4Po3!3KVMYZ1rV6<+1^{_N)gm=*Z|I@TuQ0Yw^QfNi_T07=7Sy|V;a??L_VkM@2J z1t@oQ!t3}WS$@+wx zL82b-`_THv;=cwkno%zKG144{%DOx=R)O$#PvVVwA4}LOpm&F(`xxxoyyI2>_4Uvz z@*;6<{@lSl;@UjZTQb09;|U_bfHYa}@QJK{TN}aOzvl(G#P`iHU)C9%HvX7MnB$Y&_bj``Z@2g=NoQIr2*1Y?1CY@gt5A~h*TO;zowxeTN3Ws%}R3X`5 zMwp4(U@PEez&EhWEr8QZqeYy%Y(TC4-`nX`v>nb|S*ZeFLt$UAs`TonR`}iCq$S!# z4!RZ_irKhagev_F(^v62QOM08OayR=iXsBU8juZog~$fr>}1oCd+a)%=H4S_lc&Jz zn+cP_dEL~*eg9ebeAfgQP0bWvN;yHZ6`e?nFfNeF8BRPvOv;0JfJr;kruEjrtLi4P{FMea}y zrvXAoScQeo7(zPcZ~NjNe4%I_K0!%bwFZ1|q`~PaMlkl^gmitdAh=)u!D4Y?f$}+X zd}NsMFi1*2BYeFa6^|36YubjW{fG=K#_Ut;S0`T~S29rZV&`(KhV;AEb z_U3BeAM+Cu@WtNX`mOJlun&B%Xsgcq!y%mS_K)A1G~jmll=!ey4EQ{umGrQf4PFcB zF;*0(^}l8$9JqBpP@#pRUa~{sH(YmZ=5`2LUswQbUG@i@@e62OM-(2FwSwy4fxn zz+wIxM!$R^U|&u?0Nx-UARs~p=)Fe<=-5TnJ=8%h)mlh-+2w-}W*eirQWjkYz#wWq<+ zvDwfegZ*IFl2D)8auAfo(qE=U^Ml+gYcd`R~ z6olSI=lp@KJw40q`iJv??V=(xejH)0^smVCe_rZnOKZqj4` z_7*aLN+C`*u((e)U^X->dwy30?svO8z+|ot4vPp(MVX61g%B=n^Q#{*S7*+q)pO$T z&-JMX4t?|3ZXwTx^A6#V|4Jxc82ld7`^NHzCMOZNGOpA8@@|=8e^R8*@4Fd@Eq9t< zkY5KUDNQj_N-tFttq^HZqp7><&<{VweB_S zWXdl!jP{2osJGG1VCAs4Lx|?+J!L4JaDGRnR0g#Du%w%r%mTf{S&0BgMtF$;$E?Zv z5)HC`Fo2b-2m1o}wS+#6PDBDcVck!2$(s#8V5Z@I<2(SRmdBpY_gO$WqoG6G%c4N- z4*3FoUT5M3iPmI*{s}U`TYj>>;TKuYaD=1$g`79=Y7F@>BdQMOc3v_&%V`K>Up=aP zTRMd$jF{yq7VHGs0nAICr2{y2BH8xjVZA2U%%yjL+nWpYV0*O#^zH-0 z;Oe2ltI6O3qim<6&U%>B5nMRNRt^+$6rTV06+r8AWPNt60a34Y%4z~^->CEAHfH=G zFb3Q;x;KB}j9|x`sg+$kcG++G09qc@0Jwz>RH-8yytO48NEeAdX6LR4-)X80cdTlG zn=4EQ76rKBTamUa3xYEk_UC8^^EMIasl;}5!`tszS8>Ayrcc)JQHAwPpED0!KBZYF zdYJkfm~Bqe;bX|MRLKw5riDRhJgh^*EHVE(HP39Skn`LpK9>W?v)GAnKMm z(V*O|OYr>x>aXKe=3R^$g78$rp@1%}OE6;}lP6n_FVs)onW?`k9v=D3V=hen=5uhY zApd0jDJnevTU+SK1Z+?2CIYDDIS>IHc9HcpXUKZF35NGxcbb8$vpViM>K6)1PhI!7 zRY(UHG((y^_UM97Z`1gE#0(&ZqGe#Evn2S&A3?mJ*sY0pf&WD^Ks7fn5kPXDtZ&gL z>uGNNEX89tgVQ@xZ2C89gY{o$-xke^zzf^?F2j@wEY5Dw(==Kb?j6U6GW8FRVD590 z?;}$};m4x`T=&4EFvIZn(T;2}GH+D&K!%hNs-`$x3D% z2M!TVwXtscP(oLVs8`wjl&DXu>^`m0_ykyATktBv4FPNKscpjRhvDZv`>wZi2EoV+ zJ2w?;UxXCg%+>4E3+f-C0>v3zgwx>`4U3CrHx?;hkCOqSlBJ0N4V`3zZdbCw3-QeR z&BZf-`+}U+p57SnA%7~>e|-~}lnK+b7d8Pr#~vrIT|mrWdHDFH(Kg^JEfkFx!i|t>+qm7rStsd?7oOtNmg5x-S~s zsu2!PNN0oMxZ7&GU_K6>>2$o%m(zvMV#o$Hd@Mu*nDCo<@%0M0(LFaLryB-(y!lpb z;#fP*1j&wX)lGqqS2v8ESa}ZH4dikMw^&1qKeGi~Mi=3bOs!Iwt2j&vB?G+4kRSrY zx|8)S(PX{D!4=wwMx0+KFUMG~^<5z_yQi-(Q9TI;zpKZW3V48Ft#1p=v`3(|$-34M zFLBT{M!vvymV7}v+eRXQqca&`-4I!SnOTFVKRft1vHMRI_-3B)m0MN^Q1mGbf9$xS zlTCa|z>O)){EB&>5^g5~wR*m^Zws2kM01`T2pa+LyKf}aG2RBFioExml@4Qxf;?gy ztyKYAx|iBt230V4d;I018Xc4no8@{Z69QE7UN0`{7{ZW%XGHxbwqr#7{^SfilfNZ1W(p#D3w;ub z%s|GEx|BC_Ch&({Vi~jGb|AS#K0u>of_T7{gg-dvT7W;zWPqc_szigS*JOjpZ<1#g z&H) zh@jy&e&}+4TD0j)7)agPR(E#4G$?A>8_*(X2CU9BxJH2W&^sNkePsUh7+}?$KHy?z z0O@2Ri3Th1DAC}c5&vGZ{(IoG%lM4+$UeY$$zESY^%?v*h3#!j4}mRbz8z;|Duad@ z68xus>p?yi~87 zmYg^Nwy*vtg~!%`$eu9AWlu{GBeqvF{k#>NS`~~uIwcJf`AvxzNX0f1FQ~Xn24K8O z2H2xIOw`*Ak@ZCkXJ(sRj{qYNdY_zC9dJviJeOlu3SzE zYvCpJS;|tk<1)4}6>6X8t8pBnh3yKL8S?QnfI+T0&R`2G@aCLai2rm5c%Jwo-}oL#pbG}?K9sGl0`ChAWx8s^>1dIwfYWml3o4}+5X>|@3FH@N%x zRgK?uR5$ZebD!mD&JI`*VZ-9AXaTo2je57e%!e(Dbl)T7H$sUNGC+=W5fR|mZL)#= z8=?VxHtGIxPK#dWCVOlbbN6YmuW>Rpeor6RQ&45HOV=3`uw5fgv1*t;X7b)G1%{o- z2RvL|djQT3L-vsYx+}>5SG&mu?JZ;jRz_#tr^ z+Wwg0?ozUiz3~7rcbV8+*!7d5Rh2z(SoHz4|ME7xz;m2(vRt}I?!pVOK{r&DzkVJw zIy%<|ZdZT>q3r_+cmxM@aV?5)aIXWC=G6ue?1A%xWCP8={X~PQT9YSU0pdDVX7W6R z%mrYcZ${?(UFD9Hme$a~ot1Edd1BE^hriHAFrWAKN-!*lKG)_j+X`7JMzMr$a(*5JPP#jo~_Kxg3#eS60FMv!x;#PE^OMx6~oIuAD-j{{{A zSHeEIB*FaS6(#+f#lhCI!J}gjf2c~b!W*wu?W^*`faq2A<^IctknKK2mp>F)scm^!<64@LoYYaUm z$_IyDCBiTV*}*I41VM5>KM^1;;sg=koC4WE=L%Uba)iU0?%f4Y`!@E6mwX&>U;g#! zNNp~dxN$$RGv5w0t>oACQt$c3WORHo`Y8u~zb79Mk3Z}tULes)2B^&;15}x>5Dhlp zCmR%+j|O=d+5v?U@fAg1Ll7YIgyD@R4}1`C<*8ETOG?n_3eC<~MmV5rMQ0ekLIxz`hEj&XQZQ;PdGxk`~JL0Dtz|LUQl~JJgcp%dMS` z22U4^AND2~z}KUnhz1PH{6vHCm93$EHGM$P$1@53@c`#*O>3tYhhfGWo5aUAk3-qZ zZhJf|uR+Ug=e#uPsXl*!XY^fE8g!osj^Hd?2ii7Q69EL7^@sqGr-^zTrmV9i8hq2w z7F8+!1Y!(o_|Ll~0QbVOX#JTskYmgE=ug4{Fn7o@Y?GKHbUP9_X6`QwX6MNloGP*; z9`LuD48Y__2FQIt)>{OS^#*+Tb_S0Uz*09uj@OJK2yb^0`pSfFgMr%Dk1;w=Q8ac; zJ+gVQ5#&$z^?m54fy=vD8cWr(pmw$G%bd1#kon_ZC;!qC;CBJuyZx0FxSg-zO#KuB zj_SO~J{HUi%Xp^hxZr7Ub2}@8?pZx(wvm;n@BGh{s29x8jcc3y3KnCTjk$hC0L2+P zAqL!@rXzkxjGp~+D%=!Z+?%P^57l+gS5uBS!%e?KmBxdLslZ}aVagI4d{RK;ycU2h zqn-%RJwa_itsk5q>jUZ6JyhGXNoSL<-mm-S1;EojNL1nX8?ZCS(gT(p0yn4BI@WGN zd&%g*7qT+Id$xuXUwgrohvW+?(#Zh)hsXdTC&>n`*U1JWhj!E24c39}g?(BvhfRU~ zJLyZah1Bgg1~Q!Qou(9Cf_lB+mM>e zzW;&S2RAKLWYaLwq2{WWlei{S}=q z64q%fjJJyM&IVTLg1%1mxe)vKNASusF)%B#m3RP868V7M1u}rK1Q{T0IT)IYb=YIQK1oOwq{DX=b@4*E`cpvlX$T(e4) zd#`&5Mo{O208xMWsXS5Na;97P(e(xpoFf|PZQu#;fbAPHlb>FM_mmHT=Vzngg|I@# zYdi9x+Q%pf2PtdF0kUR<^phZ?&(z^!7IE-5qMHctVXTq}kfgt!sQ>9t)`u2zb`6xD z1&ec4wt>c{0Ao(n58stSaAn)Kwe){URDg#{Ltk8a(;RdG{+;rQ#wn6sG=fs(OeL?-cz2PIRxDrs0F{hK`umzX% zPEGBR;eodl1e|tDo&cpw#!rB;AwZ$LY>TA|M z=QRosvHaavbRrHqORMR(U`;SQn?{?~!5*5y-65Y;(%{X(v3)v$9Dph60ui93#wdIQ zZ&d;UoiSfeEKYiY(%Dd}mtP6~p zF{%o+Q2^HKEr`w5#(kd2stm($s@Zu%6s zeY@!5UzJTj?r!*CW8on{QA!!VEhG#Bd-OY6)}@00){-Os)IS{;!xp0c#NT+LzMj5c zwn+6WnEYLVXU9y(fdAx{?@3%-g^yp+yccpk4R0%K%>GjO9C|1vp7puo39o3oSbeN1 zgeJNL^sIx+lyBc~BETQ#BqD$!4_R+FN!A-Z)=AA7XVFpRP#wDWtN_seI=qAK!Wj5) zwi@EO0ie`C<-+1`e<-^J?>_YDg)E>;K1RIY%_RAP28;}#Z9)c!mi8kWMBODD4DJ-p ziZ`Hs{pvSAajnVEzE@q%fOHOPkpmIl{=U|TSc+X)Iz2jj)^xb z3>|apqaJjm#zD4Yod%b0-+)3_USGO<)tk|fLCo~gTVJ>1I-C|TF^jS zCnH~pc}ce!oMH*#X)$5fId`a7{D5jYcss@#_@gKdHeKGE<)lrf)C4|!Bi2T;1qJ)K6Cp5%qODQa0@I!b`z`K$X!hc7MPqSST)- zRSy&*?r)+OjE7HpudwzUPKK#M=^u)!scU(F(U@yx5)2#~eZ!|B3qbyJB7mi1I}zag z2w5+GiL4K0D^%+h?S{JB>6Wx2l0e4y5`it$)eSD|nWEpvdV|9b!#e`-$NOQ+gRG{x zMP+b4=@s#Ugak?A1v*?;i2!5!$N-)4SwsUpcCtY!&HZ3mCrd#4;$^qzgcaDTAh=#O z{2P{8J6&|c{x@ZlbEf^_N)cd8f9>p@Z=0aTJ?2ju9cDoMnPKeHW?6VXaO5a1G3E@< z9Q&)mKD-l%s@VE>hUkMmj2anOy#N%|FpJ%@D;n6Y1U;&3FoI1DG(>|WQ(dBgxc7+- zTA5G4xz=W8a5V^smL6KB3w#a(<8tTU*`9=#Licn=c-BLWtG)xJ3(j!!)qT|tlw`P3 z*FWezs~G5dIDj8r3$Q6|g$Q7Fm~4wm|F0Pn>i@{lO6T zRj=VATu@ng`N1y7NNCYvc%4;R0BrZEKAdzI15%ZGUJ94R;H}y8=EHpn;NjO@mUePn2u)BkLom zR;H_c5A%kjeOq>#z--7-*fU@GasyC&v`7RfXxAVEG?Vqx7G!;*`uCS&e_ z${dg!dD8w#*#P*)du%I9Zveo>je2}?N`2wt^?UV~a7EBRd4+gEf~!990*yUn0HwQR z0IiE;gCIaQ==5xjQwX?B{SVN##O+PCfO&&(Y{95B5Sa9de)F3a9`=Cd4+l(up4kTa zKkwOKaK?Y1+an6GiYoS`;3m25Y7; zzr~J(_ZKT}$UA`lI>z$ew^|Zec5J8Z6mdnl#S-~E+mvA0K8RIqQyQo|iX`c@C>e{B9m_*^>THc7Yyg zB)H2^B^?93&Y8oRtsLmd1I@EpTZ7@+_5bYDGWpOo{qlaBZ{diMwCp({Cj_=u740#n zj-#;V&qARN0%#&)J#OtP1`^3H4~UxRpuZp0|M3{~1=!<)Gv>URZ7PG{!F>GiM~@&V z-g|Mwd)qj&HT(Hq53V8KzTy2W4GrjG=c>U&TNk7#n0Vb{BN~akes}hn{2{pa<{lof z#_$;rNPH8B^DMChf9SJPvqjrSXtzpbudIxNXsb=>27Bx!e6I24t32)r_T6eDMMs=a zP3^Crl)FXHdA1B+aCz4(zCdJw2$=s#1jMEN6TE~2odmz}vaM`SdoBq5rmIt#HG|`q zDlR7~NWi`Gln(Z#%Oxg;Y`|>BYF@&h zkJ5f^mgpJrpoz$~+$B06wESoAxu3ZhdZH4}<|P*cOqYx=E7@xyGq+d~0SaHGwu$ow zZ%yTu8J7VY`sx)18691viGR07qXzM{)Xk0 zrJxGLXM6br3k<}t-~mH@^rim-@=XarTnHhs(mcWA?nXoMT<)NyWzPWq!ZQNf%_czE zXkm*5X8>&FzF}D};DwmB)2}+kNxhzzBzxpH1pdUzBEiM-fcP#ueE57>QgRIBHj*RfoE8dxFeG)IlKX+ zLnbE}r!|2pXiTnxyBW#Pq`qvWF+d5ruIDswVq4H?E=*=Dw2(5@ zk@#h72QJXInZgAkDML%NR=Y{`aZUSs>uv#UBz5@Tp9jgC27aY{l%IS;D?dB+GdIw? z*QMiMbHfmGRI}Rl?RSvO{1GY_{=F!^ZWIsjvPj1R%$y0nU6U8_2U7qi-aIsfDov* zsptPH2_#)MF~(BwOaQ6p5D#Of3F-66XQ}Jo8=!Nkbb&YVF*=qMPxl#VP^*)vi$&E15KiaN&FmINVa!GKMMZunXYx9;R~RU++Tjhx$&?3t0FAy~@+?nD+G;(CBhqC{`-~%C|kfTAZVt1GE&(nHO|G*5~$h z40OprdU*i8z`uhFU$FLz2oMu7#{!Dp6wGQX0$yk6qH*avI8RfRi}QQFIA?7c z83seA^vzI@P`DN_DeL-t97QE|aFU>3AM8n!Aq)@}!HNN8+n|Uu}${k?^gC zhyu#PD5EP{c))wMIoW>!2{MErua6M)E4TkSd2I&BR?}SjX5-6#)X|R*y!Uu@74dVm0z90hbV+%yUU^pT0VkQLp_O$CsA1(sM zwwv#6o7=+LqivtVGnL`6#O-aC>lVRK(eLV3 zM#?+m>W@*M2`EQbNu5&P%jJ;GOpJH@$w=g)JrwrWNEVGdWzxkk#Q=ucYdQy~X`|$( zYFu!CAJxNuf}2WJ+g5XyL3`)tkLwCCP_=R3xQ6vA>c8%IK%+ecG48|8Y=#U`p7_ik z(=Yc$OR6_-JhDtirNI`aDzAUTh zQ2JEGthZqT(p>7t@3jTNxfisrDz14WYUhlc=y#H^P9he#@(kk(-pLUGym>@G^d!M^ zMicyGmX#5Mek0rouo)0Cc7)Y0f3jw;8G^~3WpjoQ7NoG+CUr~#3xz_Z{^xuf+Yq^G zyZH!n3vkTv4>W`t!hKcgr>u5e=mXdv_%HnoI#V8*&%=8UIqHDnBL#KzYCE0%vV0b# zUkG`zNUMn&1PH$1;vt-OQ0+MU&}tWH(9CW5W^)!u$5=DcBQEleNBfJ5wQi#e_xTe1 zv6waVv@!T#LUA}s8JqHcbGQ&~Hn#Uy7HmP7g%lo;B;tSv1e_-XH8q6b?|F`H;~`!W zYhM4J$-GiX`n~5>r42QSHJ4GYk2MySxOQNMELV}@8~bT*A8`m4AO`rS5CekS_!2OG zO2Eh{5%7nX6BmfQCIlJoa@i55CtxRiZ^3tGPq_K?+2vHGK;ULkGH~9>h@AgaXspIJ zz|=h#wRT?-*e-pr!=apxG{$^7QAr~hUIyN^{lv&ber_DO{XkY2(r!LpdH&H?6umw> zcQDKmDKwd2-omLbD5nwi^fu`w(DqjcaDhsv5iVdZZ!2(NK217ye4sSZxd~9>t=ynl zGxBzcJ;x5dZb$WJYd=ga%9F8OW%TNUcPX!QvfO=k+;sG!B5&+Rgk#-=X_XoDY`3kAb%Gd zH!#`}3yzNx3ueQJfTg2EfM`9zSJo4Ju46vaF1v%IzpO09CykOJD|#`H7D>UMug8uE z^zTA;SY>)HtFbOb^GV(IlIB5Ya_>u9efI^VH#K9^wGZ8oPdZpq{~vPL_m)+yf*<_4 zxr#?jWYFnx`4GJgN%SkHC8Tli5`3F~mXNyjB;xNV!}%_{PMoK8^E1{Ss)RE?&Id>I zhr;e7AIAel?!%{}bW?>kaR{qPZR{=HosC#aB`Xqld!d>RD(RGak?1c|-N(dGSx5|w z#RICJzQ6;%v46$|3>k!AKvXCuJ+c{P*qYf0F2q9Pvk(SdDlypfu725_?+ad;H4*Qc z9FfDiy_H|D9Ne|vg%1d`RQxvpL%W!t;{pHCNZ|o4{E}P$2^@n7!R}~%zl-CPJ0scT ztIX{V@L|op`>3=6ux31|zlkWw=qz&G>4TDr z^u)KNMj-|F&P38T3E+{R#{&+e%;5oDn-6hb$BDvY6hX}UJCwf_jKVShs>;~$IH0stZdhjfJ4lGRU^i%Mr*Ye!Lk!t`4OuFOOm@$2m}}hTK%pUU%4+{W*O32`4(h zBHG`ZSORw+zh&qDt^m20zR0bISDn$1i8hIj~vvV+lVT3oSckk6bbreh|Zg2+_5 zeB5(=$Xj3eeQBLPTAtGrN#BS^hcCM}TrD{ahb%wi0Z(6>-~j`YgkW-q7|v_af1#p& zumUlsXR5Xjq(CxT?jOG0!w@UuStn%}42$mOuVSxyqUV-Mu?O3wp=44HA22(J9mW?} z?IQyI3nKz_%m{(LB_T*G8t}aIrxsLyp5-bLae)cep_@&oj9}HH{}>H32jYvGI$RVV z4y#rB?&OxyBjd;6-NhkIkPvw20oxBF2IHhnOSei<0hXui zXdZqX`DoDpX{<J$ z{d&H9@P?T7EUM_x_U#F`_xMo=^yCL_1AAcWc#7S z&0h5~n|^e0b^E7_ftKi%ZR*ysviq=UR&ZZa8qga9g6GL6_+{oe?Ilq(_7=L&S?$e<=2)>Vkp_Pz?8 z#*I4;t}6l6`>t+5NiO8~!F<=&k6KWA`p4btyZO+Z_s(}+9)>6;U+7@uB?gd;h~I7L z8v#G>$kcPZr9n>zRL^76FKpm$_1gFFbVamwN!0Dil@NHhu43mGXMof`u;YT)dG@%V z`YiXTjD02LJaAz9?Qao4{mqi~ieN8zGmf6uq)9^PZBX<_r6kazHF^IP^F?TeJ#lMt z6#8}WF1t&C67=XN;sM$gF?ax-nH7c$c4`v>xhA!Y6!Qu6DS-2aI&~tf8o7qKaMYlo zJpFC9Ie}1}Ef`>Y-x)n!k7n>PP=v>_r1Sp<6xVFv17=Xt{hV#>$R$au66)>zz;4I>7JyLuW3zrFQ|E?KjjX{Y{0R=o!N;{ zF&H0bWh)G7F;)#SE%9g%zYUex4H47{?o`bF9_X%Z(`ElR%4n^4xnS|e70~E)Pv6zik3`RGd@f2-QC@>FWsV0d<#^)(4&1^x&+~)eWfrcSu-@?)&Y$PG z+DsJ(K|ey-uQyb{+{@(2%Rd9aQ%yIK-0gx!2WplQ-pB(s@9%~$xT4gIFF5N<1aPhs z0Y<}NxS(>F5Y*-ysop+z6O@FyF0xO#fRo}W_gZ&Ncr>rM*=4^MRn%T{aeCqbW6Iy< zCC9nY`h{oyKU<67VYX~&d5Idtv2Pj(yO$!qo#fh4G6_RutzW8slmmxxmZxb~T}Wur zl%y8kbQQ3T7M5E_PoTKB4Y&a8j^TnCD&J@R*c4zdBgL5Z$HJG^Afs=N(`dwXhh4K~ zGFsMN63STXKxZY6NDeh_HwdEBXNflZkxuE5dC`t56E$DrUYOlR2EJMQsW6h zy06LGK=cVTbT1k2SGW#eIcL*J7rUUqF|Fgpd=LzuI1+Z6`5an~6C3_6EeFLhEBJu- z62|xdLuVo&%9{vSyh897A_-p5`4zcYrW$_P{Z9Le<+;Lc+4vFCPDAK_Dm-@m*j^;# z^1ybC|0uc9MlU?Lfdpx4VjQO?(JspYuFG~98?% zEFPd9Oz;y_w{iXst;w|zS89@>%xdeb=M5P7$;5<-&p<+|E#KnPFi7I|>Z9B%M@JO3 zFiRI%8Bho#77SR?;S26_5CQH5M1Zx#PF(QcLqeeWK}v&qv<+_EDhQ8d@&J?XGa+r| z_VBkdytZtV6K!Ldo>CXj2iyG?fqguj(8M4q@?d@#?(?6&MmpsJwD;txW2xVxtt;5_ z>u;-W=x5hVWla|ut;X>N)w`M?bxD;$+a1|(M3_diiCzn}T$8{BE5U?d?DyNPWzoAy za?^_$?^+9?LTt{hUT`ltz`K*h^86h%N#bd=wV)z5bb7v+{}zvgwLLWC?DNql?{h=n zIO*W?TRA+y38Q@B^XCsi~jaTEM+>&rCniz19ObqBeO$6-TBm(lL2wqK;;Fp>dcUc_$1X#ZC zyMur31j2_s`^E!A!ocvsRS%|aHdJ_6&8oq_57=b8wrY#$gX8->y1S>DN%Bvbx#-ic z0lbfR%~HiiethT5N&n0^q?sias9e5+Jezuco6hb&oiZQ`zw~{{CE9|De7>DyEtMvR2^}2i+AKq# znQWhzxFa<2fLHPH7g?m{O9T|F69Ik_1fNq(@LGQ~_VZsnO{zOddt17`9Zqk$|5f}c zN8+}qYCT7)1X2Wp?m`n*gnTa~Y<%SA0}B&k0d|L2us4bbDA6MV80F640+&!iF!bNb z%_|1nq)g?np?mJ9L5$LmTaNM?;KX2twmdwDF5d0x?8Z#scjg7|Hx)eS(D2O%A);Qe zP~t*6m!JqqN~-dAv9m#tJ|3Ov+@pjT{7%oZh0DUP$9)<~yEIVJnEdI_x2Yml!qbf zo1!wycLLE-=YY$`luJ9Bho|0H4IYDOHcff#E+ycdavC16oNxjcm<91TL11IVdj3%fGGK2zxy?8jn*4t`+f@IDc9`AnYP_oii39fdfRQh6@c}zl zh=9G5L_o}+VVwUcPw+$MgE2m7emy9Eyc)Z2r#o!d?fJ7;LkqIh)l00l@F40u7fjFK z9B5_)i`~`Piw?OJ+g+#2f#hQMB=eUVaGaWYEpI#vIs4w8>a);9Lfd>zq7Q|kLRpC_ zBg$t!Wi3VWuRRZigdF8#YN7^c<$Mv&*KvE|{NF8@lu2z-AB>nBuio)J7Ro&Xj)fd} zjkK3CY>`qjnrPp6-YDLPn3uR0sRu5i`xrxct#>3^o|y8T26-@<{Y?5`o}^I7c^kK=&P={@bo#5%ZGA@VMl@?Uok+ZSQ<>8%?|(ct09 zp;H7W^9%Tbv!^8S1@{(+fbRd1M)0F9@@~60Pp=jU$$JjhNlBkXWvv#tfUeyL7w}Le{A_st z1w3+@R2~;5fk0-~Jtd=Ar13M43Q;~%CED_0+Sj)a@z&*Zu{eaF8jXgSgW54Dt@1b3 zb}uQ)^S^)H@PH1PWjx@3JRw+T&c_9x&1cfr9xgzAG}T<}dJ;I%_(b}zy@lPe)gbrz z3TPJ_q&GKsA^sVi5mi}vI8msI4=}0k#s{p75&^~Zxp)AB3Bmt)^N+`_K*N(QOIUV2 zbfp;v?6CEKV#Rrb_p4@bwdekP!mfizcmCDqXe<>tH9nFY6&T@DyQH4i_IBtU4?me0 zX$PTPCM8@Jok(diAkbo`1Nyp6&c7_U3fa{E__Uq!VA8jUuHwo6lEAJ|ll^s!9$L{P z__m#qClfJ>AoXFt3AHaZY4iCvXT#Z>l(U^jezcBlA(uCOaJ0Ccjou|Na26bzMIvvu zkJUemK!F}{+zinvXn`l3%Ch1J%m^Ff0qkXgctBSM!T)$c@LS}ceOdWINBR}E!NU6G z7SvIFow_JE4<9*{IIz82qQGrU)wS#9MPxG{%i{Av1_oKMHM);=LjbF%KxkaR@K%$*z2hKXO;pm_*&Pp4{6 zZI^>=Yui-1<hYIx{?PrtU=rLr>~B7wplz z!*!Qqu>~N4WxolSIF6cUZE(R^1{s{cDKWq{xQ~}q<|xaq&wLMr7MZxXYWc`39a$SZ zo8_ohoB6R=@iy`t<;)3-XBt{Ls(0;=+HDjxfKAToj!{9n=LtOE7{gOMV6~MH_?8la zN44FR_lyKcG9g^ial#c~$*3RnB6Z1GkVw)YxXE0~1+y zE_MHK&Q%m8PiCUI+6=jyIo2DGMbYzz_BfAO4OrlUix!)%wRM)HatB4d%ARf*qx!DK zC~QNfk$b}IW#5Cm6;;Olm^H~-#Uxt0%_m4l?TeA9bt{TEwwAZGNCj<-3E=?`m5G3k zSIao>xQE~uoS*v*3fYrF)vD9vSo>g*>1^nG2FmBKU#U+RUsbJx)8E}VuN|pG&%D_? zE}hx~0!N7jy-mb|V+KS(1epk^*2e`TjRb<%QQF-q@mY*C|3rHaoprsO9cZ5O6R_dnM5e3#cRi0&?*B`*t zt$GeBOeKI>`iR#?#tZ0(kByBQN1=tVq;vh8hLO({TCayE&LAVPc2z0-8VJrVd zczd>9XClTE9^UnJf8u)*#xSX)j%@sBHLODFT}%K>7+zsnXJtffu8-w=hKnKWz#D0= zCL?h1O?$jwun4KS#hc`L86n}9+f2t*9_9elIF|%c?BQac+lH{ zy}BM(g}|P{i+%sQ5LjHkdGz~*bI3nm;8v!$65L(iiZ6(Bzl|^WR6+#2|4jtMrxSd( zKEcagxvX$6wG6xyr%pwfdcmJmrwdb+#*p%{&DyG&58ZoznyoV<2GHG+SSHp@wJ9v@ zd*5}bddMLaiT(U)3EpA@#(NSP5IZ+1JM4`may(p@UC(eEjcDpQk1ZOZThl_93s|nf zlGgqMwGpS#-0(8a@29(i^PjdGs`-X5f=y*XtJP{M9LbuvYG1y9su%qv?3^paNcUAT6sfx?wT}-$n)MVjXLcAc)Jr`Zy6=OI3rokp_f>$Y_F8aQrIB2L8%!KK$lfbGv0Jb=~c8y?W}|BYGD*^6WTV-u3wYvd=P@=rs!~KfDA>0R|^))4rnvmuN@SIxeHmhdjCr zk)tTJzhu@-+YKq+HBDW7>o z&7rmd0WzrlaYh-Hh@3c=MI*pM-dVne-Dy+KL?!zFxhiQEAs@+;&-O%Yg{_OH@PKe9 zBH%x5LJ%oP2>htGCimVtOq$QmwEE#(2@(|&Ss&U-`H#d8Y=zT z_^C(|s?3Q2Q?tZ?@q8knuOC~(1F}96f>I&>_BVckVNvUt*iV@Dr1qE;rA{oljdQCMGW z{8wr5@>fzTfnzuQUFoSLx8^^GIpk2Fp!t|y-=?42pQJSge z@RTch08u|geJ_90C*`og>+fIi1*six@CDUFM1ad9BH+=fOq_QQCiuywQ9f5?8Iod4 zh6Cr55>U4&-lj=q2*V7e>t!K=$e;4v?dO9|7;uW0aGKCjp;)=}yG}9@!2GJ5XCTBB zO4tjy&rMziw^ISw)m{_C{UKH@g4POINrWir*BYU?%O}xa;@y4jsC0YdChdv}{5%qc57;hv1s}k7rOMMC>&ryhIYa=* z*W0*2`92}wmp$IyC>sI~-uzmmF7N@5W2}0kle|l0>inun6j6JtlAhjZc{cns?Dc71t?fcFfqZvDi z73>jBI4syU?!72sgnoWm#syisKjMPr!*NXGMWe7*R9bR}@-*vn{gvgZnLd2~U3FyrTla zkFJDbv0H5WVBR2PUU4cO(4ohdv#DQzLFldXa^aNoJ@W@Ue%$gvsnnPDCu~=SIqKfJ&1HC~YD5BP9g?Jj+9W0}Uk^7;hRq9d$zWhVKAlaZZ5M_k*S13DZT`5)_* zyGU0-jDDMo8Dcb~vpS{{4W~Mvzf#>{hzvCeKB}P$=T(Er?Jt79!S)_koAAX9IH{25 zxkY&ioydDh_u|A&M8b|{r3CtqqIXMgoxUVTP!5^!+9>^wLD!PXk{A!jLHiyepf}|e z9zgRq7Z;QvLU8L~~z)~%VfD>Z8DxtI=^8{&crSl< zEb~O2levNCgUE8BU@#Q$jFNEshKF?6xv@b@XJD%Ud|#Sr(u##uZ(Ke9OpSNlYO8Z#+ubCfaSvlZu2LlJAPg zABB1rB7km72p-^&PVh1~j}@TOzd=*&tXoOPoWzWH4&^{)tw|cA<}##oK8;ar7S%;MvOv~sHf1H9cdz3T^dq3~AKHzs1#q}nE}o2lRZ;lPRc{_;2p zQpAFj2PbtgOgkxGUW=6=8~a%oocvaV(v_+0zm;;4vnlW2{kfws3DKeM_xm|oVVuNq zYW0vfI>|r?w%#EGZ9HH5m6`2H2Msn~U8a2tHlfcSvW?l1FVDGt@*V6$9mTe08jLq=bILk*u5oKwO9!WtRKSzgs6!Cv10^(7<)+wZiyeM9cHyBRd(#$ zvbUuVZnf)hGji&ZcKGW4tx$aql&8hH%{Z!&*Y$F?o%(xV#}XC3V1FGkVEqLVut$do znB>;Q`NnqyU*0fbD|=I#G%on&$i6qFFl+vlq1xUUFjt5456z20XyJ<#{V#iGXtTHw zEhfDaem@P9OGcrvGP{{E*I^61S*qd{kN|J49a!a^wno^&kkY;OClPIv(xHl~Ge{)s z{jbjkS0Q)G#NSua7%5I&!TI~2sc!t^zki*7dm=9j>Ms@D*P2TO)Ypc=+0tH* zUFS2<`vZFq=aG}q)-7qQ9z$2rX~W(8cD<2EvDk*TVJ`_DmucbwZxi|OfR#7!PXO7= z1pj&N^Bp(;~Mv{8a%M| zpbWrHqT;b*_=2^wL_nej5#UwjhzlOc5rRrF3H>nvA5c6uFkrsN4@`#d`II)8!inQl z8Y-Sbh%xtOjwo{^1P}i*;cMc7<(nKXr^_p$MCO+Uzpx`*I@9(QwcSIs_9n-i-0hL# z$;>8w%;5%7+QpuuL-~}lxq&Kuz)(0$iAV$z~k@PubVsuY)s21 ze+Z^SLWm2)9zc`5t*cr!gFHviumDn21D-#SENAUMf() z+kyu$@}9v1d|sVd#Qq8NMhL+f{SyXx1wFvtZFNK0DgjuXD#~}qlHn7RWRx@gRXCgV z{Ct747pm~Hc<$Dv0@~p>oAfCI)>%350fS$NfOxhjJmAFk2LykN;N>1}Y@RTvg56(S zJ$}ac0bBAJtWjXEH5_QKbB`}Qg8Bo#JQi=b3G@YasRwK%q2_7DSHrfKaA%%Td~cc? zEGxTg)IMxN<==(-X^&Gra4qG1Q0ILG`dP@v;KOHw^lMH;Ixj{;r^cs^{Y!?ZOO)VC zcI?3USGw_(L+HyOLNH2T=Rmd)UK~*>ru;&30(C^)e3!WGd z0-lZs)-R5?fXu0w?<-#dpgpO0$AMfIsI-0X)5-KOid|`_D2#gmGB3Gmau2CPtWeHR zu5TmY?BKGPmh20wm8Uo6^hZ$q=dBlZa$i6g%QoFyiNu#EaMNk;@4rr{Z!R>+p(6!s zGees{*PcR`Q?qdaiA)HxdQ4UJr8AHi`cC|G_MrUbQ)Q8tR*s%rr0jn?n7I)7m6>8n zXc>j&nthh}dL21*DLmhsl!9*mUH=_&KniA&5gxE$xb0s6hOnOtxS(H-5WI28wv%~C zPnuY{^VPR78)DWD&Tiem49CiSzNzykfdA{By{cm&Xhc;l{dt`NJg?Bf2Q-{;#s_ea zi2#nTM1ZR%56-LZx`6Yy&-Z*C;C%Lb9=k_5qG+C)} zHlY*bmJEJco;d>-Ze})(1g`?Sx)^u`M?jwUwojZRztGnt%Q;o&aAag8^`A7&G@51H zHd!Nn0jaLpI?mAC2De~axxqkP^oW<>6_p6SZ|_ZJ1#JORcXkI=6ZX9dQk@Dz3rr7@ zMWSv!@HvREk3i5<%|w8rB~*l2X7;+v-JznkVAzeQ1ig-U2yk&F1b$eAP!#6V^N9)PdOeiNZ z+y!LQ9n>T-vA|x<7Z)L5{kmSalo(0v)3gNl?qaY!Ae;H*peT7#G!J`vT($^R{PtVF zy}p|){xMiSk2)FIOXgp)F=&FJ^7P1{*Rm+)G9fU`Aq2TI+;8dyEl3PZy}HyhJuoaW zuT8^XPEHl==X7D~L4M9h=>30dkX35~qP6lGko13^*3nKc(95_O5xFdBw8KUQ4~Tw* z<>CSGa}F28(Gr5Dx&j(6S2GgT-#1pVCU4-?UKYEQ^jahRiISX z-Q$U)JODMsfVOAE0R2lu0OL3jAZJJL<9`Ugi^E_hE#oNZB=&La<%@J3`a_1C*I zV0Wp+xb%`3S`bL)Pq<+X9ZlB?wmEx zlp|VHW7>6T+8V<5ywuoGu|_Yrl0KRmQZDTT??+$JG(ntq|Iqy7HBOzz`9oi3YClbNy56!e19l?^j%wFPB-VU0u)`n`m_D*uEP6ZBV7h9o*3eHeIVOC53uXUUVOoTbu+%;5oa16pr1$tOk5nr1vT6zxIn7XGTZ*k zS#&+9dTncY02HY{{Laj30da}@OnS+pD1>orGH*Q^MmAm?T)!a!p(PVFwrthFDie^k z=HLp&kFMV;Gow6oI&-!V#lcJ=|gXg)@oD*!#%WLU;GY$XlGGtqe46QeW-C`;-v^$kKpS3d?Xh# z6v78?9X|$s+7^Y*Y^}ihJn&GEu_tWAOqn{rdx=c?<3?-)-O+p#Q=^Vg6*_z869-R> z1B%Ot4Ss7H13k>0Pi}rPLa`O*IG??K5$AtKm|^VHGRr{jA2y@;odpRy)JM0yUO;bV zJDQMw7P30gJK}%-9l9Y9$H@{IgJk#qw{Olj1|7|Cy!C593D%M)@qpV733x!%c@oZ7 zFuub1|8}`rq9+R=*MI!yC)N~@2;F?xv~L8M4i3tYq@!TK3)9+SX6}cSY@Tu1nyWy* zKCwW0|7Uzbyg3mNYeNKlW&DE+K06SC%A&LLyT)3;bf?%IL;D~Im3NYSnc)hib9Yrv zDhQ)|u7(q`9o4|Go%Lk$kPhq#{bLm=@D7498f}Cb1Hl=iKa-~5G>YQyMsUVo@zMS4SMN57yaF#vyQX%SJe3mXBwTzCKGpU;MC8L7vQ^`L%lTn&SjdlDz8NkN3 zn&JUHJdt=n!(Kwre~=KYznQL?c}z>XCE|uIKgfc~4^vMcseA*okH~qRGZFTJsj+x} z2(q}?b+P4w68J?D15%8M0eqW8z%PFyz+ZeC=M7Jh|M3{SiLnPI!FP~x{Z}^ECI-!q z#=e`*1i<&UimTQJhtWV=jL77(Zn&2~z0nt91>gB@n(cqE3KAP8&sZ{|VAqW72Y%IG zNQHJI|rlyBWF&qSJ) z><+WX1hQbmQTLQ-Q9Me#sK)({Q4XSohy}w`#Db4&L_n|^5zwxpfD7C+C;|%qNx`K; zmAncq#t&@!DaY%z46m1cVNQc?S`O~8gafGb+k6Cz*B_V+$bR{(EC}vRRxxDdA13Ym zQfi4^9Jvjey^_5tjDqCRY3cEN+d^cTp=@T+%|>QUR25azPeOhTlY!!_&*9x2s{>yO zRggk3AxOVdYWa^>9pTn4^Ege)|HC_JdaDa|9^0nW#b`uk=C$6QtJ;aA?#y)i7pRb} zUIowAh8NF+1e(gyToBm_%rwm5=>Zi2-)b#DLl+B0zVB2+-KB zg7as@u|K$g|2+S*>lKGdZLD28bybSs@koc;o~xE{<*!AMO0onpY&j`Rt0IhUe!OP# zVNC`c&9BT(b4Q_L4|eiqM!UmhJMBghsZua%5b@9NaYeSTT#vulcNxr5WLajV9nhA% zj|TM45x{mmbG*}q^0geAFq{v>LRoOZ2lJ7$YX@pk`J6>GM_49Y6ik{a$sGjWYu?(8 zAx4Xn;7AE*>z)A&v(KeJA*xp9sE- z{`R0w$~gL&RMU&&E~PAb6;z*0o}24f|A$w@&Pl zK;x&j@u-C zkVu2sDhvWJuN2?|-15%g1FSw8;Q>u`L_p*OoxvbR5wy4vg5zAQyR1%C0{5CP%d1O4 z@Pe^;+~3s!=qxQ>k8g^jI&+hw)s_YDGRwX;TuuXodf2rruC##wRuz%Wc>!#whFMSX zHzR>}13$GhJrO4LBFVC;8s)F%2`$`oMg_0bh7HqWAeic@R?qxt6eh;5h5ZwxNmS#4 zVR@;M4-dY<^5D$sPu*PjlwLQ<6h4P`tzJm1yOWJt;2O{Ak7Gzu4pqMBjzde0J8#Z& z$Dj^|?^nnUD!{oohzFbr9l`_3zt`ft_#t1MZer!y5SQ#D*6Zxexi-G8#aIwk1?&~59;GE z7+)>Zb|M(KmIvc`FOH&^vEr``DFFx$wGC%KX+^pNGsm^Z+|kpqv zq)GFqpcssg?ERE-#UuP#{nFvn@=!Q4fCto>-@yY$PZ0v6{|hpfFL3IoY#}{UnC(=& zbqk7^+(zEfFTpEUL5yG5hf=OaZ7P#C5PQ zEFW%+soAGIJBcA(dqQw>k`RoZOo&obK<-AcLOg-~DR=mhV-v0Vi@FeAJqvM6Tg7^M1kv)13 zEr_NjqCyVp`s*uCVdBV{bg3umNbe}Y$3G@`r_qxFQEqQX>MCJ&6E&CqnSS zpAa;BFYUBc)FMfU<%+62dI<`NzJq7)OOw2PG>eQ_9zx*t2X{V`ijZkn3Fom?F%Xj{ z2GGS21D^L20bW@(SpC0%Pa}ljPy->*HXCx|kLD#^OA566`m_L|Rk7La*0wMxwf4^8 zf)vuyYo>h7&I^6x_lpa%1XvwCzQSvkhkP81V+7K@A>y`Cxv|ni2#q|}8fxW%elZ;g zmV1&2*zf^C`+R3K8LDyPTWTb%C1Ga^^;6Fv*}sH9+Vu`DxO6Wpu8Xe&v6UBV@|I-7 zDLY|m9kvMwU$bfaV3>nCcPpLWyIKvt>(76-?TSMMPlQBT2d*Kn<|BKB#Eyep^l3cc z(SB7tz;=z`Q~$HY`RxUQU)VFJ(N#>PQdhGf8LSRXeB2|{ikc@kINe6B!7lz&je8?} zkjs)}`xu7?WECI87ub*w;tT$3B?7olAv_@N+IyVW=lYHFAH}e`(c*k`)vxa1HIvJ* zM`v4dT&x}Z>JKZQ9F;_UJeBw9N8$kc^er$V@{>Afq>G64Fl5%AeZ*#e9I87;j#iG1 zLaQsaA>+l;dO9dCTFP%Ql9Ix zc+5N65CsB39A6)N^g)N`tP-#Z5*+rIzz3Yx%fkn}-S#g4dx5_0%E1Hpt~TKUk9E5*DN##g(Q*7OoY}S&f8#qJ+ILGKV5jZz?R<1 z*AC_b`)X+p_`?~Yz7qp?Um{&i`FD#BKInPd-Ie4E50Tn#;8U6E_KuUkLX5(Ay< z^6f5vjnM^rLU6f^8yCp4$oeVtErEk#_ae7mJ{)stt3Ukg3))>VbR>COE*f-*y!8}~ zBB>o8n&fsTqB&M;uCTY!sDG;a681+8Ea`gj04*^EJiz)!E6&&S6TEoUAmyvwI-g;E zzMP;UDpdF(r~%AnBfBbz`71qX2j-`$ zcc2Fstlof7t<%y5eB|t;1=L!mUV!1lorOe~HZs+oLmz(u=d?B6(?i|pWwwNqwap~3FW}k%Vb+aFg$yz96 z4<4CQ;#ex=Ph_t@T0BeO&L&yq?Od^eOj>#(;*_4ZaCD4%0hU+P3uxV=*S5VOjXjoq z9#yt{W)9gK-up-I*arE98P)Wjrwq(TK zS+UuLe4BAm+5r9E`=t$VH&oAG6e!Kl2`g1G(=Q}hdnl&+q}y-VLHJPabq9Stk2`5u z4d_ld?71T;}_YCjV69RKi;h0 zV&gIInD46nuQG}5qpzRpbZL-(t>fkVBU9E3H$P2s?fgeSKj}`>Ptp6932l~~w|_tK z5V3f%_1T$m)OddVyEOmNB=!73C6R*yu5|Z^wSC!SY~*>;%yV6@+Cx3^?M@5`?7?j~ zBh<=lH%H&qIls%PZ%H;;2ZSDu<1ZTD&k?N0thp8z_=febt=@puDVL-T$TLwdU_d-w z0G;3BT)V^lN3kA1w}uUGc19rQduQmCuWjS;dO^gTfz7kY9-GN6({^SG@#mcCpMK^- z$a?h-tl7}?6DRIL+&=XNY$;H0z_nBA1suMlUcfKwlaGe@H1HVGqjQ_$;EiOG{iBq< zmoJjEu|88)5Re|d}fri0q&peqz%~0sppTBaq0yGpZM0?-Di!5qfJ}metUnA zW{o~h9J?XGBXGHUAD6>#$hS#(&DNDZ5X|3e8{Z$H5Vmem??J^kQ)vfwKU8nP)NJ(z zSkF|?U-(Hg|Bi?OOD$)3yz=i~w(2nf@NC1c4X-wQ z+R$i2pABs`blK2kLyrwDwnm4|si47z{uV z{29_`$etm2hTIuaXULr4aE7}X&Stop;b?}N8BS)=#mtE^W;mGPUWRiSu4Oou;Z}xI zS>sYBP$)y43}rG@$xtLijSM9+RUvcKh3p_efeiIAl*dpVLvakXF_gxV%9xsrVK9cd z7{+3lieV^*nHWZ5?gBTtvt%OH7>HpWhH)6CVHk#C7KTxnnuH~fudPNP3~?}o!4L&Q z5DYOeguqGMLiGY50EYM%!ry9*=of-thZ^%`Nn{(Aq+0 z3yUr6wXoL0Rtrll?6k1bs%^B~#TpAO?6a`W!Zr)bEbOus8mla9vXIC^9t&wKWU-LM zLJkWltR#cwN}~uQu#msTYNW4_y+ZN|xhtfukh#L)3U@15>iM>2FXzyBqWfKKSKHl*&`&6kUK)^2$>@sj&L{TXq=63HNw#d zHzS;kqKlCeZIEyw~8cem;2l;Nz?D@WH|n2hwGOsASn6P>gOyHgqvKriVWES44%Ru? z=3tqFT@F?`*yJFQgFLQA8mE!PK@tZ!9HelP4348VI7r|ie}nW5vNuTHAa{e*4Kg=4 z+~975vkk5`INIQ5gOg2kv2mh|4GuQA*Wg@(Ypuqyrg5vmsitwM2~em(od#tZRB2G8 zL5&6_8dafj)P>eufC3HbGbqoXI)ma2YBMO!B$XL8k-=aFa~X_fFqOej1~VCqWFh68 z+NA~q8O&oaj=?kr!x+qBFp5!=m|7&J5r{z?24NUPVGx8t3w|Ad!MR3eqUZq9BQa912n>Ne0DH8x$l^kUv5C1lbcL zPmnv+NS!n?CpetoZi2H3uBIAClg7;iCzI%6;zS!1987R8!MOz25*$l#E5WIxaVga( zl%P(6G6||AD3YK?f)a_UkSGU#%(@x{64Xaf9zk^k#Szp-P#Q@pBWf~&!3gFe7>i&k zf}sdzA{dD_hI0=k5fBVSFb}~v1k(@LMoGhY?g%f2iaInC=0_RGN zYenN&fm;Pm6^%=UK%oM43X~~Or9hDaH42m{RE5GxDibJBpgw`}1gaA#PM|h{(nM03 zIIgZSn7~{DV+l+pFqFVd0wc*#UG6}YXd;1u1m+PKM_?L(VFYFo7)7W_L=uTWAOdj+ zgdq@xKo9~k2!tSQxjAYP2m~Mye?a&F(FX({5PLxAu|nhVfWHIY4){9Y>42XDUJm#; zYCIh9Z@{|&-v&Gz@N2-U0iOmm8qjAzn*m)0G#SuiK#QT#VQ~4-U_gHX?FDof&|E-o z0j&je7O+^rUIA+bY!$Fnz)k@xh1y2J&8V?Zz&-)%1Z)$qOu#Mys|0KkkVrrt0ciwe z5s*Yc4go2IB!l3n4FVDf$R8kmfb0R12gn^Db%4wP4hOg!;B0`a0geW^8Q^3PT@0Kk zV}OGJ?gcm(;97uV0d5626*Mjd4=5C%PJl82sstz!phkcafvONV>OzPmP#{2k0ObKx z2T&Y9Z2+Z#q%xo)0T>KmE`YHBrUDoWU?zZ(Am$=RT?ljmFc82z0OJ5m127E0EC8bb zH3>)}0SE*j4uCKKq5ud2AO?UCpaHj3Edqc50N{TB{{i|3;2&Ur0R2a6IR4=GgWC^Y zKREs1^MlI|9)AsoAN+lA_rco-XCHihaP`5{2Sy+Gd|>l|%LgVOczj^-H8^~(27?d$ zJ+Sw{-2-zEygjh?z}bUh4|+YQ^`O;*QV%*ksPt+YJx9ALYAE!e&x1M-+B_)ppv!|Q z51Kq6@_@$!8V^`JAn}010}3y};5mx32LvAQcR=3(dk5qlaCbo60doh#9qe{6+rerF zqaAE^Fxf>GJ14^2!C(h_9n5vG*1=c@TOCYw4NIK>p$>F9km*3B1Cb6iI*{lTh0aMR zb0E-xJ_qs~sB<9Bfi?%yTtb;sk~s+GAeVz!4pKP?(%rfWZL-AI)W{LEr#@1N;r(H$dM2d;{zapf|7K zc!S>!ZZ~+{;BKKS)ppAhvmQcnNBn*Nv$i*NQgH#McG04Oq5hH8UjTh~YdF5(_k!CCUN1Pk;PZmZ3m$I`hZp=^aCgDm1!ot0U2t{5(*;Hs_*`If zfy)IZ7kFG?aWyzxt_Fh({4KDzz}*6K3%o6`w!qnfVhegLsI{Qgf>H}QEvU3=8ZGy( zhC&PaEU2@f&4Mxux-6)&pveLv3wSJ`v4F(_5(_vips*4QmZLOSKwtrX1@slLS3q6? zcLmfHFjp{K!EOb!6|7b;TES)olT~D~aw5zX3|6pL!CVDv6^vD|Rl!u%uv9&OPz5>_ z$W)+Gfk*`!6-ZQyLggfsDG;bYp8|Ob)F}|BK$`+-Dxpj%$rJ=rkV`=<1*sH-QjkeO zB;6XxSxYEW5J*8D1#uLlQ4mH!76nn1l0+qtC;*}WhXNQ{0}9muLIDN^5VS96q6T>a z01EIYfS&+;0`Lj2CxD)%8jdIUo#1wY*9lH1_?+N!g2!3I;RJsZ+)eN{!Px{~6I@O3 zG=b3sJ`>nX;4*>91RfJuObrf`Bfww+e+ld*aF@Vb0&fYdC2*FYSb|;&Y9(lupj3iR z2`Z(UM#%{^6iU!1L7fC`5|l~MB|()0O%f1Ez#{>T1S}GeNWdWhg_K~B9K}%r0txsd zppSq(0`dsBBcP6eIfCH`b|aXLU^Rl#2sR^_j3SGX6Jd;CFoL}Z<|0^&U@U^I2&STj zrAUBK1UeDOM4%FZNCX-YNJNT4a1XA;^Rv622JCIe!sJgdh-tJP6_-NP{2@f-DH4ASDS(AVB~G0S*K(5THN+0s#gD z5HOg#t_A@D00{6OfPVn}0q_T~AAo)y8jc_Mec<+i*9T4?_z#y}VYSqwxmN)nSmVgQH%90p(* zKw$uc0SpEpSSq(r4e|m24B#&SzX19I;0s_c0KF_U953*@!0iIB3!E)u7J4$-U?VN z;H*Hg0=){l7L165{aUaILboWU4uXZ`UuD)ppJkz z0@?^jBMD_h=Lf+E;E2xKAvS_5C=dT0BInh3@FI}1Ot!@Kr8^M0E7aN2|y%ho5S6^B$5a~ zAOLv)!~u{7Ko|g707L;w5|BUw00;ma0AK(>0RRL53;-ZN8*YTUA3gv8==hHg|LEwC z4*uxaj}HAkH2wI|e;@t!(O)0^^wB>b{qoTtzos8P`ro79J^I_DpFR55qhCGx)1w@P5qg%YDJ3L3w4IbU!(d`}G-O(tZ`G-&KXzQhJT|YfZRvFKPZWU!A0KgFReORqW9twju-TVc(Pj#7DpZ_8Zo-RMV32M+LC`fIrG zw)41;bJ==A)xEf5;oG~Ac0G*f{9Gklq1#`YVA-X!IgyD9VaxM#*URJWNSk;v1-zHrBSuY=$f<5 zi+9y*`&qNAa(`}OJGV~Vck5OsVPP$G`RH!d3yZm{L&(PX(i_?D#|h^W+P`|@(OQ^g zcj0a1Vo!4Wjgz#1mV2H`XKC)YKkVJ5Q=}Dq&vcgN zXV?u=`JCpd@;imrWPnk`o~g^9u|D^1dxakU$R>1nwsTh(Eur7|=8;_O!CzQ|#Z8u; z@!G929)9LQ)lpWsz5A&&!8-R9=giv#lS!2!53+Ve2&+Ce-D+}DE-X*mnxMOIAaS~C z6DiK$Y=XM{r|q85q-!IElV>JHg-j+S*z)nz!UeS`In!TS{@%8>()|76L#6Vex%;_W zUQHcIzOVlLh5lu1rzhppZQFcfGjEQbextdzFn3FB(>=ZKt7eofE!CAhQZ2t$ukQo9 zp27&NVN!Wk!S8wl=T0Dx4j$j!Ds`rCcF`PCYkMzYdy%58(e}Y)^S9B``~~mSklw;hnqb|k-3_=eoyhsD1Hre; zE7`t#Th>`}X7j{r!0h8Lt1ax{k2+E-~8R0izB>*s44BF^4e90WuwJBd(G$nTjxl1c(ZCorZU~_nsR310-?5jn|-AUt8-NaCf53JQ}yDfXa ze`5XJj=7z#Rf~L281{L_R41X3ii?GBX2S5Zza#sN7$khCD3i*$0rhXUytO5oEFs-{ z+dYXBVs~Fo+BtHR5IN}Vg?iS)#7BP&xU!OPW z-qP-n5P*Lmsy2a`DRo;q>X} z8BVju33n?KMqJzzOe)-oG{1#S2dVsbE7Q$K?=2R#EiRw&U{WCQDgS()bm>5Xe*Tn} z??3XsH2-aD_)?EA z=f@KLCL^W!XWH$T$_IuOR6j~zDJa+b6usyCf{9I{&Zkd>3bM?3g|vd+e#z1T^un7L zy{F|dwfeMZzbS~Yeb_^;KdMw!kMGZKn5rjCy=s=7;^Ivbh6Oz6b9k^2-|CExAJbh3 zT)SL5$bt$Z0W~Hp~5DAH>loqgh z=3Z$6laRHl-_z2C%?V|JEpviMxw-Y_1@pU+eT%P3E7+55AuZsC)ds2DV&t$?Cy!9l zzf)c9gw4jn2P5lR4Tl&BRi1_1;N;;X!t$Z5pTh`Y-iNqetf4oR&$cM&VlhTg*8a&! z6RbSpp^&>LlZD4pthPU0Bs_1dv+=x5jG%Kr?CFQ<3FOI5YddiPx(7~4u6`!sZuQ)!X>WO{S-nP@0e<+WfmlK7#DhO`04;5=vT~xMy}(8Jt0|Axrsx&wFSdh zl85v8$X8_xgklcm$Lr1?iU6iemHj(iLl+j^_eWPJ0z7CF60 z--6!v9>n)2`v%8I%Wow(N%Ozv&e`l1C-B}i`dBoT%=!#UDt`F6gHPO{a1`(fg z!O{u>ZGY>C3phHd!Xx9#>zzqIqk5@FT5SeI(@eZiI_%qcvu!& z&j}XZ>2Jt&7&J~el~eV3qR%A3@SB0XSl)Eb9xbh^RixXCMTc#|V}(ek6@Kro#Bsub z;=!{Mdq%D3S{w&A!BCYuKni#>VL3M#%% z5EmdA`Y*Q>%R>S`4{kMg7AbFjUT727S%`VeWfkfxNzC$u{z+CdiNn^Lhs(4 zT+TF~C~V%fdh@ZiT)5yN+>s`DZV|L?UixZs#cXH(+w?g?v+|)HW8CKpky~1BtydO7 z+S$L?7Z)(bru!MOyu^CjmGW6x!u%mkien#-6B3KYjvqC72)TcIw6uan9otCrw-0Ra zR4iW-%4WCO97W9E-0i%?cNe16@h!}q4Ak*y)aRPByM34Jf*eG8j2U%2@yd%@&iQ%OwSrL)BOtA5?i z7t5!II&#lH>t_q!I}FE6w z(Io2K!ozD%Q;OBe@aWmjexz{k#Ybil^GL_@XM+!>`U;h~uAlY?Oc#!wd*)F;V!DuC zx$?1Co^Y>8VbiuenQ7?tEpKI%P^{w8@|?HMr?N-y_jz-sk&E)A@!|p&+70?CPC(Kh zCoI2{Ej;S4m~ZDhUAR@~JJkNgD6*nrqO=09uf3%Chg8cJh~-7ke%&3@D~70Aj~}AD zt*`J%cb#`L%V8vXcUoa&-&msUY-OR7*dr z#?R`R9Vs}w_jycK29j-$WjDkX?A((-Rh<7^y=l>6`PiGON2>~ANOkWbqZ=s$1nzv! zoL)Y^MiIq@kMUPL%oNHLK~5Q`X9%`Y$qU5t+CT2Pzudis zIMv!<^W#Uf;5FONq0B2@C{^64J{LWmbUxd|V>e}%a*SbwG zD)N#`G%BH1T8YeB*1XnH-gc{QoVsw$(ElnlokR;`Pj~Lar`&b>%4g~uG^smYJr$7_ z#4u6O@?6tzvUgf(J7osE-EKDnS+>4=N{$V0H%{MB+dvx3Ha9TTzTqa0_5NvW$WP61 zvrQ4`mg(ua@(-d++_g`+@xNy4+KQWWkn20tSp10Pt-Q0p@ z0pW7~*MiQrI&|Mh@cH>3iR$sMBmVdJsA>Ouyg}su9=~SBe~m8*)(w_1yWMz?*Rp@s z`<(OBW|${B3^P-YVLBQxjGht0I2bcbQZ0t*Tbp54n=(vh9fpan%P_CZ8AjKNVe)Bo ztQlrv1BR*JkYU0aG0b>dT4u*E&uEJ58KzTnhB-zP+k#=t92jPMD~4fPGfWwcjuXQy zX~!^*&J4%wb!M1K7lzr?o?#|*WEkU4bcHlCIx|drcM7?o2Vb~?VeGolh0_dDGE5ph z6dQRk%ubrl-5AEXJHy=T$uNGN40D90c`t^!L!;e?VG_L<#;_m51k&82N$AfoX9v)Y z7{oC125}5iK?kP!&;|N3Ow3@0S?td+pJ)PyFw8BQm;i>^H4QH4hBN#?SLuuU1 zOB##Ow5Eb+tpzj8Y?|jZYsb*N4PhA9@pJ=em{5j^p!rE-Fo9u~Phyyclj%5`LXQ3$ zHihm%IKvE?N++g&G1_!km(~h>NuC(cYNszrV-s3+^yN50Uyi1AX!X&TBEo`Uw5%9r zA$=(V>NCtq`f|*-VVEwB8D>3wIlkI6%#NlEv#uG#vesO%Z)5R=1(GOkV;{ zPOJYLjnb8F6|H_1jUTQ4BeeQcY4zvPst=*nUZ*RandT|WFfVAe$8=*D7h3IlJs4&w zt$KNHhPg{Kkyg2JUs{{A+O26d=h4^?q#Hpq#GBR#t?fm$mhJuMOW{Xfe?wZ;7iiki zYQ8p%VZ3QIpQrJm)oe_wc>XAc`5Z_$f>!fAngCkGX0(dO(JI!W)jON!JIx|mz1_lS z&Cn|DM{D&ijTfz1fo5(v!|aNnJ2ahP+#~5rL}NXJVX|k?*Z=lRI#D!z2gK0VgQoXv zhPh93ZVtn=pylEpeKnX`n$X&3Xl*mJwiyQ+TB8iDO-4?mprOYnLk}m0?jS?=iP0Pa z%tSg&7thS4nNPEXCS6>9I?Y;|O*Dr!$)k@dnkzI{X>QQmrMXY@hNg_>JxwLeM;dJ{ zb!7BWmqw4KK8+1cV;Vaedm1O2jx?QUI@56CKRHcbnn5(aG=pjUXok~Fq?!CzrqJOC zn%KWGpAN^vn`Y7g*7NgCa=AT~HHn+J=pFg8{M^iyl zMe~8?6U|RPMd4nYBIHpGkG?~0$rtSqE#CRIu`^GGD5l8*I5{8mMkZZJ_@t$s(|Iyp z*MRT0s*jIsO{|>Xw5pG~Y`>NwPL>@eosmQ34tz@IDwUz<$FxoO6Y%FT*YCnbhUZ!#q!t&4)4XohaS<= zfBG)aJddc%eY;q$)H2M@P*32Hlk-O_*Lk`8i%#ymd-7zR9dvtTeCvpa3ii$b^>22qD3F`+0ng=jsb)P4WYue>>sbk}XQ;hS8jPjadzsmKI((9MerFggBXrJ9 zB{EyyyNzzM)PkN~nniWzD|6(1Wf8R4X`;R@A5kvT;j3@S?TlV;Q16DpCf@$Gyi>z9 zyZ*i3g64V29=eume4vSG6Yb5K+l>^#j3=%NYnj6)IX~Q0aY1I5s$PL~Ta#3*n<5L{ zuHNXSx(2euo$BE}bU0v}G@L!Wks<#ylfK@8t?6!Ukw%)CnHp+uqJxvuR_RZWWz&Vy z>QEmt?H1@RHvVU$<-B6H-dw)mg?yN-;O~{QFXUyih*f`WPH|&JnaphUU(1rlIR8C{ zy=t`BD0`o}3K%VZSb}kjrlEV)&+RmaLW!WMGR^ZfH|6})CaihRsmoeCt*0oy(m>(L zx9V!_u1yy%R=D~$&)O}8&)hG!;|r|a44N=m|E}q%V)b*gLiM9HKXt#nC10Q~GthG2 zT~xG=#P^5H?Tl1O5y^D8_gQh69vz0-`=zn5o+gI;{e5&=(#Orv=${vluOK?Uxh=Bg zI$E;Izjis5_1vUBILy-i+Sbtrxzxr@n6g_Lgf(>bbW( zowTVQG3|alL*Z#aPHda`jD6mWoV;}Y#mn#U>h!X3urw^oiN=JvQw&m$6T8J4f~Xc!jmQ zzQE(hQ*BaS)-8F%#CNPgpZE0+%|6Gb^t5|)Z|-k4?dtK3kD}kRMsrm`?cxP?tb^}R z^SEkua*Emc`1%!W`-5j5wGTeZe%rA0Vdc7y?2?39DR*a8u$?#97Hyw@kZlp*?pME0 z1^Z%Qi|`fP{tDK`X?N9+UwhemO7qrL#jn{_>2Jt^h7*v{4W_zNz^Li;xW~BBqaHy z=iU}Jr01|EF{L-G$-v3K&wIbKAT3_nRD>pTb;!P`Catv%YLTZ4jnkLxF(6AUyDzof zp-Ue7s)mfbtWD~s9on$cjUhwRte4mO`GcMJeeRIP9jn<<*?m^on0{vS22@0Fvp%qq zpNd+t!z$U0CyfJ|_kPDVu|D0WGT|*d+VQb6ciC}y7cD;dg4}@XyRqnN&*1&4nLUgh z99El=UpJ#e58B9t@oTaU#CC4}S#69l^r${zm-#r9uCNnkyM2A=JF_LZrnknk{w52d-oxR6pZ8jko))sUZ@uh< zwYw&FnY*qjnKP;hv&!B=_!&0Z>rS*e+0)Q((U^L+g0=3wcai%!JM!$vRiCpu=E5_} zoaA#e%t%QFvxQVO61+#PsyflwhCG|GebUDHb%eX$M;|hcF(%)2TR%8Gp@GnCVe{2t zcJ;|*VbT^Z%~Tk+booB-LwaQUt<*Z{N9zf*`o2zjy~2`YWqDUO(J>La&e}VB^jd9_ zcH4cNr>>>o*>CI}PcEu1IrcN|%h*^0;q>`LBJ2B`wc{S^`G(aMJWh3Jmv-2ctn!!F z$!w!5tiIbZo1b6JDp&np>RDkb*qEEVTrt#`Snbb=sBEMygqpsZ*!{#ucJ0XXPChe? zg~ikIFZi6+C(EZ6$GYYIRt@v5Un$>K!A>J}bJM5j3(3OMfDoHII%J(PdE<$D-&A|v z4lcg!`GyVe|H(n;u}paQ%l4>uq&B(q{eY48n$If7p!y?L**<5V`Zfr*ILQd6KlRt& zSi%tP5qBmWtEf^H&x@Cx|4_`v>o>4EZ2D7m)Fy6%p6+ipC5MpG16MN|-2A0lY!Oiw z>RzOo7H<@KFDA`nB4{oc%$}s2q93Yj?ChTV!n%%IMVGOFqc^zBYMN6t7in(MJfe9; zQ;Xh(xYDpRy=lbz!l5*yX~xlr_j=-eUM9^>8u4#4%^8~SG=_9sJ(?yo;tgjAjd(w} zo8~Z$pv5#~F3|z;rtcArfi}Z5qj90>s-2S4jn94|kC9d8{&k6vuAUd^6`eGlF`PWGL zM15K@7cky4sKl>UTY5VCxMTbvtCO1ZCBqC{?7Mm1v)`&-H^$dr)xY9Dm(MZk^H3{- z1>sSBUa}YP{J@#e;Bg;*u$|sBwJ)sxS+jzQ{UdI-aMBkpblW&OrI}vM^q5b59&1bR z9FOYr8s{irgoECJ3p%quvz8UJgMPfwAsByb?V*k@V~vDyx4mCqb)o0A|M*j%2>*Kp z>Jy_=&GeqSQ`haBU&&tou0L`^UwtAihNJ%{7{`x&U}7wkE?wW1y;ZMf25AEEpL8N^ zLu#ID2Gl23X945=4;|kT67+`czbb!EXdBH6XkmjvCbO=U8wztKPjj3Y*RW=KaXxYV zjQULMNW|g4|I}yZe^xNpzRgDc`OnyoeRNJ=q?a&Ed){VJ@StLSA@}#Uhl_n|1dPX{ z`b^$};5i=YnVqAj_J39|($B?A|JP%-!f%z?#%Z#e8P3^^ewP!ZD_FKoH5|C=zgM8X z6f~`w9xG5^8rlhX-iuMC=9%1Mefb?{%6>6)dTIaZXZf}twtYUMEu8YtytRBsqna5o zqxw?v-|qtTrKL^H^#0RM4VaL7lWpBO@Z6vJziJNBmz_pzY}lC*dT8)5!E-!v>Wf=T0VCdOw>x$3{aLoQZ^^Z5(>~R#;QQEbX4xTMRZhNjKYeaxRWm*2 zQ(p}05tvKjCx3Yb+#2SzQfi_`p2{nvIm12ud$i=vStPCE+6PKP*$eO-1DV&t>{`c z(_=pM)%>4>S{g5|UwwK1=Y6>*YpAiG`(Adcm&K{3j-@p#$XOEqWkuJQs;4WIXYRB# z{I3@)Rjwge_S(zDos1^i@a3zZcUQ@pWeFv zwF33+#J8I1u>$q&#Xm3EwM8wHu65tUjvc=1YW#$IHPiDpKgV@F_gGbL*6ooyi?zh% zb6AG-sJpL!)a=T^-Ma4&*;6HJds_5I90_bBT2 zGFtkdH!HYF!A%Pmpm~h_@MVN0pOr3O#hbmQSJmvJH6I%Jq&M`^d+(p;wk_PI^1aLH z6+Xg1X2>s=KBrpHPpsd>Pq)ct`UWyP6}{4*RDWMWGUO9v)z|;|&{@2eUhPlRJkPz6 zA+M#S{%rl)BwFTNDCe)d(myE+ER*L>+}pX1j6ZQrZor$g)ip6RpxZ0Pkk5%Xs>hed z8yRTT=7(OBZ_yPS6LoS^HX8TW;%n=f=;an{@6$uZmn_n?Gv8UN{xnYLG~!=LIlgNd zXgEP;knz_C;P5fVDOv@5$}_qz!X-mP?YGiR3;m#O$h(L)JKlq7lh&a9bSRS!X}_Yy ztHrj0V43qt`uza??&YuVAEfcI|Ht_1`~Q3T-Vgrw_%;7MUi$u_fG+=E-#;X(Tku}f z>Cu9hnp8K=_^-wZ-t?_(E}#6;V3^F|kz8!~_^UCGc_Hp|+DiJh(=%!BGw0KNekxXp zcq7NJDVGa;#923M<9`~|HRIC8 zU9qVx)!Wca=I~L@k7jj7^D!Uvt&Cq+s$V06kMdmG7P`%4xv`(*Zd$1&W_r18o70wv z+cql>-k4)jEK1XVuRQik!RO%gV-2x+Gylsu`%Mw zxeNJXW5kQ!J59yLh(PZXv&F`U?(x4(#m0!ZQe3Q+bBS4jPNkuIz()YNXr~wAvQ*Q_pB2sHb#uSVZU8$j5yG? zyVMvl)YijVY>fCF-AQVU800m6y4V~84Vq-+#cmq$dF~X(K z6{#_zL#q$JpV7vMfQ8YA#m0!zV;Snkhz6H~8i|b&8|HqN8Y7gdqf%qUpg&xa*cj0? za-p@@7_sHUY^gEgDc@UajHr6mK3HswNGr{h8YB35SI&uz5l6p9J{B7z3b$XA8YA{S zdX+3TMqHFle<(Iaa86Egu`!}USC=VLV?^gmpZ1E45rs#~b;ZVrj!{9^#m0!m&G&F( zV?98giiC&Sg|o;)0(SCzvpgZ2UyQ9X-*p>tO_G8|J@jIbN;>KVq-*y(%lcl z#)!Ah+q;X65wl;cn<+L%6f}uCCpJdJY#;ER6B{GiwRUVJHb!iyJ7bO57}3Y>=Mu3o z!eZK1sWHN+U5?ZkQEufZHAXC(xkhS?h)5bIHAW1cW-?W5jJV?Z(N1iP7^q6?B{oL9 zoi{@vHby-6YuR4u^O|k_E~w+FHD}mSr#~|uKA*)CG~fP_+{UcpQo9FYoA5G|JB`IQ zVLth|{373bmu!NJsg^##Xj*{1>nrTk?W0->@!QQ3u>mzyTN;iPn}Dmu;gfuJp}rlT zdBAv*)cngk@6z+qRxe1?{!8eyf3^RbO)St+Nar;z=|96%Yo1%{tH+C{Oz|)FqxwCD z>Hd1pN#*n_FX?Zm>A#m(pm|9Ee$5p8VJc z4yxOdW550N$xv!b{%eQ-YD>roa#rJ|GPdb8>^o?=j6Q7;%V(H;Ww1ZYD-PA{ISLgd+hXLK9lZktGfTLRyRBK z^8kDGqnW1q1xsnKom&_h@>=z21&LQ0eyBDb5-+d3vo<}H3Q`rck+{8re*f>Cc1X|A z=%3d}-Ap{Qfr2&@3v_R4>L6*XZka6b=U zNBRYixOOj^V44J)RGM`(J7^BkJf;!9J2a)SrV*QL9BJCoh~E)<(*)5>poydr|DtHb z?<~ddB%jjI?;yJ|zvzJ2ZsbWbk7gN-*haLOW)F?n?sJOfCCzUdV{L}9q7nb}X$*KR zBc-{c2^|!_XcZ5z|BU#>s~Gy#H)Fw9^)MKn)>c=CxBE>SV0CkUU6YSg-%stJ_fxv+ z8y!L`_kZ2!B>x}dOa71X&Mp4;a$^5;d@>*V+BiqMqat;wnMzhbi~raC&LcYhU-vtI zy}$mt-)ZEa`1k#emK%Tb3%$27n^K!LXo*A8S2Uq6>LcTDZ9{E)I(SmL<;hKUQ&eip ziU!r(=kRN~Dk2&(rT>0!J7}Kwey+%Es#Mr$@pe^)O$n4b>>heH3 zmfi#%&UJpI*eRp8b8g8zNpRDvr6b+`(wB+}@Dw*s{7#V{oZZKcPo8LEsO>FnvUEA= zJ%|qH=Z?6o2$bcz^;R^N@y7}kANZ{U6%M+0g~To<{LO)iAbv%$OpiAkKyL({dn@?< z*K}?9t3%wH@kFKXrA^074xnSoJGxJiO`^}lZ+-bL32qh>?R@{e!@V`nL;cj_am^|6 z`1f>w?V`9F46ZpBga18UrRKTWSh^qA_}&X^*Ea}IPZGgzK&N46chDv8&Rg&%^eqO4=NR}6b8k6k|kSZ$TjiaHqA02#=n%%>|_^(~yAKvYJ zkC)%noWjL#z;dt08Y=bppWzAzPW;Zjl%_s?uPJFlX;#x5rFlxDE2BRsp&>N%2SQ8& z&0d;2G#_Zpb?A>%Xa>-P(j?IApgBoXMDv}dKK(=^r}3c)r&&a^mL^-5e#jK)Kq1XL znqM@v>91PsX*$w)(ge_i(VU=pO;eYi4cuu)(=4OePxF{Yo4!o8G#)e~XlBx+)9k0Y zMDvK|J&leb!&uU^r0HxJ!Z3a4zzCWNG_z>;)G|g3-yEMf#uW71i zzS8`r(KVuHEgEy01~hgwEoj;p@wN54bl~5=mNn+R_jDe{`$TG94C$I&GG2X6HeWMN zeNfm*M-#2il?r}fv8-#3kgdfR?4`fXD+#5q@JR8uV6OaSt7WB|?qUlL9oMy5fJ+>CmfweSyX;d^tH1BA{D@r{YN16UC}n$hE9Ak7?_ zO*A)XKGRs!msdeEk|u^`6ODLCh=;;^8u3SQjcD4^^q>i#nP3!1f6GG$R?w`a*-dkt z<|@rSnwK0+)0n0ujWdm$rW=hHjUUZOnlPFOnwd27j8k%4c{>&TNkY~D z_1R5*C>Xex@vdt$huFqlop;gS!npbH!)|FtsdDwoWOwK+d-L=(zhVe7Na2N^ioUXS z0~P#Z-c3E4tycWtgJ)p23Gy<2+Dy%}c42ODY5VjI%tIfkk0~?hddx6x#R{>pNDX=H}hvJT9nK5a-^eI<*~_koxYo`fl+^AT0{dogP^+pZp0eU;d+E zESa;oxqE}K*~F$~L`wOfS)}U0_k5ju(@9&acbR5SBgl`KE^nviO(Js|-O_ds4<$B9 zRo6~l3L##LTHH$BA4K{$+t{(7-YBxtv8sHX} z+oFM_^k?*$`9R@bLmdhW7fJoz$=kaVfglM|jhj zyfNM9;2h9_q#Se}@Ogj>(QQ_D*Oci_M80LA%YfRB#A@=mXv;y($)yjID-YY-lX?7| zloJmdlf(~gk(STmutU~k)vy;#D-Neq~}H}gQQzO*}WU|=S3{6X4@^=vS{n0Pwen9!t`ybDmLlO z`l6n(c(Bu7@rz!6bYo|{ zh&z;F>dJ1q(s*Or=B?Pnkqg@;m)o;n7oCYT9@~(8I$_gui#z6Quc0mCT@IPBYr21s z?LM!=t~M{eIo$lW@|@);0cp?o*6Fv9W1OXcNd1N@p#oL{0W3Y=lz z>E%7;p^)4eWBc7yCf@jVYmLz*<-13n3-#-svSQPpm*r2DIse9;Jn_y2on|G$U!|343H|NpNX+6-Uk zVV8QmS)jtgQ2fkgDu!6#cprU1+c68816Z(5*#+7dpq^QR` z2Pt&;J1x3c@V$c+CR&|Ra@zCGdpgI+0@DAw{jO+c7$+-9lkS$Jk5$4dX;}P49Q{$C zG%S7)r)};zsr2s3t%TlQ&<+T%ZGaa+mK+nL7eLgL7!y7fNhgro@7>;0Vvdd=e4=f|ey zAGx}ktP41>ba})kV)@78f_BkDVy$`=W?8gaXg>0_WysCx?7O-4cF&itBz0PvdalV@ zPrhB**{bcTM6$2@XkF#1HNyK=i#~cjieUX~U7mbsMH11PnfIqcnNALlTfVVSdkJaw zWo4u1+8IKITTW2F>`Clm@4%<^?{iB?oxpn|XT+=~c6BaRyq~p%)N+dr`#vi}@F*)i z)HgJgwdgxKHSAsjdHk%<=}pg-WS0q7=TgTdB7{mCsLN}4r1S4IrMf+Vl1&`8!=b8=}SoI zvQN32yc5aG)NdDedan^m=Ot+OG9AUX(%ySNsmUDDUaQX&_qYTicc_=L>vIAr9^QP_ z{?BQGwoE_o`O2Z}u^;8_AHJVOhE8=fnbka=9Ej~dGjrBL;xTPs>b6;{g=R+CdrR~E z*biJ|qr?%B=UO-+S!vnM&F` z*|2s(?rh>eJ>*Y}|9o2Ib_eJAuM`qj+*_>A4P;}Q5Jg_}WO8=RZ_DMYW|2nY$0uIC z97~>lI<)WFI&@_@nx=zX-kUo!$X%)zs3{GDm zv<@lMt#j3rO?|v`?qCY>)1_0G;;DwM@9Gi*AxJKIxN zR)2MiU^4P1{Xv(}WO6KPZNt+R(Ij)<=AY**77In@!fK~j!VVa}*3`Fj6!FTIZ8ta^ zN{SB21>LlnWYtEqT83!}f{6haw=>PW3oA3XH=5=-jCdSNn_#aymdL%TyH|Kdk{(IN z>VEKCAXFv4y!W-VGrMP;)!ePa{E79jk}&JiAmVNDd?nX%8kyI9%$K;9^94cmqiBLp z2loBvCOKPldOe>Rjvnmt?E{>3CRG2dFN^NTrx-_K|(i-}I`^yeE2UM%t=>#S#|cmcsyUUqa^8?Ys^-j0WxavEyhuZhavYph6jOh5Q$%s#lGt^ z1x3@g$%bCd*;mz_KMZ)+op4s~6UUmC4kV|3rZ$S|7(&`D-PbjyW2Dev&jq~{8++EX zpt^_tCqkz8T@(whiZZ&3GtE{>mDe6KRMMU2_ zW!{VArS=UgTRoZ_$saTCJ?*&tbI`2&A?^*>S2?e@W-W0eZ7xlVYP`8SN8VrC{?798 za56Xh*D~wJ6NO6-(Qg(Hv|{^f)ycMg+JW@Gb*hi)AwniDdE}hDEP!l#FPpS-S*TDP za($4?kGibwedn3sXI#kn$?^}6Cw3ua^~aBl$nYayr}%WAmN8cNW_hH)aSJmx&$ITx zjqYtpM!(5vb=$g;-C4P#!hdl-WL^i`pAo-;1?y$&Kf1*jv-=h#dX*hcFwO-kNbxPHhA6p~kuCvyY!*(-A{e@HB8POO*Mmxji=kXc^8J{flFPsX`L zc$st?DLl3t#e_YRv7bVZSmv*@C*3^)gHkUzk@zWV)?MA&hsYC8q+jQ@4i)~Sr$?oNU;Zcj#q%Taua4ZD5G5k{n1m9x_gM zfDm|X>y~xLs+5ZppE^$B%}D8*ZllzncK%z7 zJDHQKOSEQnscKJ_hsCS9R&^J`-IMk9+akvigj`m%9Gr){opSp#18g?Ph zuC^0v1$hXK4o>O$XHk{%V7^u3+M)EPr7wr1nvw>j=toDcIwa%V})ZMZv7nBfeh?2cXH6(9>Rfn7DuB;l`3D?b$W8n zS&IzT&*|DR&X_FuePYkc{Z8cdW&@WL+TD2Ti;km)^AD80Pknp5wzQhfD{B}y>$QxW zm=O5;{+#CI^v5s}VJt7d&2bt$)+9sRmN z`oc-Zf}Z;h$5&0aD}S~byt!N6S@!gR-WDOL1#HR5m-DQiSFw+s183JY)!~G1Ri+PS z=w&J|v@L%b_xmJ!KV#UN3%@V0O>$%2wR`!7?fyD*Sc@UQR9g+pcAj90C` zn4SG{Gt+idK3lM9Jpc7nDeI85Jx+-Htg5w3Yh(Q7WToF>%N{c(?`P*O*kwJW*->_d z&*{2GO&_woM-+K%39L{>E7n$~4&dky&RuUbAFA5U7JVK3aHs15R{vpi)PSwG*`c|{ z`p+w0s`T5uD6in-lt(sQ|580OlMU*+;Ly7vyV;gb_tqMOUuADSS=}@AT#2gIu}dRA z&WcuEZs0R|>s_8b(XmGduR2@U`Ne}8tX*}MEqkSmyt$xA#r0S-#wmQ7l1r=ZG@c_V z?3s)EMs?0u%lWO!8s&_ww}gN|(zjyJMYf4tbAJx}4OzIKzBCD_@*c{;F<5^E;U|{F{tgT(X z?UH6^RQnsP_SXI~T)E_0*Mu{zV%X&G&T{V8`vq)o^ZTPatl7>k^{P63^4oEh+t49Z zT3rV#TW#DuC8Kg0+h6DLmE-;AvCZB(7H=7r$p+tDGi-mvA(i5X>l4d<{gv|XvaQc2 zOk^)!|2@54#SFIByuC*!iH2)-kI7mBH-S;pKy8ZVqIP>Rc)v_9>2CSf{&LQ7>MVpJnn@ z-_ltb8R|FhLj3{kAOA?lbc+CXUBUi<6ThO_{q4B>Z}u}Os?2xuCp_xaQu*BZ_=Y)` zda?)W6l|%UJcvz6ZuLjIU>cihFlDa)-9%N#;objSxNoPN{>O8^!4j5z={m4Sdqp4C zD|G~aL@|-=y(w>$hy8rj-o^EWy&=}hyL%UZ(ZAD)^>?w(>Cn9!o3pLW_*O^8ur8kx zf;!EPQ9a|VqAe?S)KNx03=6JW=*<49n6vz8xSZYB=G5=f#1ZV{W@gSwe$!RAj*U)f zm}IC-aBiURE^En}M3_czvTx5;BxxrO+A)}YJZS3kXYxs^Uv+NusN}SjIT>kJt-PDC zn|6-u<$bU{tAgFfO`5Ui z!oolQ-qMqm`8QS-TaHro)cIuIBlu(2@e|MVebKZo>(lk^oA!D(?5wey?w|H$S(oCY z3sOG&sl1m@J3hs3d~-f@ z;PbBS{H^tGYsomamE3t@pRn3&iu=yg`C-m%6QhEP|EIlgkB92{|7Vx%5)tLn1?eK) zB$cweBPpUHm2`cpRJx-3MXhwxMTk1;-DOirS9DvIqS8&sE`@BNB%wOHDkAy5&Ybn8 z)qWn2-@m^{9*?J)Gy9s|nR8}dXXbgHGiST6bL!0}hdggCe9b+#(e{9NTLaIyO0Ay7 zY=?&5aGTcWejk)$GG?)H$5E;C{j6`lb$-EhAKbolW@Qa;^b$|EX|J1jf#=U!j<+#K z<8JJ5z4p;A)oV}Y>!sP*+*0lX$H2U5-ihNoS5DF8*YbL}?tPzAW`y4KTDxe=0Gm`j zvtt6z_($9|u|%xun=;B*a? zH40C1cWzwsr8YW^CpzlIsNR*q`)T>`$&1)(-n2arZuXy4sh(Q0(rEpRxWn9|-v>?A zQ%&aO#BWSt**@TfzuGmyN%<^NlePN_m51o#CxO>S3K=|v|6`B?WaZ@@MxPB zJE=_?(>oVryv_Tr31Q#0)r?kK3JD|pxRwk`)|AYU@5h$c%AVS%EkbB^s=cNvalq@h zFUQ*ut$+EusF(epDvy}!ZSO;9Cye_SKl;Vn5am3_HFVer3%9ay)cPo>xr=a=TDt`G zHeY0)O0QmOrO~BVFZtABRWG%9kRGGVzSZg_ziRsAw+MGtKC~9@rj^m3mz2weyJTOE ziyE__iuz@|waX`5hJe!=Y+_V5j_IN996AyS+xOu$A32}w*9zmTIgU)i!_B@Or4BS< zY9BdF{oO*&&eLKXkz z$dAc-IvvD$_|N;p?{=+ffj@87=6bL=__+D|*2|A$G&%pR2T5zBzbk;46lrhC_^Bw) z)oq}~xuMcl>VRf(t`@O-AjcGMkYxP}pD1d11ET?=@A2Y>mI~0yT+Ld!qOO%PcY7y} zCEo(dI1VrmK<%hN?WS-GPzr#3u~d2h#sQW9wgCiyQ~;&gRRu+54}d+u8$hWo!vJxB zR6qfs2EYPkr3GLlz#XsvumKRJ2I|R^5V;0;2%uz`9|4q{R}QZb0wx1y z0(=3>0c!!909yf}fG_|7H~}~XI0v`{xDH4G+yguYWC98Rlw|m=Mk{4*Nu7NV?tWO5 z>!-~jiVRr;*kEogjr9Tl5lOhu_BB!rJP$Be)N_y7|~ z97E{pgBG`?z5b`gJxVLzeG-_u>_qu*`w^w%R892-cJxm-i!&3ubBGHS?ZaAR^v#0Z z?dqJK{4vn9f&pg%>3~XrCN!sh07@9X2XGOP2dD)YKohb7OaiO|L<6n^iUB_XCeT`j z1EvB30eb;)07@KQ2#^7^pvCk6fVh{B72=EFAQ(U?e1(7~fcJnO09|M%{Q+D+4B!^v z6`&r_6|SfgfD(qU2ZRI80H~{943GlUz+ZF$SOG==CITq2_)@?|mLJI1A#w;154a9U z2V?>A0i}RSfD}**_ytf0^{*~~QV5#?dIJUl>;R(x&VUI3Pry8YA7BNW67FIVcl{Vx zvN5kK2QRO}dtX_PuDz;^sl#LOKWrMj?&s(z2USr*FBcj=Jq*CYjaOeptZRr6KQSk^7C?S z?i+&E=2isI+T2vjA6T2q3Z=EV78hu3?!&{hHh0wyTAMqIDaP8|+qSeemz76rbC0j0 zwYgV5(c0Yncv_p=zuyY1%^kyjfwj4-rVOC9xdE4HZElqxt<4=ii`M3D>Sutpxnoz- z+FY^qOsviQVB(Lpxt8&USeqO3oz~`Fj-|D^$x*a6SEKAW*5+Dw%){E;BPtWIHuqQF zeXPyhmOqcy=AP(4Yje%rX>IPCP8YB?cTCektj)E4>VdVnOKO6!Hh25gQmoCrwl^7T zb31S7v4zs+##^LgZElFz@I9r?)qZU^n9}Ah9yn2r(&oCokY!NX+z}g-yHMKPQA?gS zU~TR{`@WV?+T7mZtRhOA8}v=mpVHxEpv~=XaAM79(B^uX2JP5JX>^`6!~h({T2#w{5T`evCNkdt zHg~}js9R-i(wG8kPRnGe)h5-}Hh)e}8={b2M$N6%h|pe~o`jDR%V@AN$?%`jyTkph zJ29#^M}MdpL=|BUX%sA4p%=t00evYDVZAwqY%BtXLmOfM2!Z*-AWC64U_>iWx721( zOJ)_}0BNHEPJprh4{!!s3NAEEnj>w)q$EO>I$b@9#P19>BI5M$(**2udz!G`p=m$_ zm%(`P;ttJFYI2&xkWWHaR{nO;@FX-D+DY@uQhG1kv}*ad7f(A`{i#NQr!RF@xP9PF zaP5e>Fsq?AKNDC&x_k*~376x-U|N3Q5(#z8Zp=g#9`P(ly%*cZlG^1MhC9Rt2hKF6 z$Gi)g@(ILro|8<3vcL zmaGzH#rB%Qm18)^nL_J)P7E_L4HlD1ISV4wwh?K|JE#d$JUG>i$h2kjJQjv-!OZx{ z_^bu&hc&EI>1eSwo8g6{4$-~oDBd5}?9hC5N5a*OGmywhRG&xK%;7v5ZRz}bH6mIH zg-=BD%K8BB-|b@L$H_C`m%c@%v5lCavpIONzrMpP+SW{dDkDNU3JVKa{v3P-333L< zfhZZof<5?$b{jzq@q)=sA9yDK-aI#XF|I}8(e`Ys8NdI$(SG_k*`E%Z=i4#=;4FAA zox0^Xt{VxvAa{Il>|~>$N)7{XEz=_?!r~-sFkyPO7lR*-w7bS%hdYbRoQQpkfcMKHf{WfJmA~ z%>%oDbKZ-SeW(*O6GzE>STa!RN9DU<2QLf2iJ0}OAO=%km$-*>6rZ4fM4Sd<&r*3Y z6P7j?Gj*9fMx4mwgmk<|Umgc}p>#9lvxCUAIn=rUO?9E*AScc05bP#ISnZRa0(?JF zd4_*nV9jGQp5kb54~RaY%Q=C|W~8?+UEqBV?B}qaYD@E&ISV+%>(?w(H8^qd_Zi^y zqYM9>?ziaA=|OOr{`5UA9*XHv!MZGFLhv$yr*(97&wejYnRgGn6P8A3YVR;X1g zrkB|Z^w~3^fwr_3>?EH&z%`|o%rfrdD88z7k8oVq9PX`Y5T$*jRx3wDb90Gr|Ktss zY0o(nO$W<+SGO}~71I~0#Z`HW<*sVvS}jA(9QtEBNW`uRR@YYu1IDq^7;RjY&_Ti> zTf9`xJMdE0#ki;uKcJ|kI4YO~tSTM#SPt#)Ev^9U;%!DaINOaTs@?-}f!$Py0r%19 zL@`}}W^ZfMQXt&4n%z_#^-2c#Yqm2hqmLcR;rnSfrTbCtrk;GDKQqHQU`}noRd7=g z^|YH}Moi`sP5srmw5!_lR(%Aq&6wNDRXKdawW3BpR^`7JL10YZI2;=2^k!FOR)w=* zR~7sN#{+DEx~rt3hqi(f{|d3?1Z=;-)@-T9f1XNJm2mwg?|=T^9W#k3T)%XdI$91l1NO`{VXrPeS7 z8{)VJwZRs3MY|CJUO)@0cHx>*EEk*c9VZG3!9CRBXs|weBeADWUNZ1nboqsRakIw? z&u#Wt{uea1>er2zUoLs8h?&N1-k)cVuzOc-#vtsh_WY1n9XqQ~FI+C_5rQf>-U(0~ z>|iJ3WQVg*To2j;N=BGZ5Z$%Yl8+0uMlZ3XfKvg57Kz?Bi$NqM-_72KoAW=zCU?4*J{>122d4Z{th zawpX?n&Q$<%5DYs4?{Ml%SVfrHO*A^#5EnFjF{kg-K$xEp6;sI&IGF)_r^-%QXI9ft&tVBiW z@f2Yl$PJ*Kl%|0DXq4R?x&Y12N6=Cr+?bl3RIvZ=cJcD#CxmJM7w%#OcsLcanoAF- zJXX=>&3;O7LeWnd?UWCQjf#KsQwQ3OAhgc3@>8l$h`Di8>J1W)=9M}B;Eolv%Ozob+hv@P{mziwM=Bfm8TRSRoFc*)c2t2NOyOlno zoz;NNTxwi}oz-f}S*a1@-L$|@E#%r#9;;D;zMs*0W5)9mnya%!&{kCt>75=uy`I53A~kduJeyy;R5T^77)b)!W(fBN$s%5wbb0 zj~G7kqc1V-Lpm1Fm&-x!u*}~5?yyYMg=rVLiS!ujRX$gTFx$a3z^Ah5#K;4Tx42x? zHXe);9L3cLE~es#j={w(!m0A&ZY`u*sC=AN62$%E_~_#1Kc}zX^5^t@aP9sy&hlQQ zn2yI;f>PRtIq|uF9B1|P^u~EYk4mmU)jXh*{?%p|)~Xb=3nM0t<`}UWXsZ%Jub%51nU)xp zV{XoBYE>cn$(714@B-A6t3*33SK`1Yd8=)Hazz+bb9K9@bfD+%W_onk>?IRbdH4no zTct9zaTGs>hTYPCP(23T^uf@glJWj$X`0vqKfWqy5AiYd8$g;1+9-ENH{k}YO7v); zO9BpPqPp;m25%38*!yPOC_=D_?ML0-nGP~VZ{pb~Fa3!Z>}q2EFS%F6zGY>T{4@MW z#K@ekNOOnu>(8S`5U|ynogOclF#E~1rFvf!-}SICts~D{M?Ml#R|%DOVefvpHq^wI z(HBQcda*U>^Fck#BcflzzExC@Boce@%>L_-0uA0D`_VGipVRHwe@^#?+W)CP_N-Az zk05g1vJ#mURy;hYP}Ai7u|txO53@kc6Q5Ssyj6$Ph3vl%DgrF%+V6w5+Jlb5=#VIY zO4H;}j2@)Y{gHSwfM?CL9)eHyCsk@YMPPk&36?d_k5VldYg*ISCE12Y_g5ol-k-)C z1@JjFF^VctZ$})T#?$N)AkW#{D`Hx{DJcmyC<{2NZO(F{za95yLSUhx#~8+=2PdyK z>lkX<(jUh_A`DK}2z}V7f=N8lMYh6dd*0f1169h^xsG?C1=zTE(*)z--qTj$F2M=i z(TB<`aInt017S(&=)5~gNtSXsLz)xMl@Vc2v>(GW)4svJIg1`w!+vMP#~-{tcqrpH zhCX>z_phVlxT}PY#ix%_Q3w-t5JQ#l>aX6NaB$<<5hb_O_1X4xsa@JpZ)+sdzw!)5 z>kg;Wnoq|1mYU(_+uVPBTz{^6*pdC$2o&ci_VK ztc6`5^ndW^DOa-t>Gx#j@4sR9l|Clr>CI(X??u)B(0k}lGw_+fw`Vgqoca_}y{Q=X zB%#x=0Uqj2hv)=h@C06+20@0xPLVBDz@wWe<1s=aaWrok?$6Y#gs6)_ymjN@(T;G? z7@mB(^p7*IiWuoEPlqFV2trT~|Jk9McwaF|QSNsVCXuK^ z`ndB9^IVjZ^luLz$gts{%L#iI5z1?v-6b)CL}hATjKXjdIp1U4xV4`|iEg(FPE5!} zOP%k`c?9+r!y4{90Q)S-uBeh{Bs$V@WX+8X5=~vQ%PARt{HYXuckeX+nN1#=eWc?2>VXoZC39QCa2KJjdmg;v3w}z`(X5;P zzkW#3;ozsUH++|(anbrL=gE19NJeRU2T73WgQUglJVYq+hxN+(J~E{58dqZ3ONKsd zm^jSWT!t@4QJFle~`MT2<^CLV7qz}?DVVkFJs+A8QR?E#+-Go zGUOU%db%z=4;5_n<8S#gSc3X~>H75CWD&{^8!GIzL5BQI9`#mPD?{C$mS;W(>sIpsOXPy6H|AArn!e{fu%M8gS@n77>qxayzu^+J z`f9`!m6;;6z|F(%hDHqM=E_sJ-jcgi$r9 zF0Nzl5l#&{H17WN5#@Pkg~|NTSO*EJ$emKWc##OD9K0}gSdSW1#^b99y4Ud03_*3) z6XO~*!YQNU-e-B};mP_tEJq1ilXPI;gvBDHTHULozY3h0rB#mY8fB=H(-4iNbuv_c z{7ZL@D|u*d>UWFTqa{egX>+piQV|LZyO^d^0(QIX)Ws4RnmerXkAMOh;;P9EVh`sb zx4S!s3!K1D*p@J#FXM}lNHujf`<@IX_j@zB_KpngnxmVTeN%>PWkz-GA$e#w*C1Ft zMuPZRn%!2c06!5iQ`Pg73>}xIj9V;_q4+m3^XvKmnZZfpxSbm?9fq96beZlh=*dJMYakR;5DA3InKepM)5HEDD z6@SPu=ms$_7i$iYA>R%=!p3&ZLzjmnZYXn=AY04OZBy2W(8W_ddL1#7p|F0%9hP;M zp{{P-76x{aA+5q~;e5?JWOsgWZHk)&jjUD^1g#aJ$Kt+Uud`*SgG;gZISuG79lG=? zQj?(pVGD_^Kfp6~xHlnmyaWZz_vVj|S|>u829dg%HB$8HXouutsT3U_=IG1$B1IC$ zy33*?>mLL zDDR=+x*uSF{l?9+`!~R=RQv|LzLOzE8k^Z$bigBSD9jq0@<@sXsPHEV$}@72{_=;z z1rsIcZIF&&^+pl$Ief!tr%;MKFD~6ub6tvv$vY1|zA8nIjo~@Mlw8!yH6+2_U4o|8 z9gJ|>Btn{f<2RU}k)r24*}Ko5k|H$2c+;tqQk3{UHmq+#E-KomKQR;R@9b>QG6)hO zr@Lo{?uwEk7T;^;E2{%iq}~|LagLCpW$OPtwT;b1Q+>AvdwNLFR?T%MvmryDt{t*3 zhe*)`CzFPyTcv1rQ`OS*d?|7_w|FQ%n2W~Oy`XBPWYcC53jb-^K4PU5t*CZQ zSr8~i>$FVE(*31qQ<|A}k|-m)HI5ujMI0X4-{k?Vy+044iPqVXPEAG-%&>#uzEe*{*w4-$}|xuf!WX zcQ4LGGOah<`H-PWO$(`XR-wO&NUqV2{NR6 z1Wh}vFGY4A`KwhRTwfXAy+=nW^6}epnsCZRXSO6{tO0vTFniUgt9i6RoVsa%ke$jy1qz77!fyGQP> z?VpPVtZ3}r0QL^wtF+i5BGh5iy{z6JNhH~|;&XNdiC(&V&f_~nq<{J2jK{rl(a}=} z=XRMQLE9^?_O*u$6I^u`oGT{L#+Xix|3H{<$8k#a8+d)7t=s*N-E-0BGhMz71$*94 zr^^e#exk=Gor5n)G<#&P)zcxI+Gq5Px)&r0QQ7ihx=}8YIM40r2KL#BF0xRt_vd}) z^WJBX=-b!nSvMdo%S!iK1mV1*50M5PbJ2?8hl6H9hP(gTxSfFvDJ8FngAYk`IyN$C z6@;0t0_&j=Ci;jjbZ(yuuc{n7bs^Y4aG9cY8|*nhbv~WEL!t#*TJ?J&l$^{tFdM>` z)dzphY6pJeD1TqhGRSb!SGF|+GQ56w*P-1F61iqyxZH4^M8#&w$0ZORvQpn#rjmfOsEdfMrrYAS^1UT1|}hOpDWFQ%Pu5Tl>*MHi^=|91aV zT0Yo2_DR0`_$-O;`zB2gL->%tq|g255IMEeD(%8|F_O3rSWnf!#n8q0C1kKVv#So1;nhy&rZ%~nR`QZdpBt6Mn>>>YweyF3T`?T@N_?i?Y}s##lDMG$Tt zZ8$*&p)lxFk=G|NIyHcsH4*IFjqDWu0POj;VZN(QMU%)b@+SEdLZ;zrt4|R29CSCf zp+by|cd>48Aw$zBpZ-@N!-4QErI|474hr_Z*8m}-=AoAr49Dm7yJsdY6C>I69W(lZ zeS)d$tm9yBGPCg3u-#xk~IdqR`{gUOo^G8E+b{^;(QFU)qM$LaQEjkMQ;b`+cE1_NQ$jkzZm% zhjx4tB|X-kH5o#cPJ`wzDHOvGO1xfyeMDGjq%+u8AFmxKYR(otlrE)Jr|QOb<1=Ke+rl2>_U+R*sI1Iyc!7hrMxRsX8MxI_e;2K zI)tTWygQ>7K<|0>Oq-weNQ_Ru99Lcku5sSNUVV9xp|8>N&x8+&qT+lA^|>V49e(5T zZU}2RTLY}p#b|iuE7MG{7Z3>@SzzCP!N8l1vq*He>$tc6vq|(xx3W(?gl7X+XvWr zie^^6jD`%u{c3J=y-4&!_m^=pgxqy|FHD$5qLEL0V`M2}uDvRbGVf!;&wJ z9urB_`*Om{_#0y6cka^E<6wW+Ij!U%*q`ZkD|N0bi7HB*&VGb&XG~~aq#N`${>>Z1 zQm;X)?&Yvx5oDO(w_EBY$k6F$-tL>uBr2KtXq=M^oXWH{2S^CVWhd5GT@jJv^klPS;bco?E9ZRCc!ou_cNn&((c;NW&Fh2OD?e{Ys zZr|w#o)5k2NFsj5{EvDQN0TTlV|R$E6SUUh>*BU0h|#j{!LBKgq36TL`-31uRmwVn zp96_p7CcI?hw#&i{G~~wNHliptlQO>#K=Pb#-goYA2O6tW&`$7b7F_~=aHzX!~C^r z5GFF*j5i_@vFum&^}8rWHBtSqkMWT3(U3mBI#$4lrCXs*kp_oEm+DUI9Eb4B(zR=5 zaG_PN8*BFMyck{09Wt5?Kj6CI(BPwBUzfCdTm1-VbnOba9vBICLD|rClk7<(GCN*y z;G7su|24y$8geZ*IWxr_>}yS4^=H~ajUQ{~9EH%9WSh?!0sY_mw05*Zycj*`xqi-m z$Pl$pJ3SWN2jM}#K_@W&;UKK&p%s!WIotOTpIa+;Mc2w%Pym&KkIqiO2GLJz+N8NwUuPXt1SPH`Qq!~;kq?$K1aav*gg^LXKHUZumt;lzF%)owj`0$?YHb& z2pw;g=Rbn{u0N!gs*j1$^&R87YJ+{qzTr7}FfSNzM&EzBVIR23n0V>lhR`$na^zm& zi+J7x<;>W_j=b_xa3+cjnKEYVoyoX&2jLE>$dD;x#&~{GKg$eY#^e;)e+-#2X6$rn zj^1qs3}6))GG)wI?v&uYuVG}Z$dD;z#-gV_&N~f^l_Eo?j2Vl3up{2;xU>yJri>Z8 z*)42WXqNPMhKvtCsEipKbVX%Ie!a8}L#B)wdwcrDl|j8_Z5T3T%-GwV9=G}NFqNh# zvepclGG?r@HfQ%#c&C*jL#B)wbILFZaXTh!!;mRu##ZEvcqIUmLXja;#*77wUQ)cc zOxA`WQ^t(Ne;JUFtX|WKA>-2&nKEX~=$?DZ@m@7;7&2wd*nuq5l>SaNZ5T3T%-9@$ z|D+Ks=hh&J0z;;Z8SCve#$XG)LQat(Q^t&Ov`QB&2Es~_AydkXja@8xaNM+}4MV1k z8QT*Y?APs=tPMk^j2Zj-53^DAjjWj=gUyC%icBdp=J&c^cl%UX8-`37GZr2H`O(dj zK;tMdWJ;N_TyHibps8(RelrdvdrYvgu#)1L?L&k5W$S8(P88db$?fimXHPSW= znKEX~F`@Hy$2Y*PC@^G7nK9kdr3@!vt`r$EWz1Op{z+#|fw)p+$doZ-+J*1@Gfzv~ zFl0)Zv0IMsK8}h4){cp$D688W5J*xZ7YbHjkSQe?=KF=Kfndgt7R+p8i&ri>X& zZ>rz@0thPwhD4P)GG;6>KRD0v2HA!oQ^t&0K5-`+FT;#jfgw}MjP11Ae2NuMwqeMWF=NZb z3xa%3!u(HxAydYTMVhK)ss0N?_GX5RFQ>?qF=NugXY)Qr!vkjphD;eV7O`>h00S6? zD>7tCnX$9$auI(wOcWIuGG)wI(2w(3eYTTr7&2wd*a6#beY9ZysmPEiWyS}LSX7coNyC8H@a<;<9HfUr_z$doZ- z=_VWB5PmSDR$$1KGGnt=T`D{WjFloori>Z$Q9qJj4#brrL#B)wD_Uv)eLN6ViVT@j zW{kh#z27Tft`r$EWz5*2;Kl30yugnrFl5S@vBMcFgM5Lo0)~v=Op#%ROc^uge;}i5 zHV{^d44E=!Of_rZp!GmlDKcbAnX!!P^Q)48u~KBnlrdwo+W$;uyTD9Zfgw}IjO`66 z9=inyD@BG(DKl1S+T^JNgq0#gri>X|U%l#FG7wgZ44E=!EU@P>iy$DZ_{|hqGef3~ z8M~tS> zcv57@lrdwhe%|{8mSk&&jE^ZYWz5)53&*_qKv*d>^Fw&Dmv$GdUfKtdF=>+2FqOUJ1v`Yzydya9i@&?=LhpMEH|95rN__) zIa)Wu%oim8?QztDTFjw|qk_d8P!VGpT|dxh)KhT`S9Zbv|)+$?>@TV@?r^-JE#lH+l0$c~&00;re zfSZ6@fZKo+041cn3rGc|0qz0r10Dd<0S^I>0hxeoKrY|~;3Xg*Pyi?dyaE&fBr33- z%xj3e0TcsD0HuJpfOmj0zA6*HrRc0YL$stc%7K~uMvyUfVv6;rK@>F(I>~HI#V$WGlHR zEh9D?$(zBf)UpMwbv#JPYiW5WwaDxV3%6lv!z|&wX*^TV z3`ffgO)u|d^STmUYq+{ZPJ@O4v7OE9PapcYX~7l(|MOptSgk$(#QWVJ54A5#Wz0<| zHtrmidTUzP`fmM`Qi~HEt@Zc^QBuB}o#B^_NbAJ>d#tFHv^2R2#*A5B5Ax&8}RI7C0X}eB-3=fA-Cv`#v9V;>%HCvpwPVVH}(PE&QHdSi7yr zWclaFn~VVtH{RJ!ddz|c8h0B9y1r`f5PhL;;9d>Yw1A0;L$(WErhcmJZ8dG^^X4yU rE)0?vM*gGl(EHs#i@xcQsq(`7XRlBgBKN~MJ(k%v3lr@gWa|F`e|)qr diff --git a/tests/cpputest/jakstat_adjoint/tests1.cpp b/tests/cpputest/jakstat_adjoint/tests1.cpp index 7d3104b28d..6bf40b88bb 100644 --- a/tests/cpputest/jakstat_adjoint/tests1.cpp +++ b/tests/cpputest/jakstat_adjoint/tests1.cpp @@ -66,7 +66,7 @@ IGNORE_TEST(groupJakstatAdjoint, testSensitivityAdjointUnusedNanOutputs) { for (int it = 0; it < edata->nt(); ++it) { for (int iy = 0; iy < edata->nytrue(); ++iy) { if(iy == 1) - d[it * edata->nytrue() + it] = NAN; + d[it * edata->nytrue() + iy] = NAN; } } edata->setObservedData(d); @@ -76,3 +76,64 @@ IGNORE_TEST(groupJakstatAdjoint, testSensitivityAdjointUnusedNanOutputs) { for(int i = 0; i < model->nplist(); ++i) CHECK_FALSE(std::isnan(rdata->sllh[i])); } + + +TEST(groupJakstatAdjoint, testSensitivityReplicates) { + // Check that we can handle replicates correctly + + auto model = getModel(); + auto solver = model->getSolver(); + amici::hdf5::readModelDataFromHDF5( + NEW_OPTION_FILE, *model, + "/model_jakstat_adjoint/sensiadjoint/options"); + amici::hdf5::readSolverSettingsFromHDF5( + NEW_OPTION_FILE, *solver, + "/model_jakstat_adjoint/sensiadjoint/options"); + amici::ExpData edata(*model); + + // No replicate, no sensi + edata.setTimepoints({10.0}); + auto d = edata.getObservedData(); + for (int it = 0; it < edata.nt(); ++it) { + for (int iy = 0; iy < edata.nytrue(); ++iy) { + if(iy == 0) { + d[it * edata.nytrue() + iy] = 1.0; + } else { + d[it * edata.nytrue() + iy] = NAN; + } + } + } + edata.setObservedData(d); + edata.setObservedDataStdDev(1.0); + + solver->setSensitivityOrder(amici::SensitivityOrder::none); + auto rdata1 = runAmiciSimulation(*solver, &edata, *model); + auto llh1 = rdata1->llh; + + // forward + replicates + edata.setTimepoints({10.0, 10.0}); + d = edata.getObservedData(); + for (int it = 0; it < edata.nt(); ++it) { + for (int iy = 0; iy < edata.nytrue(); ++iy) { + if(iy == 0) { + d[it * edata.nytrue() + iy] = 1.0; + } else { + d[it * edata.nytrue() + iy] = NAN; + } + } + } + edata.setObservedData(d); + edata.setObservedDataStdDev(1.0); + + solver->setSensitivityOrder(amici::SensitivityOrder::first); + solver->setSensitivityMethod(amici::SensitivityMethod::forward); + auto rdata2 = runAmiciSimulation(*solver, &edata, *model); + auto llh2 = rdata2->llh; + DOUBLES_EQUAL(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); +} diff --git a/tests/cpputest/neuron/tests1.cpp b/tests/cpputest/neuron/tests1.cpp index b8bccd4a9e..82e0038585 100644 --- a/tests/cpputest/neuron/tests1.cpp +++ b/tests/cpputest/neuron/tests1.cpp @@ -19,9 +19,11 @@ TEST_GROUP(groupNeuron) TEST(groupNeuron, testSimulation) { - amici::simulateVerifyWrite("/model_neuron/nosensi/", 10*TEST_ATOL, 10*TEST_RTOL); + amici::simulateVerifyWrite("/model_neuron/nosensi/", + 100*TEST_ATOL, 100*TEST_RTOL); } TEST(groupNeuron, testSensitivityForward) { - amici::simulateVerifyWrite("/model_neuron/sensiforward/", 10*TEST_ATOL, 10*TEST_RTOL); + amici::simulateVerifyWrite("/model_neuron/sensiforward/", + 10*TEST_ATOL, 10*TEST_RTOL); } diff --git a/tests/cpputest/steadystate/tests1.cpp b/tests/cpputest/steadystate/tests1.cpp index 8ccbb927ac..d9b5bec421 100644 --- a/tests/cpputest/steadystate/tests1.cpp +++ b/tests/cpputest/steadystate/tests1.cpp @@ -108,7 +108,8 @@ TEST(groupSteadystate, testRethrow) { } TEST(groupSteadystate, testSimulation) { - amici::simulateVerifyWrite("/model_steadystate/nosensi/"); + amici::simulateVerifyWrite("/model_steadystate/nosensi/", + 100*TEST_ATOL, 100*TEST_RTOL); } TEST(groupSteadystate, testSensitivityForward) { diff --git a/tests/cpputest/testfunctions.h b/tests/cpputest/testfunctions.h index 32a70252b4..ac07dd2a0e 100644 --- a/tests/cpputest/testfunctions.h +++ b/tests/cpputest/testfunctions.h @@ -88,31 +88,41 @@ class Model_Test : public Model { virtual std::unique_ptr getSolver() override { throw AmiException("not implemented"); } - virtual void froot(realtype t, AmiVector *x, AmiVector *dx, realtype *root) override { + virtual void froot(const realtype t, const AmiVector &x, + const AmiVector &dx, gsl::span root) override { throw AmiException("not implemented"); } - virtual void fxdot(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot) override { + virtual void fxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, AmiVector &xdot) override { throw AmiException("not implemented"); } - virtual void fsxdot(realtype t, AmiVector *x, AmiVector *dx, int ip, AmiVector *sx, AmiVector *sdx, AmiVector *sxdot) override { + virtual void fsxdot(const realtype t, const AmiVector &x, + const AmiVector &dx, const int ip, const AmiVector &sx, + const AmiVector &sdx, AmiVector &sxdot) override { throw AmiException("not implemented"); } - virtual void fJ(realtype t, realtype cj, AmiVector *x, AmiVector *dx, AmiVector *xdot, SUNMatrix J) override { + virtual void fJ(const realtype t, const realtype cj, const AmiVector &x, + const AmiVector &dx, const AmiVector &xdot, SUNMatrix J) + override { throw AmiException("not implemented"); } - virtual void fJSparse(realtype t, realtype cj, AmiVector *x, AmiVector *dx, - AmiVector *xdot, SUNMatrix J) override { + virtual void fJSparse(const realtype t, const realtype cj, + const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot, SUNMatrix J) override { throw AmiException("not implemented"); } - virtual void fJDiag(realtype t, AmiVector *Jdiag, realtype cj, AmiVector *x, - AmiVector *dx) override { + virtual void fJDiag(const realtype t, AmiVector &Jdiag, + const realtype cj, const AmiVector &x, + const AmiVector &dx) override { throw AmiException("not implemented"); } - virtual void fdxdotdp(realtype t, AmiVector *x, AmiVector *dx) override { + virtual void fdxdotdp(const realtype t, const AmiVector &x, + const AmiVector &dx) override { throw AmiException("not implemented"); } - virtual void fJv(realtype t, AmiVector *x, AmiVector *dx, AmiVector *xdot, - AmiVector *v, AmiVector *nJv, realtype cj) override { + virtual void fJv(const realtype t, const AmiVector &x, const AmiVector &dx, + const AmiVector &xdot,const AmiVector &v, AmiVector &nJv, + const realtype cj) override { throw AmiException("not implemented"); } diff --git a/tests/cpputest/unittests/tests1.cpp b/tests/cpputest/unittests/tests1.cpp index e8190e50b2..5a17aa114f 100644 --- a/tests/cpputest/unittests/tests1.cpp +++ b/tests/cpputest/unittests/tests1.cpp @@ -655,13 +655,13 @@ TEST(solver, testSettersGettersWithSetup) static_cast(sensi_meth)); auto rdata = - std::unique_ptr(new ReturnData(solver, &testModel)); + 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(&x, &dx, &sx, &sdx, &testModel); + solver.setup(0, &testModel, x, dx, sx, sdx); testSolverGetterSetters(solver, sensi_meth, @@ -808,7 +808,7 @@ TEST_GROUP(sunmatrixwrapper) SUNMatrixWrapper A = SUNMatrixWrapper(3, 2); // result std::vector d{1.3753, 1.5084, 1.1655}; - + void setup() { SM_ELEMENT_D(A.get(), 0, 0) = 0.69; SM_ELEMENT_D(A.get(), 1, 0) = 0.32; @@ -817,7 +817,7 @@ TEST_GROUP(sunmatrixwrapper) SM_ELEMENT_D(A.get(), 1, 1) = 0.44; SM_ELEMENT_D(A.get(), 2, 1) = 0.38; } - + void teardown() {} }; @@ -827,7 +827,7 @@ TEST(sunmatrixwrapper, sparse_multiply) auto c(a); //copy c A_sparse.multiply(c, b); checkEqualArray(d, c, TEST_ATOL, TEST_RTOL, "multiply"); - + A_sparse = SUNMatrixWrapper(A, 0.0, CSC_MAT); c = a; //copy c A_sparse.multiply(c, b); diff --git a/tests/cpputest/unittests/testsSerialization.cpp b/tests/cpputest/unittests/testsSerialization.cpp index aae97bdce8..423ed3e1eb 100644 --- a/tests/cpputest/unittests/testsSerialization.cpp +++ b/tests/cpputest/unittests/testsSerialization.cpp @@ -158,7 +158,7 @@ TEST(dataSerialization, testString) { std::vector(nx,0.0), std::vector(nz,0)); - amici::ReturnData r(solver, &m); + amici::ReturnData r(solver, m); std::string serialized = amici::serializeToString(r); @@ -182,7 +182,3 @@ TEST(dataSerialization, testStdVec) { CHECK_TRUE(solver == v); } - - - - diff --git a/tests/testModels.py b/tests/testModels.py index bf11007d46..29f4bfba9e 100755 --- a/tests/testModels.py +++ b/tests/testModels.py @@ -78,10 +78,12 @@ def assert_fun(x): rdata = amici.runAmiciSimulation(self.model, self.solver, edata) + check_derivative_opts = dict() + if model_name == 'model_nested_events': - rtol = 1e-2 - else: - rtol = 1e-4 + check_derivative_opts['rtol'] = 1e-2 + elif model_name == 'model_events': + check_derivative_opts['atol'] = 1e-3 if edata \ and self.solver.getSensitivityMethod() \ @@ -90,26 +92,23 @@ def assert_fun(x): and not model_name.startswith('model_neuron') \ and not case.endswith('byhandpreeq'): check_derivatives(self.model, self.solver, edata, - assert_fun, rtol=rtol) + assert_fun, **check_derivative_opts) - if model_name == 'model_neuron_o2': - verify_simulation_results( - rdata, expected_results[subTest][case]['results'], - assert_fun, - atol=1e-5, rtol=1e-3 - ) - elif model_name == 'model_robertson' and \ + verify_simulation_opts = dict() + + if model_name.startswith('model_neuron'): + verify_simulation_opts['atol'] = 1e-5 + verify_simulation_opts['rtol'] = 1e-2 + + if model_name.startswith('model_robertson') and \ case == 'sensiforwardSPBCG': - verify_simulation_results( - rdata, expected_results[subTest][case]['results'], - assert_fun, - atol=1e-3, rtol=1e-3 - ) - else: - verify_simulation_results( - rdata, expected_results[subTest][case]['results'], - assert_fun, - ) + verify_simulation_opts['atol'] = 1e-3 + verify_simulation_opts['rtol'] = 1e-3 + + verify_simulation_results( + rdata, expected_results[subTest][case]['results'], + assert_fun, **verify_simulation_opts + ) if model_name == 'model_steadystate' and \ case == 'sensiforwarderrorint': @@ -132,12 +131,12 @@ def assert_fun(x): verify_simulation_results( rdatas[0], expected_results[subTest][case]['results'], - assert_fun, + assert_fun, **verify_simulation_opts ) verify_simulation_results( rdatas[1], expected_results[subTest][case]['results'], - assert_fun, + assert_fun, **verify_simulation_opts ) self.assertRaises( From 88ba233600bb426503cde10fbc82813862a089b0 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 6 May 2019 09:52:29 +0200 Subject: [PATCH 03/26] Strip 'v' off CMake version string --- cmake/version.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/version.cmake b/cmake/version.cmake index 0da979f9e7..b8b3979134 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -1,6 +1,6 @@ find_package(Git) if(Git_FOUND) - execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | tr -d '\n'" + execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | cut -c 2- | tr -d '\n'" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE PROJECT_VERSION ) From 569b796c8850a6b385ddaf82e10adffa60cf1880 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 6 May 2019 10:18:01 +0200 Subject: [PATCH 04/26] Check for correct AMICI version for model in CMake --- cmake/version.cmake | 2 +- python/amici/ode_export.py | 3 ++- src/CMakeLists.template.cmake | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/cmake/version.cmake b/cmake/version.cmake index b8b3979134..cfb533678e 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -1,6 +1,6 @@ find_package(Git) if(Git_FOUND) - execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | cut -c 2- | tr -d '\n'" + execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | cut -c 2- | tr -d '\n' | sed -r s/-/./" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE PROJECT_VERSION ) diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index de7e58b015..bfba07bab2 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -2451,7 +2451,8 @@ def _writeCMakeFile(self): + '_rowvals.cpp ') templateData = {'MODELNAME': self.modelName, - 'SOURCES': '\n'.join(sources)} + 'SOURCES': '\n'.join(sources), + 'AMICI_VERSION': __version__} applyTemplate( MODEL_CMAKE_TEMPLATE_FILE, os.path.join(self.modelPath, 'CMakeLists.txt'), diff --git a/src/CMakeLists.template.cmake b/src/CMakeLists.template.cmake index f0d9b18331..0b1e27e91f 100644 --- a/src/CMakeLists.template.cmake +++ b/src/CMakeLists.template.cmake @@ -23,7 +23,7 @@ foreach(FLAG ${MY_CXX_FLAGS}) endif() endforeach(FLAG) -find_package(Amici HINTS ${CMAKE_CURRENT_LIST_DIR}/../../build) +find_package(Amici TPL_AMICI_VERSION HINTS ${CMAKE_CURRENT_LIST_DIR}/../../build) message(STATUS "Found AMICI ${Amici_DIR}") set(MODEL_DIR ${CMAKE_CURRENT_LIST_DIR}) From 27376f778539ed2649bedaae5e3ba3ee084edf1a Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 6 May 2019 12:37:11 +0200 Subject: [PATCH 05/26] Use CLI-provided name as name for generated model --- python/bin/amici_import_petab.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/python/bin/amici_import_petab.py b/python/bin/amici_import_petab.py index 26a5ba3dc8..2cf7312b17 100755 --- a/python/bin/amici_import_petab.py +++ b/python/bin/amici_import_petab.py @@ -246,9 +246,10 @@ def main(): parameter_file=args.parameter_file_name) petab.lint_problem(pp) - import_model(args.sbml_file_name, - args.condition_file_name, - args.measurement_file_name, + import_model(model_name=args.model_name, + sbml_file=args.sbml_file_name, + condition_file=args.condition_file_name, + measurement_file=args.measurement_file_name, model_output_dir=args.model_output_dir, compile=args.compile, verbose=True) From ba856d7d80e41676c095cccde0af3248de28fdea Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 7 May 2019 15:03:33 +0200 Subject: [PATCH 06/26] Fix(python) Create manifest file for model packages to create function source distribution (Fixes #698) --- python/amici/MANIFEST.template.in | 1 + python/amici/ode_export.py | 3 ++- python/sdist/amici/MANIFEST.template.in | 1 + 3 files changed, 4 insertions(+), 1 deletion(-) create mode 100644 python/amici/MANIFEST.template.in create mode 120000 python/sdist/amici/MANIFEST.template.in diff --git a/python/amici/MANIFEST.template.in b/python/amici/MANIFEST.template.in new file mode 100644 index 0000000000..eb3b1b450f --- /dev/null +++ b/python/amici/MANIFEST.template.in @@ -0,0 +1 @@ +include *.cpp *.h diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index bfba07bab2..52f2cce95f 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -2496,7 +2496,8 @@ def _writeModuleSetup(self): 'PACKAGE_VERSION': '0.1.0'} applyTemplate(os.path.join(amiciModulePath, 'setup.template.py'), os.path.join(self.modelPath, 'setup.py'), templateData) - + applyTemplate(os.path.join(amiciModulePath, 'MANIFEST.template.in'), + os.path.join(self.modelPath, 'MANIFEST.in'), {}) # write __init__.py for the model module if not os.path.exists(os.path.join(self.modelPath, self.modelName)): os.makedirs(os.path.join(self.modelPath, self.modelName)) diff --git a/python/sdist/amici/MANIFEST.template.in b/python/sdist/amici/MANIFEST.template.in new file mode 120000 index 0000000000..7615b2cce2 --- /dev/null +++ b/python/sdist/amici/MANIFEST.template.in @@ -0,0 +1 @@ +../../amici/MANIFEST.template.in \ No newline at end of file From b42747b00a2f5f8efd62379d1260c546890d5a0f Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 8 May 2019 14:08:53 +0200 Subject: [PATCH 07/26] Fix sed -r for OSX? --- cmake/version.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/version.cmake b/cmake/version.cmake index cfb533678e..dcdbcb2f9e 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -1,6 +1,6 @@ find_package(Git) if(Git_FOUND) - execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | cut -c 2- | tr -d '\n' | sed -r s/-/./" + execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | cut -c 2- | tr -d '\n' | sed -E s/-/./" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} OUTPUT_VARIABLE PROJECT_VERSION ) From 50bcbc74f397a2814a32162363e9f3aebecdc66c Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 8 May 2019 18:00:26 +0200 Subject: [PATCH 08/26] For CMake, get AMICI version from version.txt to avoid issues of unmerged tagged branches and building from non-git directory --- cmake/version.cmake | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cmake/version.cmake b/cmake/version.cmake index dcdbcb2f9e..0a84b5cb26 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -2,7 +2,11 @@ find_package(Git) if(Git_FOUND) execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | cut -c 2- | tr -d '\n' | sed -E s/-/./" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - OUTPUT_VARIABLE PROJECT_VERSION + OUTPUT_VARIABLE PROJECT_VERSION_GIT ) endif() +execute_process(COMMAND sh -c "cat version.txt" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE PROJECT_VERSION + ) From e005bee350631f51f4454ee8cf1ae70af4fab695 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 8 May 2019 20:15:06 +0200 Subject: [PATCH 09/26] Fix(matlab) Fix gsl include path for windows --- matlab/@amimodel/compileAndLinkModel.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/matlab/@amimodel/compileAndLinkModel.m b/matlab/@amimodel/compileAndLinkModel.m index 1e709f9ac7..b5c4f23340 100644 --- a/matlab/@amimodel/compileAndLinkModel.m +++ b/matlab/@amimodel/compileAndLinkModel.m @@ -61,8 +61,9 @@ function compileAndLinkModel(modelname, modelSourceFolder, coptim, debug, funs, %% Third party libraries dependencyPath = fullfile(amiciRootPath, 'ThirdParty'); + gslPath = fullfile(dependencyPath, 'gsl'); [objectsstr, includesstr] = compileAMICIDependencies(dependencyPath, objectFolder, objectFileSuffix, COPT, DEBUG); - includesstr = strcat(includesstr,' -I"', modelSourceFolder, '"', ' -I"', dependencyPath, '/gsl/"'); + includesstr = strcat(includesstr,' -I"', modelSourceFolder, '"', ' -I"', gslPath, '"'); %% Recompile AMICI base files if necessary [objectStrAmici] = compileAmiciBase(amiciRootPath, objectFolder, objectFileSuffix, includesstr, DEBUG, COPT); From fa004776e65b8da1784bf9058f3c56168481959d Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 8 May 2019 20:15:26 +0200 Subject: [PATCH 10/26] Fix(typo) --- matlab/auxiliary/compileAMICIDependencies.m | 40 ++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/matlab/auxiliary/compileAMICIDependencies.m b/matlab/auxiliary/compileAMICIDependencies.m index 164c3ce34a..d433ce45bb 100644 --- a/matlab/auxiliary/compileAMICIDependencies.m +++ b/matlab/auxiliary/compileAMICIDependencies.m @@ -3,31 +3,31 @@ sundials_path = fullfile(dependencyPath,'sundials'); sundials_ver = '4.0.2'; - + ssparse_path = fullfile(dependencyPath,'SuiteSparse'); ssparse_ver = '4.5.3'; - + lapack_path = fullfile(dependencyPath,'lapack-3.5.0'); % currently not used, lapack implementation still needs to be done lapack_ver = '3.5.0'; - - + + version_file = fullfile(objectFolder, 'versions.txt'); [del_sundials, del_ssparse, del_lapack] = checkVersions(version_file, sundials_ver, ssparse_ver, lapack_ver); - + % assemble objectsstr objectsstr = ''; objects_sundials = getObjectsSundials(o_suffix); for j=1:length(objects_sundials) objectsstr = strcat(objectsstr,' "',fullfile(objectFolder,objects_sundials{j}),'"'); end - + objects_ssparse = getObjectsSSparse(o_suffix); for j=1:length(objects_ssparse) objectsstr = strcat(objectsstr,' "',fullfile(objectFolder,objects_ssparse{j}),'"'); end includesstr = getIncludeString(fullfile(fileparts(dependencyPath)), sundials_path, ssparse_path); - + % collect files that need to be recompiled sources_sundials = getSourcesSundials(); sourcesToCompile = ''; @@ -42,23 +42,23 @@ sourcesToCompile = [sourcesToCompile, ' ', fullfile(ssparse_path,sources_ssparse{j})]; end end - - % sundials compatiable int type for suitesparse + + % sundials compatible int type for suitesparse COPT = [COPT ' -DDLONG']; - + % compile if(~strcmp(sourcesToCompile, '')) eval(['mex ' DEBUG ' ' COPT ' -c -outdir ' ... objectFolder ... includesstr ' ' sourcesToCompile ]); end - - % only write versions.txt if we are done compiling + + % only write versions.txt if we are done compiling fid = fopen(version_file,'w'); fprintf(fid,[sundials_ver '\r']); fprintf(fid,[ssparse_ver '\r']); fprintf(fid,[lapack_ver '\r']); - fclose(fid); + fclose(fid); end function includesstr = getIncludeString(amici_root_path, sundials_path, ssparse_path) @@ -216,7 +216,7 @@ end function objects_ssparse = getObjectsSSparse(o_suffix) - + objects_ssparse = { 'klu_analyze_given.o'; 'klu_analyze.o'; @@ -254,7 +254,7 @@ 'btf_strongcomp.o'; 'SuiteSparse_config.o'; }; - + if(~strcmp(o_suffix, '.o')) objects_ssparse = strrep(objects_ssparse, '.o', o_suffix); end @@ -321,7 +321,7 @@ 'cvodes_io.o'; 'cvodes_direct.o'; }; - + if(~strcmp(o_suffix, '.o')) objects_sundials = strrep(objects_sundials, '.o', o_suffix); end @@ -337,7 +337,7 @@ % % Return values: % result: flag indicating whether ver1 ist newer than ver2 @type boolean - + ver1Parts = getParts(ver1str); ver2Parts = getParts(ver2str); if ver2Parts(1) ~= ver1Parts(1) % major version @@ -349,7 +349,7 @@ else result = ver2Parts(4) < ver1Parts(4); end -end +end function parts = getParts(V) % getParts takes an input version string and returns an array @@ -360,7 +360,7 @@ % % Return values: % parts: array containing the version numbers @type double - + parts = sscanf(V, '%d.%d.%d.%d')'; if length(parts) < 3 parts(3) = 0; % zero-fills to 3 elements @@ -368,4 +368,4 @@ if length(parts) < 4 parts(4) = 0; % zero-fills to 3 elements end -end +end From 8eab83abfabeb2719cdddffb97fd9c0a79497969 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 8 May 2019 20:19:34 +0200 Subject: [PATCH 11/26] Fix extraneous newline at end of version string --- cmake/version.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/version.cmake b/cmake/version.cmake index 0a84b5cb26..6e16787f96 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -6,7 +6,7 @@ if(Git_FOUND) ) endif() -execute_process(COMMAND sh -c "cat version.txt" +execute_process(COMMAND sh -c "cat version.txt | tr -d '\n'" WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} OUTPUT_VARIABLE PROJECT_VERSION ) From 4ffd3fa6e060a4e1078b6e533b25b2076479ca88 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Thu, 9 May 2019 13:55:00 +0200 Subject: [PATCH 12/26] Update FAQ --- documentation/FAQ.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/documentation/FAQ.md b/documentation/FAQ.md index b84f6080d5..cbdaf30353 100644 --- a/documentation/FAQ.md +++ b/documentation/FAQ.md @@ -8,10 +8,24 @@ __A__: Remove the corresponding model directory located in AMICI/models/*yourmod __Q__: It still does not compile. +__A__: Remove the directory AMICI/models/`mexext` and compile again. + +--- + +__Q__: It still does not compile. + __A__: Make an [issue](https://github.com/ICB-DCM/AMICI/issues) and we will have a look. --- +__Q__: My Python-generated model does not compile from MATLAB. + +__A__: Try building any of the available examples before. If this succeeds, +retry building the original model. Some dependencies might not be built +correctly when using only the `compileMexFile.m` script. + +--- + __Q__: I get an out of memory error while compiling my model on a Windows machine. __A__: This may be due to an old compiler version. See [issue #161](https://github.com/ICB-DCM/AMICI/issues/161) for instructions on how to install a new compiler. From 9862fa8188ea24a117e50a763833e939113cb671 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 10 May 2019 13:21:25 +0200 Subject: [PATCH 13/26] Replace version template string in CMakeLists.txt during matlab model generation --- matlab/@amimodel/generateC.m | 1 + 1 file changed, 1 insertion(+) diff --git a/matlab/@amimodel/generateC.m b/matlab/@amimodel/generateC.m index f40f869ac4..3ef9cf1ce3 100644 --- a/matlab/@amimodel/generateC.m +++ b/matlab/@amimodel/generateC.m @@ -256,6 +256,7 @@ function generateCMakeFile(this) t = template(); t.add('TPL_MODELNAME', this.modelname); t.add('TPL_SOURCES', sourceStr); + t.add('TPL_AMICI_VERSION', ''); CMakeFileName = fullfile(this.wrap_path,'models',this.modelname,'CMakeLists.txt'); CMakeTemplateFileName = fullfile(fileparts(fileparts(fileparts(mfilename('fullpath')))), 'src' , 'CMakeLists.template.cmake'); t.replace(CMakeTemplateFileName, CMakeFileName); From e65a45d66630792dd53be52c98cc6749d4d87d29 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Sun, 12 May 2019 04:50:51 +0200 Subject: [PATCH 14/26] Misc (#696) * reuse solver, fixes #541 (#630) * cvodes implementation * idas implementation * overhaul solver interface and actually reuse solver memory * improve cvodes and idas solver interfaces * bugfixes * refactor solver interfaces * cleanup * bugfixes * bugfixes * fix doc * fix undefined symbol * refactor solver code * refactor solver, forwardproblem, backwardproblem * make compilable * fix initialization of xB * fix synchronisation of NVectors and reset t0 on setup * fixes for steadystateproblem and post discontinuity reinitialization * refactor passing of AmiVectors from pointers to by-references * add more consts to AmiVector in Model and improve solver interface * fix storage of solver time for adjoint problem * fix DAE evaluation * fix extraction of results from steadystate solver * fix timepoint extraction in forwardproblem * fix solver documentation * loosen tolerances for steadystate and neuron * fix reInitPostProcessB * Add test for replicate data handling with forward and adjoint sensitivities * fix test tolerances * adress review comments * fix cppcheck issues * fix folding * Some refactoring, cleanup, doc; part 1 * Some refactoring, cleanup, doc; done * Re-add required python/examples/example_presimulation/model_presimulation.xml * fix passing of initials to ss problem and extraction of variables from solver * fixes doxygen * clang format heavily refactored code * Fix merge: const * Tidy up * Tidy up * Make backtrace code reusable * Fix checking of wrong parameter scaling vector * Use gsl::span, add make_span, cleanup checkFinite * Change const members to non-const avoid const_casts which potentially result in UB * Cleanup * Refactor(core) Move static Solver_* members (SUNDIALS callbacks) out of class * Cleanup * Show location of used AMICI library * remove calcIC in from IDASolver:reInit * fix doxygen and clang format respective headers * fix reinitialization for DAE problems, update expected results * Strip 'v' off CMake version string * Check for correct AMICI version for model in CMake * Use CLI-provided name as name for generated model * Fix(python) Create manifest file for model packages to create function source distribution (Fixes #698) * Fix sed -r for OSX? * For CMake, get AMICI version from version.txt to avoid issues of unmerged tagged branches and building from non-git directory * Fix(matlab) Fix gsl include path for windows * Fix(typo) * Fix extraneous newline at end of version string * Update FAQ * Replace version template string in CMakeLists.txt during matlab model generation --- cmake/version.cmake | 8 +++-- documentation/FAQ.md | 14 ++++++++ matlab/@amimodel/compileAndLinkModel.m | 3 +- matlab/@amimodel/generateC.m | 1 + matlab/auxiliary/compileAMICIDependencies.m | 40 ++++++++++----------- python/amici/MANIFEST.template.in | 1 + python/amici/ode_export.py | 6 ++-- python/bin/amici_import_petab.py | 7 ++-- python/sdist/amici/MANIFEST.template.in | 1 + src/CMakeLists.template.cmake | 2 +- 10 files changed, 54 insertions(+), 29 deletions(-) create mode 100644 python/amici/MANIFEST.template.in create mode 120000 python/sdist/amici/MANIFEST.template.in diff --git a/cmake/version.cmake b/cmake/version.cmake index 0da979f9e7..6e16787f96 100644 --- a/cmake/version.cmake +++ b/cmake/version.cmake @@ -1,8 +1,12 @@ find_package(Git) if(Git_FOUND) - execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | tr -d '\n'" + execute_process(COMMAND sh -c "${GIT_EXECUTABLE} describe --abbrev=4 --dirty=-dirty --always --tags | cut -c 2- | tr -d '\n' | sed -E s/-/./" WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - OUTPUT_VARIABLE PROJECT_VERSION + OUTPUT_VARIABLE PROJECT_VERSION_GIT ) endif() +execute_process(COMMAND sh -c "cat version.txt | tr -d '\n'" + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR} + OUTPUT_VARIABLE PROJECT_VERSION + ) diff --git a/documentation/FAQ.md b/documentation/FAQ.md index b84f6080d5..cbdaf30353 100644 --- a/documentation/FAQ.md +++ b/documentation/FAQ.md @@ -8,10 +8,24 @@ __A__: Remove the corresponding model directory located in AMICI/models/*yourmod __Q__: It still does not compile. +__A__: Remove the directory AMICI/models/`mexext` and compile again. + +--- + +__Q__: It still does not compile. + __A__: Make an [issue](https://github.com/ICB-DCM/AMICI/issues) and we will have a look. --- +__Q__: My Python-generated model does not compile from MATLAB. + +__A__: Try building any of the available examples before. If this succeeds, +retry building the original model. Some dependencies might not be built +correctly when using only the `compileMexFile.m` script. + +--- + __Q__: I get an out of memory error while compiling my model on a Windows machine. __A__: This may be due to an old compiler version. See [issue #161](https://github.com/ICB-DCM/AMICI/issues/161) for instructions on how to install a new compiler. diff --git a/matlab/@amimodel/compileAndLinkModel.m b/matlab/@amimodel/compileAndLinkModel.m index 1e709f9ac7..b5c4f23340 100644 --- a/matlab/@amimodel/compileAndLinkModel.m +++ b/matlab/@amimodel/compileAndLinkModel.m @@ -61,8 +61,9 @@ function compileAndLinkModel(modelname, modelSourceFolder, coptim, debug, funs, %% Third party libraries dependencyPath = fullfile(amiciRootPath, 'ThirdParty'); + gslPath = fullfile(dependencyPath, 'gsl'); [objectsstr, includesstr] = compileAMICIDependencies(dependencyPath, objectFolder, objectFileSuffix, COPT, DEBUG); - includesstr = strcat(includesstr,' -I"', modelSourceFolder, '"', ' -I"', dependencyPath, '/gsl/"'); + includesstr = strcat(includesstr,' -I"', modelSourceFolder, '"', ' -I"', gslPath, '"'); %% Recompile AMICI base files if necessary [objectStrAmici] = compileAmiciBase(amiciRootPath, objectFolder, objectFileSuffix, includesstr, DEBUG, COPT); diff --git a/matlab/@amimodel/generateC.m b/matlab/@amimodel/generateC.m index f40f869ac4..3ef9cf1ce3 100644 --- a/matlab/@amimodel/generateC.m +++ b/matlab/@amimodel/generateC.m @@ -256,6 +256,7 @@ function generateCMakeFile(this) t = template(); t.add('TPL_MODELNAME', this.modelname); t.add('TPL_SOURCES', sourceStr); + t.add('TPL_AMICI_VERSION', ''); CMakeFileName = fullfile(this.wrap_path,'models',this.modelname,'CMakeLists.txt'); CMakeTemplateFileName = fullfile(fileparts(fileparts(fileparts(mfilename('fullpath')))), 'src' , 'CMakeLists.template.cmake'); t.replace(CMakeTemplateFileName, CMakeFileName); diff --git a/matlab/auxiliary/compileAMICIDependencies.m b/matlab/auxiliary/compileAMICIDependencies.m index 164c3ce34a..d433ce45bb 100644 --- a/matlab/auxiliary/compileAMICIDependencies.m +++ b/matlab/auxiliary/compileAMICIDependencies.m @@ -3,31 +3,31 @@ sundials_path = fullfile(dependencyPath,'sundials'); sundials_ver = '4.0.2'; - + ssparse_path = fullfile(dependencyPath,'SuiteSparse'); ssparse_ver = '4.5.3'; - + lapack_path = fullfile(dependencyPath,'lapack-3.5.0'); % currently not used, lapack implementation still needs to be done lapack_ver = '3.5.0'; - - + + version_file = fullfile(objectFolder, 'versions.txt'); [del_sundials, del_ssparse, del_lapack] = checkVersions(version_file, sundials_ver, ssparse_ver, lapack_ver); - + % assemble objectsstr objectsstr = ''; objects_sundials = getObjectsSundials(o_suffix); for j=1:length(objects_sundials) objectsstr = strcat(objectsstr,' "',fullfile(objectFolder,objects_sundials{j}),'"'); end - + objects_ssparse = getObjectsSSparse(o_suffix); for j=1:length(objects_ssparse) objectsstr = strcat(objectsstr,' "',fullfile(objectFolder,objects_ssparse{j}),'"'); end includesstr = getIncludeString(fullfile(fileparts(dependencyPath)), sundials_path, ssparse_path); - + % collect files that need to be recompiled sources_sundials = getSourcesSundials(); sourcesToCompile = ''; @@ -42,23 +42,23 @@ sourcesToCompile = [sourcesToCompile, ' ', fullfile(ssparse_path,sources_ssparse{j})]; end end - - % sundials compatiable int type for suitesparse + + % sundials compatible int type for suitesparse COPT = [COPT ' -DDLONG']; - + % compile if(~strcmp(sourcesToCompile, '')) eval(['mex ' DEBUG ' ' COPT ' -c -outdir ' ... objectFolder ... includesstr ' ' sourcesToCompile ]); end - - % only write versions.txt if we are done compiling + + % only write versions.txt if we are done compiling fid = fopen(version_file,'w'); fprintf(fid,[sundials_ver '\r']); fprintf(fid,[ssparse_ver '\r']); fprintf(fid,[lapack_ver '\r']); - fclose(fid); + fclose(fid); end function includesstr = getIncludeString(amici_root_path, sundials_path, ssparse_path) @@ -216,7 +216,7 @@ end function objects_ssparse = getObjectsSSparse(o_suffix) - + objects_ssparse = { 'klu_analyze_given.o'; 'klu_analyze.o'; @@ -254,7 +254,7 @@ 'btf_strongcomp.o'; 'SuiteSparse_config.o'; }; - + if(~strcmp(o_suffix, '.o')) objects_ssparse = strrep(objects_ssparse, '.o', o_suffix); end @@ -321,7 +321,7 @@ 'cvodes_io.o'; 'cvodes_direct.o'; }; - + if(~strcmp(o_suffix, '.o')) objects_sundials = strrep(objects_sundials, '.o', o_suffix); end @@ -337,7 +337,7 @@ % % Return values: % result: flag indicating whether ver1 ist newer than ver2 @type boolean - + ver1Parts = getParts(ver1str); ver2Parts = getParts(ver2str); if ver2Parts(1) ~= ver1Parts(1) % major version @@ -349,7 +349,7 @@ else result = ver2Parts(4) < ver1Parts(4); end -end +end function parts = getParts(V) % getParts takes an input version string and returns an array @@ -360,7 +360,7 @@ % % Return values: % parts: array containing the version numbers @type double - + parts = sscanf(V, '%d.%d.%d.%d')'; if length(parts) < 3 parts(3) = 0; % zero-fills to 3 elements @@ -368,4 +368,4 @@ if length(parts) < 4 parts(4) = 0; % zero-fills to 3 elements end -end +end diff --git a/python/amici/MANIFEST.template.in b/python/amici/MANIFEST.template.in new file mode 100644 index 0000000000..eb3b1b450f --- /dev/null +++ b/python/amici/MANIFEST.template.in @@ -0,0 +1 @@ +include *.cpp *.h diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index de7e58b015..52f2cce95f 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -2451,7 +2451,8 @@ def _writeCMakeFile(self): + '_rowvals.cpp ') templateData = {'MODELNAME': self.modelName, - 'SOURCES': '\n'.join(sources)} + 'SOURCES': '\n'.join(sources), + 'AMICI_VERSION': __version__} applyTemplate( MODEL_CMAKE_TEMPLATE_FILE, os.path.join(self.modelPath, 'CMakeLists.txt'), @@ -2495,7 +2496,8 @@ def _writeModuleSetup(self): 'PACKAGE_VERSION': '0.1.0'} applyTemplate(os.path.join(amiciModulePath, 'setup.template.py'), os.path.join(self.modelPath, 'setup.py'), templateData) - + applyTemplate(os.path.join(amiciModulePath, 'MANIFEST.template.in'), + os.path.join(self.modelPath, 'MANIFEST.in'), {}) # write __init__.py for the model module if not os.path.exists(os.path.join(self.modelPath, self.modelName)): os.makedirs(os.path.join(self.modelPath, self.modelName)) diff --git a/python/bin/amici_import_petab.py b/python/bin/amici_import_petab.py index 26a5ba3dc8..2cf7312b17 100755 --- a/python/bin/amici_import_petab.py +++ b/python/bin/amici_import_petab.py @@ -246,9 +246,10 @@ def main(): parameter_file=args.parameter_file_name) petab.lint_problem(pp) - import_model(args.sbml_file_name, - args.condition_file_name, - args.measurement_file_name, + import_model(model_name=args.model_name, + sbml_file=args.sbml_file_name, + condition_file=args.condition_file_name, + measurement_file=args.measurement_file_name, model_output_dir=args.model_output_dir, compile=args.compile, verbose=True) diff --git a/python/sdist/amici/MANIFEST.template.in b/python/sdist/amici/MANIFEST.template.in new file mode 120000 index 0000000000..7615b2cce2 --- /dev/null +++ b/python/sdist/amici/MANIFEST.template.in @@ -0,0 +1 @@ +../../amici/MANIFEST.template.in \ No newline at end of file diff --git a/src/CMakeLists.template.cmake b/src/CMakeLists.template.cmake index f0d9b18331..0b1e27e91f 100644 --- a/src/CMakeLists.template.cmake +++ b/src/CMakeLists.template.cmake @@ -23,7 +23,7 @@ foreach(FLAG ${MY_CXX_FLAGS}) endif() endforeach(FLAG) -find_package(Amici HINTS ${CMAKE_CURRENT_LIST_DIR}/../../build) +find_package(Amici TPL_AMICI_VERSION HINTS ${CMAKE_CURRENT_LIST_DIR}/../../build) message(STATUS "Found AMICI ${Amici_DIR}") set(MODEL_DIR ${CMAKE_CURRENT_LIST_DIR}) From e9d820de1e0526954786760e286c88f395cc8587 Mon Sep 17 00:00:00 2001 From: Paul Stapor Date: Sun, 12 May 2019 14:09:18 +0200 Subject: [PATCH 15/26] Feature timing, Closes #524 (#699) * add changes for additional output of cpu time for forward and backward solve (private members of solver with getter functions) * add cpu times to serialization, testsSerialization, hdf5 and pySB tests --- include/amici/rdata.h | 8 +- include/amici/serialization.h | 5 + include/amici/solver.h | 18 +++ python/amici/numpy.py | 10 +- src/backwardproblem.cpp | 1 + src/forwardproblem.cpp | 1 + src/hdf5.cpp | 6 + src/solver.cpp | 13 +++ tests/cpputest/unittests/tests1.cpp | 110 +++++++++--------- .../cpputest/unittests/testsSerialization.cpp | 5 +- tests/testPYSB.py | 3 +- 11 files changed, 117 insertions(+), 63 deletions(-) diff --git a/include/amici/rdata.h b/include/amici/rdata.h index 9f24b59c4c..a2543cd0f0 100644 --- a/include/amici/rdata.h +++ b/include/amici/rdata.h @@ -202,10 +202,16 @@ class ReturnData { /** employed order forward problem (dimension: nt) */ std::vector order; + /** computation time of forward solve [ms] */ + double cpu_time = 0.0; + + /** computation time of backward solve [ms] */ + double cpu_timeB = 0.0; + /** flag indicating success of Newton solver */ int newton_status = 0; - /** computation time of the Newton solver [s] */ + /** computation time of the Newton solver [ms] */ double newton_cpu_time = 0.0; /** number of Newton steps for steady state problem diff --git a/include/amici/serialization.h b/include/amici/serialization.h index 5c0842ab78..3feb3db7a8 100644 --- a/include/amici/serialization.h +++ b/include/amici/serialization.h @@ -65,6 +65,8 @@ void serialize(Archive &ar, amici::Solver &u, const unsigned int version) { ar &u.iter; ar &u.stldet; ar &u.ordering; + ar &u.cpu_time; + ar &u.cpu_timeB; } @@ -154,6 +156,9 @@ void serialize(Archive &ar, amici::ReturnData &r, const unsigned int version) { ar &r.numnonlinsolvconvfails; ar &r.numnonlinsolvconvfailsB; ar &r.order; + ar &r.cpu_time; + ar &r.cpu_timeB; + ar &r.newton_cpu_time; ar &r.newton_status; ar &r.newton_cpu_time; diff --git a/include/amici/solver.h b/include/amici/solver.h index 0e3fd13177..48d1d2e8cd 100644 --- a/include/amici/solver.h +++ b/include/amici/solver.h @@ -630,6 +630,18 @@ class Solver { * @return t */ realtype gett() const; + + /** + * @brief Reads out the cpu time needed for forward solve + * @return cpu_time + */ + realtype getCpuTime() const; + + /** + * @brief Reads out the cpu time needed for bavkward solve + * @return cpu_timeB + */ + realtype getCpuTimeB() const; /** * @brief number of states with which the solver was initialized @@ -1412,6 +1424,12 @@ class Solver { /** relative tolerances for steadystate computation */ realtype ss_rtol_sensi = NAN; + + /** CPU time, forward solve */ + mutable realtype cpu_time = 0.0; + + /** CPU time, backward solve */ + mutable realtype cpu_timeB = 0.0; /** maximum number of allowed integration steps for backward problem */ long int maxstepsB = 0; diff --git a/python/amici/numpy.py b/python/amici/numpy.py index a72652ba1c..e0b8c88a84 100644 --- a/python/amici/numpy.py +++ b/python/amici/numpy.py @@ -154,12 +154,12 @@ class ReturnDataView(SwigPtrView): 'ts', 'x', 'x0', 'x_ss', 'sx', 'sx0', 'sx_ss', 'y', 'sigmay', 'sy', 'ssigmay', 'z', 'rz', 'sigmaz', 'sz', 'srz', 'ssigmaz', 'sllh', 's2llh', 'J', 'xdot', 'status', 'llh', - 'chi2', 'res', 'sres', 'FIM', 'wrms_steadystate', - 't_steadystate', 'newton_numlinsteps', 'newton_numsteps', + 'chi2', 'res', 'sres', 'FIM', 'wrms_steadystate', 't_steadystate', + 'newton_numlinsteps', 'newton_numsteps', 'newton_cpu_time', 'numsteps', 'numrhsevals', 'numerrtestfails', - 'numnonlinsolvconvfails', 'order', 'numstepsB', - 'numrhsevalsB', 'numerrtestfailsB', - 'numnonlinsolvconvfailsB' + 'numnonlinsolvconvfails', 'order', 'cpu_time', + 'numstepsB','numrhsevalsB', 'numerrtestfailsB', + 'numnonlinsolvconvfailsB', 'cpu_timeB' ] def __init__(self, rdata): diff --git a/src/backwardproblem.cpp b/src/backwardproblem.cpp index 10f8d32689..5188c35652 100644 --- a/src/backwardproblem.cpp +++ b/src/backwardproblem.cpp @@ -90,6 +90,7 @@ void BackwardProblem::workBackwardProblem() { } computeLikelihoodSensitivities(); + rdata->cpu_timeB = solver->getCpuTimeB(); } diff --git a/src/forwardproblem.cpp b/src/forwardproblem.cpp index 79f449b910..e1875be083 100644 --- a/src/forwardproblem.cpp +++ b/src/forwardproblem.cpp @@ -135,6 +135,7 @@ void ForwardProblem::workForwardProblem() { } storeJacobianAndDerivativeInReturnData(); + rdata->cpu_time = solver->getCpuTime(); } diff --git a/src/hdf5.cpp b/src/hdf5.cpp index 562816a7ea..694685ecd3 100644 --- a/src/hdf5.cpp +++ b/src/hdf5.cpp @@ -370,6 +370,12 @@ void writeReturnDataDiagnosis(const ReturnData &rdata, H5LTset_attribute_double(file.getId(), hdf5Location.c_str(), "newton_cpu_time", &rdata.newton_cpu_time, 1); + H5LTset_attribute_double(file.getId(), hdf5Location.c_str(), + "cpu_time", &rdata.cpu_time, 1); + + H5LTset_attribute_double(file.getId(), hdf5Location.c_str(), + "cpu_timeB", &rdata.cpu_timeB, 1); + if (!rdata.J.empty()) createAndWriteDouble2DDataset(file, hdf5Location + "/J", rdata.J, rdata.nx, rdata.nx); diff --git a/src/solver.cpp b/src/solver.cpp index 59f073e4cf..b6a9b6445d 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -8,6 +8,7 @@ #include #include #include +#include namespace amici { @@ -46,12 +47,14 @@ Solver::Solver(const Solver &other) : Solver() { int Solver::run(const realtype tout) const { setStopTime(tout); + clock_t starttime = clock(); int status; if (getAdjInitDone()) { status = solveF(tout, AMICI_NORMAL, &ncheckPtr); } else { status = solve(tout, AMICI_NORMAL); } + cpu_time += (realtype)((clock() - starttime) * 1000) / CLOCKS_PER_SEC; return status; } @@ -66,7 +69,9 @@ int Solver::step(const realtype tout) const { } void Solver::runB(const realtype tout) const { + clock_t starttime = clock(); solveB(tout, AMICI_NORMAL); + cpu_timeB += (realtype)((clock() - starttime) * 1000) / CLOCKS_PER_SEC; t = tout; } @@ -876,6 +881,14 @@ void Solver::setQuadInitDoneB(const int which) const { initializedQB.at(which) = true; } +realtype Solver::getCpuTime() const { + return cpu_time; +} + +realtype Solver::getCpuTimeB() const { + return cpu_timeB; +} + void Solver::resetMutableMemory(const int nx, const int nplist, const int nquad) const { solverMemory = nullptr; diff --git a/tests/cpputest/unittests/tests1.cpp b/tests/cpputest/unittests/tests1.cpp index 5a17aa114f..0c73cedd5f 100644 --- a/tests/cpputest/unittests/tests1.cpp +++ b/tests/cpputest/unittests/tests1.cpp @@ -119,7 +119,7 @@ TEST(model, testParameterScalingLengthMismatch) TEST(model, testSetTimepoints){ CHECK_THROWS(AmiException, - model.setTimepoints(std::vector{ 0.0, 1.0, 0.5 })) + model.setTimepoints(std::vector{ 0.0, 1.0, 0.5 })); } TEST(model, testNameIdGetterSetter) @@ -130,7 +130,7 @@ TEST(model, testNameIdGetterSetter) 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) + DOUBLES_EQUAL(ip, 5.0, 1e-16); CHECK_THROWS(AmiException, model.setParametersByIdRegex("k[\\d]+", 5.0)); model.setParameterByName("p0", 3.0); @@ -139,7 +139,7 @@ TEST(model, testNameIdGetterSetter) 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) + DOUBLES_EQUAL(ip, 5.0, 1e-16); CHECK_THROWS(AmiException, model.setParametersByNameRegex("k[\\d]+", 5.0)); model.setFixedParameterById("k0", 3.0); @@ -148,7 +148,7 @@ TEST(model, testNameIdGetterSetter) 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) + DOUBLES_EQUAL(ik, 5.0, 1e-16); CHECK_THROWS(AmiException, model.setFixedParametersByIdRegex("p[\\d]+", 5.0)); @@ -158,7 +158,7 @@ TEST(model, testNameIdGetterSetter) 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) + DOUBLES_EQUAL(ik, 5.0, 1e-16); CHECK_THROWS(AmiException, model.setFixedParametersByNameRegex("p[\\d]+", 5.0)); } @@ -322,26 +322,26 @@ TEST_GROUP(edata) TEST(edata, testConstructors1) { auto edata = ExpData(); - CHECK_TRUE(edata.nytrue() == 0) - CHECK_TRUE(edata.nztrue() == 0) - CHECK_TRUE(edata.nmaxevent() == 0) + 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()) + 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()) + 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"); } @@ -361,10 +361,10 @@ TEST(edata, testConstructors4) 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()) + 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( @@ -383,10 +383,10 @@ TEST(edata, testConstructors4) "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()) + 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, @@ -418,10 +418,10 @@ 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()) + 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, @@ -447,7 +447,7 @@ TEST(edata, testDimensionChecks) z, z_std, z, - z_std)) + z_std)); CHECK_THROWS(AmiException, ExpData(testModel.nytrue, @@ -457,7 +457,7 @@ TEST(edata, testDimensionChecks) z, bad_std, z, - z_std)) + z_std)); auto edata = ExpData(testModel); @@ -466,24 +466,24 @@ TEST(edata, testDimensionChecks) 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)) + 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.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)) + edata.setObservedEventsStdDev(bad_single_y_std, 0)); CHECK_THROWS(AmiException, - edata.setTimepoints(std::vector{ 0.0, 1.0, 0.5 })) + edata.setTimepoints(std::vector{ 0.0, 1.0, 0.5 })); } TEST(edata, testSettersGetters) @@ -521,10 +521,10 @@ TEST(edata, testSettersGetters) 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)) + 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); @@ -534,17 +534,17 @@ TEST(edata, testSettersGetters) 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.setObservedEvents(single_z, nz)); + CHECK_THROWS(std::exception, edata.setObservedEvents(single_z, -1)); CHECK_THROWS(std::exception, - edata.setObservedEventsStdDev(single_z_std, nz)) + edata.setObservedEventsStdDev(single_z_std, nz)); CHECK_THROWS(std::exception, - edata.setObservedEventsStdDev(single_z_std, -1)) + 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)) + 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); @@ -553,10 +553,10 @@ TEST(edata, testSettersGetters) 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)) + 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"); @@ -777,7 +777,7 @@ 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)) + CHECK_EQUAL(av.at(i), NV_Ith_S(nvec, i)); } TEST(amivector, vectorArray) diff --git a/tests/cpputest/unittests/testsSerialization.cpp b/tests/cpputest/unittests/testsSerialization.cpp index 423ed3e1eb..cb950043f0 100644 --- a/tests/cpputest/unittests/testsSerialization.cpp +++ b/tests/cpputest/unittests/testsSerialization.cpp @@ -48,7 +48,7 @@ void checkReturnDataEqual(amici::ReturnData const& r, amici::ReturnData const& s 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); @@ -58,6 +58,9 @@ void checkReturnDataEqual(amici::ReturnData const& r, amici::ReturnData const& s 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.newton_cpu_time == s.newton_cpu_time); CHECK_TRUE(r.newton_status == s.newton_status); CHECK_TRUE(r.newton_numsteps == s.newton_numsteps); diff --git a/tests/testPYSB.py b/tests/testPYSB.py index 332c3a5968..75561699a3 100755 --- a/tests/testPYSB.py +++ b/tests/testPYSB.py @@ -93,7 +93,8 @@ def test_compare_to_sbml_import(self): if field not in ['ptr', 't_steadystate', 'numsteps', 'newton_numsteps', 'numrhsevals', 'numerrtestfails', 'order', 'J', 'xdot', - 'wrms_steadystate', ]: + 'wrms_steadystate', 'newton_cpu_time', + 'cpu_time', 'cpu_timeB', ]: with self.subTest(field=field): if rdata_pysb[field] is None: self.assertIsNone( From 09bc9411220ec65e138e68a1fb07ec6070a4f7ae Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 28 May 2019 09:32:37 +0200 Subject: [PATCH 16/26] Add reference to bib file, recreate list (Closes #705) --- documentation/amici_refs.bib | 13 +++++++++++++ documentation/references.md | 5 ++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/documentation/amici_refs.bib b/documentation/amici_refs.bib index 0877f70358..bcc467d28b 100644 --- a/documentation/amici_refs.bib +++ b/documentation/amici_refs.bib @@ -399,4 +399,17 @@ @Article{KaltenbacherPed2018 url = {http://www.sciencedirect.com/science/article/pii/S0022247X18304414}, } +@InProceedings{10.1007/978-3-030-18174-1_14, + author = {Nousiainen, Kari and Intosalmi, Jukka and L{\"a}hdesm{\"a}ki, Harri}, + title = {A Mathematical Model for Enhancer Activation Kinetics During Cell Differentiation}, + booktitle = {Algorithms for Computational Biology}, + year = {2019}, + editor = {Holmes, Ian and Mart{\'i}n-Vide, Carlos and Vega-Rodr{\'i}guez, Miguel A.}, + pages = {191--202}, + address = {Cham}, + publisher = {Springer International Publishing}, + abstract = {Cell differentiation and development are for a great part steered by cell type specific enhancers. Transcription factor (TF) binding to an enhancer together with DNA looping result in transcription initiation. In addition to binding motifs for TFs, enhancer regions typically contain specific histone modifications. This information has been used to detect enhancer regions and classify them into different subgroups. However, it is poorly understood how TF binding and histone modifications are causally connected and what kind of molecular dynamics steer the activation process.}, + isbn = {978-3-030-18174-1}, +} + @Comment{jabref-meta: databaseType:bibtex;} diff --git a/documentation/references.md b/documentation/references.md index d767a8e7e6..f9a3b23fff 100644 --- a/documentation/references.md +++ b/documentation/references.md @@ -1,6 +1,6 @@ # References -List of publications using AMICI. Total number is 27. +List of publications using AMICI. Total number is 28.

2019

@@ -10,6 +10,9 @@ List of publications using AMICI. Total number is 27.

Gregg, Robert W, Saumendra N Sarkar, and Jason E Shoemaker. 2019. “Mathematical Modeling of the cGAS Pathway Reveals Robustness of Dna Sensing to Trex1 Feedback.” Journal of Theoretical Biology 462 (February): 148–57. https://doi.org/10.1016/j.jtbi.2018.11.001.

+
+

Nousiainen, Kari, Jukka Intosalmi, and Harri Lähdesmäki. 2019. “A Mathematical Model for Enhancer Activation Kinetics During Cell Differentiation.” In Algorithms for Computational Biology, edited by Ian Holmes, Carlos Martín-Vide, and Miguel A. Vega-Rodríguez, 191–202. Cham: Springer International Publishing.

+

Pitt, Jake Alan, and Julio R Banga. 2019. “Parameter Estimation in Models of Biological Oscillators: An Automated Regularised Estimation Approach.” BMC Bioinformatics 20 (1): 82. https://doi.org/10.1186/s12859-019-2630-y.

From d7e26a80ffbac62c1d89d28a206b020a3e876c22 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 31 May 2019 17:48:14 +0200 Subject: [PATCH 17/26] Fix encoding when reading README.md in setup.py which caused trouble on some systems --- python/sdist/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/sdist/setup.py b/python/sdist/setup.py index 1588b0c57b..2858209bcd 100755 --- a/python/sdist/setup.py +++ b/python/sdist/setup.py @@ -139,7 +139,7 @@ def main(): # Readme as long package description to go on PyPi # (https://pypi.org/project/amici/) - with open("README.md", "r") as fh: + with open("README.md", "r", encoding="utf-8") as fh: long_description = fh.read() # Remove the "-Wstrict-prototypes" compiler option, which isn't valid for From e98d71ab239e37931fd68d1b748a3f88bb9157e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Tue, 18 Jun 2019 12:13:12 -0400 Subject: [PATCH 18/26] refactor model, forwardproblem, backwardproblem (#701) * refactor model, forwardproblem, backwardproblem, fixes #332 * simplify slicing * replace reset_storage with vectro::asign, fix size types * fix slicing, make model-edata dependence more explicit * fix objective function + sensi evaluation * bugfixes * fixes event slicing * fix s2llh buffer size check * fix unittests after update * fix slice sizes * streamline and fix sllh and s2llh writing * pass AmiVector by reference instead of value in dJydx and dJydp * fix assign calls * cleanup and document Model * fix model initialization, fix doc * fix make slice for empty vectors * fix sigma & ssigma * Update include/amici/model.h Co-Authored-By: Daniel Weindl * Update include/amici/model.h Co-Authored-By: Daniel Weindl * fix typo * force homebrew to update --- .travis.yml | 2 + include/amici/edata.h | 1 + include/amici/forwardproblem.h | 19 +- include/amici/misc.h | 12 + include/amici/model.h | 1960 +++++++++++++----------- include/amici/rdata.h | 27 + matlab/@amifun/gccode.m | 2 +- src/backwardproblem.cpp | 19 +- src/forwardproblem.cpp | 216 ++- src/misc.cpp | 10 + src/model.cpp | 1988 +++++++++++++------------ src/rdata.cpp | 59 + src/solver.cpp | 2 +- src/steadystateproblem.cpp | 2 +- tests/cpputest/steadystate/tests1.cpp | 6 +- tests/cpputest/unittests/tests1.cpp | 3 +- 16 files changed, 2347 insertions(+), 1981 deletions(-) diff --git a/.travis.yml b/.travis.yml index 3c59121593..4e2fc9a656 100644 --- a/.travis.yml +++ b/.travis.yml @@ -55,6 +55,7 @@ matrix: - hdf5 - swig - gcc + update: true before_install: - travis_wait brew link --overwrite gcc # fix linker warning regarding /usr/local/include/c++ - export -f travis_fold travis_nanoseconds travis_time_start travis_time_finish @@ -76,6 +77,7 @@ matrix: - doxygen - ragel - graphviz + update: true before_install: - export PATH=/Users/travis/Library/Python/3.7/bin:/Library/TeX/texbin:$PATH - export -f travis_fold travis_nanoseconds travis_time_start travis_time_finish diff --git a/include/amici/edata.h b/include/amici/edata.h index fdc659d8d7..153c74252d 100644 --- a/include/amici/edata.h +++ b/include/amici/edata.h @@ -2,6 +2,7 @@ #define AMICI_EDATA_H #include "amici/defines.h" +#include "amici/vector.h" #include diff --git a/include/amici/forwardproblem.h b/include/amici/forwardproblem.h index 612a7a6448..1235141401 100644 --- a/include/amici/forwardproblem.h +++ b/include/amici/forwardproblem.h @@ -202,14 +202,6 @@ class ForwardProblem { */ void getEventOutput(); - /** - * @brief Preprocess the provided experimental data to compute - * event sensitivities via adjoint or forward methods later on - * - * @param ie index of current event - */ - void prepEventSensis(int ie); - /** * @brief Extracts event information for forward sensitivity analysis * @@ -231,14 +223,6 @@ class ForwardProblem { */ void getDataOutput(int it); - /** - * @brief Preprocesses the provided experimental data to compute - * sensitivities via adjoint or forward methods later on - * - * @param it index of current timepoint - */ - void prepDataSensis(int it); - /** * @brief Extracts data information for forward sensitivity analysis * @@ -352,6 +336,9 @@ class ForwardProblem { /** differential sensitivity state vector array * (dimension: nx_cl x nplist, row-major) */ AmiVectorArray sdx; + + /** sensitivity of the event timepoint (dimension: nplist) */ + std::vector stau; /** storage for last found root */ realtype tlastroot = 0.0; diff --git a/include/amici/misc.h b/include/amici/misc.h index dc2aca7a3d..fc30fff0b2 100644 --- a/include/amici/misc.h +++ b/include/amici/misc.h @@ -11,6 +11,18 @@ #include namespace amici { + +/** + * @brief creates a slice from existing data + * + * @param data to be sliced + * @param index slice index + * @param size slice size + * @return span of the slice + */ + + gsl::span slice(std::vector &data, const int index, + const unsigned size); /** * @brief Checks the values in an array for NaNs and Infs diff --git a/include/amici/model.h b/include/amici/model.h index 3b954dae10..199599677a 100644 --- a/include/amici/model.h +++ b/include/amici/model.h @@ -11,7 +11,6 @@ namespace amici { -class ReturnData; class ExpData; class Model; class Solver; @@ -69,11 +68,9 @@ class Model : public AbstractModel { * @param idlist indexes indicating algebraic components (DAE only) * @param z2event mapping of event outputs to events */ - Model(int nx_rdata, int nxtrue_rdata, int nx_solver, - int nxtrue_solver, int ny, int nytrue, int nz, - int nztrue, int ne, int nJ, int nw, - int ndwdx, int ndwdp, int ndxdotdw, - std::vector ndJydy, int nnz, + Model(int nx_rdata, int nxtrue_rdata, int nx_solver, int nxtrue_solver, + int ny, int nytrue, int nz, int nztrue, int ne, int nJ, int nw, + int ndwdx, int ndwdp, int ndxdotdw, std::vector ndJydy, int nnz, int ubw, int lbw, amici::SecondOrderMode o2mode, const std::vector &p, std::vector k, const std::vector &plist, std::vector idlist, @@ -95,6 +92,24 @@ class Model : public AbstractModel { */ virtual Model *clone() const = 0; + /** + * @brief Serialize Model (see boost::serialization::serialize) + * @param ar Archive to serialize to + * @param u Data to serialize + * @param version Version number + */ + template + friend void boost::serialization::serialize(Archive &ar, Model &u, + unsigned int version); + + /** + * @brief Check equality of data members + * @param a first model instance + * @param b second model instance + * @return equality + */ + friend bool operator==(const Model &a, const Model &b); + // Overloaded base class methods using AbstractModel::fdeltaqB; using AbstractModel::fdeltasx; @@ -136,790 +151,810 @@ class Model : public AbstractModel { using AbstractModel::fz; /** - * Expands conservation law for states - * @param x_rdata pointer to state variables with conservation laws - * expanded (stored in rdata) - * @param x_solver pointer to state variables with conservation laws - * applied (solver returns this) + * Initialization of model properties + * @param x pointer to state variables + * @param dx pointer to time derivative of states (DAE only) + * @param sx pointer to state variable sensititivies + * @param sdx pointer to time derivative of state sensitivities + * (DAE only) + * @param computeSensitivities flag indicating whether sensitivities + * are to be computed */ - void fx_rdata(AmiVector &x_rdata, const AmiVector &x_solver); + void initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx, + AmiVectorArray &sdx, bool computeSensitivities); /** - * Expands conservation law for state sensitivities - * @param sx_rdata pointer to state variable sensitivities with - * conservation laws expanded (stored in rdata) - * @param sx_solver pointer to state variable sensitivities with - * conservation laws applied (solver returns this) + * Initialization of model properties + * @param xB adjoint state variables + * @param dxB time derivative of adjoint states (DAE only) + * @param xQB adjoint quadratures */ - void fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx_solver); + void initializeB(AmiVector &xB, AmiVector &dxB, AmiVector &xQB); /** - * Initial states + * Initialization of initial states * @param x pointer to state variables */ - void fx0(AmiVector &x); + void initializeStates(AmiVector &x); /** - * Sets only those initial states that are specified via fixedParmeters + * Initialization of initial state sensitivities + * @param sx pointer to state variable sensititivies * @param x pointer to state variables */ - void fx0_fixedParameters(AmiVector &x); + void initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x); /** - * Initial value for initial state sensitivities - * @param sx pointer to state sensitivity variables + * Initialises the heaviside variables h at the intial time t0 + * heaviside variables activate/deactivate on event occurences * @param x pointer to state variables - **/ - void fsx0(AmiVectorArray &sx, const AmiVector &x); + * @param dx pointer to time derivative of states (DAE only) + */ + void initHeaviside(AmiVector &x, AmiVector &dx); /** - * Sets only those initial states sensitivities that are affected from fx0 - *fixedParmeters - * @param sx pointer to state sensitivity variables - * @param x pointer to state variables - **/ - void fsx0_fixedParameters(AmiVectorArray &sx, const AmiVector &x); + * @brief Number of parameters wrt to which sensitivities are computed + * @return length of sensitivity index vector + */ + int nplist() const; /** - * Sensitivity of derivative initial states sensitivities sdx0 (only - * necessary for DAEs) - **/ - virtual void fsdx0(); + * @brief Total number of model parameters + * @return length of parameter vector + */ + int np() const; /** - * Sensitivity of event timepoint, total derivative - * @param t current timepoint - * @param ie event index - * @param x pointer to state variables - * @param sx pointer to state sensitivity variables + * @brief Number of constants + * @return length of constant vector */ - void fstau(realtype t, int ie, const AmiVector &x, - const AmiVectorArray &sx); + int nk() const; /** - * Observables / measurements - * @param t current timepoint - * @param it timepoint index - * @param x current state - * @param rdata pointer to return data instance + * @brief Number of conservation laws + * @return difference between nx_rdata and nx_solver */ - void fy(realtype t, int it, const AmiVector &x, - ReturnData *rdata); + int ncl() const; /** - * Partial derivative of observables y w.r.t. model parameters p - * @param t current timepoint - * @param x current state + * @brief Fixed parameters + * @return pointer to constants array */ - void fdydp(realtype t, const AmiVector &x); + const double *k() const; /** - * Partial derivative of observables y w.r.t. state variables x - * @param t current timepoint - * @param x current state + * @brief Get nmaxevent + * @return maximum number of events that may occur for each type */ - void fdydx(realtype t, const AmiVector &x); + int nMaxEvent() const; - /** Event-resolved output - * @param nroots number of events for event index - * @param ie event index - * @param t current timepoint - * @param x current state - * @param rdata pointer to return data instance + /** + * @brief Set nmaxevent + * @param nmaxevent maximum number of events that may occur for each type */ - void fz(int nroots, int ie, realtype t, - const AmiVector &x, ReturnData *rdata); + void setNMaxEvent(int nmaxevent); - /** Sensitivity of z, total derivative - * @param nroots number of events for event index - * @param ie event index - * @param t current timepoint - * @param x current state - * @param sx current state sensitivities - * @param rdata pointer to return data instance + /** + * @brief Get number of timepoints + * @return number of timepoints */ - void fsz(int nroots, int ie, realtype t, - const AmiVector &x, const AmiVectorArray &sx, ReturnData *rdata); + int nt() const; /** - * Event root function of events (equal to froot but does not include - * non-output events) - * @param nroots number of events for event index - * @param ie event index - * @param t current timepoint - * @param x current state - * @param rdata pointer to return data instance + * @brief Get ParameterScale for each parameter + * @return vector of parameter scale */ - void frz(int nroots, int ie, realtype t, - const AmiVector &x, ReturnData *rdata); + std::vector const &getParameterScale() const; /** - * Sensitivity of rz, total derivative - * @param nroots number of events for event index - * @param ie event index - * @param t current timepoint - * @param x current state - * @param sx current state sensitivities - * @param rdata pointer to return data instance + * @brief Set ParameterScale for each parameter, resets initial state + * sensitivities + * @param pscale scalar parameter scale for all parameters */ - void fsrz(int nroots, int ie, realtype t, - const AmiVector &x, const AmiVectorArray &sx, ReturnData *rdata); + void setParameterScale(ParameterScaling pscale); /** - * Partial derivative of event-resolved output z w.r.t. to model parameters - * p - * @param ie event index - * @param t current timepoint - * @param x current state + * @brief Set ParameterScale for each parameter, resets initial state + * sensitivities + * @param pscaleVec vector of parameter scales */ - void fdzdp(realtype t, int ie, const AmiVector &x); + void setParameterScale(const std::vector &pscaleVec); /** - * Partial derivative of event-resolved output z w.r.t. to model states x - * @param ie event index - * @param t current timepoint - * @param x current state + * @brief Gets parameters with transformation according to ParameterScale + * applied + * @return unscaled parameters */ - void fdzdx(realtype t, int ie, const AmiVector &x); + std::vector const &getUnscaledParameters() const; /** - * Sensitivity of event-resolved root output w.r.t. to model parameters p - * @param ie event index - * @param t current timepoint - * @param x current state + * @brief Get the parameter vector + * @return The user-set parameters (see also getUnscaledParameters) */ - void fdrzdp(realtype t, int ie, const AmiVector &x); + std::vector const &getParameters() const; /** - * Sensitivity of event-resolved measurements rz w.r.t. to model states x - * @param ie event index - * @param t current timepoint - * @param x current state + * @brief Get value of first model parameter with the specified id + * @param par_id parameter id + * @return parameter value */ - void fdrzdx(realtype t, int ie, const AmiVector &x); + realtype getParameterById(std::string const &par_id) const; /** - * State update functions for events - * @param ie event index - * @param t current timepoint - * @param x current state - * @param xdot current residual function values - * @param xdot_old value of residual function before event + * @brief Get value of first model parameter with the specified name, + * @param par_name parameter name + * @return parameter value */ - void fdeltax(int ie, realtype t, const AmiVector &x, - const AmiVector &xdot, const AmiVector &xdot_old); + realtype getParameterByName(std::string const &par_name) const; /** - * Sensitivity update functions for events, total derivative - * @param ie event index - * @param t current timepoint - * @param x current state - * @param sx current state sensitivity - * @param xdot current residual function values - * @param xdot_old value of residual function before event + * @brief Sets the parameter vector + * @param p vector of parameters */ - void fdeltasx(int ie, realtype t, const AmiVector &x, - const AmiVectorArray &sx, const AmiVector &xdot, - const AmiVector &xdot_old); + void setParameters(std::vector const &p); /** - * Adjoint state update functions for events - * @param ie event index - * @param t current timepoint - * @param x current state - * @param xB current adjoint state - * @param xdot current residual function values - * @param xdot_old value of residual function before event + * @brief Set value of first model parameter with the specified id + * @param par_id parameter id + * @param value parameter value */ - void fdeltaxB(int ie, realtype t, const AmiVector &x, - const AmiVector &xB, const AmiVector &xdot, - const AmiVector &xdot_old); + void setParameterById(std::string const &par_id, realtype value); /** - * Quadrature state update functions for events - * @param ie event index - * @param t current timepoint - * @param x current state - * @param xB current adjoint state - * @param xdot current residual function values - * @param xdot_old value of residual function before event + * @brief Set all values of model parameters with ids matching the specified + * regex + * @param par_id_regex parameter id regex + * @param value parameter value + * @return number of parameter ids that matched the regex */ - void fdeltaqB(int ie, realtype t, const AmiVector &x, - const AmiVector &xB, const AmiVector &xdot, - const AmiVector &xdot_old); + int setParametersByIdRegex(std::string const &par_id_regex, realtype value); /** - * Standard deviation of measurements - * @param it timepoint index - * @param edata pointer to experimental data instance - * @param rdata pointer to return data instance + * @brief Set value of first model parameter with the specified name + * @param par_name parameter name + * @param value parameter value */ - void fsigmay(int it, ReturnData *rdata, const ExpData *edata); + void setParameterByName(std::string const &par_name, realtype value); /** - * Partial derivative of standard deviation of measurements w.r.t. model - * @param it timepoint index - * @param rdata pointer to return data instance - * @param edata pointer to ExpData data instance holding sigma values + * @brief Set all values of all model parameters with names matching the + * specified regex + * @param par_name_regex parameter name regex + * @param value parameter value + * @return number of fixed parameter names that matched the regex */ - void fdsigmaydp(int it, ReturnData *rdata, const ExpData *edata); + int setParametersByNameRegex(std::string const &par_name_regex, + realtype value); /** - * Standard deviation of events - * @param t current timepoint - * @param ie event index - * @param nroots array with event numbers - * @param edata pointer to experimental data instance - * @param rdata pointer to return data instance + * @brief Gets the fixedParameter member + * @return vector of fixed parameters */ - void fsigmaz(realtype t, int ie, const int *nroots, - ReturnData *rdata, const ExpData *edata); + std::vector const &getFixedParameters() const; /** - * Sensitivity of standard deviation of events measurements w.r.t. model - * parameters p - * @param t current timepoint - * @param ie event index - * @param nroots array with event numbers - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Get value of fixed parameter with the specified Id + * @param par_id parameter id + * @return parameter value */ - void fdsigmazdp(realtype t, int ie, const int *nroots, - ReturnData *rdata, const ExpData *edata); + realtype getFixedParameterById(std::string const &par_id) const; /** - * Negative log-likelihood of measurements y - * @param it timepoint index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Get value of fixed parameter with the specified name, + if multiple parameters have the same name, + the first parameter with matching name is returned + * @param par_name parameter name + * @return parameter value */ - void fJy(int it, ReturnData *rdata, const ExpData *edata); + realtype getFixedParameterByName(std::string const &par_name) const; /** - * Negative log-likelihood of event-resolved measurements z - * @param nroots event index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Sets the fixedParameter member + * @param k vector of fixed parameters */ - void fJz(int nroots, ReturnData *rdata, const ExpData *edata); + void setFixedParameters(std::vector const &k); /** - * Regularization of negative log-likelihood with roots of event-resolved - * measurements rz - * @param nroots event index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Set value of first fixed parameter with the specified id + * @param par_id fixed parameter id + * @param value fixed parameter value */ - void fJrz(int nroots, ReturnData *rdata, const ExpData *edata); + void setFixedParameterById(std::string const &par_id, realtype value); /** - * Model specific implementation of fdJydy colptrs - * @param indexptrs column pointers - * @param index ytrue index + * @brief Set values of all fixed parameters with the id matching the + * specified regex + * @param par_id_regex fixed parameter name regex + * @param value fixed parameter value + * @return number of fixed parameter ids that matched the regex */ - virtual void fdJydy_colptrs(sunindextype *indexptrs, int index); + int setFixedParametersByIdRegex(std::string const &par_id_regex, + realtype value); /** - * Model specific implementation of fdxdotdw row vals - * @param indexptrs row val pointers - * @param index ytrue index + * @brief Set value of first fixed parameter with the specified name, + * @param par_name fixed parameter id + * @param value fixed parameter value */ - virtual void fdJydy_rowvals(sunindextype *indexptrs, int index); + void setFixedParameterByName(std::string const &par_name, realtype value); /** - * Partial derivative of time-resolved measurement negative log-likelihood - * Jy - * @param it timepoint index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Set value of all fixed parameters with name matching the specified + * regex, + * @param par_name_regex fixed parameter name regex + * @param value fixed parameter value + * @return number of fixed parameter names that matched the regex */ - void fdJydy(int it, const ReturnData *rdata, const ExpData *edata); + int setFixedParametersByNameRegex(std::string const &par_name_regex, + realtype value); /** - * Sensitivity of time-resolved measurement negative log-likelihood Jy - * w.r.t. standard deviation sigma - * @param it timepoint index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Reports whether the model has parameter names set. + * Also returns true if the number of corresponding variables is just zero. + * @return boolean indicating whether parameter names were set */ - void fdJydsigma(int it, const ReturnData *rdata, - const ExpData *edata); + virtual bool hasParameterNames() const; /** - * Partial derivative of event measurement negative log-likelihood Jz - * @param nroots event index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Get names of the model parameters + * @return the names */ - void fdJzdz(int nroots, const ReturnData *rdata, - const ExpData *edata); + virtual std::vector getParameterNames() const; /** - * Sensitivity of event measurement negative log-likelihood Jz - * w.r.t. standard deviation sigmaz - * @param nroots event index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Reports whether the model has state names set. + * Also returns true if the number of corresponding variables is just zero. + * @return boolean indicating whether state names were set */ - void fdJzdsigma(int nroots, const ReturnData *rdata, - const ExpData *edata); + virtual bool hasStateNames() const; /** - * Partial derivative of event measurement negative log-likelihood Jz - * @param nroots event index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Get names of the model states + * @return the names */ - void fdJrzdz(int nroots, const ReturnData *rdata, - const ExpData *edata); + virtual std::vector getStateNames() const; /** - * Sensitivity of event measurement negative log-likelihood Jz - * w.r.t. standard deviation sigmaz - * @param nroots event index - * @param rdata pointer to return data instance - * @param edata pointer to experimental data instance + * @brief Reports whether the model has fixed parameter names set. + * Also returns true if the number of corresponding variables is just zero. + * @return boolean indicating whether fixed parameter names were set */ - void fdJrzdsigma(int nroots, const ReturnData *rdata, - const ExpData *edata); + virtual bool hasFixedParameterNames() const; /** - * Sensitivity of measurements y, total derivative sy = dydx * sx + dydp - * @param it timepoint index - * @param sx pointer to state sensitivities - * @param rdata pointer to return data instance + * @brief Get names of the fixed model parameters + * @return the names */ - void fsy(int it, const AmiVectorArray &sx, ReturnData *rdata); + virtual std::vector getFixedParameterNames() const; /** - * Sensitivity of z at final timepoint (ignores sensitivity of timepoint), - * total derivative - * @param nroots number of events for event index - * @param ie event index - * @param rdata pointer to return data instance + * @brief Reports whether the model has observable names set. + * Also returns true if the number of corresponding variables is just zero. + * @return boolean indicating whether observabke names were set */ - void fsz_tf(const int *nroots, int ie, ReturnData *rdata); + virtual bool hasObservableNames() const; /** - * Sensitivity of time-resolved measurement negative log-likelihood Jy, - * total derivative - * @param it timepoint index - * @param sx pointer to state sensitivities - * @param dJydx vector with values of state derivative of Jy - * @param rdata pointer to return data instance + * @brief Get names of the observables + * @return the names */ - void fsJy(int it, const std::vector &dJydx, - const AmiVectorArray &sx, ReturnData *rdata); + virtual std::vector getObservableNames() const; /** - * Compute sensitivity of time-resolved measurement negative log-likelihood - * Jy w.r.t. parameters for the given timepoint. Add result to respective - * fields in rdata. - * @param it timepoint index - * @param edata pointer to experimental data instance - * @param rdata pointer to return data instance + * @brief Reports whether the model has parameter ids set. + * Also returns true if the number of corresponding variables is just zero. + * @return boolean indicating whether parameter ids were set */ - void fdJydp(int it, ReturnData *rdata, const ExpData *edata); + virtual bool hasParameterIds() const; /** - * Sensitivity of time-resolved measurement negative log-likelihood Jy - * w.r.t. state variables - * @param dJydx vector with values of state derivative of Jy - * @param it timepoint index - * @param edata pointer to experimental data instance + * @brief Get ids of the model parameters + * @return the ids */ - void fdJydx(std::vector &dJydx, int it, - const ExpData *edata); + virtual std::vector getParameterIds() const; /** - * Sensitivity of event-resolved measurement negative log-likelihood Jz, - * total derivative - * @param nroots event index - * @param dJzdx vector with values of state derivative of Jz - * @param sx pointer to state sensitivities - * @param rdata pointer to return data instance + * @brief Reports whether the model has state ids set. + * Also returns true if the number of corresponding variables is just zero. + * @return boolean indicating whether state ids were set */ - void fsJz(int nroots, const std::vector &dJzdx, - const AmiVectorArray &sx, ReturnData *rdata); + virtual bool hasStateIds() const; /** - * Sensitivity of event-resolved measurement negative log-likelihood Jz - * w.r.t. parameters - * @param nroots event index - * @param t current timepoint - * @param edata pointer to experimental data instance - * @param rdata pointer to return data instance + * @brief Get ids of the model states + * @return the ids */ - void fdJzdp(int nroots, realtype t, const ExpData *edata, - const ReturnData *rdata); + virtual std::vector getStateIds() const; /** - * Sensitivity of event-resolved measurement negative log-likelihood Jz - * w.r.t. state variables - * @param dJzdx pointer to vector with values of state derivative of Jz - * @param nroots event index - * @param t current timepoint - * @param edata pointer to experimental data instance - * @param rdata pointer to return data instance + * @brief Reports whether the model has fixed parameter ids set. + * Also returns true if the number of corresponding variables is just zero. + * @return boolean indicating whether fixed parameter ids were set */ - void fdJzdx(std::vector *dJzdx, int nroots, realtype t, - const ExpData *edata, const ReturnData *rdata); + virtual bool hasFixedParameterIds() const; /** - * Initialization of model properties - * @param x pointer to state variables - * @param dx pointer to time derivative of states (DAE only) - * @param sx pointer to state variable sensititivies - * @param sdx pointer to time derivative of state sensitivities - * (DAE only) - * @param computeSensitivities flag indicating whether sensitivities - * are to be computed + * @brief Get ids of the fixed model parameters + * @return the ids */ - void initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx, - AmiVectorArray &sdx, bool computeSensitivities); + virtual std::vector getFixedParameterIds() const; /** - * Initialization of model properties - * @param xB adjoint state variables - * @param dxB time derivative of adjoint states (DAE only) - * @param xQB adjoint quadratures + * @brief Reports whether the model has observable ids set. + * Also returns true if the number of corresponding variables is just zero. + * @return boolean indicating whether observale ids were set */ - void initializeB(AmiVector &xB, AmiVector &dxB, AmiVector &xQB); + virtual bool hasObservableIds() const; /** - * Initialization of initial states - * @param x pointer to state variables + * @brief Get ids of the observables + * @return the ids */ - void initializeStates(AmiVector &x); + virtual std::vector getObservableIds() const; /** - * Initialization of initial state sensitivities - * @param sx pointer to state variable sensititivies - * @param x pointer to state variables + * @brief Get the timepoint vector + * @return timepoint vector */ - void initializeStateSensitivities(AmiVectorArray &sx, AmiVector &x); + std::vector const &getTimepoints() const; /** - * Initialises the heaviside variables h at the intial time t0 - * heaviside variables activate/deactivate on event occurences - * @param x pointer to state variables - * @param dx pointer to time derivative of states (DAE only) + * @brief get simulation timepoint for time index it + * @param it time index + * @return t timepoint */ - void initHeaviside(AmiVector &x, AmiVector &dx); + realtype getTimepoint(const int it) const; /** - * @brief Number of parameters wrt to which sensitivities are computed - * @return length of sensitivity index vector + * @brief Set the timepoint vector + * @param ts timepoint vector */ - int nplist() const; + void setTimepoints(std::vector const &ts); /** - * @brief Total number of model parameters - * @return length of parameter vector + * @brief get simulation start time + * @return simulation start time */ - int np() const; + double t0() const; /** - * @brief Number of constants - * @return length of constant vector + * @brief set simulation start time + * @param t0 simulation start time */ - int nk() const; + void setT0(double t0); /** - * @brief Number of conservation laws - * @return difference between nx_rdata and nx_solver + * @brief gets flags indicating whether states should be treated as + * non-negative + * @return vector of flags */ - int ncl() const; + std::vector const &getStateIsNonNegative() const; /** - * @brief Fixed parameters - * @return pointer to constants array + * @brief sets flags indicating whether states should be treated as + * non-negative + * @param stateIsNonNegative vector of flags */ - const double *k() const; + void setStateIsNonNegative(std::vector const &stateIsNonNegative); /** - * @brief Get nmaxevent - * @return maximum number of events that may occur for each type + * @brief sets flags indicating that all states should be treated as + * non-negative */ - int nMaxEvent() const; + void setAllStatesNonNegative(); /** - * @brief Set nmaxevent - * @param nmaxevent maximum number of events that may occur for each type + * @brief Get the list of parameters for which sensitivities are computed + * @return list of parameter indices */ - void setNMaxEvent(int nmaxevent); + std::vector const &getParameterList() const; /** - * @brief Get number of timepoints - * @return number of timepoints + * @brief entry in parameter list + * @param pos index + * @return entry */ - int nt() const; + int plist(int pos) const; /** - * @brief Get ParameterScale for each parameter - * @return vector of parameter scale + * @brief Set the list of parameters for which sensitivities are + * computed, resets initial state sensitivities + * @param plist list of parameter indices */ - std::vector const &getParameterScale() const; + void setParameterList(std::vector const &plist); /** - * @brief Set ParameterScale for each parameter, resets initial state - * sensitivities - * @param pscale scalar parameter scale for all parameters + * @brief Get the initial states + * @return initial state vector */ - void setParameterScale(ParameterScaling pscale); + std::vector const &getInitialStates() const; /** - * @brief Set ParameterScale for each parameter, resets initial state - * sensitivities - * @param pscaleVec vector of parameter scales + * @brief Set the initial states + * @param x0 initial state vector */ - void setParameterScale(const std::vector &pscaleVec); + void setInitialStates(std::vector const &x0); /** - * @brief Get the parameter vector - * @return The user-set parameters (see also getUnscaledParameters) + * @brief Get the initial states sensitivities + * @return vector of initial state sensitivities */ - std::vector const &getParameters() const; + std::vector const &getInitialStateSensitivities() const; /** - * @brief Sets the parameter vector - * @param p vector of parameters + * @brief Set the initial state sensitivities + * @param sx0 vector of initial state sensitivities with chainrule + * applied. This could be a slice of ReturnData::sx or ReturnData::sx0 */ - void setParameters(std::vector const &p); + void setInitialStateSensitivities(std::vector const &sx0); /** - * @brief Gets parameters with transformation according to ParameterScale - * applied - * @return unscaled parameters + * @brief Set the initial state sensitivities + * @param sx0 vector of initial state sensitivities without chainrule + * applied. This could be the readin from a model.sx0data saved to hdf5. */ - std::vector const &getUnscaledParameters() const; + void setUnscaledInitialStateSensitivities(std::vector const &sx0); /** - * @brief Gets the fixedParameter member - * @return vector of fixed parameters + * @brief Sets the mode how sensitivities are computed in the steadystate + * simulation + * @param mode steadyStateSensitivityMode */ - std::vector const &getFixedParameters() const; + void setSteadyStateSensitivityMode(SteadyStateSensitivityMode mode); /** - * @brief Sets the fixedParameter member - * @param k vector of fixed parameters + * @brief Gets the mode how sensitivities are computed in the steadystate + * simulation + * @return flag value */ - void setFixedParameters(std::vector const &k); + SteadyStateSensitivityMode getSteadyStateSensitivityMode() const; /** - * @brief Get value of fixed parameter with the specified Id - * @param par_id parameter id - * @return parameter value + * @brief Set whether initial states depending on fixedParmeters are to be + * reinitialized after preequilibration and presimulation + * @param flag true/false */ - realtype getFixedParameterById(std::string const &par_id) const; + void setReinitializeFixedParameterInitialStates(bool flag); /** - * @brief Get value of fixed parameter with the specified name, - if multiple parameters have the same name, - the first parameter with matching name is returned - * @param par_name parameter name - * @return parameter value + * @brief Get whether initial states depending on fixedParmeters are to be + * reinitialized after preequilibration and presimulation + * @return flag true/false */ - realtype getFixedParameterByName(std::string const &par_name) const; + bool getReinitializeFixedParameterInitialStates() const; /** - * @brief Set value of first fixed parameter with the specified id - * @param par_id fixed parameter id - * @param value fixed parameter value + * @brief Require computation of sensitivities for all parameters p + * [0..np[ in natural order, resets initial state sensitivities */ - void setFixedParameterById(std::string const &par_id, realtype value); + void requireSensitivitiesForAllParameters(); /** - * @brief Set values of all fixed parameters with the id matching the - * specified regex - * @param par_id_regex fixed parameter name regex - * @param value fixed parameter value - * @return number of fixed parameter ids that matched the regex - */ - int setFixedParametersByIdRegex(std::string const &par_id_regex, - realtype value); - - /** - * @brief Set value of first fixed parameter with the specified name, - * @param par_name fixed parameter id - * @param value fixed parameter value - */ - void setFixedParameterByName(std::string const &par_name, realtype value); - - /** - * @brief Set value of all fixed parameters with name matching the specified - * regex, - * @param par_name_regex fixed parameter name regex - * @param value fixed parameter value - * @return number of fixed parameter names that matched the regex + * @brief Time-resolved observables, + * @param y buffer (dimension: ny) + * @param t current timepoint + * @param x current state */ - int setFixedParametersByNameRegex(std::string const &par_name_regex, - realtype value); + void getObservable(gsl::span y, const realtype t, + const AmiVector &x); /** - * @brief Get the timepoint vector - * @return timepoint vector + * @brief Sensitivity of time-resolved observables, + * total derivative sy = dydx * sx + dydp (only for forward sensitivities) + * @param sy buffer (dimension: ny x nplist, row-major) + * @param t timpoint + * @param x state variables + * @param sx state sensitivities */ - std::vector const &getTimepoints() const; + void getObservableSensitivity(gsl::span sy, const realtype t, + const AmiVector &x, const AmiVectorArray &sx); /** - * @brief Set the timepoint vector - * @param ts timepoint vector - */ - void setTimepoints(std::vector const &ts); - - /** - * @brief gets flags indicating whether states should be treated as - * non-negative - * @return vector of flags + * @brief Time-resolved observable standard deviations + * @param sigmay buffer (dimension: ny) + * @param it timepoint index + * @param edata pointer to experimental data instance (optional, + * pass nullptr to ignore) */ - std::vector const &getStateIsNonNegative() const; + void getObservableSigma(gsl::span sigmay, const int it, + const ExpData *edata); /** - * @brief sets flags indicating whether states should be treated as - * non-negative - * @param stateIsNonNegative vector of flags + * @brief Sensitivity of time-resolved observable standard deviation, + * total derivative (can be used with both adjoint and forward sensitivity) + * @param ssigmay buffer (dimension: ny x nplist, row-major) + * @param it timepoint index + * @param edata pointer to experimental data instance (optional, + * pass nullptr to ignore) */ - void setStateIsNonNegative(std::vector const &stateIsNonNegative); + void getObservableSigmaSensitivity(gsl::span ssigmay, + const int it, const ExpData *edata); /** - * @brief sets flags indicating that all states should be treated as - * non-negative + * @brief Time-resolved measurement negative log-likelihood Jy + * @param Jy buffer (dimension: 1) + * @param it timepoint index + * @param x state variables + * @param edata experimental data instance */ - void setAllStatesNonNegative(); + void addObservableObjective(realtype &Jy, const int it, const AmiVector &x, + const ExpData &edata); /** - * @brief Get timepoint for given index - * @param idx timepoint index - * @return timepoint + * @brief Sensitivity of time-resolved measurement negative log-likelihood + * Jy, total derivative (to be used with forward senstivities) + * @param sllh first order buffer (dimension: nplist) + * @param s2llh second order buffer (dimension: nJ-1 x nplist, row-major) + * @param it timepoint index + * @param x state variables + * @param sx state sensitivities + * @param edata experimental data instance */ - double t(int idx) const; + void addObservableObjectiveSensitivity(std::vector &sllh, + std::vector &s2llh, + const int it, const AmiVector &x, + const AmiVectorArray &sx, + const ExpData &edata); /** - * @brief Get the list of parameters for which sensitivities are computed - * @return list of parameter indices + * @brief Sensitivity of time-resolved measurement negative log-likelihood + * Jy, partial derivative (to be used with adjoint senstivities) + * @param sllh first order buffer (dimension: nplist) + * @param s2llh second order buffer (dimension: nJ-1 x nplist, row-major) + * @param it timepoint index + * @param x state variables + * @param edata experimental data instance */ - std::vector const &getParameterList() const; + void addPartialObservableObjectiveSensitivity(std::vector &sllh, + std::vector &s2llh, + const int it, + const AmiVector &x, + const ExpData &edata); /** - * @brief Set the list of parameters for which sensitivities are - * computed, resets initial state sensitivities - * @param plist list of parameter indices + * @brief State sensitivity of the negative loglikelihood Jy, + * partial derivative (to be used with adjoint senstivities) + * @param dJydx buffer (dimension: nJ x nx_solver, row-major) + * @param it timepoint index + * @param x state variables + * @param edata experimental data instance */ - void setParameterList(std::vector const &plist); + void getAdjointStateObservableUpdate(gsl::span dJydx, + const int it, const AmiVector &x, + const ExpData &edata); /** - * @brief Get the initial states - * @return initial state vector + * @brief Event-resolved observables + * @param z buffer (dimension: nz) + * @param ie event index + * @param t timepoint + * @param x state variables */ - std::vector const &getInitialStates() const; - + void getEvent(gsl::span z, const int ie, const realtype t, + const AmiVector &x); /** - * @brief Set the initial states - * @param x0 initial state vector + * @brief Sensitivities of event-resolved observables, total derivative, + * total derivative (only forward sensitivities) + * @param sz buffer (dimension: nz x nplist, row-major) + * @param ie event index + * @param t timepoint + * @param x state variables + * @param sx state sensitivities */ - void setInitialStates(std::vector const &x0); + void getEventSensitivity(gsl::span sz, const int ie, + const realtype t, const AmiVector &x, + const AmiVectorArray &sx); /** - * @brief Get the initial states sensitivities - * @return vector of initial state sensitivities + * @brief Sensitivity of z at final timepoint (ignores sensitivity of + * timepoint), total derivative + * @param sz output buffer (dimension: nz x nplist, row-major) + * @param ie event index */ - std::vector const &getInitialStateSensitivities() const; + void getUnobservedEventSensitivity(gsl::span sz, const int ie); /** - * @brief Set the initial state sensitivities - * @param sx0 vector of initial state sensitivities with chainrule - * applied. This could be a slice of ReturnData::sx or ReturnData::sx0 + * @brief Regularization for event-resolved observables + * @param rz buffer (dimension: nz) + * @param ie event index + * @param t timepoint + * @param x state variables */ - void setInitialStateSensitivities(std::vector const &sx0); + void getEventRegularization(gsl::span rz, const int ie, + const realtype t, const AmiVector &x); /** - * @brief Set the initial state sensitivities - * @param sx0 vector of initial state sensitivities without chainrule - * applied. This could be the readin from a model.sx0data saved to hdf5. + * @brief Sensitivities of regularization for event-resolved observables, + * total derivative (only forward sensitivities) + * @param srz buffer (dimension: nz x nplist, row-major) + * @param ie event index + * @param t timepoint + * @param x state variables + * @param sx state sensitivities */ - void setUnscaledInitialStateSensitivities(std::vector const &sx0); - + void getEventRegularizationSensitivity(gsl::span srz, + const int ie, const realtype t, + const AmiVector &x, + const AmiVectorArray &sx); /** - * @brief get simulation start time - * @return simulation start time + * @brief Event-resolved observable standard deviations + * @param sigmaz buffer (dimension: nz) + * @param ie event index + * @param nroots event occurence + * @param t timepoint + * @param edata pointer to experimental data instance (optional, + * pass nullptr to ignore) */ - double t0() const; + void getEventSigma(gsl::span sigmaz, const int ie, + const int nroots, const realtype t, + const ExpData *edata); /** - * @brief set simulation start time - * @param t0 simulation start time + * @brief Sensitivities of event-resolved observable standard deviations, + * total derivative (only forward sensitivities) + * @param ssigmaz buffer (dimension: nz x nplist, row-major) + * @param ie event index + * @param nroots event occurence + * @param t timepoint + * @param edata pointer to experimental data instance (optional, + * pass nullptr to ignore) */ - void setT0(double t0); + void getEventSigmaSensitivity(gsl::span ssigmaz, const int ie, + const int nroots, const realtype t, + const ExpData *edata); /** - * @brief entry in parameter list - * @param pos index - * @return entry + * @brief Event-resolved observable negative log-likelihood, + * @param Jz buffer (dimension: 1) + * @param ie event index + * @param nroots event occurence + * @param t timepoint + * @param x state variables + * @param edata experimental data instance */ - int plist(int pos) const; + void addEventObjective(realtype &Jz, const int ie, const int nroots, + const realtype t, const AmiVector &x, + const ExpData &edata); /** - * @brief Require computation of sensitivities for all parameters p - * [0..np[ in natural order, resets initial state sensitivities + * @brief Event-resolved observable negative log-likelihood, + * @param Jrz buffer (dimension: 1) + * @param ie event index + * @param nroots event occurence + * @param t timepoint + * @param x state variables + * @param edata experimental data instance */ - void requireSensitivitiesForAllParameters(); + void addEventObjectiveRegularization(realtype &Jrz, const int ie, + const int nroots, const realtype t, + const AmiVector &x, + const ExpData &edata); /** - * @brief Recurring terms in xdot + * @brief Sensitivity of time-resolved measurement negative log-likelihood + * Jy, total derivative (to be used with forward senstivities) + * @param sllh first order buffer (dimension: nplist) + * @param s2llh second order buffer (dimension: nJ-1 x nplist, row-major) + * @param ie event index + * @param nroots event occurence * @param t timepoint - * @param x array with the states + * @param x state variables + * @param sx state sensitivities + * @param edata experimental data instance + */ + void addEventObjectiveSensitivity(std::vector &sllh, + std::vector &s2llh, + const int ie, const int nroots, + const realtype t, const AmiVector &x, + const AmiVectorArray &sx, + const ExpData &edata); + + /** + * @brief Sensitivity of time-resolved measurement negative log-likelihood + * Jy, partial derivative (to be used with adjoint senstivities) + * @param sllh first order buffer (dimension: nplist) + * @param s2llh second order buffer (dimension: nJ-1 x nplist, row-major) + * @param ie event index + * @param nroots event occurence + * @param t timepoint + * @param x state variables + * @param edata experimental data instance */ - void fw(realtype t, const realtype *x); + void addPartialEventObjectiveSensitivity(std::vector &sllh, + std::vector &s2llh, + const int ie, const int nroots, + const realtype t, + const AmiVector &x, + const ExpData &edata); /** - * @brief Recurring terms in xdot, parameter derivative + * @brief State sensitivity of the negative loglikelihood Jz, + * partial derivative (to be used with adjoint senstivities) + * @param dJzdx buffer (dimension: nJ x nx_solver, row-major) + * @param ie event index + * @param nroots event occurence * @param t timepoint - * @param x array with the states - * @return flag indicating whether dwdp will be returned in dense storage - * dense: true, sparse: false + * @param x state variables + * @param edata experimental data instance */ - void fdwdp(realtype t, const realtype *x); + void getAdjointStateEventUpdate(gsl::span dJzdx, const int ie, + const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata); /** - * @brief Recurring terms in xdot, state derivative + * @brief Sensitivity of event timepoint, total derivative (only forward + * sensi) + * @param stau current timepoint (dimension: nplist) * @param t timepoint - * @param x array with the states + * @param ie event index + * @param x state variables + * @param sx state sensitivities */ - void fdwdx(realtype t, const realtype *x); + void getEventTimeSensitivity(std::vector &stau, const realtype t, + const int ie, const AmiVector &x, + const AmiVectorArray &sx); /** - * Residual function - * @param it time index - * @param rdata ReturnData instance to which result will be written - * @param edata ExpData instance containing observable data + * @brief Update state variables after event + * @param x current state (will be overwritten) + * @param ie event index + * @param t current timepoint + * @param xdot current residual function values + * @param xdot_old value of residual function before event */ - void fres(int it, ReturnData *rdata, const ExpData *edata); + void addStateEventUpdate(AmiVector &x, const int ie, const realtype t, + const AmiVector &xdot, const AmiVector &xdot_old); /** - * Chi-squared function - * @param it time index - * @param rdata ReturnData instance to which result will be written + * @brief Update state sensitivity after event + * @param sx current state sensitivity (will be overwritten) + * @param ie event index + * @param t current timepoint + * @param x_old current state + * @param xdot current residual function values + * @param xdot_old value of residual function before event + * @param stau timepoint sensitivity, to be computed with + * Model::getEventTimeSensitivity */ - void fchi2(int it, ReturnData *rdata); + void addStateSensitivityEventUpdate(AmiVectorArray &sx, const int ie, + const realtype t, + const AmiVector &x_old, + const AmiVector &xdot, + const AmiVector &xdot_old, + const std::vector &stau); /** - * Residual sensitivity function - * @param it time index - * @param rdata ReturnData instance to which result will be written - * @param edata ExpData instance containing observable data + * @brief Update adjoint state after event + * @param xB current adjoint state (will be overwritten) + * @param ie event index + * @param t current timepoint + * @param x current state + * @param xdot current residual function values + * @param xdot_old value of residual function before event */ - void fsres(int it, ReturnData *rdata, const ExpData *edata); + void addAdjointStateEventUpdate(AmiVector &xB, const int ie, + const realtype t, const AmiVector &x, + const AmiVector &xdot, + const AmiVector &xdot_old); /** - * Fisher information matrix function - * @param it time index - * @param rdata ReturnData instance to which result will be written + * @brief Update adjoint quadratures after event + * @param xQB current quadrature state (will be overwritten) + * @param ie event index + * @param t current timepoint + * @param x current state + * @param xB current adjoint state + * @param xdot current residual function values + * @param xdot_old value of residual function before event */ - void fFIM(int it, ReturnData *rdata); + void addAdjointQuadratureEventUpdate(AmiVector xQB, const int ie, + const realtype t, const AmiVector &x, + const AmiVector &xB, + const AmiVector &xdot, + const AmiVector &xdot_old); /** - * Update the heaviside variables h on event occurences + * @brief Update the heaviside variables h on event occurences * * @param rootsfound provides the direction of the zero-crossing, so adding * it will give the right update to the heaviside variables (zero if no root @@ -928,40 +963,14 @@ class Model : public AbstractModel { void updateHeaviside(const std::vector &rootsfound); /** - * Updates the heaviside variables h on event occurences in the backward - problem + * @brief Updates the heaviside variables h on event occurences in the + backward problem * @param rootsfound provides the direction of the zero-crossing, so adding it will give the right update to the heaviside variables (zero if no root was found) */ void updateHeavisideB(const int *rootsfound); - /** - * @brief Serialize Model (see boost::serialization::serialize) - * @param ar Archive to serialize to - * @param u Data to serialize - * @param version Version number - */ - template - friend void boost::serialization::serialize(Archive &ar, Model &u, - unsigned int version); - - /** - * @brief Check equality of data members - * @param a first model instance - * @param b second model instance - * @return equality - */ - friend bool operator==(const Model &a, const Model &b); - - /** - * Get current timepoint from index - * @param it timepoint index - * @param rdata pointer to return data instance - * @return current timepoint - */ - realtype gett(int it, const ReturnData *rdata) const; - /** * @brief Check if the given array has only finite elements. * @@ -975,319 +984,473 @@ class Model : public AbstractModel { int checkFinite(gsl::span array, const char *fun) const; /** - * @brief Reports whether the model has parameter names set. - * Also returns true if the number of corresponding variables is just zero. - * @return boolean indicating whether parameter names were set + * @brief Set if the result of every call to Model::f* should be checked + * for finiteness + * @param alwaysCheck */ - virtual bool hasParameterNames() const; + void setAlwaysCheckFinite(bool alwaysCheck); /** - * @brief Get names of the model parameters - * @return the names + * @brief Get setting of whether the result of every call to Model::f* + * should be checked for finiteness + * @return that */ - virtual std::vector getParameterNames() const; + bool getAlwaysCheckFinite() const; /** - * @brief Reports whether the model has state names set. - * Also returns true if the number of corresponding variables is just zero. - * @return boolean indicating whether state names were set + * @brief check whether the model was generated from python + * @return that */ - virtual bool hasStateNames() const; + virtual bool wasPythonGenerated() const { return false; } /** - * @brief Get names of the model states - * @return the names + * @brief Initial states + * @param x pointer to state variables */ - virtual std::vector getStateNames() const; + void fx0(AmiVector &x); /** - * @brief Reports whether the model has fixed parameter names set. - * Also returns true if the number of corresponding variables is just zero. - * @return boolean indicating whether fixed parameter names were set + * @brief Sets only those initial states that are specified via + * fixedParmeters + * @param x pointer to state variables */ - virtual bool hasFixedParameterNames() const; + void fx0_fixedParameters(AmiVector &x); /** - * @brief Get names of the fixed model parameters - * @return the names + * @brief Initial value for initial state sensitivities + * @param sx pointer to state sensitivity variables + * @param x pointer to state variables + **/ + void fsx0(AmiVectorArray &sx, const AmiVector &x); + + /** + * @brief Sets only those initial states sensitivities that are affected + *from fx0 fixedParmeters + * @param sx pointer to state sensitivity variables + * @param x pointer to state variables + **/ + void fsx0_fixedParameters(AmiVectorArray &sx, const AmiVector &x); + + /** + * @brief Sensitivity of derivative initial states sensitivities sdx0 (only + * necessary for DAEs) + **/ + virtual void fsdx0(); + + /** + * @brief Expands conservation law for states + * @param x_rdata pointer to state variables with conservation laws + * expanded (stored in rdata) + * @param x_solver pointer to state variables with conservation laws + * applied (solver returns this) */ - virtual std::vector getFixedParameterNames() const; + void fx_rdata(AmiVector &x_rdata, const AmiVector &x_solver); /** - * @brief Reports whether the model has observable names set. - * Also returns true if the number of corresponding variables is just zero. - * @return boolean indicating whether observabke names were set + * @brief Expands conservation law for state sensitivities + * @param sx_rdata pointer to state variable sensitivities with + * conservation laws expanded (stored in rdata) + * @param sx_solver pointer to state variable sensitivities with + * conservation laws applied (solver returns this) */ - virtual bool hasObservableNames() const; + void fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx_solver); + + /** number of states */ + int nx_rdata{0}; + /** number of states in the unaugmented system */ + int nxtrue_rdata{0}; + + /** number of states with conservation laws applied */ + int nx_solver{0}; + + /** number of states in the unaugmented system with conservation laws + * applied */ + int nxtrue_solver{0}; + + /** number of observables */ + int ny{0}; + + /** number of observables in the unaugmented system */ + int nytrue{0}; + + /** number of event outputs */ + int nz{0}; + + /** number of event outputs in the unaugmented system */ + int nztrue{0}; + + /** number of events */ + int ne{0}; + + /** number of common expressions */ + int nw{0}; + + /** number of derivatives of common expressions wrt x */ + int ndwdx{0}; + + /** number of derivatives of common expressions wrt p */ + int ndwdp{0}; + + /** number of nonzero entries in dxdotdw */ + int ndxdotdw{0}; + + /** number of nonzero entries in dJydy */ + std::vector ndJydy; + + /** number of nonzero entries in jacobian */ + int nnz{0}; + + /** dimension of the augmented objective function for 2nd order ASA */ + int nJ{0}; + + /** upper bandwith of the jacobian */ + int ubw{0}; + + /** lower bandwith of the jacobian */ + int lbw{0}; + + /** flag indicating whether for sensi == AMICI_SENSI_ORDER_SECOND + * directional or full second order derivative will be computed */ + SecondOrderMode o2mode{SecondOrderMode::none}; + + /** flag array for DAE equations */ + std::vector idlist; + + /** temporary storage of dxdotdp data across functions (dimension: nplist x + * nx_solver, row-major) */ + AmiVectorArray dxdotdp; + + protected: /** - * @brief Get names of the observables - * @return the names + * @brief Writes part of a slice to a buffer according to indices specified + * in z2event + * @param slice source data slice + * @param buffer output data slice + * @param ie event index */ - virtual std::vector getObservableNames() const; + void writeSliceEvent(gsl::span slice, + gsl::span buffer, const int ie); /** - * @brief Reports whether the model has parameter ids set. - * Also returns true if the number of corresponding variables is just zero. - * @return boolean indicating whether parameter ids were set + * @brief Writes part of a sensitivity slice to a buffer according to + * indices specified in z2event + * @param slice source data slice + * @param buffer output data slice + * @param ie event index */ - virtual bool hasParameterIds() const; + void writeSensitivitySliceEvent(gsl::span slice, + gsl::span buffer, const int ie); /** - * @brief Get ids of the model parameters - * @return the ids + * @brief Seperates first and second order objective sensitivity information + * and writes them into the respective buffers + * @param dLLhdp data with mangled first and second order information + * @param sllh first order buffer + * @param s2llh second order buffer */ - virtual std::vector getParameterIds() const; + void writeLLHSensitivitySlice(const std::vector &dLLhdp, + std::vector &sllh, + std::vector &s2llh); /** - * @brief Get value of first model parameter with the specified id - * @param par_id parameter id - * @return parameter value + * @brief Verifies that the provided buffers have the expected size. + * @param sllh first order buffer + * @param s2llh second order buffer */ - realtype getParameterById(std::string const &par_id) const; + void checkLLHBufferSize(std::vector &sllh, + std::vector &s2llh); /** - * @brief Get value of first model parameter with the specified name, - * @param par_name parameter name - * @return parameter value + * @brief Set the nplist-dependent vectors to their proper sizes */ - realtype getParameterByName(std::string const &par_name) const; + void initializeVectors(); /** - * @brief Set value of first model parameter with the specified id - * @param par_id parameter id - * @param value parameter value + * @brief Observables / measurements + * @param t current timepoint + * @param x current state */ - void setParameterById(std::string const &par_id, realtype value); + void fy(realtype t, const AmiVector &x); /** - * @brief Set all values of model parameters with ids matching the specified - * regex - * @param par_id_regex parameter id regex - * @param value parameter value - * @return number of parameter ids that matched the regex + * @brief Partial derivative of observables y w.r.t. model parameters p + * @param t current timepoint + * @param x current state */ - int setParametersByIdRegex(std::string const &par_id_regex, realtype value); + void fdydp(realtype t, const AmiVector &x); /** - * @brief Set value of first model parameter with the specified name - * @param par_name parameter name - * @param value parameter value + * @brief Partial derivative of observables y w.r.t. state variables x + * @param t current timepoint + * @param x current state */ - void setParameterByName(std::string const &par_name, realtype value); + void fdydx(realtype t, const AmiVector &x); /** - * @brief Set all values of all model parameters with names matching the - * specified regex - * @param par_name_regex parameter name regex - * @param value parameter value - * @return number of fixed parameter names that matched the regex + * @brief Standard deviation of measurements + * @param it timepoint index + * @param edata pointer to experimental data instance */ - int setParametersByNameRegex(std::string const &par_name_regex, - realtype value); + void fsigmay(int it, const ExpData *edata); /** - * @brief Reports whether the model has state ids set. - * Also returns true if the number of corresponding variables is just zero. - * @return boolean indicating whether state ids were set + * @brief Partial derivative of standard deviation of measurements w.r.t. + * model + * @param it timepoint index + * @param edata pointer to ExpData data instance holding sigma values */ - virtual bool hasStateIds() const; + void fdsigmaydp(int it, const ExpData *edata); /** - * @brief Get ids of the model states - * @return the ids + * @brief Negative log-likelihood of measurements y + * @param Jy variable to which llh will be added + * @param it timepoint index + * @param y simulated observable + * @param edata pointer to experimental data instance + */ + void fJy(realtype &Jy, int it, const AmiVector &y, const ExpData &edata); + + /** + * @brief Model specific implementation of fdJydy colptrs + * @param indexptrs column pointers + * @param index ytrue index + */ + virtual void fdJydy_colptrs(sunindextype *indexptrs, int index); + + /** + * @brief Model specific implementation of fdxdotdw row vals + * @param indexptrs row val pointers + * @param index ytrue index + */ + virtual void fdJydy_rowvals(sunindextype *indexptrs, int index); + + /** + * @brief Partial derivative of time-resolved measurement negative + * log-likelihood Jy + * @param it timepoint index + * @param x state variables + * @param edata pointer to experimental data instance + */ + void fdJydy(int it, const AmiVector &x, const ExpData &edata); + + /** + * @brief Sensitivity of time-resolved measurement negative log-likelihood + * Jy w.r.t. standard deviation sigma + * @param it timepoint index + * @param x state variables + * @param edata pointer to experimental data instance + */ + void fdJydsigma(int it, const AmiVector &x, const ExpData &edata); + + /** + * @brief Compute sensitivity of time-resolved measurement negative + * log-likelihood Jy w.r.t. parameters for the given timepoint. Add result + * to respective fields in rdata. + * @param it timepoint index + * @param x state variables + * @param edata pointer to experimental data instance + */ + void fdJydp(const int it, const AmiVector &x, const ExpData &edata); + + /** + * @brief Sensitivity of time-resolved measurement negative log-likelihood + * Jy w.r.t. state variables + * @param it timepoint index + * @param x state variables + * @param edata pointer to experimental data instance + */ + void fdJydx(const int it, const AmiVector &x, const ExpData &edata); + + /** + * @brief Event-resolved output + * @param ie event index + * @param t current timepoint + * @param x current state + */ + void fz(int ie, realtype t, const AmiVector &x); + + /** + * @brief Partial derivative of event-resolved output z w.r.t. to model + * parameters p + * @param ie event index + * @param t current timepoint + * @param x current state + */ + void fdzdp(int ie, realtype t, const AmiVector &x); + + /** + * @brief Partial derivative of event-resolved output z w.r.t. to model + * states x + * @param ie event index + * @param t current timepoint + * @param x current state + */ + void fdzdx(int ie, realtype t, const AmiVector &x); + + /** + * @brief Event root function of events (equal to froot but does not include + * non-output events) + * @param ie event index + * @param t current timepoint + * @param x current state + */ + void frz(int ie, realtype t, const AmiVector &x); + + /** + * @brief Sensitivity of event-resolved root output w.r.t. to model + * parameters p + * @param ie event index + * @param t current timepoint + * @param x current state + */ + void fdrzdp(int ie, realtype t, const AmiVector &x); + + /** + * @brief Sensitivity of event-resolved measurements rz w.r.t. to model + * states x + * @param ie event index + * @param t current timepoint + * @param x current state */ - virtual std::vector getStateIds() const; + void fdrzdx(int ie, realtype t, const AmiVector &x); /** - * @brief Reports whether the model has fixed parameter ids set. - * Also returns true if the number of corresponding variables is just zero. - * @return boolean indicating whether fixed parameter ids were set + * @brief Standard deviation of events + * @param ie event index + * @param nroots event index + * @param t current timepoint + * @param edata pointer to experimental data instance */ - virtual bool hasFixedParameterIds() const; + void fsigmaz(const int ie, const int nroots, const realtype t, + const ExpData *edata); /** - * @brief Get ids of the fixed model parameters - * @return the ids + * @brief Sensitivity of standard deviation of events measurements w.r.t. + * model parameters p + * @param ie event index + * @param nroots event occurence + * @param t current timepoint + * @param edata pointer to experimental data instance */ - virtual std::vector getFixedParameterIds() const; + void fdsigmazdp(int ie, int nroots, realtype t, const ExpData *edata); /** - * @brief Reports whether the model has observable ids set. - * Also returns true if the number of corresponding variables is just zero. - * @return boolean indicating whether observale ids were set + * @brief Negative log-likelihood of event-resolved measurements z + * @param Jz variable to which llh will be added + * @param nroots event index + * @param z simulated event + * @param edata pointer to experimental data instance */ - virtual bool hasObservableIds() const; + void fJz(realtype &Jz, int nroots, const AmiVector &z, + const ExpData &edata); /** - * @brief Get ids of the observables - * @return the ids + * @brief Partial derivative of event measurement negative log-likelihood Jz + * @param ie event index + * @param nroots event index + * @param t current timepoint + * @param x state variables + * @param edata pointer to experimental data instance */ - virtual std::vector getObservableIds() const; + void fdJzdz(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata); /** - * @brief Sets the mode how sensitivities are computed in the steadystate - * simulation - * @param mode steadyStateSensitivityMode + * @brief Sensitivity of event measurement negative log-likelihood Jz + * w.r.t. standard deviation sigmaz + * @param ie event index + * @param nroots event index + * @param t current timepoint + * @param x state variables + * @param edata pointer to experimental data instance */ - void setSteadyStateSensitivityMode(SteadyStateSensitivityMode mode); + void fdJzdsigma(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata); /** - * @brief Gets the mode how sensitivities are computed in the steadystate - * simulation - * @return flag value + * @brief Sensitivity of event-resolved measurement negative log-likelihood + * Jz w.r.t. parameters + * @param ie event index + * @param nroots event index + * @param t current timepoint + * @param x state variables + * @param edata pointer to experimental data instance */ - SteadyStateSensitivityMode getSteadyStateSensitivityMode() const; + void fdJzdp(const int ie, const int nroots, realtype t, const AmiVector &x, + const ExpData &edata); /** - * @brief Set whether initial states depending on fixedParmeters are to be - * reinitialized after preequilibration and presimulation - * @param flag true/false + * @brief Sensitivity of event-resolved measurement negative log-likelihood + * Jz w.r.t. state variables + * @param ie event index + * @param nroots event index + * @param t current timepoint + * @param x state variables + * @param edata pointer to experimental data instance */ - void setReinitializeFixedParameterInitialStates(bool flag); + void fdJzdx(const int ie, const int nroots, realtype t, const AmiVector &x, + const ExpData &edata); /** - * @brief Get whether initial states depending on fixedParmeters are to be - * reinitialized after preequilibration and presimulation - * @return flag true/false + * @brief Regularization of negative log-likelihood with roots of + * event-resolved measurements rz + * @param Jrz variable to which regularization will be added + * @param nroots event index + * @param rz regularization variable + * @param edata pointer to experimental data instance */ - bool getReinitializeFixedParameterInitialStates() const; + void fJrz(realtype &Jrz, int nroots, const AmiVector &rz, + const ExpData &edata); /** - * @brief Set if the result of every call to Model::f* should be checked - * for finiteness - * @param alwaysCheck + * @brief Partial derivative of event measurement negative log-likelihood Jz + * @param ie event index + * @param nroots event index + * @param t current timepoint + * @param x state variables + * @param edata pointer to experimental data instance */ - void setAlwaysCheckFinite(bool alwaysCheck); + void fdJrzdz(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata); /** - * @brief Get setting of whether the result of every call to Model::f* - * should be checked for finiteness - * @return that + * @brief Sensitivity of event measurement negative log-likelihood Jz + * w.r.t. standard deviation sigmaz + * @param ie event index + * @param nroots event index + * @param t current timepoint + * @param x state variables + * @param edata pointer to experimental data instance */ - bool getAlwaysCheckFinite() const; + void fdJrzdsigma(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata); /** - * @brief check whether the model was generated from python - * @return that + * @brief Recurring terms in xdot + * @param t timepoint + * @param x array with the states */ - virtual bool wasPythonGenerated() const { - return false; - } - - /** number of states */ - int nx_rdata{0}; - - /** number of states in the unaugmented system */ - int nxtrue_rdata{0}; - - /** number of states with conservation laws applied */ - int nx_solver{0}; - - /** number of states in the unaugmented system with conservation laws - * applied */ - int nxtrue_solver{0}; - - /** number of observables */ - int ny{0}; - - /** number of observables in the unaugmented system */ - int nytrue{0}; - - /** number of event outputs */ - int nz{0}; - - /** number of event outputs in the unaugmented system */ - int nztrue{0}; - - /** number of events */ - int ne{0}; - - /** number of common expressions */ - int nw{0}; - - /** number of derivatives of common expressions wrt x */ - int ndwdx{0}; - - /** number of derivatives of common expressions wrt p */ - int ndwdp{0}; - - /** number of nonzero entries in dxdotdw */ - int ndxdotdw{0}; - - /** number of nonzero entries in dJydy */ - std::vector ndJydy; - - /** number of nonzero entries in jacobian */ - int nnz{0}; - - /** dimension of the augmented objective function for 2nd order ASA */ - int nJ{0}; - - /** upper bandwith of the jacobian */ - int ubw{0}; - - /** lower bandwith of the jacobian */ - int lbw{0}; - - /** flag indicating whether for sensi == AMICI_SENSI_ORDER_SECOND - * directional or full second order derivative will be computed */ - SecondOrderMode o2mode{SecondOrderMode::none}; - - /** index indicating to which event an event output belongs */ - std::vector z2event; - - /** flag array for DAE equations */ - std::vector idlist; - - /** data standard deviation for current timepoint (dimension: ny) */ - std::vector sigmay; - - /** parameter derivative of data standard deviation for current timepoint - * (dimension: nplist x ny, row-major) */ - std::vector dsigmaydp; - - /** event standard deviation for current timepoint (dimension: nz) */ - std::vector sigmaz; - - /** parameter derivative of event standard deviation for current timepoint - * (dimension: nplist x nz, row-major) */ - std::vector dsigmazdp; - - /** parameter derivative of data likelihood for current timepoint - * (dimension: nplist x nJ, row-major) */ - std::vector dJydp; - - /** parameter derivative of event likelihood for current timepoint - * (dimension: nplist x nJ, row-major) */ - std::vector dJzdp; - - /** change in x at current timepoint (dimension: nx_solver) */ - std::vector deltax; - - /** change in sx at current timepoint (dimension: nplist x nx_solver, - * row-major) */ - std::vector deltasx; + void fw(realtype t, const realtype *x); - /** change in xB at current timepoint (dimension: nJ x nxtrue_cl, row-major) + /** + * @brief Recurring terms in xdot, parameter derivative + * @param t timepoint + * @param x array with the states + * @return flag indicating whether dwdp will be returned in dense storage + * dense: true, sparse: false */ - std::vector deltaxB; - - /** change in qB at current timepoint (dimension: nJ x nplist, row-major) */ - std::vector deltaqB; - - /** tempory storage of dxdotdp data across functions (dimension: nplist x - * nx_solver, row-major) */ - AmiVectorArray dxdotdp; + void fdwdp(realtype t, const realtype *x); - protected: /** - * @brief Set the nplist-dependent vectors to their proper sizes + * @brief Recurring terms in xdot, state derivative + * @param t timepoint + * @param x array with the states */ - void initializeVectors(); + void fdwdx(realtype t, const realtype *x); /** - * Model specific implementation of fx_rdata + * @brief Model specific implementation of fx_rdata * @param x_rdata state variables with conservation laws expanded * @param x_solver state variables with conservation laws applied * @param tcl total abundances for conservation laws @@ -1296,7 +1459,7 @@ class Model : public AbstractModel { const realtype *tcl); /** - * Model specific implementation of fsx_solver + * @brief Model specific implementation of fsx_solver * @param sx_rdata state sensitivity variables with conservation laws * expanded * @param sx_solver state sensitivity variables with conservation laws @@ -1308,14 +1471,14 @@ class Model : public AbstractModel { const realtype *stcl, int ip); /** - * Model specific implementation of fx_solver + * @brief Model specific implementation of fx_solver * @param x_solver state variables with conservation laws applied * @param x_rdata state variables with conservation laws expanded **/ virtual void fx_solver(realtype *x_solver, const realtype *x_rdata); /** - * Model specific implementation of fsx_solver + * @brief Model specific implementation of fsx_solver * @param sx_rdata state sensitivity variables with conservation laws * expanded * @param sx_solver state sensitivity variables with conservation laws @@ -1324,14 +1487,14 @@ class Model : public AbstractModel { virtual void fsx_solver(realtype *sx_solver, const realtype *sx_rdata); /** - * Model specific implementation of ftotal_cl + * @brief Model specific implementation of ftotal_cl * @param total_cl total abundances of conservation laws * @param x_rdata state variables with conservation laws expanded **/ virtual void ftotal_cl(realtype *total_cl, const realtype *x_rdata); /** - * Model specific implementation of fstotal_cl + * @brief Model specific implementation of fstotal_cl * @param stotal_cl sensitivites for the total abundances of * conservation laws * @param sx_rdata state sensitivity variables with conservation laws @@ -1341,57 +1504,6 @@ class Model : public AbstractModel { virtual void fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, int ip); - /** create my slice at timepoint - * @param it timepoint index - * @param edata pointer to experimental data instance - */ - void getmy(int it, const ExpData *edata); - - /** create mz slice at event - * @param nroots event occurence - * @param edata pointer to experimental data instance - */ - void getmz(int nroots, const ExpData *edata); - - /** create y slice at timepoint - * @param it timepoint index - * @param rdata pointer to return data instance - * @return y y-slice from rdata instance - */ - const realtype *gety(int it, const ReturnData *rdata) const; - - /** create z slice at event - * @param nroots event occurence - * @param rdata pointer to return data instance - * @return z slice - */ - const realtype *getz(int nroots, const ReturnData *rdata) const; - - /** create rz slice at event - * @param nroots event occurence - * @param rdata pointer to return data instance - * @return rz slice - */ - const realtype *getrz(int nroots, const ReturnData *rdata) const; - - /** create sz slice at event - * @param nroots event occurence - * @param ip sensitivity index - * @param rdata pointer to return data instance - * @return z slice - */ - const realtype *getsz(int nroots, int ip, - const ReturnData *rdata) const; - - /** create srz slice at event - * @param nroots event occurence - * @param ip sensitivity index - * @param rdata pointer to return data instance - * @return rz slice - */ - const realtype *getsrz(int nroots, int ip, - const ReturnData *rdata) const; - /** * @brief Computes nonnegative state vector according to stateIsNonNegative * if anyStateNonNegative is set to false, i.e., all entries in @@ -1406,126 +1518,206 @@ class Model : public AbstractModel { N_Vector computeX_pos(const_N_Vector x); /** Sparse Jacobian (dimension: nnz)*/ - SUNMatrixWrapper J; + mutable SUNMatrixWrapper J; /** Sparse dxdotdw temporary storage (dimension: ndxdotdw) */ - SUNMatrixWrapper dxdotdw; + mutable SUNMatrixWrapper dxdotdw; /** Sparse dwdx temporary storage (dimension: ndwdx) */ - SUNMatrixWrapper dwdx; + mutable SUNMatrixWrapper dwdx; /** Dense Mass matrix (dimension: nx_solver x nx_solver) */ - SUNMatrixWrapper M; + mutable SUNMatrixWrapper M; /** current observable (dimension: nytrue) */ - std::vector my; + mutable std::vector my; /** current event measurement (dimension: nztrue) */ - std::vector mz; + mutable std::vector mz; + + /** Sparse observable derivative of data likelihood, + * only used if wasPythonGenerated()==true + * (dimension nytrue, nJ x ny, row-major) */ + mutable std::vector dJydy; + + /** observable derivative of data likelihood, + * only used if wasPythonGenerated()==false + * (dimension nJ x ny x nytrue, row-major) + */ + mutable std::vector dJydy_matlab; - /** Sparse observable derivative of data likelihood - * (dimension nytrue, nJ x ny, ordering = ?) */ - std::vector dJydy; + /** observable sigma derivative of data likelihood + * (dimension nJ x ny x nytrue, row-major) + */ + mutable std::vector dJydsigma; + + /** state derivative of data likelihood + * (dimension nJ x nx_solver, row-major) + */ + mutable std::vector dJydx; + + /** parameter derivative of data likelihood for current timepoint + * (dimension: nJ x nplist, row-major) + */ + mutable std::vector dJydp; - /** observable derivative of data likelihood (dimension nJ x nytrue x ny, - * ordering = ?) (only used if wasPythonGenerated()==false ) */ - std::vector dJydy_matlab; + /** event output derivative of event likelihood + * (dimension nJ x nz x nztrue, row-major) + */ + mutable std::vector dJzdz; - /** observable sigma derivative of data likelihood (dimension nJ x nytrue x - * ny, ordering = ?) */ - std::vector dJydsigma; + /** event sigma derivative of event likelihood + * (dimension nJ x nz x nztrue, row-major) + */ + mutable std::vector dJzdsigma; - /** event ouput derivative of event likelihood (dimension nJ x nztrue x nz, - * ordering = ?) */ - std::vector dJzdz; + /** event output derivative of event likelihood at final timepoint + * (dimension nJ x nz x nztrue, row-major) + */ + mutable std::vector dJrzdz; - /** event sigma derivative of event likelihood (dimension nJ x nztrue x nz, - * ordering = ?) */ - std::vector dJzdsigma; + /** event sigma derivative of event likelihood at final timepoint + * (dimension nJ x nz x nztrue, row-major) + */ + mutable std::vector dJrzdsigma; - /** event ouput derivative of event likelihood at final timepoint (dimension - * nJ x nztrue x nz, ordering = ?) */ - std::vector dJrzdz; + /** state derivative of event likelihood + * (dimension nJ x nx_solver, row-major) + */ + mutable std::vector dJzdx; - /** event sigma derivative of event likelihood at final timepoint (dimension - * nJ x nztrue x nz, ordering = ?) */ - std::vector dJrzdsigma; + /** parameter derivative of event likelihood for current timepoint + * (dimension: nJ x nplist x, row-major) + */ + mutable std::vector dJzdp; - /** state derivative of event output (dimension: nz x nx_solver, ordering = - * ?) */ - std::vector dzdx; + /** state derivative of event output + * (dimension: nz x nx_solver, row-major) + */ + mutable std::vector dzdx; - /** parameter derivative of event output (dimension: nz x nplist, ordering = - * ?) */ - std::vector dzdp; + /** parameter derivative of event output + * (dimension: nz x nplist, row-major) + */ + mutable std::vector dzdp; - /** state derivative of event timepoint (dimension: nz x nx_solver, ordering - * = ?) */ - std::vector drzdx; + /** state derivative of event regularization variable + * (dimension: nz x nx_solver, row-major) + */ + mutable std::vector drzdx; - /** parameter derivative of event timepoint (dimension: nz x nplist, - * ordering = ?) */ - std::vector drzdp; + /** parameter derivative of event regularization variable + * (dimension: nz x nplist, row-major) + */ + mutable std::vector drzdp; - /** parameter derivative of observable (dimension: nplist x ny, row-major) + /** parameter derivative of observable + * (dimension: ny x nplist, row-major) */ - std::vector dydp; + mutable std::vector dydp; - /** state derivative of observable - * (dimension: nx_solver x ny, ordering = row-major) */ - std::vector dydx; + /** state derivative of time-resolved observable + * (dimension: nx_solver x ny, row-major) + */ + mutable std::vector dydx; /** tempory storage of w data across functions (dimension: nw) */ - std::vector w; + mutable std::vector w; /** tempory storage of sparse/dense dwdp data across functions - (dimension: ndwdp) + * (dimension: ndwdp) + */ + mutable std::vector dwdp; + + /** tempory storage for flattened sx, + * (dimension: nx_solver x nplist, row-major) + */ + mutable std::vector sx; + + /** tempory storage for x_rdata (dimension: nx_rdata) */ + mutable std::vector x_rdata; + + /** tempory storage for sx_rdata slice (dimension: nx_rdata) */ + mutable std::vector sx_rdata; + + /** temporary storage for time-resolved observable (dimension: ny) */ + mutable std::vector y; + + /** data standard deviation for current timepoint (dimension: ny) */ + mutable std::vector sigmay; + + /** temporary storage for parameter derivative of data standard deviation, + * (dimension: ny x nplist, row-major) + */ + mutable std::vector dsigmaydp; + + /** temporary storage for event-resolved observable (dimension: nz) */ + mutable std::vector z; + + /** temporary storage for event regularization (dimension: nz) */ + mutable std::vector rz; + + /** temporary storage for event standard deviation (dimension: nz) */ + mutable std::vector sigmaz; + + /** temporary storage for parameter derivative of event standard deviation, + * (dimension: nz x nplist, row-major) */ - std::vector dwdp; + mutable std::vector dsigmazdp; - /** tempory storage of stau data across functions (dimension: nplist) */ - std::vector stau; + /** temporary storage for change in x after event (dimension: nx_solver) */ + mutable std::vector deltax; - /** tempory storage of sx data for flattening - (dimension: nx_solver x nplist, ordering = row-major) */ - std::vector sx; + /** temporary storage for change in sx after event + * (dimension: nx_solver x nplist, row-major) + */ + mutable std::vector deltasx; - /** tempory storage x_rdata (dimension: nx_rdata) */ - std::vector x_rdata; + /** temporary storage for change in xB after event (dimension: nx_solver) */ + mutable std::vector deltaxB; - /** tempory storage sx_rdata slice (dimension: nx_rdata) */ - std::vector sx_rdata; + /** temporary storage for change in qB after event + * (dimension: nJ x nplist, row-major) + */ + mutable std::vector deltaqB; /** flag indicating whether a certain heaviside function should be active or not (dimension: ne) */ - std::vector h; + mutable std::vector h; + + /** total abundances for conservation laws + (dimension: nx_rdata-nx_solver) */ + mutable std::vector total_cl; + + /** sensitivities of total abundances for conservation laws + (dimension: (nx_rdata-nx_solver) x np, row-major) */ + mutable std::vector stotal_cl; + + /** temporary storage of positified state variables according to + * stateIsNonNegative (dimension: nx_solver) */ + mutable AmiVector x_pos_tmp; /** unscaled parameters (dimension: np) */ std::vector unscaledParameters; - /** orignal user-provided, possibly scaled parameter array (size np) */ + /** orignal user-provided, possibly scaled parameter array (dimension: np) + */ std::vector originalParameters; /** constants (dimension: nk) */ std::vector fixedParameters; - /** total abundances for conservation laws - (dimension: nx_rdata-nx_solver) */ - std::vector total_cl; - - /** sensitivities of total abundances for conservation laws - (dimension: (nx_rdata-nx_solver) * np, ordering = row-major) */ - std::vector stotal_cl; + /** index indicating to which event an event output belongs */ + std::vector z2event; /** indexes of parameters wrt to which sensitivities are computed - * (dimension nplist) */ + * (dimension: nplist) */ std::vector plist_; /** state initialisation (size nx_solver) */ std::vector x0data; - /** sensitivity initialisation (size nx_rdata * nplist, ordering = - * row-major) */ + /** sensitivity initialisation (size nx_rdata x nplist, row-major) */ std::vector sx0data; /** timepoints (size nt) */ @@ -1538,10 +1730,6 @@ class Model : public AbstractModel { /** boolean indicating whether any entry in stateIsNonNegative is `true` */ bool anyStateNonNegative = false; - /** temporary storage of positified state variables according to - * stateIsNonNegative */ - AmiVector x_pos_tmp; - /** maximal number of events to track */ int nmaxevent = 10; diff --git a/include/amici/rdata.h b/include/amici/rdata.h index a2543cd0f0..999d453d3a 100644 --- a/include/amici/rdata.h +++ b/include/amici/rdata.h @@ -9,6 +9,7 @@ namespace amici { class Model; class ReturnData; class Solver; +class ExpData; } namespace boost { @@ -104,6 +105,32 @@ class ReturnData { */ void applyChainRuleFactorToSimulationResults(const Model *model); + + /** + * Residual function + * @param it time index + * @param edata ExpData instance containing observable data + */ + void fres(int it, const ExpData &edata); + + /** + * Chi-squared function + * @param it time index + */ + void fchi2(int it); + + /** + * Residual sensitivity function + * @param it time index + * @param edata ExpData instance containing observable data + */ + void fsres(int it, const ExpData &edata); + + /** + * Fisher information matrix function + * @param it time index + */ + void fFIM(int it); /** timepoints (dimension: nt) */ std::vector ts; diff --git a/matlab/@amifun/gccode.m b/matlab/@amifun/gccode.m index 2fe3117744..66b2476a21 100644 --- a/matlab/@amifun/gccode.m +++ b/matlab/@amifun/gccode.m @@ -139,7 +139,7 @@ cstr = regexprep(cstr,'var_deltax_([0-9]+)','deltax[$1]'); cstr = regexprep(cstr,'var_deltaxB_([0-9]+)','deltaxB[$1]'); cstr = regexprep(cstr,'var_deltasx_([0-9]+)','deltasx[$1]'); - cstr = regexprep(cstr,'var_deltaqB_([0-9]+)','deltaqB[ip+$1]'); + cstr = regexprep(cstr,'var_deltaqB_([0-9]+)','deltaqB[$1]'); cstr = regexprep(cstr,'var_sigma_y_([0-9]+)','sigmay[$1]'); cstr = regexprep(cstr,'var_sigma_z_([0-9]+)','sigmaz[$1]'); cstr = regexprep(cstr,'var_dsigma_zdp_([0-9]+)',['dsigmazdp[$1]']); diff --git a/src/backwardproblem.cpp b/src/backwardproblem.cpp index 5188c35652..5645172523 100644 --- a/src/backwardproblem.cpp +++ b/src/backwardproblem.cpp @@ -101,15 +101,15 @@ void BackwardProblem::handleEventB(const int iroot) { continue; } - model->fdeltaqB(ie, t, x_disc[iroot], xB, xdot_disc[iroot], - xdot_old_disc[iroot]); - model->fdeltaxB(ie, t, x_disc[iroot], xB, xdot_disc[iroot], - xdot_old_disc[iroot]); + model->addAdjointQuadratureEventUpdate(xQB, ie, t, x_disc[iroot], xB, + xdot_disc[iroot], + xdot_old_disc[iroot]); + model->addAdjointStateEventUpdate(xB, ie, t, x_disc[iroot], + xdot_disc[iroot], + xdot_old_disc[iroot]); for (int ix = 0; ix < model->nxtrue_solver; ++ix) { for (int iJ = 0; iJ < model->nJ; ++iJ) { - xB[ix + iJ * model->nxtrue_solver] += - model->deltaxB[ix + iJ * model->nxtrue_solver]; if (model->nz > 0) { xB[ix + iJ * model->nxtrue_solver] += dJzdx[iJ + ( ix + nroots[ie] * model->nx_solver ) * model->nJ]; @@ -117,12 +117,7 @@ void BackwardProblem::handleEventB(const int iroot) { } } - for (int iJ = 0; iJ < model->nJ; ++iJ) { - for (int ip = 0; ip < model->nplist(); ++ip) { - xQB[ip + iJ * model->nplist()] += - model->deltaqB[ip + iJ * model->nplist()]; - } - } + nroots[ie]--; } diff --git a/src/forwardproblem.cpp b/src/forwardproblem.cpp index e1875be083..e8b7cb08f3 100644 --- a/src/forwardproblem.cpp +++ b/src/forwardproblem.cpp @@ -1,6 +1,7 @@ #include "amici/forwardproblem.h" #include "amici/cblas.h" +#include "amici/misc.h" #include "amici/model.h" #include "amici/solver.h" #include "amici/exception.h" @@ -46,7 +47,8 @@ ForwardProblem::ForwardProblem(ReturnData *rdata, const ExpData *edata, xdot_old(model->nx_solver), sx(model->nx_solver,model->nplist()), sx_rdata(model->nx_rdata,model->nplist()), - sdx(model->nx_solver,model->nplist()) + sdx(model->nx_solver,model->nplist()), + stau(model->nplist()) { } @@ -92,7 +94,7 @@ void ForwardProblem::workForwardProblem() { /* loop over timepoints */ for (int it = 0; it < model->nt(); it++) { - auto nextTimepoint = model->t(it); + auto nextTimepoint = model->getTimepoint(it); if (nextTimepoint > model->t0()) { if (model->nx_solver == 0) { @@ -236,7 +238,8 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { } if (iroot < model->nMaxEvent() * model->ne) { - std::copy(rootsfound.begin(), rootsfound.end(), &rootidx[iroot * model->ne]); + std::copy(rootsfound.begin(), rootsfound.end(), + &rootidx[iroot * model->ne]); } rvaltmp = rootvals; @@ -279,7 +282,7 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { for (int ie = 0; ie < model->ne; ie++) { if (rootsfound.at(ie) == 1) { /* only consider transitions false -> true */ - model->fstau(t, ie, x, sx); + model->getEventTimeSensitivity(stau, t, ie, x, sx); } } } @@ -357,7 +360,6 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) { } } - void ForwardProblem::storeJacobianAndDerivativeInReturnData() { model->fxdot(t, x, dx, xdot); rdata->xdot = xdot.getVector(); @@ -372,9 +374,8 @@ void ForwardProblem::storeJacobianAndDerivativeInReturnData() { } } - void ForwardProblem::getEventOutput() { - if (t == model->gett(model->nt() - 1,rdata)) { + if (t == model->getTimepoint(edata->nt() - 1)) { // call from fillEvent at last timepoint model->froot(t, x, dx, rootvals); } @@ -386,188 +387,159 @@ void ForwardProblem::getEventOutput() { continue; /* only consider transitions false -> true or event filling */ - if (rootsfound.at(ie) != 1 && t != model->gett(model->nt() - 1, rdata)) { + if (rootsfound.at(ie) != 1 && + t != model->getTimepoint(model->nt() - 1)) { continue; } /* get event output */ - model->fz(nroots.at(ie), ie, t, x, rdata); + model->getEvent(slice(rdata->z, nroots.at(ie), rdata->nz), ie, t, x); /* if called from fillEvent at last timepoint, then also get the root function value */ - if (t == model->gett(model->nt() - 1,rdata)) - model->frz(nroots.at(ie), ie, t, x, rdata); + if (t == model->getTimepoint(model->nt() - 1)) + model->getEventRegularization(slice(rdata->rz, nroots.at(ie), + rdata->nz), ie, t, x); if (edata) { - model->fsigmaz(t, ie, nroots.data(), rdata, edata); - model->fJz(nroots.at(ie), rdata, edata); + model->getEventSigma(slice(rdata->sigmaz, nroots.at(ie), rdata->nz), + ie, nroots.at(ie), t, edata); + model->addEventObjective(rdata->llh, ie, nroots.at(ie), t, x, + *edata); /* if called from fillEvent at last timepoint, add regularization based on rz */ - if (t == model->gett(model->nt() - 1,rdata)) - model->fJrz(nroots.at(ie), rdata, edata); + if (t == model->getTimepoint(model->nt() - 1)) + model->addEventObjectiveRegularization( + rdata->llh, ie, nroots.at(ie), t, x, *edata); } if (solver->getSensitivityOrder() >= SensitivityOrder::first) { - prepEventSensis(ie); if (solver->getSensitivityMethod() == SensitivityMethod::forward) { getEventSensisFSA(ie); + } else { + if (edata) { + model->getAdjointStateEventUpdate(slice(dJzdx, nroots.at(ie), + model->nx_solver * model->nJ), + ie, nroots.at(ie), t, x, *edata); + model->addPartialEventObjectiveSensitivity(rdata->sllh, + rdata->s2llh, + ie, nroots.at(ie), + t, x, *edata); + } } } nroots.at(ie)++; } - if (t == model->gett(model->nt() - 1, rdata)) { + if (t == model->getTimepoint(model->nt() - 1)) { // call from fillEvent at last timepoint // loop until all events are filled - if(std::any_of(nroots.cbegin(), nroots.cend(), [&](int curNRoots){ return curNRoots < model->nMaxEvent(); })) + if (std::any_of(nroots.cbegin(), nroots.cend(), [&](int curNRoots) { + return curNRoots < model->nMaxEvent(); + })) getEventOutput(); } } - -void ForwardProblem::prepEventSensis(int ie) { - - if(!edata) - return; - - for (int iz = 0; iz < model->nztrue; iz++) { - if(model->z2event[iz] - 1 != ie) - continue; - - model->fdzdp(t, ie, x); - - model->fdzdx(t, ie, x); - - if (t == model->gett(model->nt() - 1,rdata)) { - model->fdrzdp(t, ie, x); - model->fdrzdx(t, ie, x); - } - /* extract the value for the standard deviation, if the data - value is NaN, use the parameter value. Store this value in the return - struct */ - } - model->fsigmaz(t, ie, nroots.data(), rdata, edata); - model->fdsigmazdp(t, ie, nroots.data(), rdata, edata); - model->fdJzdz(nroots.at(ie), rdata, edata); - model->fdJzdsigma(nroots.at(ie), rdata, edata); - - if (t == model->t(model->nt() - 1)) { - model->fdJrzdz(nroots.at(ie), rdata, edata); - model->fdJrzdsigma(nroots.at(ie), rdata, edata); - } - - model->fdJzdx(&dJzdx, nroots.at(ie), t, edata, rdata); - model->fdJzdp(nroots.at(ie), t, edata, rdata); - - if (solver->getSensitivityMethod() == SensitivityMethod::adjoint && model->nz > 0) { - amici_daxpy(model->nplist(), -1.0, model->dJzdp.data(), 1, rdata->sllh.data(), 1); - amici_daxpy(model->nplist(), -1.0, &model->dJzdp[1], model->nJ, rdata->s2llh.data(), model->nJ - 1); - } - -} - - void ForwardProblem::getEventSensisFSA(int ie) { - if (t == model->t(model->nt() - 1)) { + if (t == model->getTimepoint(model->nt() - 1)) { // call from fillEvent at last timepoint - model->fsz_tf(nroots.data(),ie, rdata); - model->fsrz(nroots.at(ie), ie, t, x, sx, rdata); + model->getUnobservedEventSensitivity(slice(rdata->sz, nroots.at(ie), + rdata->nz * rdata->nplist), + ie); + model->getEventRegularizationSensitivity(slice(rdata->srz, + nroots.at(ie), + rdata->nz * rdata->nplist), + ie, t, x, sx); } else { - model->fsz(nroots.at(ie), ie, t, x, sx, rdata); + model->getEventSensitivity(slice(rdata->sz, nroots.at(ie), + rdata->nz * rdata->nplist), + ie, t, x, sx); } if (edata) { - model->fsJz(nroots.at(ie), dJzdx, sx, rdata); + model->addEventObjectiveSensitivity(rdata->sllh, rdata->s2llh, ie, + nroots.at(ie), t, x, sx, *edata); } } - void ForwardProblem::handleDataPoint(int it) { model->fx_rdata(x_rdata, x); - std::copy_n(x_rdata.data(), rdata->nx, &rdata->x.at(it*rdata->nx)); + std::copy_n(x_rdata.data(), rdata->nx, &rdata->x.at(it * rdata->nx)); - if (model->t(it) > model->t0()) { + if (model->getTimepoint(it) > model->t0()) { solver->getDiagnosis(it, rdata); } getDataOutput(it); } - void ForwardProblem::getDataOutput(int it) { - model->fy(rdata->ts[it], it, x, rdata); - model->fsigmay(it, rdata, edata); - model->fJy(it, rdata, edata); - model->fres(it, rdata, edata); - model->fchi2(it, rdata); - - if (solver->getSensitivityOrder() >= SensitivityOrder::first && model->nplist() > 0) { - prepDataSensis(it); - if (solver->getSensitivityMethod() == SensitivityMethod::forward) + model->getObservable(slice(rdata->y, it, rdata->ny), rdata->ts[it], x); + model->getObservableSigma(slice(rdata->sigmay, it, rdata->ny), it, + edata); + if (edata) { + model->addObservableObjective(rdata->llh, it, x, *edata); + rdata->fres(it, *edata); + rdata->fchi2(it); + } + + if (solver->getSensitivityOrder() >= SensitivityOrder::first && + model->nplist() > 0) { + + model->getObservableSigmaSensitivity(slice(rdata->ssigmay, it, + model->nplist() * model->ny), + it, edata); + + if (solver->getSensitivityMethod() == SensitivityMethod::forward) { getDataSensisFSA(it); + } else { + if (edata) { + model->getAdjointStateObservableUpdate(slice(dJydx, it, + model->nx_solver * model->nJ), + it, x, *edata); + model->addPartialObservableObjectiveSensitivity(rdata->sllh, + rdata->s2llh, + it, x, *edata); + } + } } } - -void ForwardProblem::prepDataSensis(int it) { - model->fdydx(rdata->ts[it], x); - model->fdydp(rdata->ts[it], x); - - if (!edata) - return; - - model->fdsigmaydp(it, rdata, edata); - model->fdJydy(it, rdata, edata); - model->fdJydsigma(it, rdata, edata); - model->fdJydx(dJydx, it, edata); - model->fdJydp(it, rdata, edata); -} - - void ForwardProblem::getDataSensisFSA(int it) { model->fsx_rdata(sx_rdata, sx); for (int ix = 0; ix < rdata->nx; ix++) { for (int ip = 0; ip < model->nplist(); ip++) { rdata->sx[(it * model->nplist() + ip) * rdata->nx + ix] = - sx_rdata.at(ix,ip); + sx_rdata.at(ix, ip); } } - model->fdsigmaydp(it, rdata, edata); - - model->fsy(it, sx, rdata); + model->getObservableSensitivity(slice(rdata->sy, it, + model->nplist() * model->ny), + t, x, sx); + if (edata) { - model->fsJy(it, dJydx, sx, rdata); - model->fsres(it, rdata, edata); - model->fFIM(it, rdata); + model->addObservableObjectiveSensitivity(rdata->sllh, rdata->s2llh, + it, x, sx, *edata); + rdata->fsres(it, *edata); + rdata->fFIM(it); } } - void ForwardProblem::applyEventBolus() { - for (int ie = 0; ie < model->ne; ie++) { - if (rootsfound.at(ie) == 1) { - /* only consider transitions false -> true */ - model->fdeltax(ie, t, x, xdot, xdot_old); - - amici_daxpy(model->nx_solver, 1.0, model->deltax.data(), 1, x.data(), 1); - } - } + for (int ie = 0; ie < model->ne; ie++) + if (rootsfound.at(ie) == 1) // only consider transitions false -> true + model->addStateEventUpdate(x, ie, t, xdot, xdot_old); } - void ForwardProblem::applyEventSensiBolusFSA() { - for (int ie = 0; ie < model->ne; ie++) { - if (rootsfound.at(ie) == 1) { - /* only consider transitions false -> true */ - model->fdeltasx(ie, t, x_old, sx, xdot, xdot_old); - - for (int ip = 0; ip < model->nplist(); ip++) { - amici_daxpy(model->nx_solver, 1.0, &model->deltasx[model->nx_solver * ip], 1, sx.data(ip), 1); - } - } - } + for (int ie = 0; ie < model->ne; ie++) + if (rootsfound.at(ie) == 1) // only consider transitions false -> true + /* */ + model->addStateSensitivityEventUpdate(sx, ie, t, x_old, xdot, + xdot_old, stau); } } // namespace amici diff --git a/src/misc.cpp b/src/misc.cpp index d7a80ccff6..73d66ee913 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -18,6 +18,16 @@ #endif namespace amici { + +gsl::span slice(std::vector &data, const int index, + const unsigned size) { + if ((index + 1) * size > data.size()) + throw std::out_of_range("requested slice is out of data range"); + if (size > 0) + return gsl::make_span(&data.at(index*size), size); + else + return gsl::make_span(static_cast(nullptr), 0); +} double getUnscaledParameter(double scaledParameter, ParameterScaling scaling) { diff --git a/src/model.cpp b/src/model.cpp index c43e70d73f..3990c46267 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -14,6 +14,107 @@ namespace amici { +/** + * @brief local helper to check whether the provided buffer has the expected + * size + * @param buffer buffer to which values are to be written + * @param expected_size expected size of the buffer + */ +static void checkBufferSize(gsl::span buffer, + unsigned expected_size) { + if (buffer.size() != expected_size) + throw AmiException("Incorrect buffer size! Was %u, expected %u.", + buffer.size(), expected_size); +} + +/** + * @brief local helper function to write computed slice to provided buffer + * @param slice computed value + * @param buffer buffer to which values are to be written + */ +static void writeSlice(gsl::span slice, + gsl::span buffer) { + checkBufferSize(buffer, slice.size()); + std::copy(slice.begin(), slice.end(), buffer.data()); +} + +/** + * @brief local helper function to get parameters + * @param ids vector of name/ids of (fixed)Parameters + * @param values values of the (fixed)Parameters + * @param id name/id to look for in the vector + * @param variable_name string indicating what variable we are lookin at + * @param id_name string indicating whether name or id was specified + * @return value of the selected parameter + */ +static realtype getValueById(std::vector const &ids, + std::vector const &values, + std::string const &id, const char *variable_name, + const char *id_name) { + auto it = std::find(ids.begin(), ids.end(), id); + if (it != ids.end()) + return values.at(it - ids.begin()); + + throw AmiException("Could not find %s with specified %s", variable_name, + id_name); +} + +/** + * @brief local helper function to set parameters + * @param ids vector of names/ids of (fixed)Parameters + * @param values values of the (fixed)Parameters + * @param value for the selected parameter + * @param id name/id to look for in the vector + * @param variable_name string indicating what variable we are lookin at + * @param id_name string indicating whether name or id was specified + */ +static void setValueById(std::vector const &ids, + std::vector &values, realtype value, + std::string const &id, const char *variable_name, + const char *id_name) { + auto it = std::find(ids.begin(), ids.end(), id); + if (it != ids.end()) + values.at(it - ids.begin()) = value; + else + throw AmiException("Could not find %s with specified %s", variable_name, + id_name); +} + +/** + * @brief local helper function to set parameters via regex + * @param ids vector of names/ids of (fixed)Parameters + * @param values values of the (fixed)Parameters + * @param value for the selected parameter + * @param regex string according to which names/ids are to be matched + * @param variable_name string indicating what variable we are lookin at + * @param id_name string indicating whether name or id was specified + * @return number of matched names/ids + */ +static int setValueByIdRegex(std::vector const &ids, + std::vector &values, realtype value, + std::string const ®ex, + const char *variable_name, const char *id_name) { + try { + std::regex pattern(regex); + int n_found = 0; + for (const auto &id : ids) { + if (std::regex_match(id, pattern)) { + values.at(&id - &ids[0]) = value; + ++n_found; + } + } + + if (n_found == 0) + throw AmiException("Could not find %s with specified %s", + variable_name, id_name); + + return n_found; + } catch (std::regex_error const &e) { + throw AmiException("Specified regex pattern could not be compiled: %s", + e.what()); + } +} + Model::Model() : dxdotdp(0, 0), x_pos_tmp(0) {} Model::Model(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, @@ -29,28 +130,16 @@ Model::Model(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, nxtrue_solver(nxtrue_solver), ny(ny), nytrue(nytrue), nz(nz), nztrue(nztrue), ne(ne), nw(nw), ndwdx(ndwdx), ndwdp(ndwdp), ndxdotdw(ndxdotdw), ndJydy(std::move(ndJydy)), nnz(nnz), nJ(nJ), ubw(ubw), - lbw(lbw), o2mode(o2mode), z2event(std::move(z2event)), - idlist(std::move(idlist)), sigmay(ny, 0.0), - dsigmaydp(ny * plist.size(), 0.0), sigmaz(nz, 0.0), - dsigmazdp(nz * plist.size(), 0.0), dJydp(nJ * plist.size(), 0.0), - dJzdp(nJ * plist.size(), 0.0), deltax(nx_solver, 0.0), - deltasx(nx_solver * plist.size(), 0.0), deltaxB(nx_solver, 0.0), - deltaqB(nJ * plist.size(), 0.0), dxdotdp(nx_solver, plist.size()), + lbw(lbw), o2mode(o2mode), idlist(std::move(idlist)), J(nx_solver, nx_solver, nnz, CSC_MAT), dxdotdw(nx_solver, nw, ndxdotdw, CSC_MAT), - dwdx(nw, nx_solver, ndwdx, CSC_MAT), M(nx_solver, nx_solver), - my(nytrue, 0.0), mz(nztrue, 0.0), dJydsigma(nJ * nytrue * ny, 0.0), - dJzdz(nJ * nztrue * nz, 0.0), dJzdsigma(nJ * nztrue * nz, 0.0), - dJrzdz(nJ * nztrue * nz, 0.0), dJrzdsigma(nJ * nztrue * nz, 0.0), - dzdx(nz * nx_solver, 0.0), dzdp(nz * plist.size(), 0.0), - drzdx(nz * nx_solver, 0.0), drzdp(nz * plist.size(), 0.0), - dydp(ny * plist.size(), 0.0), dydx(ny * nx_solver, 0.0), w(nw, 0.0), - dwdp(ndwdp, 0.0), stau(plist.size(), 0.0), - sx(nx_solver * plist.size(), 0.0), x_rdata(nx_rdata, 0.0), - sx_rdata(nx_rdata, 0.0), h(ne, 0.0), unscaledParameters(p), - originalParameters(p), fixedParameters(std::move(k)), - total_cl(nx_rdata - nx_solver), stotal_cl((nx_rdata - nx_solver) * np()), - plist_(plist), stateIsNonNegative(nx_solver, false), x_pos_tmp(nx_solver), + dwdx(nw, nx_solver, ndwdx, CSC_MAT), M(nx_solver, nx_solver), w(nw), + dwdp(ndwdp), x_rdata(nx_rdata, 0.0), sx_rdata(nx_rdata, 0.0), h(ne, 0.0), + total_cl(nx_rdata - nx_solver), + stotal_cl((nx_rdata - nx_solver) * p.size()), x_pos_tmp(nx_solver), + unscaledParameters(p), originalParameters(p), + fixedParameters(std::move(k)), z2event(std::move(z2event)), plist_(plist), + stateIsNonNegative(nx_solver, false), pscale(std::vector(p.size(), ParameterScaling::none)) { // Can't use derivedClass::wasPythonGenerated() in ctor. @@ -71,264 +160,27 @@ Model::Model(const int nx_rdata, const int nxtrue_rdata, const int nx_solver, requireSensitivitiesForAllParameters(); } -void Model::fdJydy_colptrs(sunindextype * /*indexptrs*/, int /*index*/) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented -} - -void Model::fdJydy_rowvals(sunindextype * /*indexptrs*/, int /*index*/) { - throw AmiException("Requested functionality is not supported as %s " - "is not implemented for this model!", - __func__); // not implemented -} - -void Model::fsy(const int it, const AmiVectorArray &sx, ReturnData *rdata) { - if (!ny) - return; - - // copy dydp for current time to sy - std::copy(dydp.begin(), dydp.end(), &rdata->sy[it * nplist() * ny]); - - sx.flatten_to_vector(this->sx); - - // compute sy = 1.0*dydx*sx + 1.0*sy - // dydx A[ny,nx_solver] * sx B[nx_solver,nplist] = sy C[ny,nplist] - // M K K N M N - // lda ldb ldc - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, ny, nplist(), nx_solver, 1.0, - dydx.data(), ny, this->sx.data(), nx_solver, 1.0, - &rdata->sy[it * nplist() * ny], ny); - - if (alwaysCheckFinite) - checkFinite(gsl::make_span(&rdata->sy[it * nplist() * ny], - nplist() * ny), "sy"); -} - -void Model::fsz_tf(const int *nroots, const int ie, ReturnData *rdata) { - for (int iz = 0; iz < nz; ++iz) - if (z2event[iz] - 1 == ie) - for (int ip = 0; ip < nplist(); ++ip) - rdata->sz.at((nroots[ie] * nplist() + ip) * nz + iz) = 0.0; -} - -void Model::fsJy(const int it, const std::vector &dJydx, - const AmiVectorArray &sx, ReturnData *rdata) { - - // Compute dJydx*sx for current 'it' - // dJydx rdata->nt x nJ x nx_solver - // sx rdata->nt x nx_solver x nplist() - std::vector multResult(nJ * nplist(), 0); - sx.flatten_to_vector(this->sx); - - // C := alpha*op(A)*op(B) + beta*C, - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nplist(), nx_solver, 1.0, - &dJydx.at(it * nJ * nx_solver), nJ, this->sx.data(), nx_solver, - 0.0, multResult.data(), nJ); - - // multResult nJ x nplist() - // dJydp nJ x nplist() - // dJydxTmp nJ x nx_solver - // sx nx_solver x nplist() - - // sJy += multResult + dJydp - for (int iJ = 0; iJ < nJ; ++iJ) { - if (iJ == 0) - for (int ip = 0; ip < nplist(); ++ip) - rdata->sllh.at(ip) -= - multResult.at(ip * nJ) + dJydp.at(ip * nJ); - else - for (int ip = 0; ip < nplist(); ++ip) - rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - multResult.at(iJ + ip * nJ) + dJydp.at(iJ + ip * nJ); - } -} - -void Model::fdJydp(const int it, ReturnData *rdata, const ExpData *edata) { - // dJydy nJ, nytrue x ny - // dydp nplist * ny - // dJydp nplist x nJ - // dJydsigma - - getmy(it, edata); - std::fill(dJydp.begin(), dJydp.end(), 0.0); - - for (int iyt = 0; iyt < nytrue; ++iyt) { - if (isNaN(my.at(iyt))) - continue; - - if (wasPythonGenerated()) { - // dJydp = 1.0 * dJydp + 1.0 * dJydy * dydp - for (int iplist = 0; iplist < nplist(); ++iplist) { - dJydy[iyt].multiply( - gsl::span(&dJydp.at(iplist * nJ), nJ), - gsl::span(&dydp.at(iplist * ny), ny)); - } - } else { - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nplist(), ny, 1.0, - &dJydy_matlab.at(iyt * nJ * ny), nJ, dydp.data(), ny, - 1.0, dJydp.data(), nJ); - } - // dJydp = 1.0 * dJydp + 1.0 * dJydsigma * dsigmaydp - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nplist(), ny, 1.0, - &dJydsigma.at(iyt * nJ * ny), nJ, dsigmaydp.data(), ny, 1.0, - dJydp.data(), nJ); - } - - if (rdata->sensi_meth != SensitivityMethod::adjoint) - return; - - if (!ny) - return; - - for (int iJ = 0; iJ < nJ; iJ++) { - for (int ip = 0; ip < nplist(); ip++) { - if (iJ == 0) { - rdata->sllh.at(ip) -= dJydp[ip * nJ]; - } else { - rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - dJydp[iJ + ip * nJ]; - } - } - } -} - -void Model::fdJydx(std::vector &dJydx, const int it, - const ExpData *edata) { - - // dJydy: nJ, ny x nytrue - // dydx : ny x nx_solver - // dJydx: nJ x nx_solver x nt - getmy(it, edata); - - for (int iyt = 0; iyt < nytrue; ++iyt) { - if (isNaN(my.at(iyt))) - continue; - // dJydy A[nyt,nJ,ny] * dydx B[ny,nx_solver] = dJydx C[it,nJ,nx_solver] - // slice slice - // M K K N M N - // lda ldb ldc - - if (wasPythonGenerated()) { - for (int ix = 0; ix < nx_solver; ++ix) { - dJydy[iyt].multiply( - gsl::span( - &dJydx.at(it * nx_solver * nJ + ix * nJ), nJ), - gsl::span(&dydx.at(ix * ny), ny)); - } - } else { - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nx_solver, ny, 1.0, - &dJydy_matlab.at(iyt * ny * nJ), nJ, dydx.data(), ny, - 1.0, &dJydx.at(it * nx_solver * nJ), nJ); - } - } - - if (alwaysCheckFinite) { - amici::checkFinite(dJydx, "dJydx"); - } -} - -void Model::fsJz(const int nroots, const std::vector &dJzdx, - const AmiVectorArray &sx, ReturnData *rdata) { - // sJz nJ x nplist() - // dJzdp nJ x nplist() - // dJzdx nmaxevent x nJ x nx_solver - // sx rdata->nt x nx_solver x nplist() - - // Compute dJzdx*sx for current 'ie' - // dJzdx rdata->nt x nJ x nx_solver - // sx rdata->nt x nx_solver x nplist() - - std::vector multResult(nJ * nplist(), 0); - sx.flatten_to_vector(this->sx); - - // C := alpha*op(A)*op(B) + beta*C, - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nplist(), nx_solver, 1.0, - &dJzdx.at(nroots * nx_solver * nJ), nJ, this->sx.data(), - nx_solver, 1.0, multResult.data(), nJ); - - // sJy += multResult + dJydp - for (int iJ = 0; iJ < nJ; ++iJ) { - if (iJ == 0) - for (int ip = 0; ip < nplist(); ++ip) - rdata->sllh.at(ip) -= - multResult.at(ip * nJ) + dJzdp.at(ip * nJ); - else - for (int ip = 0; ip < nplist(); ++ip) - rdata->s2llh.at((iJ - 1) + ip * (nJ - 1)) -= - multResult.at(iJ + ip * nJ) + dJzdp.at(iJ + ip * nJ); - } -} - -void Model::fdJzdp(const int nroots, realtype t, const ExpData *edata, - const ReturnData *rdata) { - // dJzdz nJ x nz x nztrue - // dJzdsigma nJ x nz x nztrue - // dzdp nz x nplist() - // dJzdp nJ x nplist() - - getmz(nroots, edata); - std::fill(dJzdp.begin(), dJzdp.end(), 0.0); - for (int izt = 0; izt < nztrue; ++izt) { - if (isNaN(mz.at(izt))) - continue; - - if (t < rdata->ts.at(rdata->ts.size() - 1)) { - // with z - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, - &dJzdz.at(izt * nz * nJ), nJ, dzdp.data(), nz, 1.0, - dJzdp.data(), nJ); - } else { - // with rz - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, - &dJrzdsigma.at(izt * nz * nJ), nJ, dsigmazdp.data(), nz, - 1.0, dJzdp.data(), nJ); - - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, - &dJrzdz.at(izt * nz * nJ), nJ, dzdp.data(), nz, 1.0, - dJzdp.data(), nJ); - } - - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, - &dJzdsigma.at(izt * nz * nJ), nJ, dsigmazdp.data(), nz, 1.0, - dJzdp.data(), nJ); - } -} - -void Model::fdJzdx(std::vector *dJzdx, const int nroots, realtype t, - const ExpData *edata, const ReturnData *rdata) { - // dJzdz nJ x nz x nztrue - // dzdx nz x nx_solver - // dJzdx nJ x nx_solver x nmaxevent - getmz(nroots, edata); - for (int izt = 0; izt < nztrue; ++izt) { - if (isNaN(mz.at(izt))) - continue; +bool operator==(const Model &a, const Model &b) { + if (typeid(a) != typeid(b)) + return false; - if (t < rdata->ts.at(rdata->ts.size() - 1)) { - // z - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, - &dJzdz.at(izt * nz * nJ), nJ, dzdx.data(), nz, 1.0, - &dJzdx->at(nroots * nx_solver * nJ), nJ); - } else { - // rz - amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, - BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, - &dJrzdz.at(izt * nz * nJ), nJ, drzdx.data(), nz, 1.0, - &dJzdx->at(nroots * nx_solver * nJ), nJ); - } - } + return (a.nx_rdata == b.nx_rdata) && (a.nxtrue_rdata == b.nxtrue_rdata) && + (a.nx_solver == b.nx_solver) && + (a.nxtrue_solver == b.nxtrue_solver) && (a.ny == b.ny) && + (a.nytrue == b.nytrue) && (a.nz == b.nz) && (a.nztrue == b.nztrue) && + (a.ne == b.ne) && (a.nw == b.nw) && (a.ndwdx == b.ndwdx) && + (a.ndwdp == b.ndwdp) && (a.ndxdotdw == b.ndxdotdw) && + (a.nnz == b.nnz) && (a.nJ == b.nJ) && (a.ubw == b.ubw) && + (a.lbw == b.lbw) && (a.o2mode == b.o2mode) && + (a.z2event == b.z2event) && (a.idlist == b.idlist) && (a.h == b.h) && + (a.unscaledParameters == b.unscaledParameters) && + (a.originalParameters == b.originalParameters) && + (a.fixedParameters == b.fixedParameters) && (a.plist_ == b.plist_) && + (a.x0data == b.x0data) && (a.sx0data == b.sx0data) && + (a.ts == b.ts) && (a.nmaxevent == b.nmaxevent) && + (a.pscale == b.pscale) && + (a.stateIsNonNegative == b.stateIsNonNegative) && + (a.tstart == b.tstart); } void Model::initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx, @@ -435,85 +287,12 @@ void Model::setParameterScale(std::vector const &pscaleVec) { sx0data.clear(); } -std::vector const &Model::getParameters() const { - return originalParameters; -} - -/** - * @brief local helper function to get parameters - * @param ids vector of name/ids of (fixed)Parameters - * @param values values of the (fixed)Parameters - * @param id name/id to look for in the vector - * @param variable_name string indicating what variable we are lookin at - * @param id_name string indicating whether name or id was specified - * @return value of the selected parameter - */ -realtype getValueById(std::vector const &ids, - std::vector const &values, - std::string const &id, const char *variable_name, - const char *id_name) { - auto it = std::find(ids.begin(), ids.end(), id); - if (it != ids.end()) - return values.at(it - ids.begin()); - - throw AmiException("Could not find %s with specified %s", variable_name, - id_name); -} - -/** - * @brief local helper function to set parameters - * @param ids vector of names/ids of (fixed)Parameters - * @param values values of the (fixed)Parameters - * @param value for the selected parameter - * @param id name/id to look for in the vector - * @param variable_name string indicating what variable we are lookin at - * @param id_name string indicating whether name or id was specified - */ -void setValueById(std::vector const &ids, - std::vector &values, realtype value, - std::string const &id, const char *variable_name, - const char *id_name) { - auto it = std::find(ids.begin(), ids.end(), id); - if (it != ids.end()) - values.at(it - ids.begin()) = value; - else - throw AmiException("Could not find %s with specified %s", variable_name, - id_name); +const std::vector &Model::getUnscaledParameters() const { + return unscaledParameters; } -/** - * @brief local helper function to set parameters via regex - * @param ids vector of names/ids of (fixed)Parameters - * @param values values of the (fixed)Parameters - * @param value for the selected parameter - * @param regex string according to which names/ids are to be matched - * @param variable_name string indicating what variable we are lookin at - * @param id_name string indicating whether name or id was specified - * @return number of matched names/ids - */ -int setValueByIdRegex(std::vector const &ids, - std::vector &values, realtype value, - std::string const ®ex, const char *variable_name, - const char *id_name) { - try { - std::regex pattern(regex); - int n_found = 0; - for (const auto &id : ids) { - if (std::regex_match(id, pattern)) { - values.at(&id - &ids[0]) = value; - ++n_found; - } - } - - if (n_found == 0) - throw AmiException("Could not find %s with specified %s", - variable_name, id_name); - - return n_found; - } catch (std::regex_error const &e) { - throw AmiException("Specified regex pattern could not be compiled: %s", - e.what()); - } +std::vector const &Model::getParameters() const { + return originalParameters; } realtype Model::getParameterById(std::string const &par_id) const { @@ -585,101 +364,6 @@ int Model::setParametersByNameRegex(std::string const &par_name_regex, return n_found; } -bool Model::hasStateIds() const { - return nx_rdata == 0 || !getStateIds().empty(); -} - -std::vector Model::getStateIds() const { - return std::vector(); -} - -bool Model::hasFixedParameterIds() const { - return nk() == 0 || !getFixedParameterIds().empty(); -} - -std::vector Model::getFixedParameterIds() const { - return std::vector(); -} - -bool Model::hasObservableIds() const { - return ny == 0 || !getObservableIds().empty(); -} - -std::vector Model::getObservableIds() const { - return std::vector(); -} - -void Model::setSteadyStateSensitivityMode( - const SteadyStateSensitivityMode mode) { - steadyStateSensitivityMode = mode; -} - -SteadyStateSensitivityMode Model::getSteadyStateSensitivityMode() const { - return steadyStateSensitivityMode; -} - -void Model::setReinitializeFixedParameterInitialStates(bool flag) { - if (flag && !isFixedParameterStateReinitializationAllowed()) - throw AmiException( - "State reinitialization cannot be enabled for this model" - "as this feature was disabled at compile time. Most likely," - " this was because some initial states depending on " - "fixedParameters also depended on parameters"); - reinitializeFixedParameterInitialStates = flag; -} - -bool Model::getReinitializeFixedParameterInitialStates() const { - return reinitializeFixedParameterInitialStates; -} - -void Model::fx_rdata(realtype *x_rdata, const realtype *x_solver, - const realtype * /*tcl*/) { - if (nx_solver != nx_rdata) - throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own fx_rdata"); - std::copy_n(x_solver, nx_solver, x_rdata); -} - -void Model::fsx_rdata(realtype *sx_rdata, const realtype *sx_solver, - const realtype *stcl, const int /*ip*/) { - fx_rdata(sx_rdata, sx_solver, stcl); -} - -void Model::fx_solver(realtype *x_solver, const realtype *x_rdata) { - if (nx_solver != nx_rdata) - throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own fx_solver"); - std::copy_n(x_rdata, nx_rdata, x_solver); -} - -void Model::fsx_solver(realtype *sx_solver, const realtype *sx_rdata) { - /* for the moment we do not need an implementation of fsx_solver as - * we can simply reuse fx_solver and replace states by their - * sensitivities */ - fx_solver(sx_solver, sx_rdata); -} - -void Model::ftotal_cl(realtype * /*total_cl*/, const realtype * /*x_rdata*/) { - if (nx_solver != nx_rdata) - throw AmiException( - "A model that has differing nx_solver and nx_rdata needs " - "to implement its own ftotal_cl"); -} - -void Model::fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, - const int /*ip*/) { - /* for the moment we do not need an implementation of fstotal_cl as - * we can simply reuse ftotal_cl and replace states by their - * sensitivities */ - ftotal_cl(stotal_cl, sx_rdata); -} - -const std::vector &Model::getUnscaledParameters() const { - return unscaledParameters; -} - const std::vector &Model::getFixedParameters() const { return fixedParameters; } @@ -748,8 +432,74 @@ int Model::setFixedParametersByNameRegex(std::string const &par_name_regex, par_name_regex, "fixedParameters", "name"); } +bool Model::hasParameterNames() const { + return np() == 0 || !getParameterNames().empty(); +} + +std::vector Model::getParameterNames() const { + return std::vector(); +} + +bool Model::hasStateNames() const { + return nx_rdata == 0 || !getStateNames().empty(); +} + +std::vector Model::getStateNames() const { + return std::vector(); +} + +bool Model::hasFixedParameterNames() const { + return nk() == 0 || !getFixedParameterNames().empty(); +} + +std::vector Model::getFixedParameterNames() const { + return std::vector(); +} + +bool Model::hasObservableNames() const { + return ny == 0 || !getObservableNames().empty(); +} + +std::vector Model::getObservableNames() const { + return std::vector(); +} + +bool Model::hasParameterIds() const { + return np() == 0 || !getParameterIds().empty(); +} + +std::vector Model::getParameterIds() const { + return std::vector(); +} + +bool Model::hasStateIds() const { + return nx_rdata == 0 || !getStateIds().empty(); +} + +std::vector Model::getStateIds() const { + return std::vector(); +} + +bool Model::hasFixedParameterIds() const { + return nk() == 0 || !getFixedParameterIds().empty(); +} + +std::vector Model::getFixedParameterIds() const { + return std::vector(); +} + +bool Model::hasObservableIds() const { + return ny == 0 || !getObservableIds().empty(); +} + +std::vector Model::getObservableIds() const { + return std::vector(); +} + std::vector const &Model::getTimepoints() const { return ts; } +double Model::getTimepoint(const int it) const { return ts.at(it); } + void Model::setTimepoints(const std::vector &ts) { if (!std::is_sorted(ts.begin(), ts.end())) throw AmiException("Encountered non-monotonic timepoints, please order" @@ -758,6 +508,10 @@ void Model::setTimepoints(const std::vector &ts) { this->ts = ts; } +double Model::t0() const { return tstart; } + +void Model::setT0(double t0) { tstart = t0; } + std::vector const &Model::getStateIsNonNegative() const { return stateIsNonNegative; } @@ -782,10 +536,10 @@ void Model::setAllStatesNonNegative() { setStateIsNonNegative(std::vector(nx_solver, true)); } -double Model::t(int idx) const { return ts.at(idx); } - const std::vector &Model::getParameterList() const { return plist_; } +int Model::plist(int pos) const { return plist_.at(pos); } + void Model::setParameterList(const std::vector &plist) { int np = this->np(); // cannot capture 'this' in lambda expression if (std::any_of(plist.begin(), plist.end(), @@ -867,74 +621,414 @@ void Model::setUnscaledInitialStateSensitivities( sx0data = sx0; } -double Model::t0() const { return tstart; } +void Model::setSteadyStateSensitivityMode( + const SteadyStateSensitivityMode mode) { + steadyStateSensitivityMode = mode; +} -void Model::setT0(double t0) { tstart = t0; } +SteadyStateSensitivityMode Model::getSteadyStateSensitivityMode() const { + return steadyStateSensitivityMode; +} -int Model::plist(int pos) const { return plist_.at(pos); } +void Model::setReinitializeFixedParameterInitialStates(bool flag) { + if (flag && !isFixedParameterStateReinitializationAllowed()) + throw AmiException( + "State reinitialization cannot be enabled for this model" + "as this feature was disabled at compile time. Most likely," + " this was because some initial states depending on " + "fixedParameters also depended on parameters"); + reinitializeFixedParameterInitialStates = flag; +} -bool Model::hasParameterNames() const { - return np() == 0 || !getParameterNames().empty(); +bool Model::getReinitializeFixedParameterInitialStates() const { + return reinitializeFixedParameterInitialStates; } -std::vector Model::getParameterNames() const { - return std::vector(); +void Model::requireSensitivitiesForAllParameters() { + plist_.resize(np()); + std::iota(plist_.begin(), plist_.end(), 0); + initializeVectors(); } -bool Model::hasStateNames() const { - return nx_rdata == 0 || !getStateNames().empty(); +void Model::getObservable(gsl::span y, const realtype t, + const AmiVector &x) { + fy(t, x); + writeSlice(this->y, y); } -std::vector Model::getStateNames() const { - return std::vector(); +void Model::getObservableSensitivity(gsl::span sy, const realtype t, + const AmiVector &x, + const AmiVectorArray &sx) { + if (!ny) + return; + + fdydx(t, x); + fdydp(t, x); + + this->sx.assign(nx_solver * nplist(), 0.0); + sx.flatten_to_vector(this->sx); + + // compute sy = 1.0*dydx*sx + 1.0*sy + // dydx A[ny,nx_solver] * sx B[nx_solver,nplist] = sy C[ny,nplist] + // M K K N M N + // lda ldb ldc + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, ny, nplist(), nx_solver, 1.0, + dydx.data(), ny, this->sx.data(), nx_solver, 1.0, dydp.data(), + ny); + + writeSlice(dydp, sy); + + if (alwaysCheckFinite) + checkFinite(sy, "sy"); } -bool Model::hasFixedParameterNames() const { - return nk() == 0 || !getFixedParameterNames().empty(); +void Model::getObservableSigma(gsl::span sigmay, const int it, + const ExpData *edata) { + fsigmay(it, edata); + writeSlice(this->sigmay, sigmay); } -std::vector Model::getFixedParameterNames() const { - return std::vector(); +void Model::getObservableSigmaSensitivity(gsl::span ssigmay, + const int it, const ExpData *edata) { + fdsigmaydp(it, edata); + writeSlice(dsigmaydp, ssigmay); } -bool Model::hasObservableNames() const { - return ny == 0 || !getObservableNames().empty(); +void Model::addObservableObjective(realtype &Jy, const int it, + const AmiVector &x, const ExpData &edata) { + fy(edata.getTimepoint(it), x); + fsigmay(it, &edata); + + std::vector nllh(nJ, 0.0); + for (int iyt = 0; iyt < nytrue; iyt++) { + if (edata.isSetObservedData(it, iyt)) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJy(nllh.data(), iyt, unscaledParameters.data(), + fixedParameters.data(), y.data(), sigmay.data(), + edata.getObservedDataPtr(it)); + Jy -= nllh.at(0); + } + } } -std::vector Model::getObservableNames() const { - return std::vector(); +void Model::addObservableObjectiveSensitivity(std::vector &sllh, + std::vector &s2llh, + const int it, const AmiVector &x, + const AmiVectorArray &sx, + const ExpData &edata) { + + if (!ny) + return; + + fdJydx(it, x, edata); + fdJydp(it, x, edata); + // Compute dJydx*sx for current 'it' + // dJydx rdata->nt x nJ x nx_solver + // sx rdata->nt x nx_solver x nplist() + sx.flatten_to_vector(this->sx); + + // C := alpha*op(A)*op(B) + beta*C, + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nx_solver, 1.0, + dJydx.data(), nJ, this->sx.data(), nx_solver, 1.0, dJydp.data(), + nJ); + + writeLLHSensitivitySlice(dJydp, sllh, s2llh); } -bool Model::hasParameterIds() const { - return np() == 0 || !getParameterIds().empty(); +void Model::addPartialObservableObjectiveSensitivity( + std::vector &sllh, std::vector &s2llh, const int it, + const AmiVector &x, const ExpData &edata) { + if (!ny) + return; + + fdJydp(it, x, edata); + + writeLLHSensitivitySlice(dJydp, sllh, s2llh); } -std::vector Model::getParameterIds() const { - return std::vector(); +void Model::getAdjointStateObservableUpdate(gsl::span dJydx, + const int it, const AmiVector &x, + const ExpData &edata) { + fdJydx(it, x, edata); + writeSlice(this->dJydx, dJydx); } -void Model::initializeVectors() { - dsigmaydp.resize(ny * nplist(), 0.0); - dsigmazdp.resize(nz * nplist(), 0.0); - dJydp.resize(nJ * nplist(), 0.0); - dJzdp.resize(nJ * nplist(), 0.0); - deltasx.resize(nx_solver * nplist(), 0.0); - deltaqB.resize(nJ * nplist(), 0.0); - dxdotdp = AmiVectorArray(nx_solver, nplist()); - dzdp.resize(nz * nplist(), 0.0); - drzdp.resize(nz * nplist(), 0.0); - dydp.resize(ny * nplist(), 0.0); - stau.resize(nplist(), 0.0); - sx.resize(nx_solver * nplist(), 0.0); - sx0data.clear(); +void Model::getEvent(gsl::span z, const int ie, const realtype t, + const AmiVector &x) { + fz(ie, t, x); + writeSliceEvent(this->z, z, ie); } -void Model::fx_rdata(AmiVector &x_rdata, const AmiVector &x) { - fx_rdata(x_rdata.data(), x.data(), total_cl.data()); - if (alwaysCheckFinite) - checkFinite(x_rdata.getVector(), "x_rdata"); +void Model::getEventSensitivity(gsl::span sz, const int ie, + const realtype t, const AmiVector &x, + const AmiVectorArray &sx) { + for (int ip = 0; ip < nplist(); ip++) { + fsz(&sz.at(ip * nz), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), sx.data(ip), plist(ip)); + } +} + +void Model::getUnobservedEventSensitivity(gsl::span sz, + const int ie) { + checkBufferSize(sz, nz * nplist()); + + for (int iz = 0; iz < nz; ++iz) + if (z2event[iz] - 1 == ie) + for (int ip = 0; ip < nplist(); ++ip) + sz.at(ip * nz + iz) = 0.0; +} + +void Model::getEventRegularization(gsl::span rz, const int ie, + const realtype t, const AmiVector &x) { + frz(ie, t, x); + writeSliceEvent(this->rz, rz, ie); +} + +void Model::getEventRegularizationSensitivity(gsl::span srz, + const int ie, const realtype t, + const AmiVector &x, + const AmiVectorArray &sx) { + for (int ip = 0; ip < nplist(); ip++) { + fsrz(&srz.at(ip * nz), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), sx.data(ip), plist(ip)); + } +} + +void Model::getEventSigma(gsl::span sigmaz, const int ie, + const int nroots, const realtype t, + const ExpData *edata) { + fsigmaz(ie, nroots, t, edata); + writeSliceEvent(this->sigmaz, sigmaz, ie); +} + +void Model::getEventSigmaSensitivity(gsl::span ssigmaz, const int ie, + const int nroots, const realtype t, + const ExpData *edata) { + fdsigmazdp(ie, nroots, t, edata); + writeSensitivitySliceEvent(dsigmazdp, ssigmaz, ie); +} + +void Model::addEventObjective(realtype &Jz, const int ie, const int nroots, + const realtype t, const AmiVector &x, + const ExpData &edata) { + fz(ie, t, x); + fsigmaz(ie, nroots, t, &edata); + + std::vector nllh(nJ, 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (edata.isSetObservedEvents(nroots, iztrue)) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJz(nllh.data(), iztrue, unscaledParameters.data(), + fixedParameters.data(), z.data(), sigmaz.data(), + edata.getObservedEventsPtr(nroots)); + Jz -= nllh.at(0); + } + } +} + +void Model::addEventObjectiveRegularization(realtype &Jrz, const int ie, + const int nroots, const realtype t, + const AmiVector &x, + const ExpData &edata) { + frz(ie, t, x); + fsigmaz(ie, nroots, t, &edata); + + std::vector nllh(nJ, 0.0); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (edata.isSetObservedEvents(nroots, iztrue)) { + std::fill(nllh.begin(), nllh.end(), 0.0); + fJrz(nllh.data(), iztrue, unscaledParameters.data(), + fixedParameters.data(), rz.data(), sigmaz.data()); + Jrz -= nllh.at(0); + } + } +} + +void Model::addEventObjectiveSensitivity(std::vector &sllh, + std::vector &s2llh, + const int ie, const int nroots, + const realtype t, const AmiVector &x, + const AmiVectorArray &sx, + const ExpData &edata) { + + if (!nz) + return; + + fdJzdx(ie, nroots, t, x, edata); + fdJzdp(ie, nroots, t, x, edata); + + // sJz nJ x nplist() + // dJzdp nJ x nplist() + // dJzdx nmaxevent x nJ x nx_solver + // sx rdata->nt x nx_solver x nplist() + + // Compute dJzdx*sx for current 'ie' + // dJzdx rdata->nt x nJ x nx_solver + // sx rdata->nt x nx_solver x nplist() + sx.flatten_to_vector(this->sx); + + // C := alpha*op(A)*op(B) + beta*C, + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nx_solver, 1.0, + dJzdx.data(), nJ, this->sx.data(), nx_solver, 1.0, dJzdp.data(), + nJ); + + // sJy += multResult + dJydp + writeLLHSensitivitySlice(dJzdp, sllh, s2llh); +} + +void Model::getAdjointStateEventUpdate(gsl::span dJzdx, const int ie, + const int nroots, const realtype t, + const AmiVector &x, + const ExpData &edata) { + fdJzdx(ie, nroots, t, x, edata); + writeSlice(this->dJzdx, dJzdx); +} + +void Model::addPartialEventObjectiveSensitivity(std::vector &sllh, + std::vector &s2llh, + const int ie, const int nroots, + const realtype t, + const AmiVector &x, + const ExpData &edata) { + if (!nz) + return; + + fdJzdp(ie, nroots, t, x, edata); + + writeLLHSensitivitySlice(dJzdp, sllh, s2llh); +} + +void Model::getEventTimeSensitivity(std::vector &stau, + const realtype t, const int ie, + const AmiVector &x, + const AmiVectorArray &sx) { + + std::fill(stau.begin(), stau.end(), 0.0); + + for (int ip = 0; ip < nplist(); ip++) { + fstau(&stau.at(ip), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), sx.data(ip), plist(ip), ie); + } +} + +void Model::addStateEventUpdate(AmiVector &x, const int ie, const realtype t, + const AmiVector &xdot, + const AmiVector &xdot_old) { + + deltax.assign(nx_solver, 0.0); + + // compute update + fdeltax(deltax.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), ie, xdot.data(), xdot_old.data()); + + if (alwaysCheckFinite) { + amici::checkFinite(deltax, "deltax"); + } + + // update + amici_daxpy(nx_solver, 1.0, deltax.data(), 1, x.data(), 1); +} + +void Model::addStateSensitivityEventUpdate(AmiVectorArray &sx, const int ie, + const realtype t, + const AmiVector &x_old, + const AmiVector &xdot, + const AmiVector &xdot_old, + const std::vector &stau) { + fw(t, x_old.data()); + + for (int ip = 0; ip < nplist(); ip++) { + + deltasx.assign(nx_solver, 0.0); + + // compute update + fdeltasx(deltasx.data(), t, x_old.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), w.data(), plist(ip), ie, + xdot.data(), xdot_old.data(), sx.data(ip), &stau.at(ip)); + + if (alwaysCheckFinite) { + amici::checkFinite(deltasx, "deltasx"); + } + + amici_daxpy(nx_solver, 1.0, deltasx.data(), 1, sx.data(ip), 1); + } +} + +void Model::addAdjointStateEventUpdate(AmiVector &xB, const int ie, + const realtype t, const AmiVector &x, + const AmiVector &xdot, + const AmiVector &xdot_old) { + + deltasx.assign(nx_solver, 0.0); + + // compute update + fdeltaxB(deltaxB.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), ie, xdot.data(), xdot_old.data(), + xB.data()); + + if (alwaysCheckFinite) { + amici::checkFinite(deltaxB, "deltaxB"); + } + + // apply update + for (int ix = 0; ix < nxtrue_solver; ++ix) + for (int iJ = 0; iJ < nJ; ++iJ) + xB.at(ix + iJ * nxtrue_solver) += + deltaxB.at(ix + iJ * nxtrue_solver); +} + +void Model::addAdjointQuadratureEventUpdate( + AmiVector xQB, const int ie, const realtype t, const AmiVector &x, + const AmiVector &xB, const AmiVector &xdot, const AmiVector &xdot_old) { + for (int ip = 0; ip < nplist(); ip++) { + deltaqB.assign(nJ, 0.0); + + fdeltaqB(deltaqB.data(), t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip), ie, xdot.data(), + xdot_old.data(), xB.data()); + + for (int iJ = 0; iJ < nJ; ++iJ) + xQB.at(iJ) += deltaqB.at(iJ); + } + + if (alwaysCheckFinite) { + amici::checkFinite(deltaqB, "deltaqB"); + } +} + +void Model::updateHeaviside(const std::vector &rootsfound) { + for (int ie = 0; ie < ne; ie++) { + h.at(ie) += rootsfound.at(ie); + } +} + +void Model::updateHeavisideB(const int *rootsfound) { + for (int ie = 0; ie < ne; ie++) { + h.at(ie) -= rootsfound[ie]; + } +} + +int Model::checkFinite(gsl::span array, const char *fun) const { + auto result = amici::checkFinite(array, fun); + + if (result != AMICI_SUCCESS) { + amici::checkFinite(fixedParameters, "k"); + amici::checkFinite(unscaledParameters, "p"); + amici::checkFinite(w, "w"); + } + + return result; +} + +void Model::setAlwaysCheckFinite(bool alwaysCheck) { + alwaysCheckFinite = alwaysCheck; } +bool Model::getAlwaysCheckFinite() const { return alwaysCheckFinite; } + void Model::fx0(AmiVector &x) { std::fill(x_rdata.begin(), x_rdata.end(), 0.0); /* this function also computes initial total abundances */ @@ -960,17 +1054,8 @@ void Model::fx0_fixedParameters(AmiVector &x) { fx0_fixedParameters(x_rdata.data(), tstart, unscaledParameters.data(), fixedParameters.data()); fx_solver(x.data(), x_rdata.data()); - /* update total abundances */ - ftotal_cl(total_cl.data(), x_rdata.data()); -} - -void Model::fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx) { - realtype *stcl = nullptr; - for (int ip = 0; ip < nplist(); ip++) { - if (ncl() > 0) - stcl = &stotal_cl.at(plist(ip) * ncl()); - fsx_rdata(sx_rdata.data(ip), sx.data(ip), stcl, ip); - } + /* update total abundances */ + ftotal_cl(total_cl.data(), x_rdata.data()); } void Model::fsx0(AmiVectorArray &sx, const AmiVector &x) { @@ -1005,25 +1090,80 @@ void Model::fsx0_fixedParameters(AmiVectorArray &sx, const AmiVector &x) { void Model::fsdx0() {} -void Model::fstau(const realtype t, const int ie, const AmiVector &x, - const AmiVectorArray &sx) { - std::fill(stau.begin(), stau.end(), 0.0); +void Model::fx_rdata(AmiVector &x_rdata, const AmiVector &x) { + fx_rdata(x_rdata.data(), x.data(), total_cl.data()); + if (alwaysCheckFinite) + checkFinite(x_rdata.getVector(), "x_rdata"); +} + +void Model::fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx) { + realtype *stcl = nullptr; for (int ip = 0; ip < nplist(); ip++) { - fstau(&stau.at(ip), t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data(), sx.data(ip), plist(ip), ie); + if (ncl() > 0) + stcl = &stotal_cl.at(plist(ip) * ncl()); + fsx_rdata(sx_rdata.data(ip), sx.data(ip), stcl, ip); } } -void Model::fy(const realtype t, const int it, const AmiVector &x, - ReturnData *rdata) { +void Model::writeSliceEvent(gsl::span slice, + gsl::span buffer, const int ie) { + checkBufferSize(buffer, slice.size()); + checkBufferSize(buffer, z2event.size()); + for (unsigned izt = 0; izt < z2event.size(); ++izt) + if (z2event.at(izt) - 1 == ie) + buffer.at(izt) = slice.at(izt); +} + +void Model::writeSensitivitySliceEvent(gsl::span slice, + gsl::span buffer, + const int ie) { + checkBufferSize(buffer, slice.size()); + checkBufferSize(buffer, z2event.size() * nplist()); + for (int ip = 0; ip < nplist(); ++ip) + for (unsigned izt = 0; izt < z2event.size(); ++izt) + if (z2event.at(izt) - 1 == ie) + buffer.at(ip * nztrue + izt) = slice.at(ip * nztrue + izt); +} + +void Model::writeLLHSensitivitySlice(const std::vector &dLLhdp, + std::vector &sllh, + std::vector &s2llh) { + checkLLHBufferSize(sllh, s2llh); + + amici_daxpy(nplist(), -1.0, dLLhdp.data(), nJ, sllh.data(), 1); + for (int iJ = 1; iJ < nJ; ++iJ) + amici_daxpy(nplist(), -1.0, &dLLhdp.at(iJ), nJ, &s2llh.at(iJ - 1), + nJ - 1); +} + +void Model::checkLLHBufferSize(std::vector &sllh, + std::vector &s2llh) { + if (sllh.size() != static_cast(nplist())) + throw AmiException("Incorrect sllh buffer size! Was %u, expected %i.", + sllh.size(), nplist()); + + if (s2llh.size() != static_cast((nJ - 1) * nplist())) + throw AmiException("Incorrect s2llh buffer size! Was %u, expected %i.", + s2llh.size(), (nJ - 1) * nplist()); +} + +void Model::initializeVectors() { + dxdotdp = AmiVectorArray(nx_solver, nplist()); + sx0data.clear(); +} + +void Model::fy(const realtype t, const AmiVector &x) { if (!ny) return; + + y.assign(ny, 0.0); + fw(t, x.data()); - fy(&rdata->y.at(it * ny), t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data(), w.data()); + fy(y.data(), t, x.data(), unscaledParameters.data(), fixedParameters.data(), + h.data(), w.data()); if (alwaysCheckFinite) { - amici::checkFinite(gsl::make_span(&rdata->y.at(it * ny), ny), "y"); + amici::checkFinite(gsl::make_span(y.data(), ny), "y"); } } @@ -1031,9 +1171,11 @@ void Model::fdydp(const realtype t, const AmiVector &x) { if (!ny) return; - std::fill(dydp.begin(), dydp.end(), 0.0); + dydp.assign(ny * nplist(), 0.0); + fw(t, x.data()); fdwdp(t, x.data()); + // if dwdp is not dense, fdydp will expect the full sparse array realtype *dwdp_tmp = dwdp.data(); for (int ip = 0; ip < nplist(); ip++) { @@ -1054,7 +1196,8 @@ void Model::fdydx(const realtype t, const AmiVector &x) { if (!ny) return; - std::fill(dydx.begin(), dydx.end(), 0.0); + dydx.assign(ny * nx_solver, 0.0); + fw(t, x.data()); fdwdx(t, x.data()); fdydx(dydx.data(), t, x.data(), unscaledParameters.data(), @@ -1065,222 +1208,315 @@ void Model::fdydx(const realtype t, const AmiVector &x) { } } -void Model::fz(const int nroots, const int ie, const realtype t, - const AmiVector &x, ReturnData *rdata) { - fz(&rdata->z.at(nroots * nz), ie, t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data()); -} +void Model::fsigmay(const int it, const ExpData *edata) { + if (!ny) + return; -void Model::fsz(const int nroots, const int ie, const realtype t, - const AmiVector &x, const AmiVectorArray &sx, - ReturnData *rdata) { - for (int ip = 0; ip < nplist(); ip++) { - fsz(&rdata->sz.at((nroots * nplist() + ip) * nz), ie, t, x.data(), - unscaledParameters.data(), fixedParameters.data(), h.data(), - sx.data(ip), plist(ip)); - } -} + sigmay.assign(ny, 0.0); -void Model::frz(const int nroots, const int ie, const realtype t, - const AmiVector &x, ReturnData *rdata) { - frz(&rdata->rz.at(nroots * nz), ie, t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data()); -} + fsigmay(sigmay.data(), getTimepoint(it), unscaledParameters.data(), + fixedParameters.data()); -void Model::fsrz(const int nroots, const int ie, const realtype t, - const AmiVector &x, const AmiVectorArray &sx, - ReturnData *rdata) { - for (int ip = 0; ip < nplist(); ip++) { - fsrz(&rdata->srz.at((nroots * nplist() + ip) * nz), ie, t, x.data(), - unscaledParameters.data(), fixedParameters.data(), h.data(), - sx.data(ip), plist(ip)); + if (edata) { + auto sigmay_edata = edata->getObservedDataStdDevPtr(it); + /* extract the value for the standard deviation from ExpData, + * if the data value is NaN, use the parameter value */ + for (int iytrue = 0; iytrue < nytrue; iytrue++) { + if (edata->isSetObservedDataStdDev(it, iytrue)) + sigmay.at(iytrue) = sigmay_edata[iytrue]; + + /* TODO: when moving second order code to cpp, verify + * that this is actually what we want + */ + for (int iJ = 1; iJ < nJ; iJ++) + sigmay.at(iytrue + iJ*nytrue) = 0; + + if (edata->isSetObservedData(it, iytrue)) + checkSigmaPositivity(sigmay[iytrue], "sigmay"); + } } } -void Model::fdzdp(const realtype t, const int ie, const AmiVector &x) { - std::fill(dzdp.begin(), dzdp.end(), 0.0); - for (int ip = 0; ip < nplist(); ip++) { - fdzdp(dzdp.data(), ie, t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data(), plist(ip)); +void Model::fdsigmaydp(const int it, const ExpData *edata) { + if (!ny) + return; + + dsigmaydp.assign(ny * nplist(), 0.0); + + for (int ip = 0; ip < nplist(); ip++) + // get dsigmaydp slice (ny) for current timepoint and parameter + fdsigmaydp(&dsigmaydp.at(ip * ny), getTimepoint(it), + unscaledParameters.data(), fixedParameters.data(), + plist(ip)); + + // sigmas in edata override model-sigma -> for those sigmas, set dsigmaydp + // to zero + if (edata) { + for (int iy = 0; iy < nytrue; iy++) { + if (!edata->isSetObservedDataStdDev(it, iy)) + continue; + for (int ip = 0; ip < nplist(); ip++) { + dsigmaydp[ip * ny + iy] = 0.0; + } + } } if (alwaysCheckFinite) { - amici::checkFinite(dzdp, "dzdp"); + amici::checkFinite(dsigmaydp, "dsigmaydp"); } } -void Model::fdzdx(const realtype t, const int ie, const AmiVector &x) { - std::fill(dzdx.begin(), dzdx.end(), 0.0); - fdzdx(dzdx.data(), ie, t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data()); +void Model::fdJydy_colptrs(sunindextype * /*indexptrs*/, int /*index*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); +} - if (alwaysCheckFinite) { - amici::checkFinite(dzdx, "dzdx"); +void Model::fdJydy_rowvals(sunindextype * /*indexptrs*/, int /*index*/) { + throw AmiException("Requested functionality is not supported as %s " + "is not implemented for this model!", + __func__); +} + +void Model::fdJydy(const int it, const AmiVector &x, const ExpData &edata) { + + fy(edata.getTimepoint(it), x); + fsigmay(it, &edata); + + if (wasPythonGenerated()) { + for (int iyt = 0; iyt < nytrue; iyt++) { + dJydy[iyt].zero(); + fdJydy_colptrs(dJydy[iyt].indexptrs(), iyt); + fdJydy_rowvals(dJydy[iyt].indexvals(), iyt); + + if (!edata.isSetObservedData(it, iyt)) + continue; + + // get dJydy slice (ny) for current timepoint and observable + fdJydy(dJydy[iyt].data(), iyt, unscaledParameters.data(), + fixedParameters.data(), y.data(), sigmay.data(), + edata.getObservedDataPtr(it)); + + if (alwaysCheckFinite) { + amici::checkFinite(gsl::make_span(dJydy[iyt].get()), "dJydy"); + } + } + } else { + std::fill(dJydy_matlab.begin(), dJydy_matlab.end(), 0.0); + for (int iyt = 0; iyt < nytrue; iyt++) { + if (!edata.isSetObservedData(it, iyt)) + continue; + fdJydy(&dJydy_matlab.at(iyt * ny * nJ), iyt, + unscaledParameters.data(), fixedParameters.data(), y.data(), + sigmay.data(), edata.getObservedDataPtr(it)); + } + if (alwaysCheckFinite) { + // get dJydy slice (ny) for current timepoint and observable + amici::checkFinite(dJydy_matlab, "dJydy"); + } } } -void Model::fdrzdp(const realtype t, const int ie, const AmiVector &x) { - std::fill(drzdp.begin(), drzdp.end(), 0.0); - for (int ip = 0; ip < nplist(); ip++) { - fdrzdp(drzdp.data(), ie, t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data(), plist(ip)); +void Model::fdJydsigma(const int it, const AmiVector &x, const ExpData &edata) { + + dJydsigma.assign(nytrue * ny * nJ, 0.0); + + fy(edata.getTimepoint(it), x); + fsigmay(it, &edata); + + for (int iyt = 0; iyt < nytrue; iyt++) { + if (edata.isSetObservedData(it, iyt)) + // get dJydsigma slice (ny) for current timepoint and observable + fdJydsigma(&dJydsigma.at(iyt * ny * nJ), iyt, + unscaledParameters.data(), fixedParameters.data(), + y.data(), sigmay.data(), edata.getObservedDataPtr(it)); } if (alwaysCheckFinite) { - amici::checkFinite(drzdp, "drzdp"); + amici::checkFinite(dJydsigma, "dJydsigma"); } } -void Model::fdrzdx(const realtype t, const int ie, const AmiVector &x) { - std::fill(drzdx.begin(), drzdx.end(), 0.0); - fdrzdx(drzdx.data(), ie, t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data()); +void Model::fdJydp(const int it, const AmiVector &x, const ExpData &edata) { + // dJydy nJ, nytrue x ny + // dydp nplist * ny + // dJydp nplist x nJ + // dJydsigma - if (alwaysCheckFinite) { - amici::checkFinite(drzdx, "drzdx"); + dJydp.assign(nJ * nplist(), 0.0); + + fdJydy(it, x, edata); + fdydp(edata.getTimepoint(it), x); + + fdJydsigma(it, x, edata); + fdsigmaydp(it, &edata); + + for (int iyt = 0; iyt < nytrue; ++iyt) { + if (!edata.isSetObservedData(it, iyt)) + continue; + + if (wasPythonGenerated()) { + // dJydp = 1.0 * dJydp + 1.0 * dJydy * dydp + for (int iplist = 0; iplist < nplist(); ++iplist) { + dJydy[iyt].multiply( + gsl::span(&dJydp.at(iplist * nJ), nJ), + gsl::span(&dydp.at(iplist * ny), ny)); + } + } else { + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), ny, 1.0, + &dJydy_matlab.at(iyt * nJ * ny), nJ, dydp.data(), ny, + 1.0, dJydp.data(), nJ); + } + // dJydp = 1.0 * dJydp + 1.0 * dJydsigma * dsigmaydp + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), ny, 1.0, + &dJydsigma.at(iyt * nJ * ny), nJ, dsigmaydp.data(), ny, 1.0, + dJydp.data(), nJ); } } -void Model::fdeltax(const int ie, const realtype t, const AmiVector &x, - const AmiVector &xdot, const AmiVector &xdot_old) { - std::fill(deltax.begin(), deltax.end(), 0.0); - fdeltax(deltax.data(), t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data(), ie, xdot.data(), xdot_old.data()); +void Model::fdJydx(const int it, const AmiVector &x, const ExpData &edata) { + + dJydx.assign(nJ * nx_solver, 0.0); + + fdydx(edata.getTimepoint(it), x); + fdJydy(it, x, edata); + + // dJydy: nJ, ny x nytrue + // dydx : ny x nx_solver + // dJydx: nJ x nx_solver x nt + for (int iyt = 0; iyt < nytrue; ++iyt) { + if (!edata.isSetObservedData(it, iyt)) + continue; + // dJydy A[nyt,nJ,ny] * dydx B[ny,nx_solver] = dJydx C[it,nJ,nx_solver] + // slice slice + // M K K N M N + // lda ldb ldc + + if (wasPythonGenerated()) { + for (int ix = 0; ix < nx_solver; ++ix) { + dJydy[iyt].multiply( + gsl::span(&dJydx.at(ix * nJ), nJ), + gsl::span(&dydx.at(ix * ny), ny)); + } + } else { + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nx_solver, ny, 1.0, + &dJydy_matlab.at(iyt * ny * nJ), nJ, dydx.data(), ny, + 1.0, dJydx.data(), nJ); + } + } if (alwaysCheckFinite) { - amici::checkFinite(deltax, "deltax"); + amici::checkFinite(dJydx, "dJydx"); } } -void Model::fdeltasx(const int ie, const realtype t, const AmiVector &x, - const AmiVectorArray &sx, const AmiVector &xdot, - const AmiVector &xdot_old) { - fw(t, x.data()); - std::fill(deltasx.begin(), deltasx.end(), 0.0); - for (int ip = 0; ip < nplist(); ip++) - fdeltasx(&deltasx.at(nx_solver * ip), t, x.data(), - unscaledParameters.data(), fixedParameters.data(), h.data(), - w.data(), plist(ip), ie, xdot.data(), xdot_old.data(), - sx.data(ip), &stau.at(ip)); +void Model::fz(const int ie, const realtype t, const AmiVector &x) { - if (alwaysCheckFinite) { - amici::checkFinite(deltasx, "deltasx"); - } + z.assign(nz, 0.0); + + fz(z.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); } -void Model::fdeltaxB(const int ie, const realtype t, const AmiVector &x, - const AmiVector &xB, const AmiVector &xdot, - const AmiVector &xdot_old) { - std::fill(deltaxB.begin(), deltaxB.end(), 0.0); - fdeltaxB(deltaxB.data(), t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data(), ie, xdot.data(), xdot_old.data(), - xB.data()); +void Model::fdzdp(const int ie, const realtype t, const AmiVector &x) { + + dzdp.assign(nz * nplist(), 0.0); + + for (int ip = 0; ip < nplist(); ip++) { + fdzdp(dzdp.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip)); + } if (alwaysCheckFinite) { - amici::checkFinite(deltaxB, "deltaxB"); + amici::checkFinite(dzdp, "dzdp"); } } -void Model::fdeltaqB(const int ie, const realtype t, const AmiVector &x, - const AmiVector &xB, const AmiVector &xdot, - const AmiVector &xdot_old) { - std::fill(deltaqB.begin(), deltaqB.end(), 0.0); - for (int ip = 0; ip < nplist(); ip++) - fdeltaqB(deltaqB.data(), t, x.data(), unscaledParameters.data(), - fixedParameters.data(), h.data(), plist(ip), ie, xdot.data(), - xdot_old.data(), xB.data()); +void Model::fdzdx(const int ie, const realtype t, const AmiVector &x) { + + dzdx.assign(nz * nx_solver, 0.0); + + fdzdx(dzdx.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); if (alwaysCheckFinite) { - amici::checkFinite(deltaqB, "deltaqB"); + amici::checkFinite(dzdx, "dzdx"); } } -void Model::fsigmay(const int it, ReturnData *rdata, const ExpData *edata) { - if (!ny) - return; +void Model::frz(const int ie, const realtype t, const AmiVector &x) { - std::fill(sigmay.begin(), sigmay.end(), 0.0); + rz.assign(nz, 0.0); - fsigmay(sigmay.data(), rdata->ts.at(it), unscaledParameters.data(), - fixedParameters.data()); + frz(rz.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); +} - if (edata) { - auto sigmay_edata = edata->getObservedDataStdDevPtr(it); - /* extract the value for the standard deviation from ExpData, - * if the data value is NaN, use the parameter value */ - for (int iytrue = 0; iytrue < nytrue; iytrue++) { - if (edata->isSetObservedDataStdDev(it, iytrue)) { - sigmay.at(iytrue) = sigmay_edata[iytrue]; - } - } +void Model::fdrzdp(const int ie, const realtype t, const AmiVector &x) { + + drzdp.assign(nz * nplist(), 0.0); + + for (int ip = 0; ip < nplist(); ip++) { + fdrzdp(drzdp.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data(), plist(ip)); } - for (int i = 0; i < nytrue; ++i) { - if (edata && !std::isnan(edata->getObservedData()[it * nytrue + i])) - checkSigmaPositivity(sigmay[i], "sigmay"); + if (alwaysCheckFinite) { + amici::checkFinite(drzdp, "drzdp"); } - std::copy_n(sigmay.data(), nytrue, &rdata->sigmay[it * rdata->ny]); } -void Model::fdsigmaydp(const int it, ReturnData *rdata, const ExpData *edata) { - if (!ny) - return; - - std::fill(dsigmaydp.begin(), dsigmaydp.end(), 0.0); - - for (int ip = 0; ip < nplist(); ip++) - // get dsigmaydp slice (ny) for current timepoint and parameter - fdsigmaydp(&dsigmaydp.at(ip * ny), rdata->ts.at(it), - unscaledParameters.data(), fixedParameters.data(), - plist(ip)); +void Model::fdrzdx(const int ie, const realtype t, const AmiVector &x) { - // sigmas in edata override model-sigma -> for those sigmas, set dsigmaydp - // to zero - if (edata) { - for (int iy = 0; iy < nytrue; iy++) { - if (!edata->isSetObservedDataStdDev(it, iy)) - continue; - for (int ip = 0; ip < nplist(); ip++) { - dsigmaydp[ip * ny + iy] = 0.0; - } - } - } + drzdx.assign(nz * nx_solver, 0.0); - // copy dsigmaydp slice for current timepoint - std::copy(dsigmaydp.begin(), dsigmaydp.end(), - &rdata->ssigmay[it * nplist() * ny]); + fdrzdx(drzdx.data(), ie, t, x.data(), unscaledParameters.data(), + fixedParameters.data(), h.data()); if (alwaysCheckFinite) { - amici::checkFinite(dsigmaydp, "dsigmaydp"); + amici::checkFinite(drzdx, "drzdx"); } } -void Model::fsigmaz(const realtype t, const int ie, const int *nroots, - ReturnData *rdata, const ExpData *edata) { - std::fill(sigmaz.begin(), sigmaz.end(), 0.0); +void Model::fsigmaz(const int ie, const int nroots, const realtype t, + const ExpData *edata) { + if (!nz) + return; + + sigmaz.assign(nz, 0.0); fsigmaz(sigmaz.data(), t, unscaledParameters.data(), fixedParameters.data()); - for (int iztrue = 0; iztrue < nztrue; iztrue++) { - if (z2event.at(iztrue) - 1 == ie) { - if (edata) { - if (edata->isSetObservedEventsStdDev(nroots[ie], iztrue)) { + + if (edata) { + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (z2event.at(iztrue) - 1 == ie) { + if (edata->isSetObservedEventsStdDev(nroots, iztrue)) { auto sigmaz_edata = - edata->getObservedEventsStdDevPtr(nroots[ie]); + edata->getObservedEventsStdDevPtr(nroots); sigmaz.at(iztrue) = sigmaz_edata[iztrue]; } + + /* TODO: when moving second order code to cpp, verify + * that this is actually what we want + */ + for (int iJ = 1; iJ < nJ; iJ++) + sigmaz.at(iztrue + iJ*nztrue) = 0; + + if (edata->isSetObservedEvents(nroots, iztrue)) + checkSigmaPositivity(sigmaz[iztrue], "sigmaz"); } - rdata->sigmaz[nroots[ie] * rdata->nz + iztrue] = sigmaz.at(iztrue); } } - - if (alwaysCheckFinite) { - amici::checkFinite(sigmaz, "sigmaz"); - } } -void Model::fdsigmazdp(const realtype t, const int ie, const int *nroots, - ReturnData *rdata, const ExpData *edata) { - std::fill(dsigmazdp.begin(), dsigmazdp.end(), 0.0); +void Model::fdsigmazdp(const int ie, const int nroots, const realtype t, + const ExpData *edata) { + + dsigmazdp.assign(nz * nplist(), 0.0); + for (int ip = 0; ip < nplist(); ip++) { // get dsigmazdp slice (nz) for current event and parameter fdsigmazdp(&dsigmazdp.at(ip * nz), t, unscaledParameters.data(), @@ -1292,169 +1528,156 @@ void Model::fdsigmazdp(const realtype t, const int ie, const int *nroots, if (edata) { for (int iz = 0; iz < nztrue; iz++) { if (z2event.at(iz) - 1 == ie && - !edata->isSetObservedEventsStdDev(nroots[ie], iz)) { + !edata->isSetObservedEventsStdDev(nroots, iz)) { for (int ip = 0; ip < nplist(); ip++) dsigmazdp.at(iz + nz * ip) = 0; } } } - // copy dsigmazdp slice for current event - std::copy(dsigmazdp.begin(), dsigmazdp.end(), - &rdata->ssigmaz[nroots[ie] * nplist() * nz]); - if (alwaysCheckFinite) { amici::checkFinite(dsigmazdp, "dsigmazdp"); } } -void Model::fJy(const int it, ReturnData *rdata, const ExpData *edata) { - std::vector nllh(nJ, 0.0); - getmy(it, edata); - for (int iytrue = 0; iytrue < nytrue; iytrue++) { - if (!isNaN(my.at(iytrue))) { - std::fill(nllh.begin(), nllh.end(), 0.0); - fJy(nllh.data(), iytrue, unscaledParameters.data(), - fixedParameters.data(), gety(it, rdata), sigmay.data(), - my.data()); - rdata->llh -= nllh.at(0); - } - } -} +void Model::fdJzdz(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata) { + + dJzdz.assign(nztrue * nz * nJ, 0.0); + + fz(ie, t, x); + fsigmaz(ie, nroots, t, &edata); -void Model::fJz(const int nroots, ReturnData *rdata, const ExpData *edata) { - std::vector nllh(nJ, 0.0); - getmz(nroots, edata); for (int iztrue = 0; iztrue < nztrue; iztrue++) { - if (!isNaN(mz.at(iztrue))) { - std::fill(nllh.begin(), nllh.end(), 0.0); - fJz(nllh.data(), iztrue, unscaledParameters.data(), - fixedParameters.data(), getz(nroots, rdata), sigmaz.data(), - mz.data()); - rdata->llh -= nllh.at(0); + if (edata.isSetObservedEvents(nroots, iztrue)) { + fdJzdz(&dJzdz.at(iztrue * nz * nJ), iztrue, + unscaledParameters.data(), fixedParameters.data(), z.data(), + sigmaz.data(), edata.getObservedEventsPtr(nroots)); } } -} -void Model::fJrz(const int nroots, ReturnData *rdata, - const ExpData * /*edata*/) { - std::vector nllh(nJ, 0.0); - getrz(nroots, rdata); - for (int iztrue = 0; iztrue < nztrue; iztrue++) { - if (!isNaN(mz.at(iztrue))) { - std::fill(nllh.begin(), nllh.end(), 0.0); - fJrz(nllh.data(), iztrue, unscaledParameters.data(), - fixedParameters.data(), getrz(nroots, rdata), sigmaz.data()); - rdata->llh -= nllh.at(0); - } + if (alwaysCheckFinite) { + amici::checkFinite(dJzdz, "dJzdz"); } } -void Model::fdJydy(const int it, const ReturnData *rdata, - const ExpData *edata) { - // load measurements to my - getmy(it, edata); - - if (wasPythonGenerated()) { - for (int iytrue = 0; iytrue < nytrue; iytrue++) { - dJydy[iytrue].zero(); - fdJydy_colptrs(dJydy[iytrue].indexptrs(), iytrue); - fdJydy_rowvals(dJydy[iytrue].indexvals(), iytrue); - - if (isNaN(my.at(iytrue))) { - continue; - } - - // get dJydy slice (ny) for current timepoint and observable - fdJydy(dJydy[iytrue].data(), iytrue, unscaledParameters.data(), - fixedParameters.data(), gety(it, rdata), sigmay.data(), - my.data()); +void Model::fdJzdsigma(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata) { - if (alwaysCheckFinite) { - amici::checkFinite(gsl::make_span(dJydy[iytrue].get()), - "dJydy"); - } - } - } else { - std::fill(dJydy_matlab.begin(), dJydy_matlab.end(), 0.0); - for (int iytrue = 0; iytrue < nytrue; iytrue++) { - if (isNaN(my.at(iytrue))) { - continue; - } - fdJydy(&dJydy_matlab.at(iytrue * ny * nJ), iytrue, - unscaledParameters.data(), fixedParameters.data(), - gety(it, rdata), sigmay.data(), my.data()); - } - if (alwaysCheckFinite) { - // get dJydy slice (ny) for current timepoint and observable - amici::checkFinite(dJydy_matlab, "dJydy"); - } - } -} + dJzdsigma.assign(nztrue * nz * nJ, 0.0); -void Model::fdJydsigma(const int it, const ReturnData *rdata, - const ExpData *edata) { - // load measurements to my - getmy(it, edata); - std::fill(dJydsigma.begin(), dJydsigma.end(), 0.0); + fz(ie, t, x); + fsigmaz(ie, nroots, t, &edata); - for (int iytrue = 0; iytrue < nytrue; iytrue++) { - if (!isNaN(my.at(iytrue))) { - // get dJydsigma slice (ny) for current timepoint and observable - fdJydsigma(&dJydsigma.at(iytrue * ny * nJ), iytrue, + for (int iztrue = 0; iztrue < nztrue; iztrue++) { + if (edata.isSetObservedEvents(nroots, iztrue)) { + fdJzdsigma(&dJzdsigma.at(iztrue * nz * nJ), iztrue, unscaledParameters.data(), fixedParameters.data(), - gety(it, rdata), sigmay.data(), my.data()); + z.data(), sigmaz.data(), + edata.getObservedEventsPtr(nroots)); } } if (alwaysCheckFinite) { - amici::checkFinite(dJydsigma, "dJydsigma"); + amici::checkFinite(dJzdsigma, "dJzdsigma"); } } -void Model::fdJzdz(const int nroots, const ReturnData *rdata, - const ExpData *edata) { - getmz(nroots, edata); - std::fill(dJzdz.begin(), dJzdz.end(), 0.0); - for (int iztrue = 0; iztrue < nztrue; iztrue++) { - if (!isNaN(mz.at(iztrue))) { - fdJzdz(&dJzdz.at(iztrue * nz * nJ), iztrue, - unscaledParameters.data(), fixedParameters.data(), - getz(nroots, rdata), sigmaz.data(), mz.data()); +void Model::fdJzdp(const int ie, const int nroots, realtype t, + const AmiVector &x, const ExpData &edata) { + // dJzdz nJ x nz x nztrue + // dJzdsigma nJ x nz x nztrue + // dzdp nz x nplist() + // dJzdp nJ x nplist() + + dJzdp.assign(nJ * nplist(), 0.0); + + fdJzdz(ie, nroots, t, x, edata); + fdzdp(ie, t, x); + + fdJzdsigma(ie, nroots, t, x, edata); + fdsigmazdp(ie, nroots, t, &edata); + + for (int izt = 0; izt < nztrue; ++izt) { + if (!edata.isSetObservedEvents(nroots, izt)) + continue; + + if (t < edata.getTimepoint(edata.nt() - 1)) { + // with z + fdJzdz(ie, nroots, t, x, edata); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJzdz.at(izt * nz * nJ), nJ, dzdp.data(), nz, 1.0, + dJzdp.data(), nJ); + } else { + // with rz + fdJrzdz(ie, nroots, t, x, edata); + fdJrzdsigma(ie, nroots, t, x, edata); + + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJrzdsigma.at(izt * nz * nJ), nJ, dsigmazdp.data(), nz, + 1.0, dJzdp.data(), nJ); + + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJrzdz.at(izt * nz * nJ), nJ, dzdp.data(), nz, 1.0, + dJzdp.data(), nJ); } - } - if (alwaysCheckFinite) { - amici::checkFinite(dJzdz, "dJzdz"); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nplist(), nz, 1.0, + &dJzdsigma.at(izt * nz * nJ), nJ, dsigmazdp.data(), nz, 1.0, + dJzdp.data(), nJ); } } -void Model::fdJzdsigma(const int nroots, const ReturnData *rdata, - const ExpData *edata) { - getmz(nroots, edata); - std::fill(dJzdsigma.begin(), dJzdsigma.end(), 0.0); - for (int iztrue = 0; iztrue < nztrue; iztrue++) { - if (!isNaN(mz.at(iztrue))) { - fdJzdsigma(&dJzdsigma.at(iztrue * nz * nJ), iztrue, - unscaledParameters.data(), fixedParameters.data(), - getz(nroots, rdata), sigmaz.data(), mz.data()); - } - } +void Model::fdJzdx(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata) { + // dJzdz nJ x nz x nztrue + // dzdx nz x nx_solver + // dJzdx nJ x nx_solver x nmaxevent - if (alwaysCheckFinite) { - amici::checkFinite(dJzdsigma, "dJzdsigma"); + dJzdx.assign(nJ * nx_solver, 0.0); + + fdJzdz(ie, nroots, t, x, edata); + + for (int izt = 0; izt < nztrue; ++izt) { + if (!edata.isSetObservedEvents(nroots, izt)) + continue; + + if (t < edata.getTimepoint(edata.nt() - 1)) { + // z + fdzdx(ie, t, x); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, + &dJzdz.at(izt * nz * nJ), nJ, dzdx.data(), nz, 1.0, + dJzdx.data(), nJ); + } else { + // rz + fdJrzdz(ie, nroots, t, x, edata); + fdrzdx(ie, t, x); + amici_dgemm(BLASLayout::colMajor, BLASTranspose::noTrans, + BLASTranspose::noTrans, nJ, nx_solver, nz, 1.0, + &dJrzdz.at(izt * nz * nJ), nJ, drzdx.data(), nz, 1.0, + dJzdx.data(), nJ); + } } } -void Model::fdJrzdz(const int nroots, const ReturnData *rdata, - const ExpData *edata) { - getmz(nroots, edata); - std::fill(dJrzdz.begin(), dJrzdz.end(), 0.0); +void Model::fdJrzdz(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata) { + + dJrzdz.assign(nztrue * nz * nJ, 0.0); + + frz(ie, t, x); + fsigmaz(ie, nroots, t, &edata); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { - if (!isNaN(mz.at(iztrue))) { + if (edata.isSetObservedEvents(nroots, iztrue)) { fdJrzdz(&dJrzdz.at(iztrue * nz * nJ), iztrue, unscaledParameters.data(), fixedParameters.data(), - getrz(nroots, rdata), sigmaz.data()); + rz.data(), sigmaz.data()); } } @@ -1463,14 +1686,19 @@ void Model::fdJrzdz(const int nroots, const ReturnData *rdata, } } -void Model::fdJrzdsigma(const int nroots, const ReturnData *rdata, - const ExpData * /*edata*/) { - std::fill(dJrzdsigma.begin(), dJrzdsigma.end(), 0.0); +void Model::fdJrzdsigma(const int ie, const int nroots, const realtype t, + const AmiVector &x, const ExpData &edata) { + + dJrzdsigma.assign(nztrue * nz * nJ, 0.0); + + frz(ie, t, x); + fsigmaz(ie, nroots, t, &edata); + for (int iztrue = 0; iztrue < nztrue; iztrue++) { - if (!isNaN(mz.at(iztrue))) { + if (edata.isSetObservedEvents(nroots, iztrue)) { fdJrzdsigma(&dJrzdsigma.at(iztrue * nz * nJ), iztrue, unscaledParameters.data(), fixedParameters.data(), - getrz(nroots, rdata), sigmaz.data()); + rz.data(), sigmaz.data()); } } @@ -1496,11 +1724,11 @@ void Model::fdwdp(const realtype t, const realtype *x) { realtype *stcl = nullptr; // avoid bad memory access when slicing - if (nw == 0) + if (!nw) return; for (int ip = 0; ip < nplist(); ++ip) { - if (ncl() > 0) + if (ncl()) stcl = &stotal_cl.at(plist(ip) * ncl()); fdwdp(&dwdp.at(nw * ip), t, x, unscaledParameters.data(), fixedParameters.data(), h.data(), w.data(), total_cl.data(), @@ -1531,164 +1759,48 @@ void Model::fdwdx(const realtype t, const realtype *x) { } } -void Model::fres(const int it, ReturnData *rdata, const ExpData *edata) { - if (!edata || rdata->res.empty()) - return; - - auto observedData = edata->getObservedDataPtr(it); - for (int iy = 0; iy < nytrue; ++iy) { - int iyt_true = iy + it * edata->nytrue(); - int iyt = iy + it * rdata->ny; - if (!edata->isSetObservedData(it, iy)) - continue; - rdata->res.at(iyt_true) = - (rdata->y.at(iyt) - observedData[iy]) / rdata->sigmay.at(iyt); - } -} - -void Model::fchi2(const int it, ReturnData *rdata) { - if (rdata->res.empty()) - return; - - for (int iy = 0; iy < nytrue; ++iy) { - int iyt_true = iy + it * rdata->nytrue; - rdata->chi2 += pow(rdata->res.at(iyt_true), 2); - } -} - -void Model::fsres(const int it, ReturnData *rdata, const ExpData *edata) { - if (!edata || rdata->sres.empty()) - return; - - for (int iy = 0; iy < nytrue; ++iy) { - int iyt_true = iy + it * edata->nytrue(); - int iyt = iy + it * rdata->ny; - if (!edata->isSetObservedData(it, iy)) - continue; - for (int ip = 0; ip < nplist(); ++ip) { - rdata->sres.at(iyt_true * nplist() + ip) = - rdata->sy.at(iy + rdata->ny * (ip + it * nplist())) / - rdata->sigmay.at(iyt); - } - } -} - -void Model::fFIM(const int it, ReturnData *rdata) { - if (rdata->sres.empty()) - return; - - for (int iy = 0; iy < nytrue; ++iy) { - int iyt_true = iy + it * rdata->nytrue; - for (int ip = 0; ip < nplist(); ++ip) { - for (int jp = 0; jp < nplist(); ++jp) { - rdata->FIM.at(ip + nplist() * jp) += - rdata->sres.at(iyt_true * nplist() + ip) * - rdata->sres.at(iyt_true * nplist() + jp); - } - } - } -} - -void Model::updateHeaviside(const std::vector &rootsfound) { - for (int ie = 0; ie < ne; ie++) { - h.at(ie) += rootsfound.at(ie); - } -} - -void Model::updateHeavisideB(const int *rootsfound) { - for (int ie = 0; ie < ne; ie++) { - h.at(ie) -= rootsfound[ie]; - } -} - -void Model::getmy(const int it, const ExpData *edata) { - if (edata) { - std::copy_n(edata->getObservedDataPtr(it), nytrue, my.begin()); - } else { - std::fill(my.begin(), my.end(), getNaN()); - } -} - -const realtype *Model::gety(const int it, const ReturnData *rdata) const { - return &rdata->y.at(it * ny); -} - -realtype Model::gett(const int it, const ReturnData *rdata) const { - return rdata->ts.at(it); -} - -void Model::getmz(const int nroots, const ExpData *edata) { - if (edata) { - std::copy_n(edata->getObservedEventsPtr(nroots), nztrue, mz.begin()); - } else { - std::fill(mz.begin(), mz.end(), getNaN()); - } -} - -const realtype *Model::getz(const int nroots, const ReturnData *rdata) const { - return (&rdata->z.at(nroots * nz)); -} - -const realtype *Model::getrz(const int nroots, const ReturnData *rdata) const { - return (&rdata->rz.at(nroots * nz)); -} - -const realtype *Model::getsz(const int nroots, const int ip, - const ReturnData *rdata) const { - return (&rdata->sz.at((nroots * nplist() + ip) * nz)); +void Model::fx_rdata(realtype *x_rdata, const realtype *x_solver, + const realtype * /*tcl*/) { + if (nx_solver != nx_rdata) + throw AmiException( + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own fx_rdata"); + std::copy_n(x_solver, nx_solver, x_rdata); } -const realtype *Model::getsrz(const int nroots, const int ip, - const ReturnData *rdata) const { - return (&rdata->srz.at((nroots * nplist() + ip) * nz)); +void Model::fsx_rdata(realtype *sx_rdata, const realtype *sx_solver, + const realtype *stcl, const int /*ip*/) { + fx_rdata(sx_rdata, sx_solver, stcl); } -void Model::setAlwaysCheckFinite(bool alwaysCheck) { - alwaysCheckFinite = alwaysCheck; +void Model::fx_solver(realtype *x_solver, const realtype *x_rdata) { + if (nx_solver != nx_rdata) + throw AmiException( + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own fx_solver"); + std::copy_n(x_rdata, nx_rdata, x_solver); } -bool Model::getAlwaysCheckFinite() const { return alwaysCheckFinite; } - -int Model::checkFinite(gsl::span array, const char *fun) const { - auto result = amici::checkFinite(array, fun); - - if (result != AMICI_SUCCESS) { - amici::checkFinite(ts, "ts"); - amici::checkFinite(fixedParameters, "k"); - amici::checkFinite(unscaledParameters, "p"); - amici::checkFinite(w, "w"); - } - - return result; +void Model::fsx_solver(realtype *sx_solver, const realtype *sx_rdata) { + /* for the moment we do not need an implementation of fsx_solver as + * we can simply reuse fx_solver and replace states by their + * sensitivities */ + fx_solver(sx_solver, sx_rdata); } -void Model::requireSensitivitiesForAllParameters() { - plist_.resize(np()); - std::iota(plist_.begin(), plist_.end(), 0); - initializeVectors(); +void Model::ftotal_cl(realtype * /*total_cl*/, const realtype * /*x_rdata*/) { + if (nx_solver != nx_rdata) + throw AmiException( + "A model that has differing nx_solver and nx_rdata needs " + "to implement its own ftotal_cl"); } -bool operator==(const Model &a, const Model &b) { - if (typeid(a) != typeid(b)) - return false; - - return (a.nx_rdata == b.nx_rdata) && (a.nxtrue_rdata == b.nxtrue_rdata) && - (a.nx_solver == b.nx_solver) && - (a.nxtrue_solver == b.nxtrue_solver) && (a.ny == b.ny) && - (a.nytrue == b.nytrue) && (a.nz == b.nz) && (a.nztrue == b.nztrue) && - (a.ne == b.ne) && (a.nw == b.nw) && (a.ndwdx == b.ndwdx) && - (a.ndwdp == b.ndwdp) && (a.ndxdotdw == b.ndxdotdw) && - (a.nnz == b.nnz) && (a.nJ == b.nJ) && (a.ubw == b.ubw) && - (a.lbw == b.lbw) && (a.o2mode == b.o2mode) && - (a.z2event == b.z2event) && (a.idlist == b.idlist) && (a.h == b.h) && - (a.unscaledParameters == b.unscaledParameters) && - (a.originalParameters == b.originalParameters) && - (a.fixedParameters == b.fixedParameters) && (a.plist_ == b.plist_) && - (a.x0data == b.x0data) && (a.sx0data == b.sx0data) && - (a.ts == b.ts) && (a.nmaxevent == b.nmaxevent) && - (a.pscale == b.pscale) && - (a.stateIsNonNegative == b.stateIsNonNegative) && - (a.tstart == b.tstart); +void Model::fstotal_cl(realtype *stotal_cl, const realtype *sx_rdata, + const int /*ip*/) { + /* for the moment we do not need an implementation of fstotal_cl as + * we can simply reuse ftotal_cl and replace states by their + * sensitivities */ + ftotal_cl(stotal_cl, sx_rdata); } N_Vector Model::computeX_pos(const_N_Vector x) { diff --git a/src/rdata.cpp b/src/rdata.cpp index dc3992984a..3725ddcebc 100644 --- a/src/rdata.cpp +++ b/src/rdata.cpp @@ -2,6 +2,7 @@ #include "amici/misc.h" #include "amici/model.h" +#include "amici/edata.h" #include "amici/symbolic_functions.h" #include "amici/solver.h" #include "amici/exception.h" @@ -315,4 +316,62 @@ void ReturnData::initializeObjectiveFunction() std::fill(s2llh.begin(),s2llh.end(), 0.0); } +void ReturnData::fres(const int it, const ExpData &edata) { + if ( res.empty()) + return; + + auto observedData = edata.getObservedDataPtr(it); + for (int iy = 0; iy < nytrue; ++iy) { + int iyt_true = iy + it * edata.nytrue(); + int iyt = iy + it * ny; + if (!edata.isSetObservedData(it, iy)) + continue; + res.at(iyt_true) = + (y.at(iyt) - observedData[iy]) / sigmay.at(iyt); + } +} + +void ReturnData::fchi2(const int it) { + if (res.empty()) + return; + + for (int iy = 0; iy < nytrue; ++iy) { + int iyt_true = iy + it * nytrue; + chi2 += pow(res.at(iyt_true), 2); + } +} + +void ReturnData::fsres(const int it, const ExpData &edata) { + if (sres.empty()) + return; + + for (int iy = 0; iy < nytrue; ++iy) { + int iyt_true = iy + it * edata.nytrue(); + int iyt = iy + it * ny; + if (!edata.isSetObservedData(it, iy)) + continue; + for (int ip = 0; ip < nplist; ++ip) { + sres.at(iyt_true * nplist + ip) = + sy.at(iy + ny * (ip + it * nplist)) / + sigmay.at(iyt); + } + } +} + +void ReturnData::fFIM(const int it) { + if (sres.empty()) + return; + + for (int iy = 0; iy < nytrue; ++iy) { + int iyt_true = iy + it * nytrue; + for (int ip = 0; ip < nplist; ++ip) { + for (int jp = 0; jp < nplist; ++jp) { + FIM.at(ip + nplist * jp) += sres.at(iyt_true * nplist + ip) * + sres.at(iyt_true * nplist + jp); + } + } + } +} + + } // namespace amici diff --git a/src/solver.cpp b/src/solver.cpp index b6a9b6445d..e1433552ee 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -129,7 +129,7 @@ void Solver::setup(const realtype t0, Model *model, const AmiVector &x0, setSuppressAlg(true); /* calculate consistent DAE initial conditions (no effect for ODE) */ if (model->nt() > 1) - calcIC(model->t(1)); + calcIC(model->getTimepoint(1)); } void Solver::setupB(int *which, const realtype tf, Model *model, diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index 335d26f0e5..554d20c015 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -61,7 +61,7 @@ void SteadystateProblem::workSteadyStateProblem(ReturnData *rdata, if (it < 1) /* No previous time point computed, set t = t0 */ t = model->t0(); else /* Carry on simulating from last point */ - t = model->t(it - 1); + t = model->getTimepoint(it - 1); if (it < 0) { /* Preequilibration? -> Create a new CVode object for sim */ auto newtonSimSolver = diff --git a/tests/cpputest/steadystate/tests1.cpp b/tests/cpputest/steadystate/tests1.cpp index d9b5bec421..2e6b3dc4f5 100644 --- a/tests/cpputest/steadystate/tests1.cpp +++ b/tests/cpputest/steadystate/tests1.cpp @@ -32,9 +32,9 @@ TEST(groupSteadystate, testModelFromHDF5) { amici::checkEqualArray(kExp, model->getFixedParameters(), TEST_ATOL, TEST_RTOL, "k"); CHECK_EQUAL(51, model->nt()); - CHECK_EQUAL(0.0, model->t(0)); - CHECK_EQUAL(100.0, model->t(model->nt() - 2)); - CHECK_EQUAL(INFINITY, model->t(model->nt() - 1)); + CHECK_EQUAL(0.0, model->getTimepoint(0)); + CHECK_EQUAL(100.0, model->getTimepoint(model->nt() - 2)); + CHECK_EQUAL(INFINITY, model->getTimepoint(model->nt() - 1)); for(int i = 0; i < model->np(); ++i) { CHECK_EQUAL(pExp[i], model->getUnscaledParameters()[i]); diff --git a/tests/cpputest/unittests/tests1.cpp b/tests/cpputest/unittests/tests1.cpp index 0c73cedd5f..39227e6a9b 100644 --- a/tests/cpputest/unittests/tests1.cpp +++ b/tests/cpputest/unittests/tests1.cpp @@ -477,7 +477,8 @@ TEST(edata, testDimensionChecks) 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.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)); From e0a225598389a74096f57221b1ca67f410658c65 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 18 Jun 2019 20:38:44 +0200 Subject: [PATCH 19/26] Allow replicate measurements from matlab... (#710) * Allow replicate measurements from matlab by disabling check for non-unique timepoints * fix old homebrew ... --- matlab/@amimodel/generateMatlabWrapper.m | 3 --- 1 file changed, 3 deletions(-) diff --git a/matlab/@amimodel/generateMatlabWrapper.m b/matlab/@amimodel/generateMatlabWrapper.m index 0f6021e6dd..4ee29db931 100644 --- a/matlab/@amimodel/generateMatlabWrapper.m +++ b/matlab/@amimodel/generateMatlabWrapper.m @@ -309,9 +309,6 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,['if(~all(tout==sort(tout)))\n']); fprintf(fid,[' error(''Provided time vector is not monotonically increasing!'');\n']); fprintf(fid,['end\n']); - fprintf(fid,['if(not(length(tout)==length(unique(tout))))\n']); - fprintf(fid,[' error(''Provided time vector has non-unique entries!!'');\n']); - fprintf(fid,['end\n']); fprintf(fid,['if(max(options_ami.sens_ind)>' num2str(np) ')\n']); fprintf(fid,[' error(''Sensitivity index exceeds parameter dimension!'')\n']); fprintf(fid,['end\n']); From dfe2909688146427ea505bce43e251a72034ed0a Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Thu, 20 Jun 2019 12:08:21 +0200 Subject: [PATCH 20/26] Cleanup and update installation guide (#711) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Cleanup and update installation guide * Document matlab-compilation of python-generated model * Update INSTALL.md Co-Authored-By: Fabian Fröhlich * Add libsbml requirement * Ignore random doxygen error 'warning: unable to resolve reference to [...]' --- INSTALL.md | 332 +++++++++++++++++++++++++++------------- documentation/MATLAB.md | 13 ++ scripts/run-doxygen.sh | 6 +- 3 files changed, 247 insertions(+), 104 deletions(-) diff --git a/INSTALL.md b/INSTALL.md index 7299e8a08e..151214d1b7 100755 --- a/INSTALL.md +++ b/INSTALL.md @@ -1,88 +1,45 @@ # 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 accessible as +The sources for AMICI are available as - Source [tarball](https://github.com/ICB-DCM/AMICI/tarball/master) - Source [zip](https://github.com/ICB-DCM/AMICI/zipball/master) - GIT repository on [github](https://github.com/ICB-DCM/AMICI) -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. +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/) +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/ICB-DCM/AMICI and a direct clone is possible via +The GIT repository can currently be found at +[https://github.com/ICB-DCM/AMICI](https://github.com/ICB-DCM/AMICI) +and a direct clone is possible via git clone https://github.com/ICB-DCM/AMICI.git AMICI -### Dependencies - -The MATLAB interface only depends on the symbolic toolbox, which is needed for model compilation, but not simulation. - -#### Symbolic Engine - -The MATLAB interface requires the symbolic toolbox for model compilation. The symbolic toolbox is not required for model simulation. - -#### Math Kernel Library (MKL) - -The python and C++ interfaces require a system installation of `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 prior installation. - -#### 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 required. -HDF 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. -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 + +## Python -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 using, e.g., symbolic links: - - mkdir -p ~/bin/ && ln -s $(which swig3.0) ~/bin/swig && export PATH=~/bin/:$PATH - -#### python packages - -The python interface requires the python packages `pkgconfig` and `numpy` to be installed before AMICI can be installed. These can be installed via `pip`: - - pip3 install pkgconfig numpy - -### MATLAB - -To use AMICI from MATLAB, start MATLAB and add the AMICI/matlab direcory 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. - -### Python - -To use AMICI from python, install the module and all other requirements using pip: +To use AMICI from python, install the module and all other requirements +using pip: pip3 install amici @@ -90,30 +47,48 @@ You can now import it as python module: import amici +For Python-AMICI usage see +[https://github.com/ICB-DCM/AMICI/blob/master/documentation/PYTHON.md](https://github.com/ICB-DCM/AMICI/blob/master/documentation/PYTHON.md). + +### 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, 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/ICB-DCM/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 +### Anaconda -To use an Anaconda installation of python (https://www.anaconda.com/distribution/, Python>=3.6), proceed as follows: +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: +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: +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: +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 @@ -121,31 +96,44 @@ 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. +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. -#### Windows +### Windows 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:. +* 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/icb-dcm/amici/issues/425) for further discussion. +* Slashes can be preferable to backslashes for some environment + variables. +* See also [#425](https://github.com/icb-dcm/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.6. -* Install [mingw64](https://sourceforge.net/projects/mingw-w64/files/latest/download) (32bit will succeed to compile, but fail during linking). During installation, select Version=8.1.0, Architecture=x64_64. Add the following directory to `PATH`: +* A python environment for Windows is required. We recommend + [Anaconda](https://www.anaconda.com/distribution/) with python >=3.6. +* Install [mingw64](https://sourceforge.net/projects/mingw-w64/files/latest/download) + (32bit will succeed to compile, but fail during linking). + During installation, select Version=8.1.0, Architecture=x64_64. + 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: +* 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`: +* 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: @@ -153,11 +141,54 @@ Then, follow these steps: 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. +* 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. + + + +## 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 -### C++ + 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 @@ -165,7 +196,7 @@ To use AMICI from C++, run the ./scripts/buildSuitesparse.sh ./scripts/buildAmici.sh -script to compile amici library. +script to compile AMICI library. The static library file can then be linked from @@ -175,7 +206,9 @@ In CMake-based packages, amici can be linked via find_package(Amici) -*Optional*: To build AMICI with SuperLU_MT support, run +### Optional SuperLU_MT support + +To build AMICI with SuperLU_MT support, run ./scripts/buildSuperLUMT.sh ./scripts/buildSundials.sh @@ -183,30 +216,123 @@ In CMake-based packages, amici can be linked via cmake -DSUNDIALS_SUPERLUMT_ENABLE=ON .. make -## Dependencies - -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/ICB-DCM/AMICI/issues/307) -The python interface requires python 3.6 or newer and `cblas` 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). + +## Dependencies -The C++ interface requires `cmake` and `cblas` to be installed. +### General -The tools SUNDIALS and SuiteSparse shipped with AMICI do __not__ require explicit installation. +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) +__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 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. + +#### 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. 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 + +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 using, e.g., symbolic links: + + mkdir -p ~/bin/ && ln -s $(which swig3.0) ~/bin/swig && export PATH=~/bin/:$PATH + + +### 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/ICB-DCM/AMICI/issues/307](https://github.com/ICB-DCM/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. -__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) +### 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 diff --git a/documentation/MATLAB.md b/documentation/MATLAB.md index af59adce1a..f19a630e1e 100644 --- a/documentation/MATLAB.md +++ b/documentation/MATLAB.md @@ -170,6 +170,19 @@ and subsequently provide the generated struct to `amiwrap(...)`, instead of prov In a similar fashion, the user could also generate multiple models and pass them directly to `amiwrap(...)` without generating respective model definition scripts. + +### Compiling a Python-generated model + +Due to better performance or to avoid the Symbolic Toolbox requirement, +it might be desirable to import a model in Python and compile the +resulting code into a mex file. For Python model import, consult the +respective section of the Python documentation. Once the imported +succeeded, there will be a `compileMexFile.m` script inside the newly +created model directory which can be invoked to compile the mex file. +This mex file and `simulate_*.m` can be used as if fully created by +matlab. + + ## Model Simulation After the call to `amiwrap(...)` two files will be placed in the specified directory. One is a _modelname_.mex and the other is simulate_ _modelname_.m. The mex file should never be called directly. Instead the MATLAB script, which acts as a wrapper around the .mex simulation file should be used. diff --git a/scripts/run-doxygen.sh b/scripts/run-doxygen.sh index 1c9d579c90..39609ce460 100755 --- a/scripts/run-doxygen.sh +++ b/scripts/run-doxygen.sh @@ -64,7 +64,7 @@ rm ${MTOC_CONFIG_PATH}/mtocpp_filter.sh cd ${AMICI_PATH}/doc/latex -make +make cp ./refman.pdf ${AMICI_PATH}/AMICI_guide.pdf # suppress doxygen warnings about status badges @@ -75,6 +75,10 @@ mv ${DOXY_WARNING_FILE}_tmp ${DOXY_WARNING_FILE} grep -v "iple @param documentation sections" ${DOXY_WARNING_FILE} > ${DOXY_WARNING_FILE}_tmp mv ${DOXY_WARNING_FILE}_tmp ${DOXY_WARNING_FILE} +# suppress doxygen warning about unresolved external links (problem unclear) +grep -v "warning: unable to resolve reference to \`https" ${DOXY_WARNING_FILE} > ${DOXY_WARNING_FILE}_tmp +mv ${DOXY_WARNING_FILE}_tmp ${DOXY_WARNING_FILE} + # check if warnings log was created if [ -f ${DOXY_WARNING_FILE} ]; then # check if warnings log is empty From 1931f955a1733dc76fcf86bb49241e60ffed1a5d Mon Sep 17 00:00:00 2001 From: Paul Stapor Date: Mon, 24 Jun 2019 22:32:58 +0200 Subject: [PATCH 21/26] Fix initial amounts (fix #674) (#712) * use initial amounts instead of concentrations of only substance units are used --- python/amici/sbml_import.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py index 06db8cf8f2..3e28576b30 100644 --- a/python/amici/sbml_import.py +++ b/python/amici/sbml_import.py @@ -363,9 +363,11 @@ def processSpecies(self): amounts = [spec.getInitialAmount() for spec in species] def getSpeciesInitial(index, conc): - if not math.isnan(conc): + if not (self.speciesHasOnlySubstanceUnits[index] or math.isnan( + conc) or species[index].isSetInitialConcentration()): return sp.sympify(conc) - if not math.isnan(amounts[index]): + if species[index].isSetInitialAmount() and not math.isnan( + amounts[index]): return \ sp.sympify(amounts[index]) / self.speciesCompartment[index] return self.symbols['species']['identifier'][index] From a1fdadbe576f8db098cffd1caea93ff5aa770fec Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 26 Jun 2019 17:53:50 +0200 Subject: [PATCH 22/26] fix(python) Pass model entities as sympify locals to avoid them being shadowed by sympy builtins (Fixes #714) --- python/amici/sbml_import.py | 63 ++++++++++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 14 deletions(-) diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py index 3e28576b30..62470b049f 100644 --- a/python/amici/sbml_import.py +++ b/python/amici/sbml_import.py @@ -75,6 +75,9 @@ class SbmlImporter: fluxVector: reaction kinetic laws @type sympy.Matrix + local_symbols: model symbols for sympy to consider during sympification + see `locals`argument in `sympy.sympify` @type dict + """ def __init__( @@ -125,6 +128,8 @@ def __init__( self.symbols = dict() self.reset_symbols() + self.local_symbols = {} + def process_document(self): """Validate and simplify document. @@ -273,6 +278,7 @@ def processSBML(self, constantParameters=None): constantParameters = [] self.checkSupport() + self._gather_locals() self.processParameters(constantParameters) self.processSpecies() self.processReactions() @@ -320,6 +326,25 @@ def checkSupport(self): raise SBMLException('Non-unity stoichiometry is' ' currently not supported!') + def _gather_locals(self): + """Populate self.local_symbols with all model entities. + + This is later used during sympifications to avoid sympy builtins + shadowing model entities. + + Arguments: + + Returns: + + Raises: + + """ + for s in self.sbml.getListOfSpecies(): + self.local_symbols[s.getId()] = sp.Symbol(s.getId()) + + for p in self.sbml.getListOfParameters(): + self.local_symbols[p.getId()] = sp.Symbol(p.getId()) + def processSpecies(self): """Get species information from SBML model. @@ -384,7 +409,8 @@ def getSpeciesInitial(index, conc): initial_assignment.getId() ) symMath = sp.sympify( - sbml.formulaToL3String(initial_assignment.getMath()) + sbml.formulaToL3String(initial_assignment.getMath()), + locals=self.local_symbols ) if symMath is not None: symMath = _parse_special_functions(symMath) @@ -529,7 +555,8 @@ def processCompartments(self): initial_assignment.getId() ) self.compartmentVolume[index] = sp.sympify( - sbml.formulaToL3String(initial_assignment.getMath()) + sbml.formulaToL3String(initial_assignment.getMath()), + locals=self.local_symbols ) for comp, vol in zip(self.compartmentSymbols, self.compartmentVolume): @@ -570,7 +597,8 @@ def getElementFromAssignment(element_id): assignment = self.sbml.getInitialAssignment( element_id ) - sym = sp.sympify(sbml.formulaToL3String(assignment.getMath())) + sym = sp.sympify(sbml.formulaToL3String(assignment.getMath()), + locals=self.local_symbols) # this is an initial assignment so we need to use # initial conditions if sym is not None: @@ -631,7 +659,7 @@ def isConstant(specie): # symbol math = sbml.formulaToL3String(reaction.getKineticLaw().getMath()) try: - symMath = sp.sympify(math) + symMath = sp.sympify(math, locals=self.local_symbols) except: raise SBMLException(f'Kinetic law "{math}" contains an ' 'unsupported expression!') @@ -643,7 +671,8 @@ def isConstant(specie): for element in elements: if element.isSetId() & element.isSetStoichiometry(): symMath = symMath.subs( - sp.sympify(element.getId()), + sp.sympify(element.getId(), + locals=self.local_symbols), sp.sympify(element.getStoichiometry()) ) @@ -669,7 +698,7 @@ def processRules(self): """ rules = self.sbml.getListOfRules() - rulevars = getRuleVars(rules) + rulevars = getRuleVars(rules, local_symbols=self.local_symbols) fluxvars = self.fluxVector.free_symbols specvars = self.symbols['species']['identifier'].free_symbols volumevars = self.compartmentVolume.free_symbols @@ -684,9 +713,11 @@ def processRules(self): for rule in rules: if rule.getFormula() == '': continue - variable = sp.sympify(rule.getVariable()) + variable = sp.sympify(rule.getVariable(), + locals=self.local_symbols) # avoid incorrect parsing of pow(x, -1) in symengine - formula = sp.sympify(sbml.formulaToL3String(rule.getMath())) + formula = sp.sympify(sbml.formulaToL3String(rule.getMath()), + locals=self.local_symbols) formula = _parse_special_functions(formula) _check_unsupported_functions(formula, 'Rule') @@ -721,7 +752,8 @@ def processRules(self): if variable in rulevars: for nested_rule in rules: nested_formula = sp.sympify( - sbml.formulaToL3String(nested_rule.getMath())) + sbml.formulaToL3String(nested_rule.getMath()), + locals=self.local_symbols) nested_formula = \ nested_formula.subs(variable, formula) nested_rule.setFormula(str(nested_formula)) @@ -733,7 +765,7 @@ def processRules(self): # rules for variable in assignments.keys(): self.replaceInAllExpressions( - sp.sympify(variable), + sp.sympify(variable, locals=self.local_symbols), assignments[variable] ) @@ -841,7 +873,8 @@ def processObservables(self, observables, sigmas, noise_distributions): observables[observable]['formula'] = repl observableValues = sp.Matrix([ - sp.sympify(observables[observable]['formula']) + sp.sympify(observables[observable]['formula'], + locals=self.local_symbols) for observable in observables ]) observableNames = [ @@ -994,11 +1027,12 @@ def replaceSpecialConstants(self): ) -def getRuleVars(rules): +def getRuleVars(rules, local_symbols=None): """Extract free symbols in SBML rule formulas. Arguments: rules: sbml definitions of rules @type list + local_symbols: locals to pass to sympy.sympify Returns: Tuple of free symbolic variables in the formulas all provided rules @@ -1007,8 +1041,9 @@ def getRuleVars(rules): """ return sp.Matrix( - [sp.sympify(sbml.formulaToL3String(rule.getMath())) for rule in rules - if rule.getFormula() != ''] + [sp.sympify(sbml.formulaToL3String(rule.getMath()), + locals=local_symbols) + for rule in rules if rule.getFormula() != ''] ).free_symbols From bbf784430cf88a4c7ee5e54dcd2605a0b3812b1b Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 26 Jun 2019 19:17:02 +0200 Subject: [PATCH 23/26] Extend documentation for AMICI Mac/Anaconda installation (Closes #663) --- INSTALL.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/INSTALL.md b/INSTALL.md index 151214d1b7..26fc235574 100755 --- a/INSTALL.md +++ b/INSTALL.md @@ -101,6 +101,27 @@ 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/ICB-DCM/AMICI/issues/357) + + ### Windows To install AMICI on Windows using python, you can proceed as follows: From 5acc7c3e88b9910b0853956bcfcb2324cce30b9c Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Thu, 27 Jun 2019 20:30:02 +0200 Subject: [PATCH 24/26] Allow setting model parameter from dict (#717) * Allow setting model parameter from id-value dict * Allow setting parameters via name-value dicts * Add tests for setting parameters via dicts --- include/amici/model.h | 21 +++++++++++++++++ src/model.cpp | 34 +++++++++++++++++++++++---- swig/amici.i | 1 + tests/testSBML.py | 54 +++++++++++++++++++++++++++++++++++++------ 4 files changed, 99 insertions(+), 11 deletions(-) diff --git a/include/amici/model.h b/include/amici/model.h index 199599677a..2453590988 100644 --- a/include/amici/model.h +++ b/include/amici/model.h @@ -8,6 +8,7 @@ #include #include +#include namespace amici { @@ -293,6 +294,16 @@ class Model : public AbstractModel { */ void setParameters(std::vector const &p); + /** + * @brief Sets model parameters according to the parameter IDs and mapped + * values. + * @param p map of parameters IDs and values + * @param ignoreErrors Ignore errors such as parameter IDs in p which are + * not model parameters + */ + void setParameterById(std::map const &p, + bool ignoreErrors = false); + /** * @brief Set value of first model parameter with the specified id * @param par_id parameter id @@ -316,6 +327,16 @@ class Model : public AbstractModel { */ void setParameterByName(std::string const &par_name, realtype value); + /** + * @brief Sets model parameters according to the parameter name and mapped + * values. + * @param p map of parameters names and values + * @param ignoreErrors Ignore errors such as parameter names in p which are + * not model parameters + */ + void setParameterByName(std::map const &p, + bool ignoreErrors = false); + /** * @brief Set all values of all model parameters with names matching the * specified regex diff --git a/src/model.cpp b/src/model.cpp index 3990c46267..9e21e5a6d6 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -320,6 +320,19 @@ void Model::setParameters(const std::vector &p) { unscaleParameters(originalParameters, pscale, unscaledParameters); } +void Model::setParameterById(const std::map &p, + bool ignoreErrors) +{ + for (auto& kv : p) { + try { + setParameterById(kv.first, kv.second); + } catch (AmiException&) { + if(!ignoreErrors) + throw; + } + } +} + void Model::setParameterById(std::string const &par_id, realtype value) { if (!hasParameterIds()) throw AmiException( @@ -351,6 +364,19 @@ void Model::setParameterByName(std::string const &par_name, realtype value) { unscaleParameters(originalParameters, pscale, unscaledParameters); } +void Model::setParameterByName(const std::map &p, + bool ignoreErrors) +{ + for (auto& kv : p) { + try { + setParameterByName(kv.first, kv.second); + } catch (AmiException&) { + if(!ignoreErrors) + throw; + } + } +} + int Model::setParametersByNameRegex(std::string const &par_name_regex, realtype value) { if (!hasParameterNames()) @@ -1224,13 +1250,13 @@ void Model::fsigmay(const int it, const ExpData *edata) { for (int iytrue = 0; iytrue < nytrue; iytrue++) { if (edata->isSetObservedDataStdDev(it, iytrue)) sigmay.at(iytrue) = sigmay_edata[iytrue]; - + /* TODO: when moving second order code to cpp, verify * that this is actually what we want */ for (int iJ = 1; iJ < nJ; iJ++) sigmay.at(iytrue + iJ*nytrue) = 0; - + if (edata->isSetObservedData(it, iytrue)) checkSigmaPositivity(sigmay[iytrue], "sigmay"); } @@ -1498,13 +1524,13 @@ void Model::fsigmaz(const int ie, const int nroots, const realtype t, edata->getObservedEventsStdDevPtr(nroots); sigmaz.at(iztrue) = sigmaz_edata[iztrue]; } - + /* TODO: when moving second order code to cpp, verify * that this is actually what we want */ for (int iJ = 1; iJ < nJ; iJ++) sigmaz.at(iztrue + iJ*nztrue) = 0; - + if (edata->isSetObservedEvents(nroots, iztrue)) checkSigmaPositivity(sigmaz[iztrue], "sigmaz"); } diff --git a/swig/amici.i b/swig/amici.i index 9a1f7c7753..b1f3c4522d 100644 --- a/swig/amici.i +++ b/swig/amici.i @@ -30,6 +30,7 @@ import_array(); %template(IntVector) std::vector; %template(BoolVector) std::vector; %template(StringVector) std::vector; +%template(StringDoubleMap) std::map; // Let numpy access std::vector %{ diff --git a/tests/testSBML.py b/tests/testSBML.py index bbe434bd60..1767e2c162 100755 --- a/tests/testSBML.py +++ b/tests/testSBML.py @@ -84,9 +84,16 @@ def test_steadystate_scaled(self): """ Test SBML import and simulation from AMICI python interface """ - def assert_fun(x): - return self.assertTrue(x) + model_module = self.test_steadystate_import() + + self.steadystate_simulation(model_module=model_module) + + # Run some additional tests which need a working Model, + # but don't need precomputed expectations. + test_set_parameters_by_dict(model_module) + + def test_steadystate_import(self): sbmlFile = os.path.join(os.path.dirname(__file__), '..', 'python', 'examples', 'example_steadystate', 'model_steadystate_scaled.xml') @@ -95,8 +102,8 @@ def assert_fun(x): observables = amici.assignmentRules2observables( sbmlImporter.sbml, filter_function=lambda variable: - variable.getId().startswith('observable_') and - not variable.getId().endswith('_sigma') + variable.getId().startswith('observable_') and + not variable.getId().endswith('_sigma') ) outdir = 'test_model_steadystate_scaled' @@ -105,12 +112,18 @@ def assert_fun(x): observables=observables, constantParameters=['k0'], sigmas={'observable_x1withsigma': - 'observable_x1withsigma_sigma'}) + 'observable_x1withsigma_sigma'}) sys.path.insert(0, outdir) - import test_model_steadystate_scaled as modelModule + import test_model_steadystate_scaled as model_module - model = modelModule.getModel() + return model_module + + def steadystate_simulation(self, model_module): + def assert_fun(x): + return self.assertTrue(x) + + model = model_module.getModel() model.setTimepoints(np.linspace(0, 60, 60)) solver = model.getSolver() solver.setSensitivityOrder(amici.SensitivityOrder_first) @@ -175,6 +188,7 @@ def assert_fun(x): check_derivatives(model, solver, edata[0], assert_fun, atol=1e-3, rtol=1e-3, epsilon=1e-4) + def test_likelihoods(self): """ Test the custom noise distributions used to define cost functions. @@ -254,6 +268,32 @@ def assert_fun(x): self.assertTrue(np.any(rdata['sllh'])) +def test_set_parameters_by_dict(model_module): + """Test setting parameter via id/name => value dicts""" + + model = model_module.getModel() + old_parameter_values = model.getParameters() + parameter_ids = model.getParameterIds() + change_par_id = parameter_ids[-1] + new_par_val = 0.1234 + old_par_val = model.getParameterById(change_par_id) + + assert model.getParameterById(change_par_id) != new_par_val + model.setParameterById({change_par_id: new_par_val}) + assert model.getParameterById(change_par_id) == new_par_val + # reset and check we are back to original + model.setParameterById(change_par_id, old_par_val) + assert model.getParameters() == old_parameter_values + + # Same for by-name + parameter_names = model.getParameterNames() + change_par_name = parameter_names[-1] + model.setParameterByName({change_par_name: new_par_val}) + assert model.getParameterByName(change_par_name) == new_par_val + model.setParameterByName(change_par_name, old_par_val) + assert model.getParameters() == old_parameter_values + + if __name__ == '__main__': suite = unittest.TestSuite() suite.addTest(TestAmiciSBMLModel()) From ed17287955f6c010e03bc4e72735fb4abbdecdcc Mon Sep 17 00:00:00 2001 From: Paul Stapor Date: Mon, 1 Jul 2019 09:05:38 +0200 Subject: [PATCH 25/26] fix sbml initial concentration import (#723) --- python/amici/sbml_import.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py index 62470b049f..1e04621298 100644 --- a/python/amici/sbml_import.py +++ b/python/amici/sbml_import.py @@ -389,7 +389,7 @@ def processSpecies(self): def getSpeciesInitial(index, conc): if not (self.speciesHasOnlySubstanceUnits[index] or math.isnan( - conc) or species[index].isSetInitialConcentration()): + conc) or not species[index].isSetInitialConcentration()): return sp.sympify(conc) if species[index].isSetInitialAmount() and not math.isnan( amounts[index]): From 0ce296e418b8c2a5bdc69869883334c37c87a1f4 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Thu, 4 Jul 2019 11:56:09 +0200 Subject: [PATCH 26/26] Add AMICI interface overview / workflow figure and show in README (#725) * Add AMICI interface overview / workflow figure and show in README --- README.md | 11 + documentation/gfx/amici_workflow.png | Bin 0 -> 97373 bytes documentation/gfx/amici_workflow.svg | 2168 ++++++++++++++++++++++++++ 3 files changed, 2179 insertions(+) create mode 100644 documentation/gfx/amici_workflow.png create mode 100644 documentation/gfx/amici_workflow.svg diff --git a/README.md b/README.md index a3a194067c..8914d79fab 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,17 @@ Online documentation is available as [github-pages](http://icb-dcm.github.io/AMI [Full list](documentation/references.md) +## Interfaces & workflow + +The AMICI workflow starts with importing a model from either +[SBML](http://sbml.org/) (Matlab, Python) or a Matlab definition of the +model (Matlab-only). From this input, all equations for model simulation +are derived symbolically and C++ code is generated. This code is then +compiled into a C++ library, a Python module, or a Matab mex file and +is then used for model simulation. + +![AMICI workflow](documentation/gfx/amici_workflow.png) + ## Current build status [![PyPI version](https://badge.fury.io/py/amici.svg)](https://badge.fury.io/py/amici) diff --git a/documentation/gfx/amici_workflow.png b/documentation/gfx/amici_workflow.png new file mode 100644 index 0000000000000000000000000000000000000000..c412a115331520039bfcee872427659f35bc4292 GIT binary patch literal 97373 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�FR(Zz!F6 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + .a / .so + amici::runAmiciSimulation(...) + + + + + + + + + + + + + + + + + + + + Symbolic Math ToolboxMatlab < R2018a + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Matlab + + + .mex + simulate_...(...) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + >= 3.6 + + + Python module / package + amici.runAmiciSimulation(...) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Model + Model importsymbolic processing& C++ code generation + Model compilation + Model simulation + AMICI interfaces + + + + +