From 88371ad6c48330c1ab6b3a05ebea1bf6e706d391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Wed, 16 Mar 2022 13:19:48 -0400 Subject: [PATCH 01/31] use correct tolerances for convergence check in newton solver (#1728) --- src/newton_solver.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/newton_solver.cpp b/src/newton_solver.cpp index b770f04d8b..ca032d982a 100644 --- a/src/newton_solver.cpp +++ b/src/newton_solver.cpp @@ -64,8 +64,8 @@ std::unique_ptr NewtonSolver::getSolver(realtype *t, AmiVector *x, default: throw NewtonFailure(AMICI_NOT_IMPLEMENTED, "getSolver"); } - solver->atol_ = simulationSolver.getAbsoluteTolerance(); - solver->rtol_ = simulationSolver.getRelativeTolerance(); + solver->atol_ = simulationSolver.getAbsoluteToleranceSteadyState(); + solver->rtol_ = simulationSolver.getRelativeToleranceSteadyState(); solver->max_lin_steps_ = simulationSolver.getNewtonMaxLinearSteps(); solver->max_steps = simulationSolver.getNewtonMaxSteps(); solver->damping_factor_mode_ = simulationSolver.getNewtonDampingFactorMode(); From d8d164c69c5a06cca504cb4c75892583c89da490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Wed, 16 Mar 2022 16:47:30 -0400 Subject: [PATCH 02/31] remove SPBCG solver (#1729) * remove SPBCG solver * remove test * fixup * retry deleting from hdf5 * fixup remove options * remove python test --- include/amici/newton_solver.h | 90 ----------- python/tests/test_preequilibration.py | 16 -- src/newton_solver.cpp | 152 +----------------- src/steadystateproblem.cpp | 5 - tests/cpp/expectedResults.h5 | Bin 4198368 -> 4198368 bytes tests/cpp/steadystate/tests1.cpp | 6 - tests/cpp/testOptions.h5 | Bin 345728 -> 345728 bytes .../generateTestConfig/example_steadystate.py | 11 -- 8 files changed, 1 insertion(+), 279 deletions(-) diff --git a/include/amici/newton_solver.h b/include/amici/newton_solver.h index 01c033aeb6..9dd3c8a92f 100644 --- a/include/amici/newton_solver.h +++ b/include/amici/newton_solver.h @@ -254,96 +254,6 @@ class NewtonSolverSparse : public NewtonSolver { SUNLinearSolver linsol_ {nullptr}; }; -/** - * @brief The NewtonSolverIterative provides access to the iterative linear - * solver for the Newton method. - */ - -class NewtonSolverIterative : public NewtonSolver { - - public: - /** - * @brief Constructor, initializes all members with the provided objects - * @param t pointer to time variable - * @param x pointer to state variables - * @param model pointer to the model object - */ - NewtonSolverIterative(realtype *t, AmiVector *x, Model *model); - - ~NewtonSolverIterative() override = default; - - /** - * @brief Solves the linear system for the Newton step by passing it to - * linsolveSPBCG - * - * @param rhs containing the RHS of the linear system, will be - * overwritten by solution to the linear system - */ - void solveLinearSystem(AmiVector &rhs) override; - - /** - * Writes the Jacobian (J) for the Newton iteration and passes it to the linear - * solver. - * Also wraps around getSensis for iterative linear solver. - * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step - */ - void prepareLinearSystem(int ntry, int nnewt) override; - - /** - * Writes the Jacobian (JB) for the Newton iteration and passes it to the linear - * solver. - * Also wraps around getSensis for iterative linear solver. - * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step - */ - void prepareLinearSystemB(int ntry, int nnewt) override; - - /** - * Iterative linear solver created from SPILS BiCG-Stab. - * Solves the linear system within each Newton step if iterative solver is - * chosen. - * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step - * @param ns_delta Newton step - */ - void linsolveSPBCG(int ntry, int nnewt, AmiVector &ns_delta); - - private: - /** number of tries */ - int newton_try_ {0}; - /** number of iterations */ - int i_newton_ {0}; - /** ??? */ - AmiVector ns_p_; - /** ??? */ - AmiVector ns_h_; - /** ??? */ - AmiVector ns_t_; - /** ??? */ - AmiVector ns_s_; - /** ??? */ - AmiVector ns_r_; - /** ??? */ - AmiVector ns_rt_; - /** ??? */ - AmiVector ns_v_; - /** ??? */ - AmiVector ns_Jv_; - /** ??? */ - AmiVector ns_tmp_; - /** ??? */ - AmiVector ns_Jdiag_; - /** temporary storage of Jacobian */ - SUNMatrixWrapper ns_J_; -}; - } // namespace amici diff --git a/python/tests/test_preequilibration.py b/python/tests/test_preequilibration.py index 2af6da888b..5cd67a39d3 100644 --- a/python/tests/test_preequilibration.py +++ b/python/tests/test_preequilibration.py @@ -359,19 +359,3 @@ def test_newton_solver_equilibration(preeq_fixture): rdatas[settings[1]][variable], 1e-6, 1e-6 ).all(), variable - - # test failure for iterative linear solver with sensitivities - edata.fixedParametersPreequilibration = () - edata.t_presim = 0.0 - edata.fixedParametersPresimulation = () - - solver.setLinearSolver(amici.LinearSolver.SPBCG) - solver.setSensitivityMethod(amici.SensitivityMethod.adjoint) - solver.setSensitivityOrder(amici.SensitivityOrder.first) - model.setSteadyStateSensitivityMode( - amici.SteadyStateSensitivityMode.newtonOnly) - solver.setNewtonMaxSteps(10) - solver.setNewtonMaxLinearSteps(10) - rdata_spbcg = amici.runAmiciSimulation(model, solver, edata) - - assert rdata_spbcg['status'] == amici.AMICI_ERROR diff --git a/src/newton_solver.cpp b/src/newton_solver.cpp index ca032d982a..f10006ff92 100644 --- a/src/newton_solver.cpp +++ b/src/newton_solver.cpp @@ -49,8 +49,7 @@ std::unique_ptr NewtonSolver::getSolver(realtype *t, AmiVector *x, throw NewtonFailure(AMICI_NOT_IMPLEMENTED, "getSolver"); case LinearSolver::SPBCG: - solver.reset(new NewtonSolverIterative(t, x, model)); - break; + throw NewtonFailure(AMICI_NOT_IMPLEMENTED, "getSolver"); case LinearSolver::SPTFQMR: throw NewtonFailure(AMICI_NOT_IMPLEMENTED, "getSolver"); @@ -71,8 +70,6 @@ std::unique_ptr NewtonSolver::getSolver(realtype *t, AmiVector *x, solver->damping_factor_mode_ = simulationSolver.getNewtonDampingFactorMode(); solver->damping_factor_lower_bound = simulationSolver.getNewtonDampingFactorLowerBound(); - if (simulationSolver.getLinearSolver() == LinearSolver::SPBCG) - solver->num_lin_steps_.resize(simulationSolver.getNewtonMaxSteps(), 0); return solver; } @@ -220,152 +217,5 @@ NewtonSolverSparse::~NewtonSolverSparse() { SUNLinSolFree_KLU(linsol_); } -/* ------------------------------------------------------------------------- */ -/* - Iterative linear solver------------------------------------------------ */ -/* ------------------------------------------------------------------------- */ - -NewtonSolverIterative::NewtonSolverIterative(realtype *t, AmiVector *x, - Model *model) - : NewtonSolver(t, x, model), 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), ns_J_(model->nx_solver, model->nx_solver) - { -} - -/* ------------------------------------------------------------------------- */ - -void NewtonSolverIterative::prepareLinearSystem(int ntry, int nnewt) { - newton_try_ = ntry; - i_newton_ = nnewt; - if (nnewt == -1) { - throw NewtonFailure(AMICI_NOT_IMPLEMENTED, - "Linear solver SPBCG does not support sensitivity " - "computation for steady state problems."); - } - - // Get the Jacobian and its diagonal for preconditioning - model_->fJ(*t_, 0.0, *x_, dx_, xdot_, ns_J_.get()); - ns_J_.refresh(); - model_->fJDiag(*t_, ns_Jdiag_, 0.0, *x_, dx_); - - // Ensure positivity of entries in ns_Jdiag - ns_p_.set(1.0); - ns_Jdiag_.abs(); - N_VCompare(1e-15, ns_Jdiag_.getNVector(), ns_tmp_.getNVector()); - linearSum(-1.0, ns_tmp_, 1.0, ns_p_, ns_tmp_); - linearSum(1.0, ns_Jdiag_, 1.0, ns_tmp_, ns_Jdiag_); -} - -/* ------------------------------------------------------------------------- */ - -void NewtonSolverIterative::prepareLinearSystemB(int ntry, int nnewt) { - newton_try_ = ntry; - i_newton_ = nnewt; - if (nnewt == -1) { - throw AmiException("Linear solver SPBCG does not support sensitivity " - "computation for steady state problems."); - } - - // Get the Jacobian and its diagonal for preconditioning - model_->fJB(*t_, 0.0, *x_, dx_, xB_, dxB_, xdot_, ns_J_.get()); - ns_J_.refresh(); - // Get the diagonal and ensure negativity of entries is ns_J. Note that diag(JB) = -diag(J). - model_->fJDiag(*t_, ns_Jdiag_, 0.0, *x_, dx_); - - ns_p_.set(1.0); - ns_Jdiag_.abs(); - N_VCompare(1e-15, ns_Jdiag_.getNVector(), ns_tmp_.getNVector()); - linearSum(-1.0, ns_tmp_, 1.0, ns_p_, ns_tmp_); - linearSum(1.0, ns_Jdiag_, 1.0, ns_tmp_, ns_Jdiag_); - ns_Jdiag_.minus(); -} - -/* ------------------------------------------------------------------------- */ - -void NewtonSolverIterative::solveLinearSystem(AmiVector &rhs) { - linsolveSPBCG(newton_try_, i_newton_, rhs); - rhs.minus(); -} - -/* ------------------------------------------------------------------------- */ - -void NewtonSolverIterative::linsolveSPBCG(int /*ntry*/, int nnewt, - AmiVector &ns_delta) { - xdot_ = ns_delta; - xdot_.minus(); - - // Initialize for linear solve - ns_p_.zero(); - ns_v_.zero(); - ns_delta.zero(); - ns_tmp_.zero(); - double rho = 1.0; - double omega = 1.0; - double alpha = 1.0; - - ns_J_.multiply(ns_Jv_, ns_delta); - - // ns_r = xdot - ns_Jv; - linearSum(-1.0, ns_Jv_, 1.0, xdot_, ns_r_); - ns_r_ /= ns_Jdiag_; - ns_rt_ = ns_r_; - - for (int i_linstep = 0; i_linstep < max_lin_steps_; i_linstep++) { - // Compute factors - double rho1 = rho; - rho = dotProd(ns_rt_, ns_r_); - double beta = rho * alpha / (rho1 * omega); - - // ns_p = ns_r + beta * (ns_p - omega * ns_v); - linearSum(1.0, ns_p_, -omega, ns_v_, ns_p_); - linearSum(1.0, ns_r_, beta, ns_p_, ns_p_); - - // ns_v = J * ns_p - ns_v_.zero(); - ns_J_.multiply(ns_v_, ns_p_); - ns_v_ /= ns_Jdiag_; - - // Compute factor - alpha = rho / dotProd(ns_rt_, ns_v_); - - // ns_h = ns_delta + alpha * ns_p; - linearSum(1.0, ns_delta, alpha, ns_p_, ns_h_); - // ns_s = ns_r - alpha * ns_v; - linearSum(1.0, ns_r_, -alpha, ns_v_, ns_s_); - - // ns_t = J * ns_s - ns_t_.zero(); - ns_J_.multiply(ns_t_, ns_s_); - ns_t_ /= ns_Jdiag_; - - // Compute factor - omega = dotProd(ns_t_, ns_s_) / dotProd(ns_t_, ns_t_); - - // ns_delta = ns_h + omega * ns_s; - linearSum(1.0, ns_h_, omega, ns_s_, ns_delta); - // ns_r = ns_s - omega * ns_t; - linearSum(1.0, ns_s_, -omega, ns_t_, ns_r_); - - // Compute the (unscaled) residual - ns_r_ *= ns_Jdiag_; - double res = sqrt(dotProd(ns_r_, ns_r_)); - - // Test convergence - if (res < atol_) { - // Write number of steps needed - num_lin_steps_.at(nnewt) = i_linstep + 1; - - // Return success - return; - } - - // Scale back - ns_r_ /= ns_Jdiag_; - } - throw NewtonFailure(AMICI_CONV_FAILURE, "linsolveSPBCG"); -} - } // namespace amici diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index d7c3850e64..1bdae812e8 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -26,11 +26,6 @@ SteadystateProblem::SteadystateProblem(const Solver &solver, const Model &model) xB_(model.nJ * model.nx_solver), xQ_(model.nJ * model.nx_solver), xQB_(model.nplist()), xQBdot_(model.nplist()), dJydx_(model.nJ * model.nx_solver * model.nt(), 0.0) { - /* maxSteps must be adapted if iterative linear solvers are used */ - if (solver.getLinearSolver() == LinearSolver::SPBCG) { - max_steps_ = solver.getNewtonMaxSteps(); - numlinsteps_.resize(2 * max_steps_, 0); - } /* Check for compatibility of options */ if (solver.getSensitivityMethod() == SensitivityMethod::forward && solver.getSensitivityMethodPreequilibration() == SensitivityMethod::adjoint && diff --git a/tests/cpp/expectedResults.h5 b/tests/cpp/expectedResults.h5 index a24be851566e794a6dffd0f0564bed2660e06996..a68de6ea7eda08275a442ed7b639639b8429aacd 100644 GIT binary patch delta 314 zcmW;GJ5B;o06kY#mOxMG0+eJzE7g=NfZ9xJsAvfr zUZ7I*UlFNa%b~&qoEV+fWe}}SQzm4SI{3ky% zObM@Jm_GkBFcPjqx;@_XA7+!S4}Js?#1TR`#tFjE5y2_W5QV}yVz@vYm$*U#NnGOw yw@4w446?XG4)@5TfFk~%N_fN*$}mtt6*bh+z%!a?;RS7U&_xe@XR>{L@9qKhI))hl delta 345 zcmXZPJ5B;o0EJ=hz=aV(6u3T7L*>1a$`fVCNG zD|;GCc3?&CAK)Zka{k$SUz=eZ7LI|kl(3X$DW|1epGtl-G*j5$4ViWd_vEiBQ}G|i za^sx7IAYabtNzXFlaMLrBcmnZwvCoYANsnly-v6H(l)El^-*o=fsQqJu?`>n*gycA z*g_B?grTsF9qeKc`#3-Zhd4qM$B5wsahxK7B+hV-6fSUyG_G)s8yL979q#df46?}K S5qT8wgd$2P+qG%st@sZ$&4B6v diff --git a/tests/cpp/steadystate/tests1.cpp b/tests/cpp/steadystate/tests1.cpp index 505f8f15ed..93f48ceb27 100644 --- a/tests/cpp/steadystate/tests1.cpp +++ b/tests/cpp/steadystate/tests1.cpp @@ -174,12 +174,6 @@ TEST(ExampleSteadystate, SensitivityForwardDense) amici::simulateVerifyWrite("/model_steadystate/sensiforwarddense/"); } -TEST(ExampleSteadystate, SensitivityForwardSPBCG) -{ - amici::simulateVerifyWrite( - "/model_steadystate/nosensiSPBCG/", 10 * TEST_ATOL, 10 * TEST_RTOL); -} - TEST(ExampleSteadystate, SensiFwdNewtonPreeq) { amici::simulateVerifyWrite("/model_steadystate/sensifwdnewtonpreeq/"); diff --git a/tests/cpp/testOptions.h5 b/tests/cpp/testOptions.h5 index e2bf3f658e83688bab6aa03e5fe7a117d1360861..25280e9cec208ed2aaecb54b27a2106c61723607 100644 GIT binary patch delta 73 zcmZqZ6>R{b7RDB)EzA=`85y=u3T4(|W@MkfKZ03vxR{b7RDB)EzA=`85y@v3T4(|X5^T@KZ03PDS!b2PV8m|GZ>-#4^Y0qzUhx5 jn7?g{Vm`5+CojJ^HLo}`IKau-z5NO&%l0drtZQrl-;o Date: Thu, 17 Mar 2022 00:18:36 -0400 Subject: [PATCH 03/31] harmonize convergence checks (#1731) * harmonize convergence checks * use global convergence threshold * fixup * adress reviewer comments * fixup * fixup wrms_ --- include/amici/steadystateproblem.h | 33 +++++++-- src/steadystateproblem.cpp | 108 ++++++++++++++++------------- 2 files changed, 87 insertions(+), 54 deletions(-) diff --git a/include/amici/steadystateproblem.h b/include/amici/steadystateproblem.h index b59eeb404c..fc4dc5cfea 100644 --- a/include/amici/steadystateproblem.h +++ b/include/amici/steadystateproblem.h @@ -158,14 +158,20 @@ class SteadystateProblem { AmiVector &ewt) const; /** - * @brief Checks convergence for state and respective sensitivities - * @param solver Solver instance - * @param model instance - * @param checkSensitivities flag whether sensitivities should be checked - * @return boolean indicating convergence + * @brief Checks convergence for state or adjoint quadratures, depending on sensi method + * @param model Model instance + * @param sensi_method sensitivity method + * @return weighted root mean squared residuals of the RHS + */ + realtype getWrms(Model *model, + SensitivityMethod sensi_method); + + /** + * @brief Checks convergence for state sensitivities + * @param model Model instance + * @return weighted root mean squared residuals of the RHS */ - bool checkConvergence(const Solver *solver, Model *model, - SensitivityMethod checkSensitivities); + realtype getWrmsFSA(Model *model); /** * @brief Runs the Newton solver iterations and checks for convergence @@ -410,6 +416,19 @@ class SteadystateProblem { * approaches [newton, simulation, newton] (length = 3) */ std::vector steady_state_status_; + + /** absolute tolerance for convergence check (state)*/ + realtype atol_ {NAN}; + /** relative tolerance for convergence check (state)*/ + realtype rtol_ {NAN}; + /** absolute tolerance for convergence check (state sensi)*/ + realtype atol_sensi_ {NAN}; + /** relative tolerance for convergence check (state sensi)*/ + realtype rtol_sensi_ {NAN}; + /** absolute tolerance for convergence check (quadratures)*/ + realtype atol_quad_ {NAN}; + /** relative tolerance for convergence check (quadratures)*/ + realtype rtol_quad_ {NAN}; }; } // namespace amici diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index 1bdae812e8..7c404bd990 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -16,6 +16,8 @@ #include #include +constexpr realtype conv_thresh = 1.0; + namespace amici { SteadystateProblem::SteadystateProblem(const Solver &solver, const Model &model) @@ -25,7 +27,13 @@ SteadystateProblem::SteadystateProblem(const Solver &solver, const Model &model) sx_(model.nx_solver, model.nplist()), sdx_(model.nx_solver, model.nplist()), xB_(model.nJ * model.nx_solver), xQ_(model.nJ * model.nx_solver), xQB_(model.nplist()), xQBdot_(model.nplist()), - dJydx_(model.nJ * model.nx_solver * model.nt(), 0.0) { + dJydx_(model.nJ * model.nx_solver * model.nt(), 0.0), + atol_(solver.getAbsoluteToleranceSteadyState()), + rtol_(solver.getRelativeToleranceSteadyState()), + atol_sensi_(solver.getAbsoluteToleranceSteadyStateSensi()), + rtol_sensi_(solver.getRelativeToleranceSteadyStateSensi()), + atol_quad_(solver.getAbsoluteToleranceQuadratures()), + rtol_quad_(solver.getRelativeToleranceQuadratures()) { /* Check for compatibility of options */ if (solver.getSensitivityMethod() == SensitivityMethod::forward && solver.getSensitivityMethodPreequilibration() == SensitivityMethod::adjoint && @@ -425,49 +433,55 @@ realtype SteadystateProblem::getWrmsNorm(const AmiVector &x, AmiVector &ewt) const { /* Depending on what convergence we want to check (xdot, sxdot, xQBdot) we need to pass ewt[QB], as xdot and xQBdot have different sizes */ + // ewt = x N_VAbs(const_cast(x.getNVector()), ewt.getNVector()); + // ewt *= rtol N_VScale(rtol, ewt.getNVector(), ewt.getNVector()); + // ewt += atol N_VAddConst(ewt.getNVector(), atol, ewt.getNVector()); + // ewt = 1/ewt (ewt = 1/(rtol*x+atol)) N_VInv(ewt.getNVector(), ewt.getNVector()); + // wrms = sqrt(sum((xdot/ewt)**2)) return N_VWrmsNorm(const_cast(xdot.getNVector()), ewt.getNVector()); } -bool SteadystateProblem::checkConvergence(const Solver *solver, - Model *model, - SensitivityMethod checkSensitivities) { - if (checkSensitivities == SensitivityMethod::adjoint) { +realtype SteadystateProblem::getWrms(Model *model, + SensitivityMethod sensi_method) { + realtype wrms = INFINITY; + if (sensi_method == SensitivityMethod::adjoint) { /* In the adjoint case, only xQB contributes to the gradient, the exact steadystate is less important, as xB = xQdot may even not converge to zero at all. So we need xQBdot, hence compute xQB first. */ computeQBfromQ(model, xQ_, xQB_); computeQBfromQ(model, xB_, xQBdot_); - wrms_ = getWrmsNorm(xQB_, xQBdot_, solver->getAbsoluteToleranceQuadratures(), - solver->getRelativeToleranceQuadratures(), ewtQB_); + wrms = getWrmsNorm(xQB_, xQBdot_, atol_quad_, rtol_quad_, ewtQB_); } else { /* If we're doing a forward simulation (with or without sensitivities: Get RHS and compute weighted error norm */ model->fxdot(t_, x_, dx_, xdot_); - wrms_ = getWrmsNorm(x_, xdot_, solver->getAbsoluteToleranceSteadyState(), - solver->getRelativeToleranceSteadyState(), ewt_); + wrms = getWrmsNorm(x_, xdot_, atol_, rtol_, ewt_); } - bool converged = wrms_ < RCONST(1.0); - - if (checkSensitivities != SensitivityMethod::forward) - return converged; + return wrms; +} + +realtype SteadystateProblem::getWrmsFSA(Model *model) { /* Forward sensitivities: Compute weighted error norm for their RHS */ + realtype wrms = 0.0; for (int ip = 0; ip < model->nplist(); ++ip) { - if (converged) { - sx_ = solver->getStateSensitivity(t_); - model->fsxdot(t_, x_, dx_, ip, sx_[ip], dx_, xdot_); - wrms_ = getWrmsNorm( - sx_[ip], xdot_, solver->getAbsoluteToleranceSteadyStateSensi(), - solver->getRelativeToleranceSteadyStateSensi(), ewt_); - converged = wrms_ < RCONST(1.0); - } + model->fsxdot(t_, x_, dx_, ip, sx_[ip], dx_, xdot_); + wrms = getWrmsNorm(sx_[ip], xdot_, atol_sensi_, rtol_sensi_, ewt_); + /* ideally this function would report the maximum of all wrms over + all ip, but for practical purposes we can just report the wrms for + the first ip where we know that the convergence threshold is not + satisfied. */ + if (wrms > conv_thresh) + break; } - return converged; + /* just report the parameter for the last ip, value doesn't matter it's + only important that all of them satisfy the convergence threshold */ + return wrms; } bool SteadystateProblem::checkSteadyStateSuccess() const { @@ -502,9 +516,8 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, x_old_ = x_; xdot_old_ = xdot_; - wrms_ = getWrmsNorm(x_, xdot_, newtonSolver->atol_, - newtonSolver->rtol_, ewt_); - bool converged = newton_retry ? false : wrms_ < RCONST(1.0); + wrms_ = getWrms(model, SensitivityMethod::none); + bool converged = newton_retry ? false : wrms_ < conv_thresh; while (!converged && i_newtonstep < newtonSolver->max_steps) { /* If Newton steps are necessary, compute the initial search direction */ @@ -522,9 +535,7 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, linearSum(1.0, x_old_, gamma, delta_, x_); /* Compute new xdot and residuals */ - model->fxdot(t_, x_, dx_, xdot_); - realtype wrms_tmp = getWrmsNorm(x_, xdot_, newtonSolver->atol_, - newtonSolver->rtol_, ewt_); + realtype wrms_tmp = getWrms(model, SensitivityMethod::none); if (wrms_tmp < wrms_) { /* If new residuals are smaller than old ones, update state */ @@ -533,9 +544,9 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, xdot_old_ = xdot_; /* New linear solve due to new state */ compNewStep = true; - /* Check residuals vs tolerances */ - converged = wrms_ < RCONST(1.0); - + + // precheck convergence + converged = wrms_ < conv_thresh; if (converged) { /* Ensure positivity of the found state and recheck if the convergence still holds */ @@ -547,15 +558,15 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, } } if (recheck_convergence) { - model->fxdot(t_, x_, dx_, xdot_); - wrms_ = getWrmsNorm(x_, xdot_, newtonSolver->atol_, - newtonSolver->rtol_, ewt_); - converged = wrms_ < RCONST(1.0); + wrms_ = getWrms(model, SensitivityMethod::none); + converged = wrms_ < conv_thresh; } - } else if (newtonSolver->damping_factor_mode_==NewtonDampingFactorMode::on) { - /* increase dampening factor (superfluous, if converged) */ - gamma = fmin(1.0, 2.0 * gamma); } + + // update dampening + if (newtonSolver->damping_factor_mode_==NewtonDampingFactorMode::on) + gamma = fmin(1.0, 2.0 * gamma); + } else if (newtonSolver->damping_factor_mode_==NewtonDampingFactorMode::on) { /* Reduce dampening factor and raise an error when becomes too small */ gamma = gamma / 4.0; @@ -605,10 +616,10 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, sensitivityFlag = SensitivityMethod::adjoint; /* If run after Newton's method checks again if it converged */ - bool converged = checkConvergence(solver, model, sensitivityFlag); + wrms_ = getWrms(model, sensitivityFlag); int sim_steps = 0; - while (!converged) { + while (true) { /* One step of ODE integration reason for tout specification: max with 1 ensures correct direction (any positive value would do) @@ -624,15 +635,22 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, } /* Check for convergence */ - converged = checkConvergence(solver, model, sensitivityFlag); + wrms_ = getWrms(model, sensitivityFlag); + if (wrms_ < conv_thresh && sensitivityFlag == SensitivityMethod::forward) { + sx_ = solver->getStateSensitivity(t_); + wrms_ = getWrmsFSA(model); + } + + if (wrms_ < conv_thresh) + break; // converged /* increase counter, check for maxsteps */ sim_steps++; - if (sim_steps >= solver->getMaxSteps() && !converged) { + if (sim_steps >= solver->getMaxSteps()) { numsteps_.at(1) = sim_steps; throw NewtonFailure(AMICI_TOO_MUCH_WORK, "exceeded maximum number of steps"); } - if (t_ >= 1e200 && !converged) { + if (t_ >= 1e200) { numsteps_.at(1) = sim_steps; throw NewtonFailure(AMICI_NO_STEADY_STATE, "simulated to late time" " point without convergence of RHS"); @@ -644,10 +662,6 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, numstepsB_ = sim_steps; } else { numsteps_.at(1) = sim_steps; - if (solver->getSensitivityOrder() > SensitivityOrder::none && - model->getSteadyStateSensitivityMode() == - SteadyStateSensitivityMode::simulationFSA) - sx_ = solver->getStateSensitivity(t_); } } From 1995d7b056bb48dfb38f16bf118861fbe0e76b59 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 18 Mar 2022 11:06:50 +0100 Subject: [PATCH 04/31] Doc: Fix sphinx-hoverxref master->main (#1735) --- documentation/rtd_requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/documentation/rtd_requirements.txt b/documentation/rtd_requirements.txt index d386993726..357294df4f 100644 --- a/documentation/rtd_requirements.txt +++ b/documentation/rtd_requirements.txt @@ -12,7 +12,7 @@ recommonmark>=0.6.0 sphinx_rtd_theme>=1.0.0 petab>=0.1.20 sphinx-autodoc-typehints==1.13.0 -git+https://github.com/readthedocs/sphinx-hoverxref@master +git+https://github.com/readthedocs/sphinx-hoverxref@main ipython>=7.28.0 breathe==4.31.0 #exhale>=0.2.3 From b5927c2db486d75e5b1a1cbc74a5eea8e7c20adb Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 18 Mar 2022 16:20:48 +0100 Subject: [PATCH 05/31] Make sundials' KLU_INDEXTYPE match actual klu index type (#1733) * Make sundials' KLU_INDEXTYPE match actual klu index type This is currently not the case on Windows * Make SuiteSparse use 64bit on OSX, matching sundials * Update SuiteSparse build script * fixup * .. * fix format --- .../SuiteSparse/AMD/Include/amd_internal.h | 4 ++++ ThirdParty/SuiteSparse/AMD/Source/amd_2.c | 2 +- .../SuiteSparse/CAMD/Include/camd_internal.h | 4 ++++ ThirdParty/SuiteSparse/CAMD/Source/camd_2.c | 2 +- ThirdParty/SuiteSparse/COLAMD/Source/colamd.c | 6 ++++- .../SuiteSparse_config/SuiteSparse_config.h | 8 ++++--- .../SuiteSparse/include/SuiteSparse_config.h | 8 ++++--- .../src/sunlinsol/klu/sunlinsol_klu.c | 22 +++++++++---------- 8 files changed, 36 insertions(+), 20 deletions(-) diff --git a/ThirdParty/SuiteSparse/AMD/Include/amd_internal.h b/ThirdParty/SuiteSparse/AMD/Include/amd_internal.h index 67305273ec..937d61ab83 100644 --- a/ThirdParty/SuiteSparse/AMD/Include/amd_internal.h +++ b/ThirdParty/SuiteSparse/AMD/Include/amd_internal.h @@ -159,6 +159,8 @@ #define ID SuiteSparse_long_id #define Int_MAX SuiteSparse_long_max +#define UnsignedInt SuiteSparse_unsigned_long + #define AMD_order amd_l_order #define AMD_defaults amd_l_defaults #define AMD_control amd_l_control @@ -180,6 +182,8 @@ #define ID "%d" #define Int_MAX INT_MAX +#define UnsignedInt unsigned int + #define AMD_order amd_order #define AMD_defaults amd_defaults #define AMD_control amd_control diff --git a/ThirdParty/SuiteSparse/AMD/Source/amd_2.c b/ThirdParty/SuiteSparse/AMD/Source/amd_2.c index f144722cd9..85557090bc 100644 --- a/ThirdParty/SuiteSparse/AMD/Source/amd_2.c +++ b/ThirdParty/SuiteSparse/AMD/Source/amd_2.c @@ -462,7 +462,7 @@ GLOBAL void AMD_2 nvi, nvj, nvpiv, slenme, wbig, we, wflg, wnvi, ok, ndense, ncmpa, dense, aggressive ; - unsigned Int hash ; /* unsigned, so that hash % n is well defined.*/ + UnsignedInt hash ; /* unsigned, so that hash % n is well defined.*/ /* * deg: the degree of a variable or element diff --git a/ThirdParty/SuiteSparse/CAMD/Include/camd_internal.h b/ThirdParty/SuiteSparse/CAMD/Include/camd_internal.h index 92407c8080..fddae6e96a 100644 --- a/ThirdParty/SuiteSparse/CAMD/Include/camd_internal.h +++ b/ThirdParty/SuiteSparse/CAMD/Include/camd_internal.h @@ -160,6 +160,8 @@ #define ID SuiteSparse_long_id #define Int_MAX SuiteSparse_long_max +#define UnsignedInt SuiteSparse_unsigned_long + #define CAMD_order camd_l_order #define CAMD_defaults camd_l_defaults #define CAMD_control camd_l_control @@ -182,6 +184,8 @@ #define ID "%d" #define Int_MAX INT_MAX +#define UnsignedInt unsigned int + #define CAMD_order camd_order #define CAMD_defaults camd_defaults #define CAMD_control camd_control diff --git a/ThirdParty/SuiteSparse/CAMD/Source/camd_2.c b/ThirdParty/SuiteSparse/CAMD/Source/camd_2.c index ac8d63691e..f19050d47f 100644 --- a/ThirdParty/SuiteSparse/CAMD/Source/camd_2.c +++ b/ThirdParty/SuiteSparse/CAMD/Source/camd_2.c @@ -504,7 +504,7 @@ GLOBAL void CAMD_2 nvi, nvj, nvpiv, slenme, wbig, we, wflg, wnvi, ok, ndense, ncmpa, nnull, dense, aggressive ; - unsigned Int hash ; /* unsigned, so that hash % n is well defined.*/ + UnsignedInt hash ; /* unsigned, so that hash % n is well defined.*/ /* * deg: the degree of a variable or element diff --git a/ThirdParty/SuiteSparse/COLAMD/Source/colamd.c b/ThirdParty/SuiteSparse/COLAMD/Source/colamd.c index 8d56c389b5..bb5b0ac16e 100644 --- a/ThirdParty/SuiteSparse/COLAMD/Source/colamd.c +++ b/ThirdParty/SuiteSparse/COLAMD/Source/colamd.c @@ -663,6 +663,8 @@ #define ID SuiteSparse_long_id #define Int_MAX SuiteSparse_long_max +#define UnsignedInt SuiteSparse_unsigned_long + #define COLAMD_recommended colamd_l_recommended #define COLAMD_set_defaults colamd_l_set_defaults #define COLAMD_MAIN colamd_l @@ -676,6 +678,8 @@ #define ID "%d" #define Int_MAX INT_MAX +#define UnsignedInt unsigned int + #define COLAMD_recommended colamd_recommended #define COLAMD_set_defaults colamd_set_defaults #define COLAMD_MAIN colamd @@ -2193,7 +2197,7 @@ PRIVATE Int find_ordering /* return the number of garbage collections */ Int col ; /* a column index */ Int max_score ; /* maximum possible score */ Int cur_score ; /* score of current column */ - unsigned Int hash ; /* hash value for supernode detection */ + UnsignedInt hash ; /* hash value for supernode detection */ Int head_column ; /* head of hash bucket */ Int first_col ; /* first column in hash bucket */ Int tag_mark ; /* marker value for mark array */ diff --git a/ThirdParty/SuiteSparse/SuiteSparse_config/SuiteSparse_config.h b/ThirdParty/SuiteSparse/SuiteSparse_config/SuiteSparse_config.h index 9e28c0530b..1698a79621 100644 --- a/ThirdParty/SuiteSparse/SuiteSparse_config/SuiteSparse_config.h +++ b/ThirdParty/SuiteSparse/SuiteSparse_config/SuiteSparse_config.h @@ -44,6 +44,7 @@ extern "C" { #include #include +#include /* ========================================================================== */ /* === SuiteSparse_long ===================================================== */ @@ -59,12 +60,13 @@ extern "C" { #else -#define SuiteSparse_long long -#define SuiteSparse_long_max LONG_MAX -#define SuiteSparse_long_idd "ld" +#define SuiteSparse_long int64_t +#define SuiteSparse_long_max INT64_MAX +#define SuiteSparse_long_idd PRId64 #endif #define SuiteSparse_long_id "%" SuiteSparse_long_idd +#define SuiteSparse_unsigned_long uint64_t #endif /* ========================================================================== */ diff --git a/ThirdParty/SuiteSparse/include/SuiteSparse_config.h b/ThirdParty/SuiteSparse/include/SuiteSparse_config.h index 9e28c0530b..1698a79621 100644 --- a/ThirdParty/SuiteSparse/include/SuiteSparse_config.h +++ b/ThirdParty/SuiteSparse/include/SuiteSparse_config.h @@ -44,6 +44,7 @@ extern "C" { #include #include +#include /* ========================================================================== */ /* === SuiteSparse_long ===================================================== */ @@ -59,12 +60,13 @@ extern "C" { #else -#define SuiteSparse_long long -#define SuiteSparse_long_max LONG_MAX -#define SuiteSparse_long_idd "ld" +#define SuiteSparse_long int64_t +#define SuiteSparse_long_max INT64_MAX +#define SuiteSparse_long_idd PRId64 #endif #define SuiteSparse_long_id "%" SuiteSparse_long_idd +#define SuiteSparse_unsigned_long uint64_t #endif /* ========================================================================== */ diff --git a/ThirdParty/sundials/src/sunlinsol/klu/sunlinsol_klu.c b/ThirdParty/sundials/src/sunlinsol/klu/sunlinsol_klu.c index 7f37803da3..51dc2334c6 100644 --- a/ThirdParty/sundials/src/sunlinsol/klu/sunlinsol_klu.c +++ b/ThirdParty/sundials/src/sunlinsol/klu/sunlinsol_klu.c @@ -48,9 +48,9 @@ */ #if defined(SUNDIALS_INT64_T) -#define KLU_INDEXTYPE long int +#define KLU_INDEXTYPE int64_t #else -#define KLU_INDEXTYPE int +#define KLU_INDEXTYPE int32_t #endif /* @@ -331,31 +331,31 @@ int SUNLinSolSetup_KLU(SUNLinearSolver S, SUNMatrix A) if ( COMMON(S).rcond < uround_twothirds ) { /* Condition number may be getting large. - Compute more accurate estimate */ + Compute more accurate estimate */ retval = sun_klu_condest((KLU_INDEXTYPE*) SUNSparseMatrix_IndexPointers(A), SUNSparseMatrix_Data(A), SYMBOLIC(S), NUMERIC(S), &COMMON(S)); if (retval == 0) { - LASTFLAG(S) = SUNLS_PACKAGE_FAIL_REC; + LASTFLAG(S) = SUNLS_PACKAGE_FAIL_REC; return(LASTFLAG(S)); } if ( COMMON(S).condest > (ONE/uround_twothirds) ) { - /* More accurate estimate also says condition number is - large, so recompute the numeric factorization */ - sun_klu_free_numeric(&NUMERIC(S), &COMMON(S)); - NUMERIC(S) = sun_klu_factor((KLU_INDEXTYPE*) SUNSparseMatrix_IndexPointers(A), + /* More accurate estimate also says condition number is + large, so recompute the numeric factorization */ + sun_klu_free_numeric(&NUMERIC(S), &COMMON(S)); + NUMERIC(S) = sun_klu_factor((KLU_INDEXTYPE*) SUNSparseMatrix_IndexPointers(A), (KLU_INDEXTYPE*) SUNSparseMatrix_IndexValues(A), SUNSparseMatrix_Data(A), SYMBOLIC(S), &COMMON(S)); - if (NUMERIC(S) == NULL) { - LASTFLAG(S) = SUNLS_PACKAGE_FAIL_UNREC; + if (NUMERIC(S) == NULL) { + LASTFLAG(S) = SUNLS_PACKAGE_FAIL_UNREC; return(LASTFLAG(S)); - } + } } } From af0cf5f83c471bb765bc1757a3295f637ff07c37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Fri, 18 Mar 2022 11:21:16 -0400 Subject: [PATCH 06/31] Check condition number when computing sensitivities via newton (#1730) * add implementation for sparse solver * add warning if matrix is singular * add dense implementation * use SUNLS_SUCCESS * Update src/newton_solver.cpp Co-authored-by: Daniel Weindl * fixup * Update newton_solver.cpp * use reinterpret after all * Make sundials' KLU_INDEXTYPE match actual klu index type This is currently not the case on Windows * Make SuiteSparse use 64bit on OSX, matching sundials * Update SuiteSparse build script * fixup * .. * .. * .. * fix format Co-authored-by: Daniel Weindl Co-authored-by: Daniel Weindl --- include/amici/newton_solver.h | 12 ++++++++ src/newton_solver.cpp | 53 +++++++++++++++++++++++++++++------ 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/include/amici/newton_solver.h b/include/amici/newton_solver.h index 9dd3c8a92f..0c8bd743d3 100644 --- a/include/amici/newton_solver.h +++ b/include/amici/newton_solver.h @@ -97,6 +97,14 @@ class NewtonSolver { * overwritten by solution to the linear system */ virtual void solveLinearSystem(AmiVector &rhs) = 0; + + /** + * @brief Checks whether linear system is singular + * + * @return boolean indicating whether the linear system is singular + * (condition number < 1/machine precision) + */ + virtual bool is_singular() const = 0; virtual ~NewtonSolver() = default; @@ -185,6 +193,8 @@ class NewtonSolverDense : public NewtonSolver { * @param nnewt integer number of current Newton step */ void prepareLinearSystemB(int ntry, int nnewt) override; + + bool is_singular() const override; private: /** temporary storage of Jacobian */ @@ -245,6 +255,8 @@ class NewtonSolverSparse : public NewtonSolver { * @param nnewt integer number of current Newton step */ void prepareLinearSystemB(int ntry, int nnewt) override; + + bool is_singular() const override; private: /** temporary storage of Jacobian */ diff --git a/src/newton_solver.cpp b/src/newton_solver.cpp index f10006ff92..1d16edf650 100644 --- a/src/newton_solver.cpp +++ b/src/newton_solver.cpp @@ -5,6 +5,7 @@ #include "sunlinsol/sunlinsol_klu.h" // sparse solver #include "sunlinsol/sunlinsol_dense.h" // dense solver +#include // roundoffs #include #include @@ -89,6 +90,10 @@ void NewtonSolver::computeNewtonSensis(AmiVectorArray &sx) { prepareLinearSystem(0, -1); model_->fdxdotdp(*t_, *x_, dx_); + if (is_singular()) + model_->app->warningF("AMICI:newton", + "Jacobian is singular at steadystate, sensitivities may be inaccurate"); + if (model_->pythonGenerated) { for (int ip = 0; ip < model_->nplist(); ip++) { N_VConst(0.0, sx.getNVector(ip)); @@ -117,7 +122,7 @@ NewtonSolverDense::NewtonSolverDense(realtype *t, AmiVector *x, Model *model) : NewtonSolver(t, x, model), Jtmp_(model->nx_solver, model->nx_solver), linsol_(SUNLinSol_Dense(x->getNVector(), Jtmp_.get())) { int status = SUNLinSolInitialize_Dense(linsol_); - if(status != AMICI_SUCCESS) + if(status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolInitialize_Dense"); } @@ -127,7 +132,7 @@ void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { model_->fJ(*t_, 0.0, *x_, dx_, xdot_, Jtmp_.get()); Jtmp_.refresh(); int status = SUNLinSolSetup_Dense(linsol_, Jtmp_.get()); - if(status != AMICI_SUCCESS) + if(status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_Dense"); } @@ -137,7 +142,7 @@ void NewtonSolverDense::prepareLinearSystemB(int /*ntry*/, int /*nnewt*/) { model_->fJB(*t_, 0.0, *x_, dx_, xB_, dxB_, xdot_, Jtmp_.get()); Jtmp_.refresh(); int status = SUNLinSolSetup_Dense(linsol_, Jtmp_.get()); - if(status != AMICI_SUCCESS) + if(status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_Dense"); } @@ -151,12 +156,21 @@ void NewtonSolverDense::solveLinearSystem(AmiVector &rhs) { Jtmp_.refresh(); // last argument is tolerance and does not have any influence on result - if(status != AMICI_SUCCESS) + if(status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSolve_Dense"); } /* ------------------------------------------------------------------------- */ +bool NewtonSolverDense::is_singular() const { + // dense solver doesn't have any implementation for rcond/condest, so use + // sparse solver interface, not the most efficient solution, but who is + // concerned about speed and used the dense solver anyways ¯\_(ツ)_/¯ + NewtonSolverSparse sparse_solver(t_, x_, model_); + sparse_solver.prepareLinearSystem(0, 0); + return sparse_solver.is_singular(); +} + NewtonSolverDense::~NewtonSolverDense() { if(linsol_) SUNLinSolFree_Dense(linsol_); @@ -172,7 +186,7 @@ NewtonSolverSparse::NewtonSolverSparse(realtype *t, AmiVector *x, Model *model) Jtmp_(model->nx_solver, model->nx_solver, model->nnz, CSC_MAT), linsol_(SUNKLU(x->getNVector(), Jtmp_.get())) { int status = SUNLinSolInitialize_KLU(linsol_); - if(status != AMICI_SUCCESS) + if(status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolInitialize_KLU"); } @@ -183,7 +197,7 @@ void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { model_->fJSparse(*t_, 0.0, *x_, dx_, xdot_, Jtmp_.get()); Jtmp_.refresh(); int status = SUNLinSolSetup_KLU(linsol_, Jtmp_.get()); - if(status != AMICI_SUCCESS) + if(status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_KLU"); } @@ -194,7 +208,7 @@ void NewtonSolverSparse::prepareLinearSystemB(int /*ntry*/, int /*nnewt*/) { model_->fJSparseB(*t_, 0.0, *x_, dx_, xB_, dxB_, xdot_, Jtmp_.get()); Jtmp_.refresh(); int status = SUNLinSolSetup_KLU(linsol_, Jtmp_.get()); - if(status != AMICI_SUCCESS) + if(status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_KLU"); } @@ -206,12 +220,35 @@ void NewtonSolverSparse::solveLinearSystem(AmiVector &rhs) { rhs.getNVector(), rhs.getNVector(), 0.0); // last argument is tolerance and does not have any influence on result - if(status != AMICI_SUCCESS) + if(status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSolve_KLU"); } /* ------------------------------------------------------------------------- */ +bool NewtonSolverSparse::is_singular() const { + // adapted from SUNLinSolSetup_KLU in sunlinsol/klu/sunlinsol_klu.c + auto content = (SUNLinearSolverContent_KLU)(linsol_->content); + // first cheap check via rcond + auto status = sun_klu_rcond(content->symbolic, content->numeric, + &content->common); + if(status == 0) + throw NewtonFailure(content->last_flag, "sun_klu_rcond"); + + auto precision = std::numeric_limits::epsilon(); + + if (content->common.rcond < precision) { + // cheap check indicates singular, expensive check via condest + status = sun_klu_condest(SM_INDEXPTRS_S(Jtmp_.get()), + SM_DATA_S(Jtmp_.get()), content->symbolic, content->numeric, + &content->common); + if(status == 0) + throw NewtonFailure(content->last_flag, "sun_klu_rcond"); + return content->common.condest > 1.0/precision; + } + return false; +} + NewtonSolverSparse::~NewtonSolverSparse() { if(linsol_) SUNLinSolFree_KLU(linsol_); From 93798eb668c696c829ab4b466478b324073264ec Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 18 Mar 2022 20:52:29 +0100 Subject: [PATCH 07/31] Fix Model::setStateIsNonNegative (#1736) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix Model::setStateIsNonNegative Fixes a logic error when handling all-false input for models with conservation laws. And adds at test. * Update src/model.cpp Co-authored-by: Fabian Fröhlich Co-authored-by: Fabian Fröhlich --- .../tests/test_compare_conservation_laws_sbml.py | 15 ++++++++++++--- src/model.cpp | 14 +++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/python/tests/test_compare_conservation_laws_sbml.py b/python/tests/test_compare_conservation_laws_sbml.py index 1ca270fb77..2c760ae014 100644 --- a/python/tests/test_compare_conservation_laws_sbml.py +++ b/python/tests/test_compare_conservation_laws_sbml.py @@ -33,7 +33,8 @@ def edata_fixture(): return edata_pre, edata_post, edata_full -def generate_models(): +@pytest.fixture(scope="session") +def models(): # SBML model we want to import sbml_file = os.path.join(os.path.dirname(__file__), '..', 'examples', 'example_constant_species', @@ -100,9 +101,9 @@ def get_results(model, edata=None, sensi_order=0, return amici.runAmiciSimulation(model, solver, edata) -def test_compare_conservation_laws_sbml(edata_fixture): +def test_compare_conservation_laws_sbml(models, edata_fixture): # first, create the model - model_with_cl, model_without_cl = generate_models() + model_with_cl, model_without_cl = models assert model_with_cl.ncl() > 0 assert model_without_cl.nx_rdata == model_with_cl.nx_rdata @@ -217,3 +218,11 @@ def test_adjoint_pre_and_post_equilibration(edata_fixture): # assert gradients are close (quadrature tolerances are laxer) assert np.isclose(raa_cl['sllh'], raa['sllh'], 1e-5, 1e-5).all() + + +def test_get_set_model_settings(models): + """test amici.(get|set)_model_settings cycles for models with and without + conservation laws""" + + for model in models: + amici.set_model_settings(model, amici.get_model_settings(model)) diff --git a/src/model.cpp b/src/model.cpp index f195bbd80a..1edc211dde 100644 --- a/src/model.cpp +++ b/src/model.cpp @@ -617,11 +617,15 @@ std::vector const &Model::getStateIsNonNegative() const { void Model::setStateIsNonNegative(std::vector const &nonNegative) { auto any_state_non_negative = std::any_of(nonNegative.begin(), nonNegative.end(), - [](bool x) { return x; }); - - if (any_state_non_negative && nx_solver != nx_rdata) { - throw AmiException("Non-negative states are not supported with" - " conservation laws enabled"); + [](bool x) { + return x; }); + if (nx_solver != nx_rdata) { + if(any_state_non_negative) + throw AmiException("Non-negative states are not supported with" + " conservation laws enabled."); + // nothing to do, as `state_is_non_negative_` will always be all-false + // in case of conservation laws + return; } if (state_is_non_negative_.size() != static_cast(nx_rdata)) { throw AmiException("Dimension of input stateIsNonNegative (%u) does " From f5e525883c80c0797d83d39bb3cec21d91fb9a1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Fri, 18 Mar 2022 20:03:50 -0400 Subject: [PATCH 08/31] Cleanup steadystate (#1732) * remove xdot_old_ * Update steadystateproblem.cpp * make newton solver a class attribute * Update include/amici/steadystateproblem.h Co-authored-by: Daniel Weindl * replace x_, sx_ and t_ by respective state members * add state initializzation, more cleanup * clang format * c++14 * cleanup * fixups * cleanup * fixup * cleanup & more model updating * remove model as attribute from newton solver * cleanup newton solver * cleanup & add reinitialization * fix doc * fix dims * Update include/amici/steadystateproblem.h Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> * fixup & remove numlinsteps * fixup * fix matlab * cleanup test hdf5 * fix doc * fixup * fixup merge * fix doc add more const * fix code smells and nonnegativity check * fixup * revert expected results, fixup * fixup * clang format * Update src/newton_solver.cpp Co-authored-by: Daniel Weindl * fixup * fix matlab Co-authored-by: Daniel Weindl Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> --- include/amici/newton_solver.h | 199 +++----- include/amici/rdata.h | 14 - include/amici/serialization.h | 3 - include/amici/solver.h | 20 +- include/amici/steadystateproblem.h | 367 +++++++------- matlab/@amioption/amioption.m | 8 - python/amici/numpy.py | 11 +- scripts/run-cpp-tests.sh | 2 +- src/hdf5.cpp | 20 - src/interface_matlab.cpp | 4 - src/newton_solver.cpp | 211 ++++---- src/rdata.cpp | 8 - src/returndata_matlab.cpp | 12 +- src/solver.cpp | 13 - src/solver_cvodes.cpp | 3 + src/solver_idas.cpp | 3 + src/steadystateproblem.cpp | 592 +++++++++++----------- tests/cpp/expectedResults.h5 | Bin 4198368 -> 4198368 bytes tests/cpp/testOptions.h5 | Bin 345728 -> 339328 bytes tests/cpp/unittests/testMisc.cpp | 4 - tests/cpp/unittests/testSerialization.cpp | 3 - tests/generateTestConfig/example.py | 1 - 22 files changed, 645 insertions(+), 853 deletions(-) diff --git a/include/amici/newton_solver.h b/include/amici/newton_solver.h index 0c8bd743d3..ac937b6009 100644 --- a/include/amici/newton_solver.h +++ b/include/amici/newton_solver.h @@ -1,10 +1,11 @@ #ifndef amici_newton_solver_h #define amici_newton_solver_h -#include "amici/vector.h" #include "amici/defines.h" -#include "amici/sundials_matrix_wrapper.h" +#include "amici/solver.h" #include "amici/sundials_linsol_wrapper.h" +#include "amici/sundials_matrix_wrapper.h" +#include "amici/vector.h" #include @@ -23,25 +24,22 @@ class NewtonSolver { public: /** - * @brief Initializes all members with the provided objects + * @brief Initializes solver according to the dimensions in the provided + * model * - * @param t pointer to time variable - * @param x pointer to state variables * @param model pointer to the model object */ - NewtonSolver(realtype *t, AmiVector *x, Model *model); + explicit NewtonSolver(const Model *model); /** * @brief Factory method to create a NewtonSolver based on linsolType * - * @param t pointer to time variable - * @param x pointer to state variables * @param simulationSolver solver with settings - * @param model pointer to the model object + * @param model pointer to the model instance * @return solver NewtonSolver according to the specified linsolType */ - static std::unique_ptr getSolver( - realtype *t, AmiVector *x, Solver &simulationSolver, Model *model); + static std::unique_ptr + getSolver(const Solver &simulationSolver, const Model *model); /** * @brief Computes the solution of one Newton iteration @@ -51,24 +49,21 @@ class NewtonSolver { * @param nnewt integer number of current Newton step * @param delta containing the RHS of the linear system, will be * overwritten by solution to the linear system + * @param model pointer to the model instance + * @param state current simulation state */ - void getStep(int ntry, int nnewt, AmiVector &delta); + void getStep(int ntry, int nnewt, AmiVector &delta, Model *model, + const SimulationState &state); /** * @brief Computes steady state sensitivities * * @param sx pointer to state variable sensitivities + * @param model pointer to the model instance + * @param state current simulation state */ - void computeNewtonSensis(AmiVectorArray &sx); - - /** - * @brief Accessor for numlinsteps - * - * @return numlinsteps - */ - const std::vector &getNumLinSteps() const { - return num_lin_steps_; - } + void computeNewtonSensis(AmiVectorArray &sx, Model *model, + const SimulationState &state); /** * @brief Writes the Jacobian for the Newton iteration and passes it to the @@ -77,18 +72,24 @@ class NewtonSolver { * @param ntry integer newton_try integer start number of Newton solver * (1 or 2) * @param nnewt integer number of current Newton step + * @param model pointer to the model instance + * @param state current simulation state */ - virtual void prepareLinearSystem(int ntry, int nnewt) = 0; + virtual void prepareLinearSystem(int ntry, int nnewt, Model *model, + const SimulationState &state) = 0; /** - * Writes the Jacobian (JB) for the Newton iteration and passes it to the linear - * solver + * Writes the Jacobian (JB) for the Newton iteration and passes it to the + * linear solver * * @param ntry integer newton_try integer start number of Newton solver * (1 or 2) * @param nnewt integer number of current Newton step + * @param model pointer to the model instance + * @param state current simulation state */ - virtual void prepareLinearSystemB(int ntry, int nnewt) = 0; + virtual void prepareLinearSystemB(int ntry, int nnewt, Model *model, + const SimulationState &state) = 0; /** * @brief Solves the linear system for the Newton step @@ -97,47 +98,35 @@ class NewtonSolver { * overwritten by solution to the linear system */ virtual void solveLinearSystem(AmiVector &rhs) = 0; - + + /** + * @brief Reinitialize the linear solver + * + */ + virtual void reinitialize() = 0; + /** * @brief Checks whether linear system is singular * + * @param model pointer to the model instance + * @param state current simulation state * @return boolean indicating whether the linear system is singular * (condition number < 1/machine precision) */ - virtual bool is_singular() const = 0; + virtual bool is_singular(Model *model, + const SimulationState &state) const = 0; virtual ~NewtonSolver() = default; - /** maximum number of allowed linear steps per Newton step for steady state - * computation */ - int max_lin_steps_ {0}; - /** maximum number of allowed Newton steps for steady state computation */ - int max_steps {0}; - /** absolute tolerance */ - realtype atol_ {1e-16}; - /** relative tolerance */ - realtype rtol_ {1e-8}; - /** damping factor flag */ - NewtonDampingFactorMode damping_factor_mode_ {NewtonDampingFactorMode::on}; - /** damping factor lower bound */ - realtype damping_factor_lower_bound {1e-8}; - protected: - /** time variable */ - realtype *t_; - /** pointer to the model object */ - Model *model_; - /** right hand side AmiVector */ + /** dummy rhs, used as dummy argument when computing J and JB */ AmiVector xdot_; - /** current state */ - AmiVector *x_; - /** current state time derivative (DAE) */ - AmiVector dx_; - /** history of number of linear steps */ - std::vector num_lin_steps_; - /** current adjoint state */ + /** dummy state, attached to linear solver */ + AmiVector x_; + /** dummy adjoint state, used as dummy argument when computing JB */ AmiVector xB_; - /** current adjoint state time derivative (DAE) */ + /** dummy differential adjoint state, used as dummy argument when computing + * JB */ AmiVector dxB_; }; @@ -150,58 +139,36 @@ class NewtonSolverDense : public NewtonSolver { public: /** - * @brief Constructor, initializes all members with the provided objects - * and initializes temporary storage objects + * @brief constructor for sparse solver * - * @param t pointer to time variable - * @param x pointer to state variables - * @param model pointer to the model object + * @param model model instance that provides problem dimensions */ + explicit NewtonSolverDense(const Model *model); - NewtonSolverDense(realtype *t, AmiVector *x, Model *model); - - NewtonSolverDense(const NewtonSolverDense&) = delete; + NewtonSolverDense(const NewtonSolverDense &) = delete; - NewtonSolverDense& operator=(const NewtonSolverDense& other) = delete; + NewtonSolverDense &operator=(const NewtonSolverDense &other) = delete; ~NewtonSolverDense() override; - /** - * @brief Solves the linear system for the Newton step - * - * @param rhs containing the RHS of the linear system, will be - * overwritten by solution to the linear system - */ void solveLinearSystem(AmiVector &rhs) override; - /** - * @brief Writes the Jacobian for the Newton iteration and passes it to the - * linear solver - * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step - */ - void prepareLinearSystem(int ntry, int nnewt) override; + void prepareLinearSystem(int ntry, int nnewt, Model *model, + const SimulationState &state) override; - /** - * Writes the Jacobian (JB) for the Newton iteration and passes it to the linear - * solver - * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step - */ - void prepareLinearSystemB(int ntry, int nnewt) override; - - bool is_singular() const override; + void prepareLinearSystemB(int ntry, int nnewt, Model *model, + const SimulationState &state) override; + + void reinitialize() override; + + bool is_singular(Model *model, const SimulationState &state) const override; private: /** temporary storage of Jacobian */ SUNMatrixWrapper Jtmp_; /** dense linear solver */ - SUNLinearSolver linsol_ {nullptr}; + SUNLinearSolver linsol_{nullptr}; }; /** @@ -213,60 +180,38 @@ class NewtonSolverSparse : public NewtonSolver { public: /** - * @brief Constructor, initializes all members with the provided objects, - * initializes temporary storage objects and the klu solver + * @brief constructor for dense solver * - * @param t pointer to time variable - * @param x pointer to state variables - * @param model pointer to the model object + * @param model model instance that provides problem dimensions */ - NewtonSolverSparse(realtype *t, AmiVector *x, Model *model); + explicit NewtonSolverSparse(const Model *model); - NewtonSolverSparse(const NewtonSolverSparse&) = delete; + NewtonSolverSparse(const NewtonSolverSparse &) = delete; - NewtonSolverSparse& operator=(const NewtonSolverSparse& other) = delete; + NewtonSolverSparse &operator=(const NewtonSolverSparse &other) = delete; ~NewtonSolverSparse() override; - /** - * @brief Solves the linear system for the Newton step - * - * @param rhs containing the RHS of the linear system, will be - * overwritten by solution to the linear system - */ void solveLinearSystem(AmiVector &rhs) override; - /** - * @brief Writes the Jacobian for the Newton iteration and passes it to the - * linear solver - * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step - */ - void prepareLinearSystem(int ntry, int nnewt) override; + void prepareLinearSystem(int ntry, int nnewt, Model *model, + const SimulationState &state) override; - /** - * Writes the Jacobian (JB) for the Newton iteration and passes it to the linear - * solver - * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step - */ - void prepareLinearSystemB(int ntry, int nnewt) override; - - bool is_singular() const override; + void prepareLinearSystemB(int ntry, int nnewt, Model *model, + const SimulationState &state) override; + + bool is_singular(Model *model, const SimulationState &state) const override; + + void reinitialize() override; private: /** temporary storage of Jacobian */ SUNMatrixWrapper Jtmp_; /** sparse linear solver */ - SUNLinearSolver linsol_ {nullptr}; + SUNLinearSolver linsol_{nullptr}; }; - } // namespace amici #endif // NEWTON_SOLVER diff --git a/include/amici/rdata.h b/include/amici/rdata.h index f8b82875b3..a320418163 100644 --- a/include/amici/rdata.h +++ b/include/amici/rdata.h @@ -251,13 +251,6 @@ class ReturnData: public ModelDimensions { */ std::vector preeq_numsteps; - /** - * number of linear steps by Newton step for steady state problem. this - * will only be filled for iterative solvers (preequilibration) - * (shape `newton_maxsteps * 2`) - */ - std::vector preeq_numlinsteps; - /** * number of simulation steps for adjoint steady state problem * (preequilibration) [== 0 if analytical solution worked, > 0 otherwise] @@ -270,13 +263,6 @@ class ReturnData: public ModelDimensions { */ std::vector posteq_numsteps; - /** - * number of linear steps by Newton step for steady state problem. this - * will only be filled for iterative solvers (postequilibration) - * (shape `newton_maxsteps * 2`) - */ - std::vector posteq_numlinsteps; - /** * number of simulation steps for adjoint steady state problem * (postequilibration) [== 0 if analytical solution worked, > 0 otherwise] diff --git a/include/amici/serialization.h b/include/amici/serialization.h index b62f0a4ffa..d53e37a904 100644 --- a/include/amici/serialization.h +++ b/include/amici/serialization.h @@ -70,7 +70,6 @@ void serialize(Archive &ar, amici::Solver &s, const unsigned int /*version*/) { ar &s.maxstepsB_; ar &s.requires_preequilibration_; ar &s.newton_maxsteps_; - ar &s.newton_maxlinsteps_; ar &s.newton_damping_factor_mode_; ar &s.newton_damping_factor_lower_bound_; ar &s.ism_; @@ -211,14 +210,12 @@ void serialize(Archive &ar, amici::ReturnData &r, const unsigned int /*version*/ ar &r.preeq_cpu_timeB; ar &r.preeq_status; ar &r.preeq_numsteps; - ar &r.preeq_numlinsteps; ar &r.preeq_wrms; ar &r.preeq_t; ar &r.posteq_cpu_time; ar &r.posteq_cpu_timeB; ar &r.posteq_status; ar &r.posteq_numsteps; - ar &r.posteq_numlinsteps; ar &r.posteq_wrms; ar &r.posteq_t; ar &r.x0; diff --git a/include/amici/solver.h b/include/amici/solver.h index 3c39fd4789..ba1ced532e 100644 --- a/include/amici/solver.h +++ b/include/amici/solver.h @@ -243,20 +243,6 @@ class Solver { */ void setPreequilibration(bool require_preequilibration); - /** - * @brief Get maximum number of allowed linear steps per Newton step for - * steady state computation - * @return - */ - int getNewtonMaxLinearSteps() const; - - /** - * @brief Set maximum number of allowed linear steps per Newton step for - * steady state computation - * @param newton_maxlinsteps - */ - void setNewtonMaxLinearSteps(int newton_maxlinsteps); - /** * @brief Get a state of the damping factor used in the Newton solver * @return @@ -1677,6 +1663,9 @@ class Solver { /** flag to force reInitPostProcessB before next call to solveB */ mutable bool force_reinit_postprocess_B_ {false}; + + /** flag indicating whether sensInit1 was called */ + mutable bool sens_initialized_ {false}; private: @@ -1776,9 +1765,6 @@ class Solver { /** flag indicating whether init was called */ mutable bool initialized_ {false}; - /** flag indicating whether sensInit1 was called */ - mutable bool sens_initialized_ {false}; - /** flag indicating whether adjInit was called */ mutable bool adj_initialized_ {false}; diff --git a/include/amici/steadystateproblem.h b/include/amici/steadystateproblem.h index fc4dc5cfea..420d91b9b3 100644 --- a/include/amici/steadystateproblem.h +++ b/include/amici/steadystateproblem.h @@ -2,9 +2,9 @@ #define AMICI_STEADYSTATEPROBLEM_H #include "amici/defines.h" -#include "amici/vector.h" -#include "amici/solver_cvodes.h" #include "amici/forwardproblem.h" +#include "amici/solver_cvodes.h" +#include "amici/vector.h" #include #include @@ -29,8 +29,7 @@ class SteadystateProblem { * @param solver Solver instance * @param model Model instance */ - explicit SteadystateProblem(const Solver &solver, - const Model &model); + explicit SteadystateProblem(const Solver &solver, Model &model); /** * @brief Handles steady state computation in the forward case: @@ -42,7 +41,6 @@ class SteadystateProblem { */ void workSteadyStateProblem(Solver *solver, Model *model, int it); - /** * Integrates over the adjoint state backward in time by solving a linear * system of equations, which gives the analytical solution. @@ -54,27 +52,128 @@ class SteadystateProblem { void workSteadyStateBackwardProblem(Solver *solver, Model *model, const BackwardProblem *bwd); + /** + * @brief Returns the stored SimulationState + * @return stored SimulationState + */ + const SimulationState &getFinalSimulationState() const { return state_; }; + + /** + * @brief Returns the quadratures from pre- or postequilibration + * @return xQB Vector with quadratures + */ + const AmiVector &getEquilibrationQuadratures() const { return xQB_; } + /** + * @brief Returns state at steadystate + * @return x + */ + const AmiVector &getState() const { return state_.x; }; + + /** + * @brief Returns state sensitivity at steadystate + * @return sx + */ + const AmiVectorArray &getStateSensitivity() const { return state_.sx; }; + + /** + * @brief Accessor for dJydx + * @return dJydx + */ + std::vector const &getDJydx() const { return dJydx_; } + + /** + * @brief Accessor for run_time of the forward problem + * @return run_time + */ + double getCPUTime() const { return cpu_time_; } + + /** + * @brief Accessor for run_time of the backward problem + * @return run_time + */ + double getCPUTimeB() const { return cpu_timeB_; } + + /** + * @brief Accessor for steady_state_status + * @return steady_state_status + */ + std::vector const &getSteadyStateStatus() const { + return steady_state_status_; + } + + /** + * @brief Get model time at which steadystate was found through simulation + * @return t + */ + realtype getSteadyStateTime() const { return state_.t; } + + /** + * @brief Accessor for wrms + * @return wrms + */ + realtype getResidualNorm() const { return wrms_; } + + /** + * @brief Accessor for numsteps + * @return numsteps + */ + const std::vector &getNumSteps() const { return numsteps_; } + + /** + * @brief Accessor for numstepsB + * @return numstepsB + */ + int getNumStepsB() const { return numstepsB_; } + + /** + * @brief computes adjoint updates dJydx according to provided model and + * expdata + * @param model Model instance + * @param edata experimental data + */ + void getAdjointUpdates(Model &model, const ExpData &edata); + + /** + * @brief Return the adjoint state + * @return xB adjoint state + */ + AmiVector const &getAdjointState() const { return xB_; } + + /** + * @brief Accessor for xQB + * @return xQB + */ + AmiVector const &getAdjointQuadrature() const { return xQB_; } + + /** + * @brief Accessor for hasQuadrature_ + * @return hasQuadrature_ + */ + bool hasQuadrature() const { return hasQuadrature_; } + + /** + * @brief computes adjoint updates dJydx according to provided model and + * expdata + * @return convergence of steady state solver + */ + bool checkSteadyStateSuccess() const; + + private: /** * @brief Handles the computation of the steady state, throws an * AmiException, if no steady state was found * @param solver pointer to the solver object - * @param newtonSolver pointer to the newtonSolver solver object * @param model pointer to the model object * @param it integer with the index of the current time step */ - void findSteadyState(Solver *solver, - NewtonSolver *newtonSolver, - Model *model, int it); + void findSteadyState(const Solver *solver, Model *model, int it); /** * @brief Tries to determine the steady state by using Newton's method - * @param newtonSolver pointer to the newtonSolver solver object * @param model pointer to the model object * @param newton_retry bool flag indicating whether being relaunched */ - void findSteadyStateByNewtonsMethod(NewtonSolver *newtonSolver, - Model *model, - bool newton_retry); + void findSteadyStateByNewtonsMethod(Model *model, bool newton_retry); /** * @brief Tries to determine the steady state by using forward simulation @@ -82,8 +181,7 @@ class SteadystateProblem { * @param model pointer to the model object * @param it integer with the index of the current time step */ - void findSteadyStateBySimulation(const Solver *solver, - Model *model, + void findSteadyStateBySimulation(const Solver *solver, Model *model, int it); /** @@ -92,16 +190,14 @@ class SteadystateProblem { * @param solver pointer to the solver object * @param model pointer to the model object */ - void computeSteadyStateQuadrature(NewtonSolver *newtonSolver, - const Solver *solver, Model *model); + void computeSteadyStateQuadrature(const Solver *solver, Model *model); /** * @brief Computes the quadrature in steady state backward mode by * solving the linear system defined by the backward Jacobian - * @param newtonSolver pointer to the newtonSolver solver object * @param model pointer to the model object */ - void getQuadratureByLinSolve(NewtonSolver *newtonSolver, Model *model); + void getQuadratureByLinSolve(Model *model); /** * @brief Computes the quadrature in steady state backward mode by @@ -113,19 +209,16 @@ class SteadystateProblem { /** * @brief Stores state and throws an exception if equilibration failed - * @param solver pointer to the solver object - * @param model pointer to the model object */ - [[noreturn]] void handleSteadyStateFailure(const Solver *solver, - Model *model); + [[noreturn]] void handleSteadyStateFailure(); /** * @brief Assembles the error message to be thrown. * @param errorString const pointer to string with error message * @param status Entry of steady_state_status to be processed */ - void writeErrorString(std::string *errorString, SteadyStateStatus - status) const; + void writeErrorString(std::string *errorString, + SteadyStateStatus status) const; /** * @brief Checks depending on the status of the Newton solver, @@ -151,21 +244,18 @@ class SteadystateProblem { * @param ewt error weight vector * @return root-mean-square norm */ - realtype getWrmsNorm(AmiVector const &x, - AmiVector const &xdot, - realtype atol, - realtype rtol, - AmiVector &ewt) const; + realtype getWrmsNorm(AmiVector const &x, AmiVector const &xdot, + realtype atol, realtype rtol, AmiVector &ewt) const; /** - * @brief Checks convergence for state or adjoint quadratures, depending on sensi method + * @brief Checks convergence for state or adjoint quadratures, depending on + * sensi method * @param model Model instance * @param sensi_method sensitivity method * @return weighted root mean squared residuals of the RHS */ - realtype getWrms(Model *model, - SensitivityMethod sensi_method); - + realtype getWrms(Model *model, SensitivityMethod sensi_method); + /** * @brief Checks convergence for state sensitivities * @param model Model instance @@ -177,19 +267,19 @@ class SteadystateProblem { * @brief Runs the Newton solver iterations and checks for convergence * to steady state * @param model pointer to the model object - * @param newtonSolver pointer to the NewtonSolver object * @param newton_retry flag indicating if Newton solver is rerun */ - void applyNewtonsMethod(Model *model, NewtonSolver *newtonSolver, - bool newton_retry); + void applyNewtonsMethod(Model *model, bool newton_retry); /** - * @brief Simulation is launched, if Newton solver or linear system solve fails + * @brief Simulation is launched, if Newton solver or linear system solve + * fails * @param solver pointer to the solver object * @param model pointer to the model object * @param backward flag indicating adjoint mode (including quadrature) */ - void runSteadystateSimulation(const Solver *solver, Model *model, bool backward); + void runSteadystateSimulation(const Solver *solver, Model *model, + bool backward); /** * @brief Initialize CVodeSolver instance for preequilibration simulation @@ -205,8 +295,15 @@ class SteadystateProblem { bool backward) const; /** - * @brief Initialize backward computation by setting state, time, adjoint - * state and checking for preequilibration mode + * @brief Initialize forward computation + * @param it integer with the index of the current time step + * @param solver pointer to the solver object + * @param model pointer to the model object + */ + void initializeForwardProblem(int it, const Solver *solver, Model *model); + + /** + * @brief Initialize backward computation * @param solver pointer to the solver object * @param model pointer to the model object * @param bwd pointer to backward problem @@ -222,155 +319,37 @@ class SteadystateProblem { * @param yQ vector to be multiplied with dxdotdp * @param yQB resulting vector after multiplication */ - void computeQBfromQ(Model *model, const AmiVector &yQ, AmiVector &yQB) const; - - /** - * @brief Store carbon copy of current simulation state variables as SimulationState - * @param model model carrying the ModelState to be used - * @param storesensi flag to enable storage of sensitivities - */ - void storeSimulationState(Model *model, bool storesensi); - - /** - * @brief Returns the stored SimulationState - * @return stored SimulationState - */ - const SimulationState &getFinalSimulationState() const { - return state_; - }; - - /** - * @brief Returns the quadratures from pre- or postequilibration - * @return xQB Vector with quadratures - */ - const AmiVector &getEquilibrationQuadratures() const { - return xQB_; - } - /** - * @brief Returns state at steadystate - * @return x - */ - const AmiVector &getState() const { - return x_; - }; - - /** - * @brief Returns state sensitivity at steadystate - * @return sx - */ - const AmiVectorArray &getStateSensitivity() const { - return sx_; - }; - - /** - * @brief Accessor for dJydx - * @return dJydx - */ - std::vector const& getDJydx() const { - return dJydx_; - } - - /** - * @brief Accessor for run_time of the forward problem - * @return run_time - */ - double getCPUTime() const { return cpu_time_; } - - /** - * @brief Accessor for run_time of the backward problem - * @return run_time - */ - double getCPUTimeB() const { return cpu_timeB_; } - - /** - * @brief Accessor for steady_state_status - * @return steady_state_status - */ - std::vector const& getSteadyStateStatus() const - { return steady_state_status_; } - - /** - * @brief Accessor for t - * @return t - */ - realtype getSteadyStateTime() const { return t_; } + void computeQBfromQ(Model *model, const AmiVector &yQ, + AmiVector &yQB) const; /** - * @brief Accessor for wrms - * @return wrms - */ - realtype getResidualNorm() const { return wrms_; } - - /** - * @brief Accessor for numsteps - * @return numsteps - */ - const std::vector &getNumSteps() const { return numsteps_; } - - /** - * @brief Accessor for numstepsB - * @return numstepsB - */ - int getNumStepsB() const { return numstepsB_; } - - /** - * @brief Accessor for numlinsteps - * @return numlinsteps - */ - const std::vector &getNumLinSteps() const { return numlinsteps_; } - - /** - * @brief computes adjoint updates dJydx according to provided model and expdata - * @param model Model instance - * @param edata experimental data - */ - void getAdjointUpdates(Model &model, const ExpData &edata); - - /** - * @brief Return the adjoint state - * @return xB adjoint state - */ - AmiVector const& getAdjointState() const { return xB_; } - - /** - * @brief Accessor for xQB - * @return xQB - */ - AmiVector const& getAdjointQuadrature() const { return xQB_; } - - /** - * @brief Accessor for hasQuadrature_ - * @return hasQuadrature_ + * @brief ensures state positivity, if requested and repeats convergence + * check, if necessary + * @param model pointer to the model object */ - bool hasQuadrature() const { return hasQuadrature_; } + bool makePositiveAndCheckConvergence(Model *model); /** - * @brief computes adjoint updates dJydx according to provided model and expdata - * @return convergence of steady state solver + * @brief updates the damping factor gamma that determines step size + * + * @param step_successful flag indicating whether the previous step was + * successful + * @return boolean flag indicating whether search direction should be + * updated (true) or the same direction should be retried with the updated + * dampening (false) */ - bool checkSteadyStateSuccess() const; + bool updateDampingFactor(bool step_successful); - private: - /** time variable for simulation steadystate finding */ - realtype t_; /** newton step */ AmiVector delta_; /** error weights for solver state, dimension nx_solver */ AmiVector ewt_; /** error weights for backward quadratures, dimension nplist() */ AmiVector ewtQB_; - /** state vector */ - AmiVector x_; /** old state vector */ AmiVector x_old_; - /** differential state vector */ - AmiVector dx_; /** time derivative state vector */ AmiVector xdot_; - /** old time derivative state vector */ - AmiVector xdot_old_; - /** state sensitivities */ - AmiVectorArray sx_; /** state differential sensitivities */ AmiVectorArray sdx_; /** adjoint state vector */ @@ -383,10 +362,10 @@ class SteadystateProblem { AmiVector xQBdot_; /** maximum number of steps for Newton solver for allocating numlinsteps */ - int max_steps_ {0}; + int max_steps_{0}; /** weighted root-mean-square error */ - realtype wrms_ {NAN}; + realtype wrms_{NAN}; /** state derivative of data likelihood * (dimension nJ x nx x nt, ordering =?) */ @@ -395,40 +374,48 @@ class SteadystateProblem { SimulationState state_; /** stores diagnostic information about employed number of steps */ - std::vector numsteps_ {std::vector(3, 0)}; - - /** stores diagnostic information about employed number of linear steps */ - std::vector numlinsteps_; + std::vector numsteps_{std::vector(3, 0)}; /** stores information about employed number of backward steps */ - int numstepsB_ {0}; + int numstepsB_{0}; /** stores diagnostic information about runtime */ - double cpu_time_ {0.0}; + double cpu_time_{0.0}; /** stores diagnostic information about runtime backward */ - double cpu_timeB_ {0.0}; + double cpu_timeB_{0.0}; /** flag indicating whether backward mode was run */ - bool hasQuadrature_ {false}; + bool hasQuadrature_{false}; + + /** stepsize for newton step */ + double gamma_{1.0}; /** stores diagnostic information about execution success of the different * approaches [newton, simulation, newton] (length = 3) */ std::vector steady_state_status_; - + /** absolute tolerance for convergence check (state)*/ - realtype atol_ {NAN}; + realtype atol_{NAN}; /** relative tolerance for convergence check (state)*/ - realtype rtol_ {NAN}; + realtype rtol_{NAN}; /** absolute tolerance for convergence check (state sensi)*/ - realtype atol_sensi_ {NAN}; + realtype atol_sensi_{NAN}; /** relative tolerance for convergence check (state sensi)*/ - realtype rtol_sensi_ {NAN}; + realtype rtol_sensi_{NAN}; /** absolute tolerance for convergence check (quadratures)*/ - realtype atol_quad_ {NAN}; + realtype atol_quad_{NAN}; /** relative tolerance for convergence check (quadratures)*/ - realtype rtol_quad_ {NAN}; + realtype rtol_quad_{NAN}; + + /** newton solver */ + std::unique_ptr newton_solver_{nullptr}; + + /** damping factor flag */ + NewtonDampingFactorMode damping_factor_mode_{NewtonDampingFactorMode::on}; + /** damping factor lower bound */ + realtype damping_factor_lower_bound_{1e-8}; }; } // namespace amici diff --git a/matlab/@amioption/amioption.m b/matlab/@amioption/amioption.m index 6dcfdaf2b5..3b4873fc95 100644 --- a/matlab/@amioption/amioption.m +++ b/matlab/@amioption/amioption.m @@ -57,8 +57,6 @@ sx0 = double.empty(); % newton solver: maximum newton steps newton_maxsteps = 40; - % newton solver: maximum linear steps - newton_maxlinsteps = 100; % preequilibration of system via newton solver newton_preeq = false; % mapping of event ouputs to events @@ -241,12 +239,6 @@ this.newton_maxsteps = value; end - function this = set.newton_maxlinsteps(this,value) - assert(isnumeric(value),'The option newton_maxlinsteps must have a numeric value!') - assert(floor(value)==value,'The option newton_maxlinsteps must be an integer!') - this.newton_maxlinsteps = value; - end - function this = set.newton_preeq(this,value) if(isnumeric(value)) assert(floor(value)==value,'The option newton_preeq must be a logical!') diff --git a/python/amici/numpy.py b/python/amici/numpy.py index a2c593df9c..1dde73aad5 100644 --- a/python/amici/numpy.py +++ b/python/amici/numpy.py @@ -151,12 +151,11 @@ 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', 'w', - 'preeq_wrms', 'preeq_t', 'preeq_numlinsteps', 'preeq_numsteps', - 'preeq_numstepsB', 'preeq_status', 'preeq_cpu_time', - 'preeq_cpu_timeB', 'posteq_wrms', 'posteq_t', 'posteq_numlinsteps', - 'posteq_numsteps', 'posteq_numstepsB', 'posteq_status', - 'posteq_cpu_time', 'posteq_cpu_timeB', 'numsteps', 'numrhsevals', + 'chi2', 'res', 'sres', 'FIM', 'w', 'preeq_wrms', 'preeq_t', + 'preeq_numsteps', 'preeq_numstepsB', 'preeq_status', 'preeq_cpu_time', + 'preeq_cpu_timeB', 'posteq_wrms', 'posteq_t', 'posteq_numsteps', + 'posteq_numstepsB', 'posteq_status', 'posteq_cpu_time', + 'posteq_cpu_timeB', 'numsteps', 'numrhsevals', 'numerrtestfails', 'numnonlinsolvconvfails', 'order', 'cpu_time', 'numstepsB', 'numrhsevalsB', 'numerrtestfailsB', 'numnonlinsolvconvfailsB', 'cpu_timeB' diff --git a/scripts/run-cpp-tests.sh b/scripts/run-cpp-tests.sh index 963ef3c51c..6afb96eaeb 100755 --- a/scripts/run-cpp-tests.sh +++ b/scripts/run-cpp-tests.sh @@ -13,7 +13,7 @@ fi # run tests cd "${AMICI_PATH}/build" -ctest -V +ctest -V --output-on-failure ret=$? if [[ $ret != 0 ]]; then exit $ret; fi mv "${AMICI_PATH}/tests/cpp/writeResults.h5" \ diff --git a/src/hdf5.cpp b/src/hdf5.cpp index 7248a5c963..4b486b06f2 100644 --- a/src/hdf5.cpp +++ b/src/hdf5.cpp @@ -399,11 +399,6 @@ void writeReturnDataDiagnosis(const ReturnData &rdata, createAndWriteInt1DDataset(file, hdf5Location + "/preeq_numsteps", rdata.preeq_numsteps); - if (!rdata.preeq_numlinsteps.empty()) - createAndWriteInt2DDataset(file, hdf5Location + "/preeq_numlinsteps", - rdata.preeq_numlinsteps, - rdata.newton_maxsteps, 2); - H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), "preeq_numstepsB", &rdata.preeq_numstepsB, 1); @@ -431,11 +426,6 @@ void writeReturnDataDiagnosis(const ReturnData &rdata, createAndWriteInt1DDataset(file, hdf5Location + "/posteq_numsteps", rdata.posteq_numsteps); - if (!rdata.posteq_numlinsteps.empty()) - createAndWriteInt2DDataset(file, hdf5Location + "/posteq_numlinsteps", - rdata.posteq_numlinsteps, - rdata.newton_maxsteps, 2); - H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), "posteq_numstepsB", &rdata.posteq_numstepsB, 1); @@ -735,10 +725,6 @@ void writeSolverSettingsToHDF5(Solver const& solver, H5LTset_attribute_double(file.getId(), hdf5Location.c_str(), "newton_damping_factor_lower_bound", &dbuffer, 1); - ibuffer = static_cast(solver.getNewtonMaxLinearSteps()); - H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), - "newton_maxlinsteps", &ibuffer, 1); - ibuffer = static_cast(solver.getLinearSolver()); H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), "linsol", &ibuffer, 1); @@ -901,12 +887,6 @@ void readSolverSettingsFromHDF5(H5::H5File const& file, Solver &solver, getDoubleScalarAttribute(file, datasetPath, "newton_damping_factor_lower_bound")); } - if(attributeExists(file, datasetPath, "newton_maxlinsteps")) { - solver.setNewtonMaxLinearSteps( - getIntScalarAttribute(file, datasetPath, - "newton_maxlinsteps")); - } - if(attributeExists(file, datasetPath, "linsol")) { solver.setLinearSolver( static_cast( diff --git a/src/interface_matlab.cpp b/src/interface_matlab.cpp index a4fff0834b..168af5461c 100644 --- a/src/interface_matlab.cpp +++ b/src/interface_matlab.cpp @@ -328,10 +328,6 @@ void setSolverOptions(const mxArray *prhs[], int nrhs, Solver &solver) if (mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_maxsteps")) { solver.setNewtonMaxSteps(dbl2int(mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_maxsteps")))); } - - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_maxlinsteps")) { - solver.setNewtonMaxLinearSteps(dbl2int(mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_maxlinsteps")))); - } } } diff --git a/src/newton_solver.cpp b/src/newton_solver.cpp index 1d16edf650..873aa7abc6 100644 --- a/src/newton_solver.cpp +++ b/src/newton_solver.cpp @@ -3,26 +3,22 @@ #include "amici/model.h" #include "amici/solver.h" -#include "sunlinsol/sunlinsol_klu.h" // sparse solver #include "sunlinsol/sunlinsol_dense.h" // dense solver -#include // roundoffs +#include "sunlinsol/sunlinsol_klu.h" // sparse solver +#include // roundoffs +#include #include #include -#include namespace amici { -NewtonSolver::NewtonSolver(realtype *t, AmiVector *x, Model *model) - : t_(t), model_(model), xdot_(model->nx_solver), x_(x), - dx_(model->nx_solver), xB_(model->nx_solver), dxB_(model->nx_solver) { -} +NewtonSolver::NewtonSolver(const Model *model) + : xdot_(model->nx_solver), x_(model->nx_solver), + xB_(model->nJ * model->nx_solver), dxB_(model->nJ * model->nx_solver) {} -/* ------------------------------------------------------------------------- */ - -std::unique_ptr NewtonSolver::getSolver(realtype *t, AmiVector *x, - Solver &simulationSolver, - Model *model) { +std::unique_ptr +NewtonSolver::getSolver(const Solver &simulationSolver, const Model *model) { std::unique_ptr solver; @@ -30,7 +26,7 @@ std::unique_ptr NewtonSolver::getSolver(realtype *t, AmiVector *x, /* DIRECT SOLVERS */ case LinearSolver::dense: - solver.reset(new NewtonSolverDense(t, x, model)); + solver.reset(new NewtonSolverDense(model)); break; case LinearSolver::band: @@ -59,180 +55,166 @@ std::unique_ptr NewtonSolver::getSolver(realtype *t, AmiVector *x, case LinearSolver::SuperLUMT: throw NewtonFailure(AMICI_NOT_IMPLEMENTED, "getSolver"); case LinearSolver::KLU: - solver.reset(new NewtonSolverSparse(t, x, model)); + solver.reset(new NewtonSolverSparse(model)); break; default: throw NewtonFailure(AMICI_NOT_IMPLEMENTED, "getSolver"); } - solver->atol_ = simulationSolver.getAbsoluteToleranceSteadyState(); - solver->rtol_ = simulationSolver.getRelativeToleranceSteadyState(); - solver->max_lin_steps_ = simulationSolver.getNewtonMaxLinearSteps(); - solver->max_steps = simulationSolver.getNewtonMaxSteps(); - solver->damping_factor_mode_ = simulationSolver.getNewtonDampingFactorMode(); - solver->damping_factor_lower_bound = - simulationSolver.getNewtonDampingFactorLowerBound(); - return solver; } -/* ------------------------------------------------------------------------- */ - -void NewtonSolver::getStep(int ntry, int nnewt, AmiVector &delta) { - prepareLinearSystem(ntry, nnewt); +void NewtonSolver::getStep(int ntry, int nnewt, AmiVector &delta, Model *model, + const SimulationState &state) { + prepareLinearSystem(ntry, nnewt, model, state); delta.minus(); solveLinearSystem(delta); } -/* ------------------------------------------------------------------------- */ - -void NewtonSolver::computeNewtonSensis(AmiVectorArray &sx) { - prepareLinearSystem(0, -1); - model_->fdxdotdp(*t_, *x_, dx_); +void NewtonSolver::computeNewtonSensis(AmiVectorArray &sx, Model *model, + const SimulationState &state) { + prepareLinearSystem(0, -1, model, state); + model->fdxdotdp(state.t, state.x, state.dx); - if (is_singular()) - model_->app->warningF("AMICI:newton", - "Jacobian is singular at steadystate, sensitivities may be inaccurate"); + if (is_singular(model, state)) + model->app->warningF("AMICI:newton", + "Jacobian is singular at steadystate, " + "sensitivities may be inaccurate"); - if (model_->pythonGenerated) { - for (int ip = 0; ip < model_->nplist(); ip++) { + if (model->pythonGenerated) { + for (int ip = 0; ip < model->nplist(); ip++) { N_VConst(0.0, sx.getNVector(ip)); - model_->get_dxdotdp_full().scatter(model_->plist(ip), -1.0, nullptr, - gsl::make_span(sx.getNVector(ip)), - 0, nullptr, 0); + model->get_dxdotdp_full().scatter(model->plist(ip), -1.0, nullptr, + gsl::make_span(sx.getNVector(ip)), + 0, nullptr, 0); solveLinearSystem(sx[ip]); } } else { - for (int ip = 0; ip < model_->nplist(); ip++) { - for (int ix = 0; ix < model_->nx_solver; ix++) - sx.at(ix,ip) = -model_->get_dxdotdp().at(ix, ip); + for (int ip = 0; ip < model->nplist(); ip++) { + for (int ix = 0; ix < model->nx_solver; ix++) + sx.at(ix, ip) = -model->get_dxdotdp().at(ix, ip); solveLinearSystem(sx[ip]); } } } -/* ------------------------------------------------------------------------- */ -/* - Dense linear solver --------------------------------------------------- */ -/* ------------------------------------------------------------------------- */ - -/* Derived class for dense linear solver */ -NewtonSolverDense::NewtonSolverDense(realtype *t, AmiVector *x, Model *model) - : NewtonSolver(t, x, model), Jtmp_(model->nx_solver, model->nx_solver), - linsol_(SUNLinSol_Dense(x->getNVector(), Jtmp_.get())) { - int status = SUNLinSolInitialize_Dense(linsol_); - if(status != SUNLS_SUCCESS) +NewtonSolverDense::NewtonSolverDense(const Model *model) + : NewtonSolver(model), Jtmp_(model->nx_solver, model->nx_solver), + linsol_(SUNLinSol_Dense(x_.getNVector(), Jtmp_.get())) { + auto status = SUNLinSolInitialize_Dense(linsol_); + if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolInitialize_Dense"); } -/* ------------------------------------------------------------------------- */ - -void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { - model_->fJ(*t_, 0.0, *x_, dx_, xdot_, Jtmp_.get()); +void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/, + Model *model, + const SimulationState &state) { + model->fJ(state.t, 0.0, state.x, state.dx, xdot_, Jtmp_.get()); Jtmp_.refresh(); - int status = SUNLinSolSetup_Dense(linsol_, Jtmp_.get()); - if(status != SUNLS_SUCCESS) + auto status = SUNLinSolSetup_Dense(linsol_, Jtmp_.get()); + if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_Dense"); } -/* ------------------------------------------------------------------------- */ - -void NewtonSolverDense::prepareLinearSystemB(int /*ntry*/, int /*nnewt*/) { - model_->fJB(*t_, 0.0, *x_, dx_, xB_, dxB_, xdot_, Jtmp_.get()); +void NewtonSolverDense::prepareLinearSystemB(int /*ntry*/, int /*nnewt*/, + Model *model, + const SimulationState &state) { + model->fJB(state.t, 0.0, state.x, state.dx, xB_, dxB_, xdot_, Jtmp_.get()); Jtmp_.refresh(); - int status = SUNLinSolSetup_Dense(linsol_, Jtmp_.get()); - if(status != SUNLS_SUCCESS) + auto status = SUNLinSolSetup_Dense(linsol_, Jtmp_.get()); + if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_Dense"); } - -/* ------------------------------------------------------------------------- */ - void NewtonSolverDense::solveLinearSystem(AmiVector &rhs) { - int status = SUNLinSolSolve_Dense(linsol_, Jtmp_.get(), - rhs.getNVector(), rhs.getNVector(), - 0.0); + auto status = SUNLinSolSolve_Dense(linsol_, Jtmp_.get(), rhs.getNVector(), + rhs.getNVector(), 0.0); Jtmp_.refresh(); // last argument is tolerance and does not have any influence on result - if(status != SUNLS_SUCCESS) + if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSolve_Dense"); } -/* ------------------------------------------------------------------------- */ +void NewtonSolverDense::reinitialize(){ + /* dense solver does not need reinitialization */ +}; -bool NewtonSolverDense::is_singular() const { +bool NewtonSolverDense::is_singular(Model *model, + const SimulationState &state) const { // dense solver doesn't have any implementation for rcond/condest, so use // sparse solver interface, not the most efficient solution, but who is // concerned about speed and used the dense solver anyways ¯\_(ツ)_/¯ - NewtonSolverSparse sparse_solver(t_, x_, model_); - sparse_solver.prepareLinearSystem(0, 0); - return sparse_solver.is_singular(); + NewtonSolverSparse sparse_solver(model); + sparse_solver.prepareLinearSystem(0, 0, model, state); + return sparse_solver.is_singular(model, state); } NewtonSolverDense::~NewtonSolverDense() { - if(linsol_) + if (linsol_) SUNLinSolFree_Dense(linsol_); } -/* ------------------------------------------------------------------------- */ -/* - Sparse linear solver -------------------------------------------------- */ -/* ------------------------------------------------------------------------- */ - -/* Derived class for sparse linear solver */ -NewtonSolverSparse::NewtonSolverSparse(realtype *t, AmiVector *x, Model *model) - : NewtonSolver(t, x, model), +NewtonSolverSparse::NewtonSolverSparse(const Model *model) + : NewtonSolver(model), Jtmp_(model->nx_solver, model->nx_solver, model->nnz, CSC_MAT), - linsol_(SUNKLU(x->getNVector(), Jtmp_.get())) { - int status = SUNLinSolInitialize_KLU(linsol_); - if(status != SUNLS_SUCCESS) + linsol_(SUNKLU(x_.getNVector(), Jtmp_.get())) { + auto status = SUNLinSolInitialize_KLU(linsol_); + if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolInitialize_KLU"); } -/* ------------------------------------------------------------------------- */ - -void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/) { +void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/, + Model *model, + const SimulationState &state) { /* Get sparse Jacobian */ - model_->fJSparse(*t_, 0.0, *x_, dx_, xdot_, Jtmp_.get()); + model->fJSparse(state.t, 0.0, state.x, state.dx, xdot_, Jtmp_.get()); Jtmp_.refresh(); - int status = SUNLinSolSetup_KLU(linsol_, Jtmp_.get()); - if(status != SUNLS_SUCCESS) + auto status = SUNLinSolSetup_KLU(linsol_, Jtmp_.get()); + if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_KLU"); } -/* ------------------------------------------------------------------------- */ - -void NewtonSolverSparse::prepareLinearSystemB(int /*ntry*/, int /*nnewt*/) { +void NewtonSolverSparse::prepareLinearSystemB(int /*ntry*/, int /*nnewt*/, + Model *model, + const SimulationState &state) { /* Get sparse Jacobian */ - model_->fJSparseB(*t_, 0.0, *x_, dx_, xB_, dxB_, xdot_, Jtmp_.get()); + model->fJSparseB(state.t, 0.0, state.x, state.dx, xB_, dxB_, xdot_, + Jtmp_.get()); Jtmp_.refresh(); - int status = SUNLinSolSetup_KLU(linsol_, Jtmp_.get()); - if(status != SUNLS_SUCCESS) + auto status = SUNLinSolSetup_KLU(linsol_, Jtmp_.get()); + if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_KLU"); } -/* ------------------------------------------------------------------------- */ - void NewtonSolverSparse::solveLinearSystem(AmiVector &rhs) { /* Pass pointer to the linear solver */ - int status = SUNLinSolSolve_KLU(linsol_, Jtmp_.get(), - rhs.getNVector(), rhs.getNVector(), 0.0); + auto status = SUNLinSolSolve_KLU(linsol_, Jtmp_.get(), rhs.getNVector(), + rhs.getNVector(), 0.0); // last argument is tolerance and does not have any influence on result - if(status != SUNLS_SUCCESS) + if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSolve_KLU"); } -/* ------------------------------------------------------------------------- */ +void NewtonSolverSparse::reinitialize() { + /* partial reinitialization, don't need to reallocate Jtmp_ */ + auto status = SUNLinSol_KLUReInit(linsol_, Jtmp_.get(), Jtmp_.capacity(), + SUNKLU_REINIT_PARTIAL); + if (status != SUNLS_SUCCESS) + throw NewtonFailure(status, "SUNLinSol_KLUReInit"); +} -bool NewtonSolverSparse::is_singular() const { +bool NewtonSolverSparse::is_singular(Model * /*model*/, + const SimulationState & /*state*/) const { // adapted from SUNLinSolSetup_KLU in sunlinsol/klu/sunlinsol_klu.c auto content = (SUNLinearSolverContent_KLU)(linsol_->content); // first cheap check via rcond - auto status = sun_klu_rcond(content->symbolic, content->numeric, - &content->common); - if(status == 0) + auto status = + sun_klu_rcond(content->symbolic, content->numeric, &content->common); + if (status == 0) throw NewtonFailure(content->last_flag, "sun_klu_rcond"); auto precision = std::numeric_limits::epsilon(); @@ -240,19 +222,18 @@ bool NewtonSolverSparse::is_singular() const { if (content->common.rcond < precision) { // cheap check indicates singular, expensive check via condest status = sun_klu_condest(SM_INDEXPTRS_S(Jtmp_.get()), - SM_DATA_S(Jtmp_.get()), content->symbolic, content->numeric, - &content->common); - if(status == 0) + SM_DATA_S(Jtmp_.get()), content->symbolic, + content->numeric, &content->common); + if (status == 0) throw NewtonFailure(content->last_flag, "sun_klu_rcond"); - return content->common.condest > 1.0/precision; + return content->common.condest > 1.0 / precision; } return false; } NewtonSolverSparse::~NewtonSolverSparse() { - if(linsol_) + if (linsol_) SUNLinSolFree_KLU(linsol_); } - } // namespace amici diff --git a/src/rdata.cpp b/src/rdata.cpp index 00b08e7353..70c5139f5c 100644 --- a/src/rdata.cpp +++ b/src/rdata.cpp @@ -206,10 +206,6 @@ void ReturnData::processPreEquilibration(SteadystateProblem const &preeq, preeq_t = preeq.getSteadyStateTime(); if (!preeq_numsteps.empty()) writeSlice(preeq.getNumSteps(), preeq_numsteps); - if (!preeq.getNumLinSteps().empty() && !preeq_numlinsteps.empty()) { - preeq_numlinsteps.resize(newton_maxsteps * 2, 0); - writeSlice(preeq.getNumLinSteps(), preeq_numlinsteps); - } } void ReturnData::processPostEquilibration(SteadystateProblem const &posteq, @@ -231,10 +227,6 @@ void ReturnData::processPostEquilibration(SteadystateProblem const &posteq, posteq_t = posteq.getSteadyStateTime(); if (!posteq_numsteps.empty()) writeSlice(posteq.getNumSteps(), posteq_numsteps); - if (!posteq.getNumLinSteps().empty() && !posteq_numlinsteps.empty()) { - posteq_numlinsteps.resize(newton_maxsteps * 2, 0); - writeSlice(posteq.getNumLinSteps(), posteq_numlinsteps); - } } void ReturnData::processForwardProblem(ForwardProblem const &fwd, Model &model, diff --git a/src/returndata_matlab.cpp b/src/returndata_matlab.cpp index 739d23cfaf..528a7e1d8a 100644 --- a/src/returndata_matlab.cpp +++ b/src/returndata_matlab.cpp @@ -101,7 +101,7 @@ mxArray *initMatlabReturnFields(ReturnData const *rdata) { } mxArray *initMatlabDiagnosisFields(ReturnData const *rdata) { - const int numFields = 27; + const int numFields = 25; const char *field_names_sol[numFields] = {"xdot", "J", "numsteps", @@ -116,7 +116,6 @@ mxArray *initMatlabDiagnosisFields(ReturnData const *rdata) { "preeq_status", "preeq_numsteps", "preeq_numstepsB", - "preeq_numlinsteps", "preeq_cpu_time", "preeq_cpu_timeB", "preeq_t", @@ -124,7 +123,6 @@ mxArray *initMatlabDiagnosisFields(ReturnData const *rdata) { "posteq_status", "posteq_numsteps", "posteq_numstepsB", - "posteq_numlinsteps", "posteq_cpu_time", "posteq_cpu_timeB", "posteq_t", @@ -168,10 +166,6 @@ mxArray *initMatlabDiagnosisFields(ReturnData const *rdata) { writeMatlabField1(matlabDiagnosisStruct, "preeq_status", gsl::make_span(rdata->preeq_status), 3); writeMatlabField1(matlabDiagnosisStruct, "preeq_numsteps", gsl::make_span(rdata->preeq_numsteps), 3); - writeMatlabField2(matlabDiagnosisStruct, "preeq_numlinsteps", - rdata->preeq_numlinsteps, - rdata->preeq_numlinsteps.size() > 0 - ? rdata->newton_maxsteps : 0, 2, perm1); writeMatlabField0(matlabDiagnosisStruct, "preeq_numstepsB", rdata->preeq_numstepsB); writeMatlabField0(matlabDiagnosisStruct, "preeq_cpu_time", rdata->preeq_cpu_time); writeMatlabField0(matlabDiagnosisStruct, "preeq_cpu_timeB", rdata->preeq_cpu_timeB); @@ -180,10 +174,6 @@ mxArray *initMatlabDiagnosisFields(ReturnData const *rdata) { writeMatlabField1(matlabDiagnosisStruct, "posteq_status", gsl::make_span(rdata->posteq_status), 3); writeMatlabField1(matlabDiagnosisStruct, "posteq_numsteps", gsl::make_span(rdata->posteq_numsteps), 3); - writeMatlabField2(matlabDiagnosisStruct, "posteq_numlinsteps", - rdata->posteq_numlinsteps, - rdata->posteq_numlinsteps.size() > 0 - ? rdata->newton_maxsteps : 0, 2, perm1); writeMatlabField0(matlabDiagnosisStruct, "posteq_numstepsB", rdata->posteq_numstepsB); writeMatlabField0(matlabDiagnosisStruct, "posteq_cpu_time", rdata->posteq_cpu_time); writeMatlabField0(matlabDiagnosisStruct, "posteq_cpu_timeB", rdata->posteq_cpu_timeB); diff --git a/src/solver.cpp b/src/solver.cpp index c774615669..a4dfa8c8ed 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -24,7 +24,6 @@ Solver::Solver(const Solver &other) sensi_meth_(other.sensi_meth_), sensi_meth_preeq_(other.sensi_meth_preeq_), stldet_(other.stldet_), ordering_(other.ordering_), newton_maxsteps_(other.newton_maxsteps_), - newton_maxlinsteps_(other.newton_maxlinsteps_), newton_damping_factor_mode_(other.newton_damping_factor_mode_), newton_damping_factor_lower_bound_(other.newton_damping_factor_lower_bound_), requires_preequilibration_(other.requires_preequilibration_), @@ -490,7 +489,6 @@ bool operator==(const Solver &a, const Solver &b) { (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_damping_factor_mode_ == b.newton_damping_factor_mode_) && (a.newton_damping_factor_lower_bound_ == b.newton_damping_factor_lower_bound_) && (a.requires_preequilibration_ == b.requires_preequilibration_) && (a.ism_ == b.ism_) && @@ -635,14 +633,6 @@ void Solver::setPreequilibration(const bool require_preequilibration) { requires_preequilibration_ = require_preequilibration; } -int Solver::getNewtonMaxLinearSteps() const { return newton_maxlinsteps_; } - -void Solver::setNewtonMaxLinearSteps(const int newton_maxlinsteps) { - if (newton_maxlinsteps < 0) - throw AmiException("newton_maxlinsteps must be a non-negative number"); - newton_maxlinsteps_ = newton_maxlinsteps; -} - NewtonDampingFactorMode Solver::getNewtonDampingFactorMode() const { return newton_damping_factor_mode_; } void Solver::setNewtonDampingFactorMode(NewtonDampingFactorMode dampingFactorMode) { @@ -1035,8 +1025,6 @@ void Solver::setInitDone() const { initialized_ = true; }; void Solver::setSensInitDone() const { sens_initialized_ = true; } -void Solver::setSensInitOff() const { sens_initialized_ = false; } - void Solver::setAdjInitDone() const { adj_initialized_ = true; } void Solver::setInitDoneB(const int which) const { @@ -1055,7 +1043,6 @@ void Solver::setQuadInitDone() const { quad_initialized_ = true; } void Solver::switchForwardSensisOff() const { sensToggleOff(); - setSensInitOff(); } realtype Solver::getCpuTime() const { diff --git a/src/solver_cvodes.cpp b/src/solver_cvodes.cpp index fb0c8fd0f6..c37b1207cd 100644 --- a/src/solver_cvodes.cpp +++ b/src/solver_cvodes.cpp @@ -533,6 +533,9 @@ void CVodeSolver::sensToggleOff() const { auto status = CVodeSensToggleOff(solver_memory_.get()); if (status != CV_SUCCESS) throw CvodeException(status, "CVodeSensToggleOff"); + /* need to deallocate sensi memory, otherwise can't reenable */ + CVodeSensFree(solver_memory_.get()); + sens_initialized_ = false; } void CVodeSolver::quadReInitB(int which, const AmiVector &yQB0) const { diff --git a/src/solver_idas.cpp b/src/solver_idas.cpp index 18033f42c9..0742f9c095 100644 --- a/src/solver_idas.cpp +++ b/src/solver_idas.cpp @@ -451,6 +451,9 @@ void IDASolver::sensToggleOff() const { auto status = IDASensToggleOff(solver_memory_.get()); if (status != IDA_SUCCESS) throw IDAException(status, "IDASensToggleOff"); + IDASensFree(solver_memory_.get()); + /* need to deallocate sensi memory, otherwise can't reenable */ + sens_initialized_ = false; } void IDASolver::reInitB(const int which, const realtype tB0, diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index 7c404bd990..b2e41e4541 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -1,116 +1,98 @@ #include "amici/steadystateproblem.h" +#include "amici/backwardproblem.h" #include "amici/defines.h" -#include "amici/model.h" -#include "amici/solver.h" -#include "amici/solver_cvodes.h" #include "amici/edata.h" #include "amici/forwardproblem.h" -#include "amici/backwardproblem.h" -#include "amici/newton_solver.h" #include "amici/misc.h" +#include "amici/model.h" +#include "amici/newton_solver.h" +#include "amici/solver.h" +#include "amici/solver_cvodes.h" #include #include #include -#include -#include #include +#include +#include constexpr realtype conv_thresh = 1.0; namespace amici { -SteadystateProblem::SteadystateProblem(const Solver &solver, const Model &model) +SteadystateProblem::SteadystateProblem(const Solver &solver, Model &model) : delta_(model.nx_solver), ewt_(model.nx_solver), ewtQB_(model.nplist()), - x_(model.nx_solver), x_old_(model.nx_solver), dx_(model.nx_solver), - xdot_(model.nx_solver), xdot_old_(model.nx_solver), - sx_(model.nx_solver, model.nplist()), sdx_(model.nx_solver, model.nplist()), - xB_(model.nJ * model.nx_solver), xQ_(model.nJ * model.nx_solver), - xQB_(model.nplist()), xQBdot_(model.nplist()), + x_old_(model.nx_solver), xdot_(model.nx_solver), + sdx_(model.nx_solver, model.nplist()), xB_(model.nJ * model.nx_solver), + xQ_(model.nJ * model.nx_solver), xQB_(model.nplist()), + xQBdot_(model.nplist()), max_steps_(solver.getNewtonMaxSteps()), dJydx_(model.nJ * model.nx_solver * model.nt(), 0.0), + state_({INFINITY, // t + AmiVector(model.nx_solver), // x + AmiVector(model.nx_solver), // dx + AmiVectorArray(model.nx_solver, model.nplist()), // sx + model.getModelState()}), // state atol_(solver.getAbsoluteToleranceSteadyState()), rtol_(solver.getRelativeToleranceSteadyState()), atol_sensi_(solver.getAbsoluteToleranceSteadyStateSensi()), rtol_sensi_(solver.getRelativeToleranceSteadyStateSensi()), atol_quad_(solver.getAbsoluteToleranceQuadratures()), - rtol_quad_(solver.getRelativeToleranceQuadratures()) { - /* Check for compatibility of options */ - if (solver.getSensitivityMethod() == SensitivityMethod::forward && - solver.getSensitivityMethodPreequilibration() == SensitivityMethod::adjoint && - solver.getSensitivityOrder() > SensitivityOrder::none) - throw AmiException("Preequilibration using adjoint sensitivities " - "is not compatible with using forward " - "sensitivities during simulation"); - } + rtol_quad_(solver.getRelativeToleranceQuadratures()), + newton_solver_(NewtonSolver::getSolver(solver, &model)), + damping_factor_mode_(solver.getNewtonDampingFactorMode()), + damping_factor_lower_bound_(solver.getNewtonDampingFactorLowerBound()) { + /* Check for compatibility of options */ + if (solver.getSensitivityMethod() == SensitivityMethod::forward && + solver.getSensitivityMethodPreequilibration() == + SensitivityMethod::adjoint && + solver.getSensitivityOrder() > SensitivityOrder::none) + throw AmiException("Preequilibration using adjoint sensitivities " + "is not compatible with using forward " + "sensitivities during simulation"); +} void SteadystateProblem::workSteadyStateProblem(Solver *solver, Model *model, int it) { + initializeForwardProblem(it, solver, model); - /* process solver handling for pre- or postequilibration */ - if (it == -1) { - /* solver was not run before, set up everything */ - model->initialize(x_, dx_, sx_, sdx_, - solver->getSensitivityOrder() >= - SensitivityOrder::first); - t_ = model->t0(); - solver->setup(t_, model, x_, dx_, sx_, sdx_); - } else { - /* solver was run before, extract current state from solver */ - solver->writeSolution(&t_, x_, dx_, sx_, xQ_); - } - - /* create a Newton solver object */ - auto newtonSolver = NewtonSolver::getSolver(&t_, &x_, *solver, model); - - /* Compute steady state and get the computation time */ + /* Compute steady state, track computation time */ clock_t starttime = clock(); - findSteadyState(solver, newtonSolver.get(), model, it); + findSteadyState(solver, model, it); cpu_time_ = (double)((clock() - starttime) * 1000) / CLOCKS_PER_SEC; /* Check whether state sensis still need to be computed */ - if (getSensitivityFlag(model, solver, it, SteadyStateContext::newtonSensi)) - { + if (getSensitivityFlag(model, solver, it, + SteadyStateContext::newtonSensi)) { try { /* this might still fail, if the Jacobian is singular and simulation did not find a steady state */ - newtonSolver->computeNewtonSensis(sx_); + newton_solver_->computeNewtonSensis(state_.sx, model, state_); } catch (NewtonFailure const &) { - /* No steady state could be inferred. Store simulation state */ - storeSimulationState(model, solver->getSensitivityOrder() >= - SensitivityOrder::first); - throw AmiException("Steady state sensitivity computation failed due " - "to unsuccessful factorization of RHS Jacobian"); + throw AmiException( + "Steady state sensitivity computation failed due " + "to unsuccessful factorization of RHS Jacobian"); } } - - /* Get output of steady state solver, write it to x0 and reset time - if necessary */ - storeSimulationState(model, getSensitivityFlag(model, solver, it, - SteadyStateContext::sensiStorage)); } -void SteadystateProblem::workSteadyStateBackwardProblem(Solver *solver, - Model *model, - const BackwardProblem *bwd) { - /* initialize and check if there is something to be done */ +void SteadystateProblem::workSteadyStateBackwardProblem( + Solver *solver, Model *model, const BackwardProblem *bwd) { + if (!initializeBackwardProblem(solver, model, bwd)) return; - /* Get the Newton solver */ - auto newtonSolver = NewtonSolver::getSolver(&t_, &x_, *solver, model); - - /* get the run time */ + /* compute quadratures, track computation time */ clock_t starttime = clock(); - computeSteadyStateQuadrature(newtonSolver.get(), solver, model); + computeSteadyStateQuadrature(solver, model); cpu_timeB_ = (double)((clock() - starttime) * 1000) / CLOCKS_PER_SEC; } -void SteadystateProblem::findSteadyState(Solver *solver, - NewtonSolver *newtonSolver, - Model *model, int it) { - /* First, try to run the Newton solver */ +void SteadystateProblem::findSteadyState(const Solver *solver, Model *model, + int it) { steady_state_status_.resize(3, SteadyStateStatus::not_run); - findSteadyStateByNewtonsMethod(newtonSolver, model, false); + + /* First, try to run the Newton solver */ + findSteadyStateByNewtonsMethod(model, false); /* Newton solver didn't work, so try to simulate to steady state */ if (!checkSteadyStateSuccess()) @@ -118,73 +100,51 @@ void SteadystateProblem::findSteadyState(Solver *solver, /* Simulation didn't work, retry the Newton solver from last sim state. */ if (!checkSteadyStateSuccess()) - findSteadyStateByNewtonsMethod(newtonSolver, model, true); + findSteadyStateByNewtonsMethod(model, true); /* Nothing worked, throw an as informative error as possible */ if (!checkSteadyStateSuccess()) - handleSteadyStateFailure(solver, model); + handleSteadyStateFailure(); } -void SteadystateProblem::findSteadyStateByNewtonsMethod(NewtonSolver *newtonSolver, - Model *model, +void SteadystateProblem::findSteadyStateByNewtonsMethod(Model *model, bool newton_retry) { int ind = newton_retry ? 2 : 0; try { - applyNewtonsMethod(model, newtonSolver, newton_retry); + applyNewtonsMethod(model, newton_retry); steady_state_status_[ind] = SteadyStateStatus::success; } catch (NewtonFailure const &ex) { /* nothing to be done */ switch (ex.error_code) { - case AMICI_TOO_MUCH_WORK: - steady_state_status_[ind] = - SteadyStateStatus::failed_convergence; - break; - case AMICI_NO_STEADY_STATE: - steady_state_status_[ind] = - SteadyStateStatus::failed_too_long_simulation; - break; - case AMICI_SINGULAR_JACOBIAN: - steady_state_status_[ind] = - SteadyStateStatus::failed_factorization; - break; - case AMICI_DAMPING_FACTOR_ERROR: - steady_state_status_[ind] = SteadyStateStatus::failed_damping; - break; - default: - steady_state_status_[ind] = SteadyStateStatus::failed; - break; - } - } - - /* copy number of linear steps used */ - if (max_steps_ > 0) { - if (newton_retry) { - std::copy_n(newtonSolver->getNumLinSteps().begin(), - max_steps_, &numlinsteps_.at(max_steps_)); - } else { - std::copy_n(newtonSolver->getNumLinSteps().begin(), - max_steps_, numlinsteps_.begin()); + case AMICI_TOO_MUCH_WORK: + steady_state_status_[ind] = SteadyStateStatus::failed_convergence; + break; + case AMICI_NO_STEADY_STATE: + steady_state_status_[ind] = + SteadyStateStatus::failed_too_long_simulation; + break; + case AMICI_SINGULAR_JACOBIAN: + steady_state_status_[ind] = SteadyStateStatus::failed_factorization; + break; + case AMICI_DAMPING_FACTOR_ERROR: + steady_state_status_[ind] = SteadyStateStatus::failed_damping; + break; + default: + steady_state_status_[ind] = SteadyStateStatus::failed; + break; } } } void SteadystateProblem::findSteadyStateBySimulation(const Solver *solver, - Model *model, - int it) { - /* set starting timepoint for the simulation solver */ - if (it < 1) /* No previous time point computed, set t = t0 */ - t_ = model->t0(); - else /* Carry on simulating from last point */ - t_ = model->getTimepoint(it - 1); - + Model *model, int it) { try { if (it < 0) { - /* Preequilibration? -> Create a new CVode object for sim */ - bool integrateSensis = getSensitivityFlag(model, solver, it, - SteadyStateContext::solverCreation); - auto newtonSimSolver = createSteadystateSimSolver(solver, model, - integrateSensis, - false); + /* Preequilibration? -> Create a new solver instance for sim */ + bool integrateSensis = getSensitivityFlag( + model, solver, it, SteadyStateContext::solverCreation); + auto newtonSimSolver = createSteadystateSimSolver( + solver, model, integrateSensis, false); runSteadystateSimulation(newtonSimSolver.get(), model, false); } else { /* Solver was already created, use this one */ @@ -193,49 +153,74 @@ void SteadystateProblem::findSteadyStateBySimulation(const Solver *solver, steady_state_status_[1] = SteadyStateStatus::success; } catch (NewtonFailure const &ex) { switch (ex.error_code) { - case AMICI_TOO_MUCH_WORK: - steady_state_status_[1] = SteadyStateStatus::failed_convergence; - break; - case AMICI_NO_STEADY_STATE: - steady_state_status_[1] = SteadyStateStatus::failed_too_long_simulation; - break; - default: - model->app->warningF("AMICI:newton", - "AMICI newton method failed: %s\n", - ex.what()); - steady_state_status_[1] = SteadyStateStatus::failed; + case AMICI_TOO_MUCH_WORK: + steady_state_status_[1] = SteadyStateStatus::failed_convergence; + break; + case AMICI_NO_STEADY_STATE: + steady_state_status_[1] = + SteadyStateStatus::failed_too_long_simulation; + break; + default: + model->app->warningF("AMICI:newton", + "AMICI newton method failed: %s\n", ex.what()); + steady_state_status_[1] = SteadyStateStatus::failed; } } catch (AmiException const &ex) { model->app->warningF("AMICI:equilibration", - "AMICI equilibration failed: %s\n", - ex.what()); + "AMICI equilibration failed: %s\n", ex.what()); steady_state_status_[1] = SteadyStateStatus::failed; } } -bool SteadystateProblem::initializeBackwardProblem(Solver *solver, - Model *model, +void SteadystateProblem::initializeForwardProblem(int it, const Solver *solver, + Model *model) { + newton_solver_->reinitialize(); + /* process solver handling for pre- or postequilibration */ + if (it == -1) { + /* solver was not run before, set up everything */ + model->initialize(state_.x, state_.dx, state_.sx, sdx_, + solver->getSensitivityOrder() >= + SensitivityOrder::first); + state_.t = model->t0(); + solver->setup(state_.t, model, state_.x, state_.dx, state_.sx, sdx_); + } else { + /* solver was run before, extract current state from solver */ + solver->writeSolution(&state_.t, state_.x, state_.dx, state_.sx, xQ_); + } + + /* overwrite starting timepoint */ + if (it < 1) /* No previous time point computed, set t = t0 */ + state_.t = model->t0(); + else /* Carry on simulating from last point */ + state_.t = model->getTimepoint(it - 1); + + state_.state = model->getModelState(); +} + +bool SteadystateProblem::initializeBackwardProblem(Solver *solver, Model *model, const BackwardProblem *bwd) { + newton_solver_->reinitialize(); + /* note that state_ is still set from forward run */ if (bwd) { - /* If preequilibration but not adjoint mode, there's nothing to do */ + /* preequilibration */ if (solver->getSensitivityMethodPreequilibration() != SensitivityMethod::adjoint) - return false; + return false; /* if not adjoint mode, there's nothing to do */ /* If we need to reinitialize solver states, this won't work yet. */ if (model->nx_reinit() > 0) - throw NewtonFailure(AMICI_NOT_IMPLEMENTED, + throw NewtonFailure( + AMICI_NOT_IMPLEMENTED, "Adjoint preequilibration with reinitialization of " "non-constant states is not yet implemented. Stopping."); - /* If we have a backward problem, we're in preequilibration. - Hence, quantities like t, x, and xB must be set. */ - solver->reInit(t_, x_, x_); + solver->reInit(state_.t, state_.x, state_.dx); solver->updateAndReinitStatesAndSensitivities(model); xB_.copy(bwd->getAdjointState()); } + /* postequilibration does not need a reInit */ - /* Will need to write quadratures: set to 0 */ + /* initialize quadratures */ xQ_.zero(); xQB_.zero(); xQBdot_.zero(); @@ -243,8 +228,7 @@ bool SteadystateProblem::initializeBackwardProblem(Solver *solver, return true; } -void SteadystateProblem::computeSteadyStateQuadrature(NewtonSolver *newtonSolver, - const Solver *solver, +void SteadystateProblem::computeSteadyStateQuadrature(const Solver *solver, Model *model) { /* This routine computes the quadratures: xQB = Integral[ xB(x(t), t, p) * dxdot/dp(x(t), t, p) | dt ] @@ -254,7 +238,7 @@ void SteadystateProblem::computeSteadyStateQuadrature(NewtonSolver *newtonSolver matrix-vector multiplication */ /* Try to compute the analytical solution for quadrature algebraically */ - getQuadratureByLinSolve(newtonSolver, model); + getQuadratureByLinSolve(model); /* Analytical solution didn't work, perform simulation instead */ if (!hasQuadrature()) @@ -262,13 +246,13 @@ void SteadystateProblem::computeSteadyStateQuadrature(NewtonSolver *newtonSolver /* If analytic solution and integration did not work, throw an Exception */ if (!hasQuadrature()) - throw AmiException("Steady state backward computation failed: Linear " + throw AmiException( + "Steady state backward computation failed: Linear " "system could not be solved (possibly due to singular Jacobian), " "and numerical integration did not equilibrate within maxsteps"); } -void SteadystateProblem::getQuadratureByLinSolve(NewtonSolver *newtonSolver, - Model *model) { +void SteadystateProblem::getQuadratureByLinSolve(Model *model) { /* Computes the integral over the adjoint state xB: If the Jacobian has full rank, this has an analytical solution, since d/dt[ xB(t) ] = JB^T(x(t), p) xB(t) = JB^T(x_ss, p) xB(t) @@ -284,8 +268,8 @@ void SteadystateProblem::getQuadratureByLinSolve(NewtonSolver *newtonSolver, /* try to solve the linear system */ try { /* compute integral over xB and write to xQ */ - newtonSolver->prepareLinearSystemB(0, -1); - newtonSolver->solveLinearSystem(xQ_); + newton_solver_->prepareLinearSystemB(0, -1, model, state_); + newton_solver_->solveLinearSystem(xQ_); /* Compute the quadrature as the inner product xQ * dxdotdp */ computeQBfromQ(model, xQ_, xQB_); /* set flag that quadratures is available (for processing in rdata) */ @@ -305,7 +289,7 @@ void SteadystateProblem::getQuadratureBySimulation(const Solver *solver, x is not time dependent, no forward trajectory is needed. */ /* set starting timepoint for the simulation solver */ - t_ = model->t0(); + state_.t = model->t0(); /* xQ was written in getQuadratureByLinSolve() -> set to zero */ xQ_.zero(); @@ -321,12 +305,7 @@ void SteadystateProblem::getQuadratureBySimulation(const Solver *solver, } } -[[noreturn]] void SteadystateProblem::handleSteadyStateFailure(const Solver *solver, - Model *model) { - /* No steady state could be inferred. Store simulation state */ - storeSimulationState(model, solver->getSensitivityOrder() >= - SensitivityOrder::first); - +[[noreturn]] void SteadystateProblem::handleSteadyStateFailure() { /* Throw error message according to error codes */ std::string errorString = "Steady state computation failed. " "First run of Newton solver failed"; @@ -343,30 +322,31 @@ void SteadystateProblem::writeErrorString(std::string *errorString, SteadyStateStatus status) const { /* write error message according to steady state status */ switch (status) { - case SteadyStateStatus::failed_too_long_simulation: - (*errorString).append(": System could not be equilibrated via" - " simulating to a late time point."); - break; - case SteadyStateStatus::failed_damping: - (*errorString).append(": Damping factor reached lower bound."); - break; - case SteadyStateStatus::failed_factorization: - (*errorString).append(": RHS could not be factorized."); - break; - case SteadyStateStatus::failed_convergence: - (*errorString).append(": No convergence was achieved."); - break; - case SteadyStateStatus::failed: - (*errorString).append("."); - break; - default: - break; + case SteadyStateStatus::failed_too_long_simulation: + (*errorString) + .append(": System could not be equilibrated via" + " simulating to a late time point."); + break; + case SteadyStateStatus::failed_damping: + (*errorString).append(": Damping factor reached lower bound."); + break; + case SteadyStateStatus::failed_factorization: + (*errorString).append(": RHS could not be factorized."); + break; + case SteadyStateStatus::failed_convergence: + (*errorString).append(": No convergence was achieved."); + break; + case SteadyStateStatus::failed: + (*errorString).append("."); + break; + default: + break; } } bool SteadystateProblem::getSensitivityFlag(const Model *model, - const Solver *solver, - int it, SteadyStateContext context) { + const Solver *solver, int it, + SteadyStateContext context) { /* We need to check whether we need to compute forward sensitivities. Depending on the situation (pre-/postequilibration) and the solver settings, the logic may be involved and is handled here. @@ -380,22 +360,24 @@ bool SteadystateProblem::getSensitivityFlag(const Model *model, bool forwardSensisAlreadyComputed = solver->getSensitivityOrder() >= SensitivityOrder::first && steady_state_status_[1] == SteadyStateStatus::success && - model->getSteadyStateSensitivityMode() == SteadyStateSensitivityMode::simulationFSA; + model->getSteadyStateSensitivityMode() == + SteadyStateSensitivityMode::simulationFSA; bool simulationStartedInSteadystate = steady_state_status_[0] == SteadyStateStatus::success && numsteps_[0] == 0; /* Do we need forward sensis for postequilibration? */ - bool needForwardSensisPosteq = !preequilibration && - !forwardSensisAlreadyComputed && + bool needForwardSensisPosteq = + !preequilibration && !forwardSensisAlreadyComputed && solver->getSensitivityOrder() >= SensitivityOrder::first && solver->getSensitivityMethod() == SensitivityMethod::forward; /* Do we need forward sensis for preequilibration? */ - bool needForwardSensisPreeq = preequilibration && - !forwardSensisAlreadyComputed && - solver->getSensitivityMethodPreequilibration() == SensitivityMethod::forward && + bool needForwardSensisPreeq = + preequilibration && !forwardSensisAlreadyComputed && + solver->getSensitivityMethodPreequilibration() == + SensitivityMethod::forward && solver->getSensitivityOrder() >= SensitivityOrder::first; /* Do we need to do the linear system solve to get forward sensitivities? */ @@ -404,50 +386,48 @@ bool SteadystateProblem::getSensitivityFlag(const Model *model, !simulationStartedInSteadystate; /* When we're creating a new solver object */ - bool needForwardSensiAtCreation = needForwardSensisPreeq && - model->getSteadyStateSensitivityMode() == SteadyStateSensitivityMode::simulationFSA; + bool needForwardSensiAtCreation = + needForwardSensisPreeq && model->getSteadyStateSensitivityMode() == + SteadyStateSensitivityMode::simulationFSA; /* Check if we need to store sensis */ switch (context) { - case SteadyStateContext::newtonSensi: - return needForwardSensisNewton; + case SteadyStateContext::newtonSensi: + return needForwardSensisNewton; - case SteadyStateContext::sensiStorage: - return needForwardSensisNewton || - forwardSensisAlreadyComputed || - simulationStartedInSteadystate; + case SteadyStateContext::sensiStorage: + return needForwardSensisNewton || forwardSensisAlreadyComputed || + simulationStartedInSteadystate; - case SteadyStateContext::solverCreation: - return needForwardSensiAtCreation; + case SteadyStateContext::solverCreation: + return needForwardSensiAtCreation; - default: - throw AmiException("Requested invalid context in sensitivity " - "processing during steady state computation"); + default: + throw AmiException("Requested invalid context in sensitivity " + "processing during steady state computation"); } } realtype SteadystateProblem::getWrmsNorm(const AmiVector &x, - const AmiVector &xdot, - realtype atol, - realtype rtol, - AmiVector &ewt) const { + const AmiVector &xdot, realtype atol, + realtype rtol, AmiVector &ewt) const { /* Depending on what convergence we want to check (xdot, sxdot, xQBdot) we need to pass ewt[QB], as xdot and xQBdot have different sizes */ - // ewt = x + /* ewt = x */ N_VAbs(const_cast(x.getNVector()), ewt.getNVector()); - // ewt *= rtol + /* ewt *= rtol */ N_VScale(rtol, ewt.getNVector(), ewt.getNVector()); - // ewt += atol + /* ewt += atol */ N_VAddConst(ewt.getNVector(), atol, ewt.getNVector()); - // ewt = 1/ewt (ewt = 1/(rtol*x+atol)) + /* ewt = 1/ewt (ewt = 1/(rtol*x+atol)) */ N_VInv(ewt.getNVector(), ewt.getNVector()); - // wrms = sqrt(sum((xdot/ewt)**2)) + /* wrms = sqrt(sum((xdot/ewt)**2)) */ return N_VWrmsNorm(const_cast(xdot.getNVector()), ewt.getNVector()); } realtype SteadystateProblem::getWrms(Model *model, - SensitivityMethod sensi_method) { + SensitivityMethod sensi_method) { realtype wrms = INFINITY; if (sensi_method == SensitivityMethod::adjoint) { /* In the adjoint case, only xQB contributes to the gradient, the exact @@ -459,19 +439,20 @@ realtype SteadystateProblem::getWrms(Model *model, } else { /* If we're doing a forward simulation (with or without sensitivities: Get RHS and compute weighted error norm */ - model->fxdot(t_, x_, dx_, xdot_); - wrms = getWrmsNorm(x_, xdot_, atol_, rtol_, ewt_); + model->fxdot(state_.t, state_.x, state_.dx, xdot_); + wrms = getWrmsNorm(state_.x, xdot_, atol_, rtol_, ewt_); } return wrms; } - realtype SteadystateProblem::getWrmsFSA(Model *model) { /* Forward sensitivities: Compute weighted error norm for their RHS */ realtype wrms = 0.0; for (int ip = 0; ip < model->nplist(); ++ip) { - model->fsxdot(t_, x_, dx_, ip, sx_[ip], dx_, xdot_); - wrms = getWrmsNorm(sx_[ip], xdot_, atol_sensi_, rtol_sensi_, ewt_); + model->fsxdot(state_.t, state_.x, state_.dx, ip, state_.sx[ip], + state_.dx, xdot_); + wrms = + getWrmsNorm(state_.sx[ip], xdot_, atol_sensi_, rtol_sensi_, ewt_); /* ideally this function would report the maximum of all wrms over all ip, but for practical purposes we can just report the wrms for the first ip where we know that the convergence threshold is not @@ -486,45 +467,37 @@ realtype SteadystateProblem::getWrmsFSA(Model *model) { bool SteadystateProblem::checkSteadyStateSuccess() const { /* Did one of the attempts yield s steady state? */ - if (std::any_of(steady_state_status_.begin(), steady_state_status_.end(), - [](SteadyStateStatus status) - {return status == SteadyStateStatus::success;})) { - return true; - } else { - return false; - } + return std::any_of(steady_state_status_.begin(), steady_state_status_.end(), + [](SteadyStateStatus status) { + return status == SteadyStateStatus::success; + }); } -void SteadystateProblem::applyNewtonsMethod(Model *model, - NewtonSolver *newtonSolver, - bool newton_retry) { +void SteadystateProblem::applyNewtonsMethod(Model *model, bool newton_retry) { int i_newtonstep = 0; - int ix = 0; - double gamma = 1.0; - bool compNewStep = true; + gamma_ = 1.0; + bool update_direction = true; + bool step_successful = false; if (model->nx_solver == 0) return; /* initialize output of linear solver for Newton step */ delta_.zero(); - - model->fxdot(t_, x_, dx_, xdot_); - - /* Check for relative error, but make sure not to divide by 0! - Ensure positivity of the state */ - x_old_ = x_; - xdot_old_ = xdot_; + x_old_.copy(state_.x); wrms_ = getWrms(model, SensitivityMethod::none); bool converged = newton_retry ? false : wrms_ < conv_thresh; - while (!converged && i_newtonstep < newtonSolver->max_steps) { + while (!converged && i_newtonstep < max_steps_) { - /* If Newton steps are necessary, compute the initial search direction */ - if (compNewStep) { + /* If Newton steps are necessary, compute the initial search direction + */ + if (update_direction) { try { - delta_ = xdot_; - newtonSolver->getStep(newton_retry ? 2 : 1, i_newtonstep, delta_); + // xdot_ computed in getWrms + delta_.copy(xdot_); + newton_solver_->getStep(newton_retry ? 2 : 1, i_newtonstep, + delta_, model, state_); } catch (NewtonFailure const &) { numsteps_.at(newton_retry ? 2 : 0) = i_newtonstep; throw; @@ -532,52 +505,24 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, } /* Try a full, undamped Newton step */ - linearSum(1.0, x_old_, gamma, delta_, x_); + linearSum(1.0, x_old_, gamma_, delta_, state_.x); /* Compute new xdot and residuals */ realtype wrms_tmp = getWrms(model, SensitivityMethod::none); - if (wrms_tmp < wrms_) { + step_successful = wrms_tmp < wrms_; + if (step_successful) { /* If new residuals are smaller than old ones, update state */ wrms_ = wrms_tmp; - x_old_ = x_; - xdot_old_ = xdot_; - /* New linear solve due to new state */ - compNewStep = true; - + x_old_.copy(state_.x); + // precheck convergence converged = wrms_ < conv_thresh; if (converged) { - /* Ensure positivity of the found state and recheck if - the convergence still holds */ - bool recheck_convergence = false; - for (ix = 0; ix < model->nx_solver; ix++) { - if (x_[ix] < 0.0) { - x_[ix] = 0.0; - recheck_convergence = true; - } - } - if (recheck_convergence) { - wrms_ = getWrms(model, SensitivityMethod::none); - converged = wrms_ < conv_thresh; - } + converged = makePositiveAndCheckConvergence(model); } - - // update dampening - if (newtonSolver->damping_factor_mode_==NewtonDampingFactorMode::on) - gamma = fmin(1.0, 2.0 * gamma); - - } else if (newtonSolver->damping_factor_mode_==NewtonDampingFactorMode::on) { - /* Reduce dampening factor and raise an error when becomes too small */ - gamma = gamma / 4.0; - if (gamma < newtonSolver->damping_factor_lower_bound) - throw NewtonFailure(AMICI_DAMPING_FACTOR_ERROR, - "Newton solver failed: the damping factor " - "reached its lower bound"); - - /* No new linear solve, only try new dampening */ - compNewStep = false; } + update_direction = updateDampingFactor(step_successful); /* increase step counter */ i_newtonstep++; } @@ -588,10 +533,43 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, throw NewtonFailure(AMICI_TOO_MUCH_WORK, "applyNewtonsMethod"); } +bool SteadystateProblem::makePositiveAndCheckConvergence(Model *model) { + /* Ensure positivity of the found state and recheck if + the convergence still holds */ + bool recheck_convergence = false; + bool converged = true; + auto nonnegative = model->getStateIsNonNegative(); + for (int ix = 0; ix < model->nx_solver; ix++) { + if (state_.x[ix] < 0.0 && nonnegative[ix]) { + state_.x[ix] = 0.0; + recheck_convergence = true; + } + } + if (recheck_convergence) { + wrms_ = getWrms(model, SensitivityMethod::none); + converged = wrms_ < conv_thresh; + } + return converged; +} + +bool SteadystateProblem::updateDampingFactor(bool step_successful) { + if (damping_factor_mode_ != NewtonDampingFactorMode::on) + return true; + + if (step_successful) + gamma_ = fmin(1.0, 2.0 * gamma_); + else + gamma_ = gamma_ / 4.0; + + if (gamma_ < damping_factor_lower_bound_) + throw NewtonFailure(AMICI_DAMPING_FACTOR_ERROR, + "Newton solver failed: the damping factor " + "reached its lower bound"); + return step_successful; +} + void SteadystateProblem::runSteadystateSimulation(const Solver *solver, - Model *model, - bool backward) -{ + Model *model, bool backward) { if (model->nx_solver == 0) return; /* Loop over steps and check for convergence @@ -608,7 +586,8 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, /* If flag for forward sensitivity computation by simulation is not set, disable forward sensitivity integration. Sensitivities will be computed by newtonSolver->computeNewtonSensis then */ - if (model->getSteadyStateSensitivityMode() == SteadyStateSensitivityMode::newtonOnly) { + if (model->getSteadyStateSensitivityMode() == + SteadyStateSensitivityMode::newtonOnly) { solver->switchForwardSensisOff(); sensitivityFlag = SensitivityMethod::none; } @@ -627,20 +606,22 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, stable computation value is not important for AMICI_ONE_STEP mode, only direction w.r.t. current t */ - solver->step(std::max(t_, 1.0) * 10); + solver->step(std::max(state_.t, 1.0) * 10); if (backward) { - solver->writeSolution(&t_, xB_, dx_, sx_, xQ_); + solver->writeSolution(&state_.t, xB_, state_.dx, state_.sx, xQ_); } else { - solver->writeSolution(&t_, x_, dx_, sx_, xQ_); + solver->writeSolution(&state_.t, state_.x, state_.dx, state_.sx, + xQ_); } /* Check for convergence */ wrms_ = getWrms(model, sensitivityFlag); - if (wrms_ < conv_thresh && sensitivityFlag == SensitivityMethod::forward) { - sx_ = solver->getStateSensitivity(t_); + if (wrms_ < conv_thresh && + sensitivityFlag == SensitivityMethod::forward) { + state_.sx = solver->getStateSensitivity(state_.t); wrms_ = getWrmsFSA(model); } - + if (wrms_ < conv_thresh) break; // converged /* increase counter, check for maxsteps */ @@ -650,9 +631,10 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, throw NewtonFailure(AMICI_TOO_MUCH_WORK, "exceeded maximum number of steps"); } - if (t_ >= 1e200) { + if (state_.t >= 1e200) { numsteps_.at(1) = sim_steps; - throw NewtonFailure(AMICI_NO_STEADY_STATE, "simulated to late time" + throw NewtonFailure(AMICI_NO_STEADY_STATE, + "simulated to late time" " point without convergence of RHS"); } } @@ -665,20 +647,21 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, } } -std::unique_ptr SteadystateProblem::createSteadystateSimSolver( - const Solver *solver, Model *model, bool forwardSensis, bool backward) const -{ +std::unique_ptr +SteadystateProblem::createSteadystateSimSolver(const Solver *solver, + Model *model, bool forwardSensis, + bool backward) const { /* Create new CVode solver object */ auto sim_solver = std::unique_ptr(solver->clone()); switch (solver->getLinearSolver()) { - case LinearSolver::dense: - break; - case LinearSolver::KLU: - break; - default: - throw NewtonFailure(AMICI_NOT_IMPLEMENTED, - "invalid solver for steadystate simulation"); + case LinearSolver::dense: + break; + case LinearSolver::KLU: + break; + default: + throw NewtonFailure(AMICI_NOT_IMPLEMENTED, + "invalid solver for steadystate simulation"); } /* do we need sensitivities? */ if (forwardSensis) { @@ -690,12 +673,14 @@ std::unique_ptr SteadystateProblem::createSteadystateSimSolver( } /* use x and sx as dummies for dx and sdx (they wont get touched in a CVodeSolver) */ - sim_solver->setup(model->t0(), model, x_, x_, sx_, sx_); + sim_solver->setup(model->t0(), model, state_.x, state_.dx, state_.sx, sdx_); if (backward) { - sim_solver->setup(model->t0(), model, xB_, xB_, sx_, sx_); - sim_solver->setupSteadystate(model->t0(), model, x_, x_, xB_, xB_, xQ_); + sim_solver->setup(model->t0(), model, xB_, xB_, state_.sx, sdx_); + sim_solver->setupSteadystate(model->t0(), model, state_.x, state_.dx, + xB_, xB_, xQ_); } else { - sim_solver->setup(model->t0(), model, x_, x_, sx_, sx_); + sim_solver->setup(model->t0(), model, state_.x, state_.dx, state_.sx, + sdx_); } return sim_solver; @@ -710,36 +695,27 @@ void SteadystateProblem::computeQBfromQ(Model *model, const AmiVector &yQ, /* multiply */ if (model->pythonGenerated) { /* fill dxdotdp with current values */ - const auto& plist = model->getParameterList(); - model->fdxdotdp(t_, x_, x_); + const auto &plist = model->getParameterList(); + model->fdxdotdp(state_.t, state_.x, state_.dx); model->get_dxdotdp_full().multiply(yQB.getNVector(), yQ.getNVector(), plist, true); } else { - for (int ip=0; ipnplist(); ++ip) + for (int ip = 0; ip < model->nplist(); ++ip) yQB[ip] = dotProd(yQ, model->get_dxdotdp()[ip]); } } -void SteadystateProblem::getAdjointUpdates(Model &model, - const ExpData &edata) { +void SteadystateProblem::getAdjointUpdates(Model &model, const ExpData &edata) { xB_.zero(); - for (int it=0; it < model.nt(); it++) { + for (int it = 0; it < model.nt(); it++) { if (std::isinf(model.getTimepoint(it))) { model.getAdjointStateObservableUpdate( - slice(dJydx_, it, model.nx_solver * model.nJ), it, x_, edata); + slice(dJydx_, it, model.nx_solver * model.nJ), it, state_.x, + edata); for (int ix = 0; ix < model.nxtrue_solver; ix++) xB_[ix] += dJydx_[ix + it * model.nx_solver]; } } } -void SteadystateProblem::storeSimulationState(Model *model, bool storesensi) { - state_.t = INFINITY; - state_.x = x_; - state_.dx = xdot_; - if (storesensi) - state_.sx = sx_; - state_.state = model->getModelState(); -} - } // namespace amici diff --git a/tests/cpp/expectedResults.h5 b/tests/cpp/expectedResults.h5 index a68de6ea7eda08275a442ed7b639639b8429aacd..4c406822138f20658567ab8748a5d782e2c185d5 100644 GIT binary patch delta 1296 zcmajfUr1A76aa9yyWP!g-mPV|{IhPFWoB!wve`Dhj1g&K{zF7WOe%;Z7WL%QWl2}e zU^wXmD;p!kpvF+!p#?qo;Dd-pL{LG9?LlHLsiz2{bA3p?yQ_!a%XiNG&bjBFZTT^; zTt+do2?9ZFT0!2WC==;hqjY3^om#xTY8F<_3Z#TpGZp;=v)r~U0kdFyUDbCPTH4jL zlFG^9$kG~B4E7Fnq(wZOQ;=Al%S+y;DCY0YHejEt9bFLVrnf%C_t2vh7>sn|1({H1 zE0oM&{`Mwq5Z2Ru6pX&4uU!0B!VIgygx-K=YejBA^SxC(%+e%m=j-lKJ6Vp3m*%0CTP@h`-JikWL|XFE!tbj zLZ(d(4d8-kNPF=;&W|JF5g$2J6 zna~boMmteH+J$zbJ;;Lgq5@P13;v?`ka%5GegQZ75t7PcWJUWBi}s@uREiFuGIS6f zLgnZ%B$XAv2mgDsFH|A;LPt;~I*M%Q7_y_|$bn9vljszx0$=EKbO=^foLSa|?Wj&` zNHpct0bB0uPl&0RgM5>87Tf?wdv!)gzzcxwI5YMQl;>RQ3duT#xKf(De&w=4e!Yvi zCgxxxcCVI~EQpv0-QXH-1&7`(Et7y}dG5uhFbgsvcfx(=QkD&w_DrfET;>_On`Pyv pJ>Fo}P?w4{EKxoftgmSqmi-xk=|fFtGfY*~N?XQ1k;+=`%O6KZBdq`c delta 981 zcmZXTZ%7ki9LKra?l$Li>sn=|c3GCGm08(z{uwgD)M^=+jF1A0P+u(QMM%hD!HF4- z7Cj;TIV2*)WR&c8uJ^u(D2d)wG$efyA%*m^kiNH@IrPQn&FA-hzW3Zc_sl&}_&E@h z+|=*4$2q1H*c2w`)Qzyj+@ z1>f$hJnU?ZZaz$1;0r+!*bZc{0~CXuU>7I>yTKk%3ii?hU$#87zA6wcQv=a*PyzOV z{onwo1P4JCI0O!ZBcPfB(W8^Y4BvB^1?Rnb>dz**v*xFbO)h#JB$s;o6RA02?&WB9 z!JB`@6k11(ms1hOg4)qvrVKaxYB`p8?JFivi!Zm=?1t4@B<>re5cwi!((AM9P>5?3 z8aG9yZiv`)oph1RBTk)V_*MOLD`_S5qqhv5zJCPW6j>wASy|hAOMr%28rnyUSv61Y TPYt2d={~KzQ75gU(LetWkp+S& diff --git a/tests/cpp/testOptions.h5 b/tests/cpp/testOptions.h5 index 25280e9cec208ed2aaecb54b27a2106c61723607..407b6ff4fe09b3419c0a9c54a6cbb967be67c7a1 100644 GIT binary patch delta 11225 zcmbVSeRNdSwa>lhPM8ms2@=R8d<<5@NEAkVYD{z)LEcJ9^#uV9P)Y2o4Tp@E@mmFwF_ePr{vZCUg`1^%Cv73TZcfT%Ou?@(-dY5%8C zR@TCn_QiUn+#RiQmr!cmD}suYESv6{T&p_^264t{q~It@W5VGkHQo~QZJ1s)Jen-q zkPU*#nex<`@`;v$g&sXpV2^sI93+Bq&l6c z_eCLy8T>)5e)J{51hPXRD%<24HM%=mvviG0$gbZn*24>)iw;|Mmjc^*{owLJx;KA5 z*&TYXUO%zIygUD@*p>FL)z7aKnxt}PSdjF{yPoKOh1*3;j=q_CLIU2}1_7sHN;Zfm zQq07tAVrf=8gv$$v70EF#wMEB^fGnJ^?PFCmO<19eb_Q*G_C{3?;{F^)&~?6EM2tpVj(?gVMU1Ll#Ks zjusz*pqC!2*QftY8ol9v!RFFq!oCa?W^RuQbEW2kCxmA3AbgS{k$JHBw6HT;^9?{| z?S+i%rvPeg`$W0AuOg@FuDdQA`N(+_xpn8kC1{4|5XaW z2mF~+C?8<|My=bh`@kWc+CQp@*w0~P#ETS~6YRR4c*=_52c5#D+mrx)-LK^GUO~OW z6*nlU*uI-*1ji~eF?i9f(sILJqR`af#_`fq8I}`wNuy~!8{r9x$dC|@z3(X9MzDfq zr{Er*ozef1%Fc<(ACVpB6lt0?W586C8N`>asY*#dmYiBV?*Vzh@XiEZbeiV9wUQ12 zX$+VGLVO85EPbiQyB?Jqs6#;<8mZJ^@9oHI1ZFGqXt$zynm#9$r;Wd2X)1l9k7=r_ zPYW<^Y%m!_ybAx?l%dT{(t4xjY39}13jus(b9$OPnoUB8SMlGOymGckuSh>9HS{vC zp(Ub*mZq|l-})o6RI*%}C$lteh1AAt=v7`ruS>^S4XrZO&|BXZG=IGmWcVZI8hShR zfMf}pFqF@`sk9jJcX|1A^f51_d_;)@P)Oy};I;2ToZ%`YjoApsOvzdFku=)yck=St zlO{zcO&OoqEA3_F^WTsh(|rhO2_E3Zb66D5XQ{ND`0I~Ii}R>7Or~YP=O!a)@qDgq zwu|C9|D`-&gioc4=ialD4l(IDD`AG4_Nope=O4;}jH?~GTH+$}rCCu~n_j7^vQ1Cd z)V{ardHqyzdq`jA+kqytQtnXUzO1`Lt;LTFlm`rVt{PC3bH!o}(?dgBmZQdW^0XJMII#a%fFwNIJx=(==6uTc_hbb@9L84lyHTH9?tomYVOPU9&&Anr2Ju zp#H0ZD!oH-M~hmGPybq42uLGIsm0F5R4OBl;E%~~gA9?h_F4SbEHHUd`7HCI`8nam z%I8^9mcJyEvNj}_!~}7XrL>RLrFJ^f7)f0f(=~Q3m(PsIausgQo>dk?=DZ;p0bJfU z0bF#J6dIxUlj~SUmapgFdMjcA*CQJQxJ-W3<9QvTkTCJ99xvJ=_YlX{S(oZDroE?%2^r7XDIY?Pd>@%|v^}0KZuxP7xkGzoRt?e;?YO}RJ0whzpxWpr6fQ3wOz9bsS zd=QLhOhVOo>~MHMQ)DEDOYy|^kx29eX1mXFq3AU`-Oe)p z4%}U0ESrk%LBI8JLgEBt74OLQlWgxh#s0SPcjx4QbiTYR*}gcJ{Y?nI1sQzLa=z!; z-*b$wAj`KVow1VdgX@g>)6hb4OJLVb83>ksH9js+R(o@CK9cr{yz~@cAa;&#>e|Yv2AnJfHx=!Pn4nW7-P{URhkB4EtM5 zDwbKPSZ-3Wf~g>Fss?`$25!J8QPGCf;N3WXHG0nV*hBSC%zi95=b?ulH`cC3U(jw0 zm+<*Nq-2HHFl3$lm^eQ9?eA$8a06tyH!@^_Zxc65-=Ik*I#8#obm@~^BaQerv^}M6 zyHtn6AE08C*o#`)hw5RsedCAdW7YWhk7yXZRf!Ek9gFRsSG^YGaJ$^?2X1VV7=9pdqE zE_((S^?9kHIf$$9q|@+pqyJQm2-0$t{4W_Xo^go2kwPT>T&h(hX{sHGQ%ph@K5`0; zbQFKXw)Xb+|5NnJ-?$Yo9+5ozn2<-Ja^y66 z2;Xy3agEDPFZv|kyB&-!Tx>`(Wg;J8zK@+Ifjk4Dp&<8keaHBE%LwYa2q=tfb>KZ` zNG6)Tg}wi$cJi(~;ydJ0aGBdyX?*!}?Uy1eS!bD0Z${|EcM81Lqt$x6oU77;ivrmp zZ8mZgZLn%2hG-EoKH~Q^O~N_Ih4hvLmnlH_?qq?gYH#nSV(%-OLmi;Cv_AkL zsrZRjKWG6LnM|{bPM3J)63wN$2>Vc-1OO#3qf3CP>Hun@+)gT_y}vdbmm$@m_6KU^ zHaqgVfj6|2D9jB~Syi{_(@n2Brm4=YE0vWm!S7~e4+ggwv{bg4VB=0hxQika%kri zZ%g}N92}$BCkla`9jq1wOp|=?-p1$wp6D3AWCdThymayQG1}OEC-^E;{sKoQ;~SC{s7_ZL;`_*hB(>MW=R{;5Cus)-BdZA)5SUdS4<12vI3AuYq@3;z^`T$ zX}V=VrL|o1GqOyt;)dhAnbi1)VUwcC}ybo*FD*5*^OXGr% z|CM#z&E;9vF&%mBB35|PWKzfO`>3P$EO6nLboB`CtVZ!@!SXRi)1@uCK8qg&csy@i=Yu!?h8M34_(!tfU~I()rK zUMHfobHB>0j$6PS-tyYGU@M%n2~0!|=d7d?*lSFnsdG2{fw*Yu+*`QF4BVwAu-73v zFoCUt=)eRPW>h+Btp+OX+~sco6(+DZ83RmUZDehlz+PfTWoYax(pQAo@m^B^6WFDg z*aXJ8Eb_!BF!>cr!#(TNLi>8AJJq^F>+qr-q;+q_>Q2(SH%NeEf2ZbA*K7LPcdtw5bN`X;{p(2pO|82;%IH?;Ub>rf?hYD!J@gHEP3~Zwn|yeg z^aiC z8=|{ireNtkWrg;Sm_P|>hx!TemLvLqNayAfGvLJm?NU1-HnhYe_`%&IBoR9qKXw9@ zwu9I%x?JiY#N9xojXUu%U1BnbIMhFprKxfM?QLdIhQ7YSdEYzicyDosw)5>Jm{{8x zxRHsnNDgn@rimSw-bB7X{DqwWJTG;k9CfcoKdd>DN4Zma%j-V-ezx($Yjx}yHAmg2 zkq>P*Vf$Cwt$BOdQvvQ)zTbp%NOU)n8<2i*Yy3NbSQq28g00@P)s5l_h;sZea;V2( znMjg=AE!$_(dJM;1)~3JLOep3E+nU|uD@W%;uhG3b{*hyt-wvjlD#x8M6LBk@up8JF;z~?WY+BOKWm6 zNo$g&wb)_1wJbaRU)vS)eU+Wj?c9Vg|7Ag>zmxB~an9H2cXHOZOkc0*&$_@8O^VR1 z+GxGGFS9vR8(qTm+4eQ$zlKu(nU?<~B1}C0>n^v+{QJ00Z(6`iV?K1Xa?WLyf9xG> zb2x=le-{9Vdu#*%h{d8;0JL(!Rsf*)wM4)=&iI1}@D3vg;EYxXFr^5_vV?<~R}6Jq zGlD?CPUhuorJ4W_zAoN&hNUE#79R22HS)Pw7JfqFeVNFD636Wh+i+Y%sh0zl-u+cq z0B?j+`;+CP@cvqKwXGrdJ4+>fXpnfuNLwK9EIalV3Dofc_n+$luLfH+DRDmGa19Vc-d&%?0n8BkYAHJ*xo)=(fzJG zH8v7Y_uIy(Lu_du9NRoL`a?xQ*qf;bdw5p*VK4ts-w2)8bFdGAjZja*ItRzh9DTW+;x|kzy{-PUoh* m2*=7uEu?JJi>A!gL-*$yHFw&sMfuk;H5S!iastzTUi<$jJYaMH delta 15832 zcmcgze|!{0mY?qG3DYyTFd#qjgC7%4l7S$Mple9n4*|i8WW^w$k*s9&yi3*`lEY;a zlXV@k;b?5D?|ZMRdhneX>3e)s<7DCHwkdB~(0r5X`pJPaV33Gejg8nW zBb*6PJwfk=Z6XqKJo`Au?24wg6^d`KGw5#46Npr`(%r&aAeIDzB}F;$TD5ZSxj{uP zK5r_LKdfB2xQV+qZRvJD|FyDU!{}hAKj!z-TL;Qv7CzBRSco;{?HnkgF%f~hAjo~~ z3r2&OiPY~i+Ib7Yeh)_4pTd4{Jd2Qdl_!P43;Omhz!1N^&&UxBUenjTA=B4=`%T!y ze-%uBJ#0sd>=-4KE4Lp`L}D6?=u3xbM4v5JwjCcR!m#Z4-!f%qcOn3I1{xkg?+I>P zV+l-Y9M?U;+*5?E`JYrP%g*qoB3MSx5G?aQNkoGxi&3?I0%Dpl!OUKrO$16e3NNtx z9M38e|AF19L4PkMCPj14g@I6M8L8@X68_u2F;TWUA!6y`s3Me;!k0)+s=GxfgLWVo z8Wm~pQT>vDQH4cA1S7z37Emi}0!af1eVA_N&iS3eEr)qO5C-BBrx2Vwl&cx997eN+ zG*uoZv@wteqK5!h-yJPze1)|xb8YpRhT2rmM1dp#0`jUXLqX`tk$O~{o1m;<^rqx+ zg)w?+C`Z8Tg^p_sy+Kle8Atn-);LFD{yw1sZuSVH5{j!%py*n`m{_}0)v6gnjKXmJ zEDm~%!)igEfJqGNZW0FiLFdqO^SEGWR95}-1&zx_mO#8*r>+RoEz^fXJ~Qf{4pxe@621Zmt~7%ez@#?lAPhcOWmy%SK5OqLl1~ z+g}q#85Vq;mqR)RuYZ#RoW|gSPL`LC7^+mzRtBCs$^|2ym;XcTy)GG@ex-uVJ^zaM{QaI$8B65jB+#rai+qYIxjBVeRNg{7c_&q>eJ47_y zzSY~fnhC@nSv-ml8<-U5c&H(d{|c7b49YFL#Pjg2&d#<7KryUuuma#&D8bXi`3;8@zwd% zNRdc(%qMz58Jt+mMTX=ToI2VU#o(@n-AlN_Br{hn70Hgd;|?Pj|6ia+YafUo4t3Rj zpXhPm{n0SA_g>i#oayustuCw*@u>uMG>K*K!bAO*i>2s6nJ@ymwH(^(`b7v9LJECq zppM`?z*8{+#AjUeC`GqD%2h_NEm%vjZFsC7w$va(<4wP&G;Vp)NQT@E!R?La@6pN= zH~73o&rtY_u2;Jiv6J2QL3;~FN=qK07LgoG`nDvYZ`F&OnJ~!sj!kuKC5Z#y0U4Q2 z%3yQ{<#NkQh7gc4*dEZOj{A2S`Q<@Xhj}%u?JyFgz$3?0!}YJ43lZMc@8t^Nn2htT zc^@B=dXi2`UL)xQk-Z$QdV?d0Q_A=^b!0yfHdoqm7A2JvM>ugq$c{>7>>dstc;9d? zwd$CT^tC6rf`s(Wla%zO9~gNn?tscqjASVGb$>N~-_T>o9D+Dnd}3B%F zxw@gC0)F%b2Of;{6Mv^jmllEYXYxQ|ZPw<(nc!tm4i^Y~0iPNb0QY*1&45yS3TVc) z3|TYKUSh~FK=Kmj72%RaElKAKAWPDTC9rf6sv$EbfW&=FJY}en2{DThKG`6# z1Y$vAmRoPu48GR5Su^L%LG_0zvLIzfiT zeK1k5n6;}8sr7e(b!aCD9m_e8VRw%&BkoozpQwOuS8zC?$F=*X2Dqxe`v(b!s+E*{ z$dC&7U^Pb(v8G06%^HqSf;IJq*8GZck8GKE0g>jO~PhgTM`X zj)JDPaf;t*+x0qlM-09)d~6Cy^vHUyaU{zB$bZKSKMYz0=)MiCRe#hDo)*&o1V~7Q zSzu{@@b|g?x5irjCZAX2=zDae6P|hxWXpx!v}se8Rx>{U4gnT?DM@PJA+Qq{B+QCQA5sl2 zQw{N8iw)SQ8P7FIOR;inY3lGE@SkEZe1}sIqHupNNJAE2LQU@c3pHV+sR^-9#XXLm z^K^hM3SiN_QkE)w3WkY_PqqPf6e#&Otp*lBf-T3uGwGbx*g0@r+x@_;S(a- z`(@d_q?0FwIo^CucC!)mCj%Nz{dqtk+Ry`JIkqk}{V>=hs)2K0xB!nx5<}gq&6qUk zkrY~5EHXmYY0VP;LCsq$jTS|h4AP6zmB!CyaXfiKnM5dnnGR`! z;w1H4Ie%v6VPB-Z+-e&`m$waicQ<9;8 z#zXHuFa^1@p-!rCkD!Z7#s+M;QAS2--|Q8Ru&b}4s*>?53CVevF#r-G+{Z?k69GnY z#K+(-Z;%eivCSRKoGN9DH8W}afYj-dlKS)bzmd(?&O%(1(}g^d2r)U&;>7t%Si5E^?eC&; zV=WamiGq!+9jHYtxkm9gQsvw^R3u8uW%&AdzKG0E1+JNrQdz&^Cfh$HPUK)ElT=a)~8%HX(JvR7Cap_z~|>gb-ZgktS^LNU~UfI^&6@ zZ-2!kHouD@M*ncK1m40BI(gX$b2@2d)uCIM>JX$uAS3r?x}eVh>BU=&A7P_IvZ2qcbI=sA1cS5O%efSTWbF8k8D`5Ys>)@$WV&x68RqrN z7(gj;hK(?%gf7Fx;t<}m7-g713Cl1Wmr6?S`PFjOzcP48tl3HvW38lw#M+#Kf1^@b z7N}tDbq~;fE!Lte+BLbNMgs5s#NFO0 zqAcD;dT|~1{tJ4Hha1lKHB3V?-k{)aWFySMjm2ARaU~Ltx93IGRV+;w(kto8!U-(U z1zitQlT^^*GpId|Hq3B}Swa(F_pG+g|50%qeV1m-FNCdy*K zvXbl)97~U!i z>)`(NC`JQ&W${VuUR=94ZoJNSe!(B;yQjB8QE zx6-vIW5Jcj8Z5dFap-qcRZ{LP-Fle=Dt{OsU`~fvs2#M&z7a^UZwK0Obh1L$cnGfF zfeP9CQ2aeAWN*h}40>Kdg{)IjwnrbBdy=k43$Xxehd-qKT8Q-=N|bZHLED~A>{D{((@mc6!7K`!-bBgNw-fpN&!{(E$BH@-JWN4S4j^@`k&Q41 zG*-vrD^qLt;yQMMt|-h2tnK?z9b?lc8&^FJ9meF+CqnC(V1f5^No8bvR`dp9HJdmY zR?|A>KSe;qo9w`8{7CP7{z7`mozF-3k=*&5A)VyT=VKyD?tJzi>sQ9k{E@Cc86&Pd zv`AHY?_H`Y8PAddf4htUG}VqgOjVduAr@N)9J;F(n18*HW&4y)z`h%hJL}Y5bOChJ zYB}1+pi-JPyKPb0@X!woihrXGbe%_+eDkk|wm(a=(tGK9E$%k5cKdm$P5XpU_dcTg zXH;&mcxnXUuZbJ+*8}le@)$XWHYT54KpVwKs|?$s$hDp>6jT@STZf17IV4z{+Ezn! zt-3}1#c}Bl?QZoera2jRP;^r-P+QH>C4m+V%J%q*Ssn9#jO&<%eq#-D!j06i3Z^BQ zctiWfqv#ZiKS*c&f{jZ}n7IEX2eJ;|Pdc7O9|F!wrEWGzyRxHW`wyZwBMSOwFb>_P zVx{uaEK#+7A}CC;;zK-ejj_Ux^U^e%gRWCg5Swfz1j&s`vkE5A_ZmTRS-T{Y z_G^jR&%Q}(a-FQ*1LxSS3CDQL59r82se}?y=4dn)XAEZsV#he>{2cLSJbolcjaED9 z_2S;q_%X(&=vdTbY})a+IGfx|dGc&3WFrP=6LejYWV^O*o0Zb(m9Q1DX)Il{A)7Wb zEy=J6?}}n{+Q9}UO(&PtTB@Ot44t4e#hPZ1dmZn=U(@50 z$Bjd&Ow^R<>^3hVTZfBR6K^{0R#}{cABm5{nWWc?df}4jTC;7_s6Opuad?DP z7II+s2y3-{Dt+afW`&O5OIfy=iK308kmxknJj%KWvR=Lm406ri-BhxNMAd zx_G@c_Sp@ehgxUgys#ppz(Z%(rl?@;bu+AZ3NEZ3`fDJ#e5}yN225aW9!Cf7mz}8fPt(amici::SUNLinSolKLU::StateOrdering::COLAMD)); solver.setInterpolationType(amici::InterpolationType::polynomial); diff --git a/tests/generateTestConfig/example.py b/tests/generateTestConfig/example.py index 99a9d91cb0..03bbeebf0b 100644 --- a/tests/generateTestConfig/example.py +++ b/tests/generateTestConfig/example.py @@ -55,7 +55,6 @@ def __init__(self): 'linsol': 9, 'lmm': 2, 'maxsteps' : 1e4, - 'newton_maxlinsteps': 100, 'newton_maxsteps' : 40, 'newton_preeq' : 0, 'nmaxevent' : 10, From fee0d5a069aba864a92ded7da50ae68ea7ea43cc Mon Sep 17 00:00:00 2001 From: Kristian Meyer Date: Wed, 23 Mar 2022 15:00:56 +0100 Subject: [PATCH 09/31] Fix undefined reference to dladdr (#1738) Add libdl as public dependency to libamici, fixing undefined references if AMICI is built without HDF5 support. --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 423b156773..f7f9d0ac2f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,6 +201,7 @@ target_link_libraries(${PROJECT_NAME} PUBLIC ${SUITESPARSE_LIBRARIES} PUBLIC ${HDF5_LIBRARIES} PUBLIC ${BLAS_LIBRARIES} + PUBLIC ${CMAKE_DL_LIBS} ) # Create targets to make the sources show up in IDEs for convenience From 9b133301984ca3fafb92bad2cb067c9a29150c8d Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 25 Mar 2022 11:55:59 +0100 Subject: [PATCH 10/31] fix doc: pin jinja2 version, incompatible with current nbsphinx (#1742) See https://github.com/spatialaudio/nbsphinx/issues/641 --- documentation/rtd_requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/documentation/rtd_requirements.txt b/documentation/rtd_requirements.txt index 357294df4f..5b545ff7f6 100644 --- a/documentation/rtd_requirements.txt +++ b/documentation/rtd_requirements.txt @@ -24,3 +24,4 @@ git+https://github.com/dweindl/exhale@ea77a313777c1382a7830ce9ee6c405ce47f5f3b#e sphinxcontrib-matlabdomain>=0.12.0 sphinxcontrib-napoleon pygments==2.10.0 +Jinja2==3.0.3 # https://github.com/spatialaudio/nbsphinx/issues/641 From f60957b23c9ab6575b02e272484d3cfd5d48c396 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 25 Mar 2022 14:51:09 +0100 Subject: [PATCH 11/31] Fix HDF5 OSX intermediate group creation errors (#1741) Still not sure what the original problem was, but going through the HDF5 C++ API seems to fix the following error occurring in `amici::hdf5::hdf5CreateGroup` with recent HDF5 versions on OSX: ``` > return _amici.writeSolverSettingsToHDF5(*args) E RuntimeError: Failed to create group in hdf5CreateGroup: ssettings ../sdist/amici/amici.py:6129: RuntimeError ----------------------------- Captured stderr call ----------------------------- HDF5-DIAG: Error detected in HDF5 (1.10.8) thread 0: #000: H5Plcpl.c line 152 in H5Pset_create_intermediate_group(): can't find object for ID major: Object atom minor: Unable to find atom information (already closed?) #001: H5Pint.c line 3805 in H5P_object_verify(): property list is not a member of the class major: Property lists minor: Unable to register new atom #002: H5Pint.c line 3756 in H5P_isa_class(): not a property list major: Invalid arguments to routine minor: Inappropriate type HDF5-DIAG: Error detected in HDF5 (1.10.8) thread 0: #000: H5G.c line 288 in H5Gcreate2(): not link creation property list major: Invalid arguments to routine minor: Inappropriate type #001: H5Pint.c line 3756 in H5P_isa_class(): not a property list major: Invalid arguments to routine minor: Inappropriate type HDF5-DIAG: Error detected in HDF5 (1.10.8) thread 0: #000: H5P.c line 1488 in H5Pclose(): not a property list major: Invalid arguments to routine minor: Inappropriate type ``` `H5::LinkCreatPropList::setCreateIntermediateGroup` was only added in HDF5 1.10.6, but current Ubuntu LTS still comes with HDF5 1.10.4. Therefore we still need to keep the C version. --- .github/workflows/test_pypi.yml | 7 ++----- .github/workflows/test_python_cplusplus.yml | 8 +++----- src/hdf5.cpp | 7 ++++++- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/.github/workflows/test_pypi.yml b/.github/workflows/test_pypi.yml index 4b68180830..d3c4341135 100644 --- a/.github/workflows/test_pypi.yml +++ b/.github/workflows/test_pypi.yml @@ -31,11 +31,8 @@ jobs: - name: homebrew run: | if [[ ${{ matrix.os }} == macos* ]] ; then \ - brew install hdf5@1.10 swig gcc libomp \ - && echo "/usr/local/opt/hdf5@1.10/bin" >> $GITHUB_PATH \ - && echo LDFLAGS="-L/usr/local/lib/ -L/usr/local/opt/hdf5@1.10/lib" >> $GITHUB_ENV \ - && echo CPPFLAGS="-I/usr/local/opt/hdf5@1.10/include" >> $GITHUB_ENV \ - && echo HDF5_BASE="/usr/local/opt/hdf5@1.10/" >> $GITHUB_ENV + brew install hdf5 swig gcc libomp \ + && echo LDFLAGS="-L/usr/local/lib/" >> $GITHUB_ENV fi - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/test_python_cplusplus.yml b/.github/workflows/test_python_cplusplus.yml index ac3a10fa76..5998c9a4b7 100644 --- a/.github/workflows/test_python_cplusplus.yml +++ b/.github/workflows/test_python_cplusplus.yml @@ -152,13 +152,11 @@ jobs: # install amici dependencies - name: homebrew run: | - brew install hdf5@1.10 swig gcc cppcheck libomp boost \ + brew install hdf5 swig gcc cppcheck libomp boost \ && brew ls -v boost \ && brew ls -v libomp \ - && echo "/usr/local/opt/hdf5@1.10/bin" >> $GITHUB_PATH \ - && echo LDFLAGS="-L/usr/local/lib/ -L/usr/local/opt/hdf5@1.10/lib -L/usr/local/Cellar/boost/1.78.0_1/lib/" >> $GITHUB_ENV \ - && echo CPPFLAGS="-I/usr/local/opt/hdf5@1.10/include -I/usr/local/Cellar/boost/1.78.0_1/include/" >> $GITHUB_ENV \ - && echo HDF5_BASE="/usr/local/opt/hdf5@1.10/" >> $GITHUB_ENV + && echo LDFLAGS="-L/usr/local/lib/ -L/usr/local/Cellar/boost/1.78.0_1/lib/" >> $GITHUB_ENV \ + && echo CPPFLAGS="-I/usr/local/Cellar/boost/1.78.0_1/include/" >> $GITHUB_ENV - name: Build AMICI run: | diff --git a/src/hdf5.cpp b/src/hdf5.cpp index 4b486b06f2..40571ffaab 100644 --- a/src/hdf5.cpp +++ b/src/hdf5.cpp @@ -84,7 +84,11 @@ void checkEventDimensionsCompatible(hsize_t m, hsize_t n, Model const& model) { void createGroup(H5::H5File const& file, std::string const& groupPath, bool recursively) { - +#if H5_VERSION_GE(1, 10, 6) + H5::LinkCreatPropList lcpl; + lcpl.setCreateIntermediateGroup(recursively); + file.createGroup(groupPath.c_str(), lcpl); +#else auto groupCreationPropertyList = H5P_DEFAULT; if (recursively) { @@ -101,6 +105,7 @@ void createGroup(H5::H5File const& file, throw(AmiException("Failed to create group in hdf5CreateGroup: %s", groupPath.c_str())); H5Gclose(group); +#endif } std::unique_ptr readSimulationExpData(std::string const& hdf5Filename, From 8d6288e3055f52338fde8eeaed2bdfc74511b627 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Fri, 25 Mar 2022 09:52:21 -0400 Subject: [PATCH 12/31] add newton step convergence checks to steadystate solver (#1737) * add code but all deactivated * fixup * refactor * simplify logic * fix doc * make configurable * add tests * fix doc * Apply suggestions from code review Co-authored-by: Daniel Weindl * fix hdf field names * avoid try/catch * use reference instead of pointer * fix doc * fixup tests * fixup * fixup python test * Update test_preequilibration.py Co-authored-by: Daniel Weindl --- include/amici/newton_solver.h | 24 ++--- include/amici/serialization.h | 2 + include/amici/solver.h | 38 ++++++++ include/amici/steadystateproblem.h | 45 ++++++++- python/tests/test_preequilibration.py | 48 ++++++++++ src/hdf5.cpp | 20 +++- src/newton_solver.cpp | 20 ++-- src/solver.cpp | 4 + src/steadystateproblem.cpp | 131 ++++++++++++++++++-------- 9 files changed, 262 insertions(+), 70 deletions(-) diff --git a/include/amici/newton_solver.h b/include/amici/newton_solver.h index ac937b6009..926ea08842 100644 --- a/include/amici/newton_solver.h +++ b/include/amici/newton_solver.h @@ -44,16 +44,12 @@ class NewtonSolver { /** * @brief Computes the solution of one Newton iteration * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step * @param delta containing the RHS of the linear system, will be * overwritten by solution to the linear system * @param model pointer to the model instance * @param state current simulation state */ - void getStep(int ntry, int nnewt, AmiVector &delta, Model *model, - const SimulationState &state); + void getStep(AmiVector &delta, Model *model, const SimulationState &state); /** * @brief Computes steady state sensitivities @@ -69,26 +65,20 @@ class NewtonSolver { * @brief Writes the Jacobian for the Newton iteration and passes it to the * linear solver * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step * @param model pointer to the model instance * @param state current simulation state */ - virtual void prepareLinearSystem(int ntry, int nnewt, Model *model, + virtual void prepareLinearSystem(Model *model, const SimulationState &state) = 0; /** * Writes the Jacobian (JB) for the Newton iteration and passes it to the * linear solver * - * @param ntry integer newton_try integer start number of Newton solver - * (1 or 2) - * @param nnewt integer number of current Newton step * @param model pointer to the model instance * @param state current simulation state */ - virtual void prepareLinearSystemB(int ntry, int nnewt, Model *model, + virtual void prepareLinearSystemB(Model *model, const SimulationState &state) = 0; /** @@ -153,10 +143,10 @@ class NewtonSolverDense : public NewtonSolver { void solveLinearSystem(AmiVector &rhs) override; - void prepareLinearSystem(int ntry, int nnewt, Model *model, + void prepareLinearSystem(Model *model, const SimulationState &state) override; - void prepareLinearSystemB(int ntry, int nnewt, Model *model, + void prepareLinearSystemB(Model *model, const SimulationState &state) override; void reinitialize() override; @@ -194,10 +184,10 @@ class NewtonSolverSparse : public NewtonSolver { void solveLinearSystem(AmiVector &rhs) override; - void prepareLinearSystem(int ntry, int nnewt, Model *model, + void prepareLinearSystem(Model *model, const SimulationState &state) override; - void prepareLinearSystemB(int ntry, int nnewt, Model *model, + void prepareLinearSystemB(Model *model, const SimulationState &state) override; bool is_singular(Model *model, const SimulationState &state) const override; diff --git a/include/amici/serialization.h b/include/amici/serialization.h index d53e37a904..4dedd912ca 100644 --- a/include/amici/serialization.h +++ b/include/amici/serialization.h @@ -82,6 +82,8 @@ void serialize(Archive &ar, amici::Solver &s, const unsigned int /*version*/) { ar &s.ordering_; ar &s.cpu_time_; ar &s.cpu_timeB_; + ar &s.newton_step_steadystate_conv_; + ar &s.check_sensi_steadystate_conv_; ar &s.rdata_mode_; ar &s.maxtime_; } diff --git a/include/amici/solver.h b/include/amici/solver.h index ba1ced532e..4f04ef5fe5 100644 --- a/include/amici/solver.h +++ b/include/amici/solver.h @@ -867,6 +867,38 @@ class Solver { std::vector const& getLastOrder() const { return order_; } + + /** + * @brief Returns how convergence checks for steadystate computation are performed. + * @return boolean flag indicating newton step (true) or the right hand side (false) + */ + bool getNewtonStepSteadyStateCheck() const { + return newton_step_steadystate_conv_; + } + + /** + * @brief Returns how convergence checks for steadystate computation are performed. + * @return boolean flag indicating state and sensitivity equations (true) or only state variables (false). + */ + bool getSensiSteadyStateCheck() const { + return check_sensi_steadystate_conv_; + } + + /** + * @brief Sets how convergence checks for steadystate computation are performed. + * @param flag boolean flag to pick newton step (true) or the right hand side (false, default) + */ + void setNewtonStepSteadyStateCheck(bool flag) { + newton_step_steadystate_conv_ = flag; + } + + /** + * @brief Sets for which variables convergence checks for steadystate computation are performed. + * @param flag boolean flag to pick state and sensitivity equations (true, default) or only state variables (false). + */ + void setSensiSteadyStateCheck(bool flag) { + check_sensi_steadystate_conv_ = flag; + } /** * @brief Serialize Solver (see boost::serialization::serialize) @@ -1749,6 +1781,12 @@ class Solver { realtype ss_rtol_sensi_ {NAN}; RDataReporting rdata_mode_ {RDataReporting::full}; + + /** whether newton step should be used for convergence steps */ + bool newton_step_steadystate_conv_ {false}; + + /** whether sensitivities should be checked for convergence to steadystate */ + bool check_sensi_steadystate_conv_ {true}; /** CPU time, forward solve */ mutable realtype cpu_time_ {0.0}; diff --git a/include/amici/steadystateproblem.h b/include/amici/steadystateproblem.h index 420d91b9b3..ebeca17f88 100644 --- a/include/amici/steadystateproblem.h +++ b/include/amici/steadystateproblem.h @@ -323,14 +323,14 @@ class SteadystateProblem { AmiVector &yQB) const; /** - * @brief ensures state positivity, if requested and repeats convergence + * @brief Ensures state positivity, if requested and repeats convergence * check, if necessary * @param model pointer to the model object */ bool makePositiveAndCheckConvergence(Model *model); /** - * @brief updates the damping factor gamma that determines step size + * @brief Updates the damping factor gamma that determines step size * * @param step_successful flag indicating whether the previous step was * successful @@ -339,9 +339,38 @@ class SteadystateProblem { * dampening (false) */ bool updateDampingFactor(bool step_successful); + + /** + * @brief Updates member variables to indicate that state_.x has been updated and xdot_, delta_, etc. + * need to be recomputed. + */ + void flagUpdatedState(); + + /** + * @brief Retrieves simulation sensitivities from the provided solver and sets the corresponding flag + * to indicate they are up to date + * @param solver simulation solver instance + */ + void updateSensiSimulation(const Solver &solver); + + /** + * @brief Computes the right hand side for the current state_.x and sets the corresponding flag to + * indicate xdot_ is up to date. + * @param model model instance + */ + void updateRightHandSide(Model &model); + + /** + * @brief Computes the newton step for the current state_.x and sets the corresponding flag to + * indicate delta_ is up to date. + * @param model model instance + */ + void getNewtonStep(Model &model); /** newton step */ AmiVector delta_; + /** previous newton step */ + AmiVector delta_old_; /** error weights for solver state, dimension nx_solver */ AmiVector ewt_; /** error weights for backward quadratures, dimension nplist() */ @@ -416,6 +445,18 @@ class SteadystateProblem { NewtonDampingFactorMode damping_factor_mode_{NewtonDampingFactorMode::on}; /** damping factor lower bound */ realtype damping_factor_lower_bound_{1e-8}; + /** whether newton step should be used for convergence steps */ + bool newton_step_conv_ {false}; + /** whether sensitivities should be checked for convergence to steadystate */ + bool check_sensi_conv_ {true}; + + /** flag indicating whether xdot_ has been computed for the current state */ + bool xdot_updated_ {false}; + /** flag indicating whether delta_ has been computed for the current state */ + bool delta_updated_ {false}; + /** flag indicating whether simulation sensitivities have been retrieved for the current state */ + bool sensis_updated_ {false}; + }; } // namespace amici diff --git a/python/tests/test_preequilibration.py b/python/tests/test_preequilibration.py index 5cd67a39d3..0aefd9dfcc 100644 --- a/python/tests/test_preequilibration.py +++ b/python/tests/test_preequilibration.py @@ -336,6 +336,8 @@ def test_newton_solver_equilibration(preeq_fixture): settings = [amici.SteadyStateSensitivityMode.simulationFSA, amici.SteadyStateSensitivityMode.newtonOnly] + solver.setNewtonStepSteadyStateCheck(True) + for equil_meth in settings: # set sensi method sensi_meth = amici.SensitivityMethod.forward @@ -344,6 +346,7 @@ def test_newton_solver_equilibration(preeq_fixture): if equil_meth == amici.SteadyStateSensitivityMode.newtonOnly: solver.setNewtonMaxSteps(10) else: + solver.setSensiSteadyStateCheck(False) solver.setNewtonMaxSteps(0) # add rdatas @@ -357,5 +360,50 @@ def test_newton_solver_equilibration(preeq_fixture): assert np.isclose( rdatas[settings[0]][variable], rdatas[settings[1]][variable], + 1e-5, 1e-5 + ).all(), variable + + +def test_newton_steadystate_check(preeq_fixture): + """Test data replicates""" + + model, solver, edata, edata_preeq, edata_presim, edata_sim, pscales, \ + plists = preeq_fixture + + # we don't want presim + edata.t_presim = 0.0 + edata.fixedParametersPresimulation = () + + # add infty timepoint + y = edata.getObservedData() + stdy = edata.getObservedDataStdDev() + ts = np.hstack([*edata.getTimepoints(), np.inf]) + edata.setTimepoints(sorted(ts)) + edata.setObservedData(np.hstack([y, y[0]])) + edata.setObservedDataStdDev(np.hstack([stdy, stdy[0]])) + + solver.setNewtonMaxSteps(100) + + rdatas = {} + for newton_check in [True, False]: + # set sensi method + sensi_meth = amici.SensitivityMethod.forward + solver.setSensitivityMethod(sensi_meth) + solver.setNewtonStepSteadyStateCheck(newton_check) + + # add rdatas + rdatas[newton_check] = amici.runAmiciSimulation(model, solver, edata) + + # assert successful simulation + assert rdatas[newton_check]['status'] == amici.AMICI_SUCCESS + + # assert correct results + for variable in ['llh', 'sllh', 'sx0', 'sx_ss', 'x_ss']: + assert np.isclose( + rdatas[True][variable], + rdatas[False][variable], 1e-6, 1e-6 ).all(), variable + + + diff --git a/src/hdf5.cpp b/src/hdf5.cpp index 40571ffaab..4a4ffdf388 100644 --- a/src/hdf5.cpp +++ b/src/hdf5.cpp @@ -728,7 +728,7 @@ void writeSolverSettingsToHDF5(Solver const& solver, dbuffer = solver.getNewtonDampingFactorLowerBound(); H5LTset_attribute_double(file.getId(), hdf5Location.c_str(), - "newton_damping_factor_lower_bound", &dbuffer, 1); + "newton_damping_factor_lower_bound", &dbuffer, 1); ibuffer = static_cast(solver.getLinearSolver()); H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), @@ -741,6 +741,14 @@ void writeSolverSettingsToHDF5(Solver const& solver, ibuffer = static_cast(solver.getReturnDataReportingMode()); H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), "rdrm", &ibuffer, 1); + + ibuffer = static_cast(solver.getNewtonStepSteadyStateCheck()); + H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), + "newton_step_steadystate_conv", &ibuffer, 1); + + ibuffer = static_cast(solver.getSensiSteadyStateCheck()); + H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), + "check_sensi_steadystate_conv", &ibuffer, 1); } void readSolverSettingsFromHDF5(H5::H5File const& file, Solver &solver, @@ -909,6 +917,16 @@ void readSolverSettingsFromHDF5(H5::H5File const& file, Solver &solver, static_cast( getIntScalarAttribute(file, datasetPath, "rdrm"))); } + + if(attributeExists(file, datasetPath, "newton_step_steadystate_conv")) { + solver.setNewtonStepSteadyStateCheck( + getIntScalarAttribute(file, datasetPath, "newton_step_steadystate_conv")); + } + + if(attributeExists(file, datasetPath, "check_sensi_steadystate_conv")) { + solver.setSensiSteadyStateCheck( + getIntScalarAttribute(file, datasetPath, "check_sensi_steadystate_conv")); + } } void readSolverSettingsFromHDF5(const std::string &hdffile, Solver &solver, diff --git a/src/newton_solver.cpp b/src/newton_solver.cpp index 873aa7abc6..5924649623 100644 --- a/src/newton_solver.cpp +++ b/src/newton_solver.cpp @@ -63,9 +63,9 @@ NewtonSolver::getSolver(const Solver &simulationSolver, const Model *model) { return solver; } -void NewtonSolver::getStep(int ntry, int nnewt, AmiVector &delta, Model *model, +void NewtonSolver::getStep(AmiVector &delta, Model *model, const SimulationState &state) { - prepareLinearSystem(ntry, nnewt, model, state); + prepareLinearSystem(model, state); delta.minus(); solveLinearSystem(delta); @@ -73,7 +73,7 @@ void NewtonSolver::getStep(int ntry, int nnewt, AmiVector &delta, Model *model, void NewtonSolver::computeNewtonSensis(AmiVectorArray &sx, Model *model, const SimulationState &state) { - prepareLinearSystem(0, -1, model, state); + prepareLinearSystem(model, state); model->fdxdotdp(state.t, state.x, state.dx); if (is_singular(model, state)) @@ -108,8 +108,7 @@ NewtonSolverDense::NewtonSolverDense(const Model *model) throw NewtonFailure(status, "SUNLinSolInitialize_Dense"); } -void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/, - Model *model, +void NewtonSolverDense::prepareLinearSystem(Model *model, const SimulationState &state) { model->fJ(state.t, 0.0, state.x, state.dx, xdot_, Jtmp_.get()); Jtmp_.refresh(); @@ -118,8 +117,7 @@ void NewtonSolverDense::prepareLinearSystem(int /*ntry*/, int /*nnewt*/, throw NewtonFailure(status, "SUNLinSolSetup_Dense"); } -void NewtonSolverDense::prepareLinearSystemB(int /*ntry*/, int /*nnewt*/, - Model *model, +void NewtonSolverDense::prepareLinearSystemB(Model *model, const SimulationState &state) { model->fJB(state.t, 0.0, state.x, state.dx, xB_, dxB_, xdot_, Jtmp_.get()); Jtmp_.refresh(); @@ -148,7 +146,7 @@ bool NewtonSolverDense::is_singular(Model *model, // sparse solver interface, not the most efficient solution, but who is // concerned about speed and used the dense solver anyways ¯\_(ツ)_/¯ NewtonSolverSparse sparse_solver(model); - sparse_solver.prepareLinearSystem(0, 0, model, state); + sparse_solver.prepareLinearSystem(model, state); return sparse_solver.is_singular(model, state); } @@ -166,8 +164,7 @@ NewtonSolverSparse::NewtonSolverSparse(const Model *model) throw NewtonFailure(status, "SUNLinSolInitialize_KLU"); } -void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/, - Model *model, +void NewtonSolverSparse::prepareLinearSystem(Model *model, const SimulationState &state) { /* Get sparse Jacobian */ model->fJSparse(state.t, 0.0, state.x, state.dx, xdot_, Jtmp_.get()); @@ -177,8 +174,7 @@ void NewtonSolverSparse::prepareLinearSystem(int /*ntry*/, int /*nnewt*/, throw NewtonFailure(status, "SUNLinSolSetup_KLU"); } -void NewtonSolverSparse::prepareLinearSystemB(int /*ntry*/, int /*nnewt*/, - Model *model, +void NewtonSolverSparse::prepareLinearSystemB(Model *model, const SimulationState &state) { /* Get sparse Jacobian */ model->fJSparseB(state.t, 0.0, state.x, state.dx, xB_, dxB_, xdot_, diff --git a/src/solver.cpp b/src/solver.cpp index a4dfa8c8ed..47cdc219a3 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -33,6 +33,8 @@ Solver::Solver(const Solver &other) quad_rtol_(other.quad_rtol_), ss_atol_(other.ss_atol_), ss_rtol_(other.ss_rtol_), ss_atol_sensi_(other.ss_atol_sensi_), ss_rtol_sensi_(other.ss_rtol_sensi_), rdata_mode_(other.rdata_mode_), + newton_step_steadystate_conv_(other.newton_step_steadystate_conv_), + check_sensi_steadystate_conv_(other.check_sensi_steadystate_conv_), maxstepsB_(other.maxstepsB_), sensi_(other.sensi_) {} @@ -511,6 +513,8 @@ bool operator==(const Solver &a, const Solver &b) { (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_) && + (a.newton_step_steadystate_conv_ == b.newton_step_steadystate_conv_) && + (a.check_sensi_steadystate_conv_ == b.check_sensi_steadystate_conv_) && (a.rdata_mode_ == b.rdata_mode_); } diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index b2e41e4541..5e05864580 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -21,7 +21,8 @@ constexpr realtype conv_thresh = 1.0; namespace amici { SteadystateProblem::SteadystateProblem(const Solver &solver, Model &model) - : delta_(model.nx_solver), ewt_(model.nx_solver), ewtQB_(model.nplist()), + : delta_(model.nx_solver), delta_old_(model.nx_solver), + ewt_(model.nx_solver), ewtQB_(model.nplist()), x_old_(model.nx_solver), xdot_(model.nx_solver), sdx_(model.nx_solver, model.nplist()), xB_(model.nJ * model.nx_solver), xQ_(model.nJ * model.nx_solver), xQB_(model.nplist()), @@ -40,7 +41,9 @@ SteadystateProblem::SteadystateProblem(const Solver &solver, Model &model) rtol_quad_(solver.getRelativeToleranceQuadratures()), newton_solver_(NewtonSolver::getSolver(solver, &model)), damping_factor_mode_(solver.getNewtonDampingFactorMode()), - damping_factor_lower_bound_(solver.getNewtonDampingFactorLowerBound()) { + damping_factor_lower_bound_(solver.getNewtonDampingFactorLowerBound()), + newton_step_conv_(solver.getNewtonStepSteadyStateCheck()), + check_sensi_conv_(solver.getSensiSteadyStateCheck()) { /* Check for compatibility of options */ if (solver.getSensitivityMethod() == SensitivityMethod::forward && solver.getSensitivityMethodPreequilibration() == @@ -195,6 +198,7 @@ void SteadystateProblem::initializeForwardProblem(int it, const Solver *solver, state_.t = model->getTimepoint(it - 1); state_.state = model->getModelState(); + flagUpdatedState(); } bool SteadystateProblem::initializeBackwardProblem(Solver *solver, Model *model, @@ -268,7 +272,7 @@ void SteadystateProblem::getQuadratureByLinSolve(Model *model) { /* try to solve the linear system */ try { /* compute integral over xB and write to xQ */ - newton_solver_->prepareLinearSystemB(0, -1, model, state_); + newton_solver_->prepareLinearSystemB(model, state_); newton_solver_->solveLinearSystem(xQ_); /* Compute the quadrature as the inner product xQ * dxdotdp */ computeQBfromQ(model, xQ_, xQB_); @@ -435,12 +439,21 @@ realtype SteadystateProblem::getWrms(Model *model, to zero at all. So we need xQBdot, hence compute xQB first. */ computeQBfromQ(model, xQ_, xQB_); computeQBfromQ(model, xB_, xQBdot_); + if (newton_step_conv_) + throw NewtonFailure( + AMICI_NOT_IMPLEMENTED, + "Newton type convergence check is not implemented for adjoint " + "steady state computations. Stopping."); wrms = getWrmsNorm(xQB_, xQBdot_, atol_quad_, rtol_quad_, ewtQB_); } else { /* If we're doing a forward simulation (with or without sensitivities: Get RHS and compute weighted error norm */ - model->fxdot(state_.t, state_.x, state_.dx, xdot_); - wrms = getWrmsNorm(state_.x, xdot_, atol_, rtol_, ewt_); + if (newton_step_conv_) + getNewtonStep(*model); + else + updateRightHandSide(*model); + wrms = getWrmsNorm(state_.x, newton_step_conv_ ? delta_ : xdot_, + atol_, rtol_, ewt_); } return wrms; } @@ -448,9 +461,17 @@ realtype SteadystateProblem::getWrms(Model *model, realtype SteadystateProblem::getWrmsFSA(Model *model) { /* Forward sensitivities: Compute weighted error norm for their RHS */ realtype wrms = 0.0; + + /* we don't need to call prepareLinearSystem in this function, since it was + already computed in the preceding getWrms call and both equations have the + same jacobian */ + + xdot_updated_ = false; for (int ip = 0; ip < model->nplist(); ++ip) { model->fsxdot(state_.t, state_.x, state_.dx, ip, state_.sx[ip], state_.dx, xdot_); + if (newton_step_conv_) + newton_solver_->solveLinearSystem(xdot_); wrms = getWrmsNorm(state_.sx[ip], xdot_, atol_sensi_, rtol_sensi_, ewt_); /* ideally this function would report the maximum of all wrms over @@ -474,7 +495,8 @@ bool SteadystateProblem::checkSteadyStateSuccess() const { } void SteadystateProblem::applyNewtonsMethod(Model *model, bool newton_retry) { - int i_newtonstep = 0; + int &i_newtonstep = numsteps_.at(newton_retry ? 2 : 0); + i_newtonstep = 0; gamma_ = 1.0; bool update_direction = true; bool step_successful = false; @@ -485,28 +507,25 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, bool newton_retry) { /* initialize output of linear solver for Newton step */ delta_.zero(); x_old_.copy(state_.x); - + bool converged = false; wrms_ = getWrms(model, SensitivityMethod::none); - bool converged = newton_retry ? false : wrms_ < conv_thresh; + converged = newton_retry ? false : wrms_ < conv_thresh; while (!converged && i_newtonstep < max_steps_) { - /* If Newton steps are necessary, compute the initial search direction - */ + /* If Newton steps are necessary, compute the initial search + direction */ if (update_direction) { - try { - // xdot_ computed in getWrms - delta_.copy(xdot_); - newton_solver_->getStep(newton_retry ? 2 : 1, i_newtonstep, - delta_, model, state_); - } catch (NewtonFailure const &) { - numsteps_.at(newton_retry ? 2 : 0) = i_newtonstep; - throw; - } + getNewtonStep(*model); + /* we store delta_ here as later convergence checks may + update it */ + delta_old_.copy(delta_); } - /* Try a full, undamped Newton step */ - linearSum(1.0, x_old_, gamma_, delta_, state_.x); - + /* Try step with new gamma_/delta_ */ + linearSum(1.0, x_old_, gamma_, + update_direction ? delta_ : delta_old_, state_.x); + flagUpdatedState(); + /* Compute new xdot and residuals */ realtype wrms_tmp = getWrms(model, SensitivityMethod::none); @@ -514,21 +533,20 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, bool newton_retry) { if (step_successful) { /* If new residuals are smaller than old ones, update state */ wrms_ = wrms_tmp; - x_old_.copy(state_.x); - - // precheck convergence + /* precheck convergence */ converged = wrms_ < conv_thresh; if (converged) { converged = makePositiveAndCheckConvergence(model); } + /* update x_old_ _after_ positivity was enforced */ + x_old_.copy(state_.x); } + update_direction = updateDampingFactor(step_successful); /* increase step counter */ i_newtonstep++; } - /* Set return values */ - numsteps_.at(newton_retry ? 2 : 0) = i_newtonstep; if (!converged) throw NewtonFailure(AMICI_TOO_MUCH_WORK, "applyNewtonsMethod"); } @@ -536,20 +554,15 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, bool newton_retry) { bool SteadystateProblem::makePositiveAndCheckConvergence(Model *model) { /* Ensure positivity of the found state and recheck if the convergence still holds */ - bool recheck_convergence = false; - bool converged = true; auto nonnegative = model->getStateIsNonNegative(); for (int ix = 0; ix < model->nx_solver; ix++) { if (state_.x[ix] < 0.0 && nonnegative[ix]) { state_.x[ix] = 0.0; - recheck_convergence = true; + flagUpdatedState(); } } - if (recheck_convergence) { - wrms_ = getWrms(model, SensitivityMethod::none); - converged = wrms_ < conv_thresh; - } - return converged; + wrms_ = getWrms(model, SensitivityMethod::none); + return wrms_ < conv_thresh; } bool SteadystateProblem::updateDampingFactor(bool step_successful) { @@ -612,15 +625,25 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, } else { solver->writeSolution(&state_.t, state_.x, state_.dx, state_.sx, xQ_); + flagUpdatedState(); } - + + try { /* Check for convergence */ wrms_ = getWrms(model, sensitivityFlag); - if (wrms_ < conv_thresh && + /* getWrms needs to be called before getWrmsFSA such that the linear + system is prepared for newton type convergence check */ + if (wrms_ < conv_thresh && check_sensi_conv_ && sensitivityFlag == SensitivityMethod::forward) { - state_.sx = solver->getStateSensitivity(state_.t); + updateSensiSimulation(*solver); wrms_ = getWrmsFSA(model); } + + } catch (NewtonFailure const &) { + /* linear solves in getWrms failed */ + numsteps_.at(1) = sim_steps; + throw; + } if (wrms_ < conv_thresh) break; // converged @@ -638,6 +661,10 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, " point without convergence of RHS"); } } + + // if check_sensi_conv_ is deactivated, we still have to update sensis + if (sensitivityFlag == SensitivityMethod::forward) + updateSensiSimulation(*solver); /* store information about steps and sensitivities, if necessary */ if (backward) { @@ -718,4 +745,32 @@ void SteadystateProblem::getAdjointUpdates(Model &model, const ExpData &edata) { } } +void SteadystateProblem::flagUpdatedState() { + xdot_updated_ = false; + delta_updated_ = false; + sensis_updated_ = false; +} + +void SteadystateProblem::updateSensiSimulation(const Solver &solver) { + if (sensis_updated_) + return; + state_.sx = solver.getStateSensitivity(state_.t); + sensis_updated_ = true; +} + +void SteadystateProblem::updateRightHandSide(Model &model) { + if (xdot_updated_) + return; + model.fxdot(state_.t, state_.x, state_.dx, xdot_); + xdot_updated_ = true; +} + +void SteadystateProblem::getNewtonStep(Model &model) { + if (delta_updated_) + return; + updateRightHandSide(model); + delta_.copy(xdot_); + newton_solver_->getStep(delta_, &model, state_); + delta_updated_ = true; +} } // namespace amici From c095b0059ab239f56706271f915628989ddf5bad Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 25 Mar 2022 16:02:54 +0100 Subject: [PATCH 13/31] Model import: parallelize computation of derivatives (#1740) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Allows to compute derivatives in parallel (using `multiprocessing`). Disabled by default. Enable by setting environment variable `AMICI_IMPORT_NPROCS` to the number of processes to use. For smaller models, the multiprocessing overhead dominates, therefore, this is only advisable to use for larger models (import time of ⪆ 5min). Closes #1739 Tested with https://github.com/Benchmarking-Initiative/Benchmark-Models-PEtab/tree/master/Benchmark-Models/Chen_MSB2009 on 8 core CPU, model import without compilation: | n_procs | walltime m:ss | |---------|---------------| | 1 | 4:25.61 | | 2 | 4:14.30 | | 4 | 3:39.20 | | 8 | 3:37.07 | Tested with https://github.com/ICB-DCM/CS_Signalling_ERBB_RAS_AKT/tree/master/FroehlichKes2018/PEtab: n_procs=1 - walltime (m:ss): 25:05.64 n_procs=2 - walltime (m:ss): 23:25.90 n_procs=8 - walltime (m:ss): 17:29.66 --- .github/workflows/test_performance.yml | 4 ++-- python/amici/ode_export.py | 31 +++++++++++++++++++++----- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test_performance.yml b/.github/workflows/test_performance.yml index 3ee2aad159..1d012ce958 100644 --- a/.github/workflows/test_performance.yml +++ b/.github/workflows/test_performance.yml @@ -4,7 +4,7 @@ on: branches: - develop - master - - compile_without_optimization + - feature_1739_par_jac pull_request: branches: @@ -63,7 +63,7 @@ jobs: # import test model - name: Import test model run: | - check_time.sh petab_import python tests/performance/test.py import + AMICI_IMPORT_NPROCS=2 check_time.sh petab_import python tests/performance/test.py import - name: "Upload artifact: CS_Signalling_ERBB_RAS_AKT_petab" uses: actions/upload-artifact@v1 diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index 8c9fa7e96a..dd96678e30 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -418,15 +418,27 @@ def smart_jacobian(eq: sp.MutableDenseMatrix, :return: jacobian of eq wrt sym_var """ - if min(eq.shape) and min(sym_var.shape) \ - and not smart_is_zero_matrix(eq) \ - and not smart_is_zero_matrix(sym_var): + if ( + not min(eq.shape) + or not min(sym_var.shape) + or smart_is_zero_matrix(eq) + or smart_is_zero_matrix(sym_var) + ): + return sp.zeros(eq.shape[0], sym_var.shape[0]) + + if (n_procs := int(os.environ.get("AMICI_IMPORT_NPROCS", 1))) == 1: + # serial return sp.Matrix([ - eq[i, :].jacobian(sym_var) if eq[i, :].has(*sym_var.flat()) - else [0] * sym_var.shape[0] + _jacobian_row(eq[i, :], sym_var) for i in range(eq.shape[0]) ]) - return sp.zeros(eq.shape[0], sym_var.shape[0]) + + # parallel + from multiprocessing import Pool + with Pool(n_procs) as p: + mapped = p.starmap(_jacobian_row, + ((eq[i, :], sym_var) for i in range(eq.shape[0]))) + return sp.Matrix(mapped) @log_execution_time('running smart_multiply', logger) @@ -3322,3 +3334,10 @@ def _custom_pow_eval_derivative(self, s): (self.base, sp.And(sp.Eq(self.base, 0), sp.Eq(dbase, 0))), (part2, True) ) + + +def _jacobian_row(eq_i, sym_var): + """Compute a row of a jacobian""" + if eq_i.has(*sym_var.flat()): + return eq_i.jacobian(sym_var) + return [0] * sym_var.shape[0] From 3bbf6e409b317249117e660b9c3953bd33f1816c Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 25 Mar 2022 17:31:27 +0100 Subject: [PATCH 14/31] =?UTF-8?q?Remove=20legacy=20options/members=20`amio?= =?UTF-8?q?ption.newton=5Fpreeq`=20and=20`Solver::r=E2=80=A6=20(#1744)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Remove legacy options/members `amioption.newton_preeq` and `Solver::requires_preequilibration_` Removes `amioption.newton_preeq` (Matlab) and `Solver::requires_preequilibration_`, `Solver::{get|set}Preequilibration` (C++, Python), that became obsolete. See discussion in https://github.com/AMICI-dev/AMICI/issues/1148. Closes #1148. --- include/amici/serialization.h | 1 - include/amici/simulation_parameters.h | 2 +- include/amici/solver.h | 17 +----- matlab/@amimodel/generateMatlabWrapper.m | 50 +++++++++--------- matlab/@amioption/amioption.m | 12 ----- matlab/tests/testModels.m | 3 -- models/model_calvetti/model_calvetti.h | 4 +- models/model_dirac/model_dirac.h | 4 +- models/model_events/model_events.h | 4 +- .../model_jakstat_adjoint.h | 4 +- .../model_jakstat_adjoint_o2.h | 4 +- .../model_nested_events/model_nested_events.h | 4 +- models/model_neuron/model_neuron.h | 4 +- models/model_neuron_o2/model_neuron_o2.h | 4 +- models/model_robertson/model_robertson.h | 4 +- models/model_steadystate/model_steadystate.h | 4 +- src/amici.cpp | 3 +- src/hdf5.cpp | 9 ---- src/interface_matlab.cpp | 4 -- src/solver.cpp | 9 +--- tests/cpp/expectedResults.h5 | Bin 4198368 -> 4198368 bytes tests/cpp/unittests/testMisc.cpp | 5 +- tests/cpp/unittests/testSerialization.cpp | 1 - tests/generateTestConfig/example.py | 1 - .../generateTestConfig/example_steadystate.py | 8 --- 25 files changed, 49 insertions(+), 116 deletions(-) diff --git a/include/amici/serialization.h b/include/amici/serialization.h index 4dedd912ca..8af3f0db86 100644 --- a/include/amici/serialization.h +++ b/include/amici/serialization.h @@ -68,7 +68,6 @@ void serialize(Archive &ar, amici::Solver &s, const unsigned int /*version*/) { ar &s.ss_rtol_sensi_; ar &s.maxsteps_; ar &s.maxstepsB_; - ar &s.requires_preequilibration_; ar &s.newton_maxsteps_; ar &s.newton_damping_factor_mode_; ar &s.newton_damping_factor_lower_bound_; diff --git a/include/amici/simulation_parameters.h b/include/amici/simulation_parameters.h index bc5a110e67..fe4bed9cf3 100644 --- a/include/amici/simulation_parameters.h +++ b/include/amici/simulation_parameters.h @@ -120,7 +120,7 @@ class SimulationParameters { /** * @brief Model constants for pre-equilibration * - * Vector of size Model::nk() or empty. Overrides Solver::newton_preeq + * Vector of size Model::nk() or empty. */ std::vector fixedParametersPreequilibration; diff --git a/include/amici/solver.h b/include/amici/solver.h index 4f04ef5fe5..d78176133c 100644 --- a/include/amici/solver.h +++ b/include/amici/solver.h @@ -231,18 +231,6 @@ class Solver { */ void setNewtonMaxSteps(int newton_maxsteps); - /** - * @brief Get if model preequilibration is enabled - * @return - */ - bool getPreequilibration() const; - - /** - * @brief Enable/disable model preequilibration - * @param require_preequilibration - */ - void setPreequilibration(bool require_preequilibration); - /** * @brief Get a state of the damping factor used in the Newton solver * @return @@ -1695,7 +1683,7 @@ class Solver { /** flag to force reInitPostProcessB before next call to solveB */ mutable bool force_reinit_postprocess_B_ {false}; - + /** flag indicating whether sensInit1 was called */ mutable bool sens_initialized_ {false}; @@ -1738,9 +1726,6 @@ class Solver { /** Lower bound of the damping factor. */ realtype newton_damping_factor_lower_bound_ {1e-8}; - /** Enable model preequilibration */ - bool requires_preequilibration_ {false}; - /** linear solver specification */ LinearSolver linsol_ {LinearSolver::KLU}; diff --git a/matlab/@amimodel/generateMatlabWrapper.m b/matlab/@amimodel/generateMatlabWrapper.m index 4ee29db931..b19521db14 100644 --- a/matlab/@amimodel/generateMatlabWrapper.m +++ b/matlab/@amimodel/generateMatlabWrapper.m @@ -32,20 +32,20 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi nytrue = ny; o2flag = o2flag; end - + [commit_hash,branch,url] = getCommitHash(amiciRootDir); if(isempty(commit_hash)) commit_hash = '#'; end - + if(o2flag) nxtrue = amimodelo2.nxtrue; nytrue = amimodelo2.nytrue; end - - + + %% Generation of the simulation file - + fid = fopen(wrapperFilename,'w'); fprintf(fid,['%% simulate_' modelname '.m is the matlab interface to the cvodes mex\n'... '%% which simulates the ordinary differential equation and respective\n'... @@ -142,8 +142,6 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi '%% a value of 0 will disable the newton solver\n'... '%% .newton_maxlinsteps ... maximum linear steps\n'... '%% default value is 100\n'... - '%% .newton_preeq ... preequilibration of system via newton solver\n'... - '%% default value is false\n'... '%% .pscale ... parameter scaling\n'... '%% []: (DEFAULT) use value specified in the model (fallback: ''lin'')\n'... '%% 0 or ''lin'': linear\n'... @@ -165,8 +163,8 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi '%% sol.z ... event output\n'... '%% sol.sz ... sensitivity of event output\n'... ]); - - + + fprintf(fid,['function varargout = simulate_' modelname '(varargin)\n\n']); fprintf(fid,'%% DO NOT CHANGE ANYTHING IN THIS FILE UNLESS YOU ARE VERY SURE ABOUT WHAT YOU ARE DOING\n'); fprintf(fid,'%% MANUAL CHANGES TO THIS FILE CAN RESULT IN WRONG SOLUTIONS AND CRASHING OF MATLAB\n'); @@ -176,7 +174,7 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,' tout=varargin{1};\n'); fprintf(fid,' theta=varargin{2};\n'); fprintf(fid,'end\n'); - + fprintf(fid,'if(nargin>=3)\n'); fprintf(fid,' kappa=varargin{3};\n'); fprintf(fid,'else\n'); @@ -188,12 +186,12 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,' kappa = [];\n'); fprintf(fid,'end\n'); end - + fprintf(fid,['if(length(theta)<' num2str(np) ')\n']); fprintf(fid,' error(''provided parameter vector is too short'');\n'); fprintf(fid,'end\n'); fprintf(fid,'\n'); - + fprintf(fid,'\n'); fprintf(fid,'xscale = [];\n'); fprintf(fid,'if(nargin>=5)\n'); @@ -214,12 +212,12 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,'end\n'); end fprintf(fid,'\n'); - - + + fprintf(fid,'if(isempty(options_ami.pscale))\n'); fprintf(fid,[' options_ami.pscale = ''' pscale ''' ;\n']); fprintf(fid,'end\n'); - + if(o2flag == 2) fprintf(fid,'if(nargin>=6)\n'); fprintf(fid,' chainRuleFactor = getChainRuleFactors(options_ami.pscale, theta, options_ami.sens_ind);\n'); @@ -231,7 +229,7 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,' end\n'); fprintf(fid,'end\n'); end - + if(o2flag) fprintf(fid,'if(nargout>1)\n'); fprintf(fid,' if(nargout>6)\n'); @@ -315,13 +313,13 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,['if(length(kappa)<' num2str(nk) ')\n']); fprintf(fid,' error(''provided condition vector is too short'');\n'); fprintf(fid,'end\n'); - + if(o2flag == 2) fprintf(fid,'if(nargin>=6)\n'); fprintf(fid,' kappa = [kappa(:);v(:)];\n'); fprintf(fid,'end\n'); end - + fprintf(fid,'init = struct();\n'); fprintf(fid,'if(~isempty(options_ami.x0))\n'); fprintf(fid,' if(size(options_ami.x0,2)~=1)\n'); @@ -340,8 +338,8 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,' error(''Number of rows in sx0 field does not agree with number of states!'');\n'); fprintf(fid,' end\n'); fprintf(fid,'end\n'); - - + + if(o2flag) fprintf(fid,'if(options_ami.sensi<2)\n'); fprintf(fid,[' sol = ami_' modelname '(tout,theta(1:' num2str(np) '),kappa(1:' num2str(nk) '),options_ami,plist,xscale,init,data);\n']); @@ -356,11 +354,11 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi else fprintf(fid,['sol = ami_' modelname '(tout,theta(1:' num2str(np) '),kappa(1:' num2str(nk) '),options_ami,plist,xscale,init,data);\n']); end - + if(o2flag) fprintf(fid,'if(options_ami.sensi == 2)\n'); fprintf(fid, ' if(~(options_ami.sensi_meth==2))\n'); - + fprintf(fid,[' sz = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); fprintf(fid,[' ssigmaz = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); fprintf(fid,[' srz = zeros(size(sol.z,1),' num2str(nztrue) ',length(theta(options_ami.sens_ind)));\n']); @@ -418,7 +416,7 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,[' sol.sigmaz = sol.sigmaz(:,1:' num2str(nztrue) ');\n']); fprintf(fid,'end\n'); end - + fprintf(fid,'if(nargout>1)\n'); fprintf(fid,' varargout{1} = sol.status;\n'); fprintf(fid,' varargout{2} = sol.t;\n'); @@ -437,7 +435,7 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,'else\n'); fprintf(fid,' varargout{1} = sol;\n'); fprintf(fid,'end\n'); - + fprintf(fid,'function chainRuleFactors = getChainRuleFactors(pscale, theta, sens_ind)\n'); fprintf(fid,' if(length(pscale) == 1 && length(sens_ind) ~= length(pscale))\n'); fprintf(fid,' chainRuleFactors = arrayfun(@(x, ip) getChainRuleFactor(x, theta(ip)), repmat(pscale, 1, length(sens_ind)), sens_ind);\n'); @@ -460,8 +458,8 @@ function generateMatlabWrapper(nx, ny, np, nk, nz, o2flag, amimodelo2, wrapperFi fprintf(fid,'\n'); fprintf(fid,'end\n'); - + fclose(fid); - + end diff --git a/matlab/@amioption/amioption.m b/matlab/@amioption/amioption.m index 3b4873fc95..2a92dc32e1 100644 --- a/matlab/@amioption/amioption.m +++ b/matlab/@amioption/amioption.m @@ -57,8 +57,6 @@ sx0 = double.empty(); % newton solver: maximum newton steps newton_maxsteps = 40; - % preequilibration of system via newton solver - newton_preeq = false; % mapping of event ouputs to events z2event = double.empty(); % parameter scaling @@ -239,16 +237,6 @@ this.newton_maxsteps = value; end - function this = set.newton_preeq(this,value) - if(isnumeric(value)) - assert(floor(value)==value,'The option newton_preeq must be a logical!') - assert(value<=1,'Only 0 and 1 are valid options for newton_preeq!') - assert(value>=0,'Only 0 and 1 are valid options for newton_preeq!') - else - assert(islogical(value),'The option newton_preeq must have a logical value!') - end - this.newton_preeq = value; - end end methods(Static) diff --git a/matlab/tests/testModels.m b/matlab/tests/testModels.m index 047f86daf2..479dcda199 100644 --- a/matlab/tests/testModels.m +++ b/matlab/tests/testModels.m @@ -71,9 +71,6 @@ function testModels() options = rmfield(options,'kappa'); t = options.ts; options = rmfield(options,'ts'); - if isempty(options.newton_preeq) - options.newton_preeq = false; - end if(isfield(options, 'sx0')) options.sx0 = transpose(options.sx0); end diff --git a/models/model_calvetti/model_calvetti.h b/models/model_calvetti/model_calvetti.h index 10db9447c5..8c4fb709f9 100644 --- a/models/model_calvetti/model_calvetti.h +++ b/models/model_calvetti/model_calvetti.h @@ -1,6 +1,6 @@ #ifndef _amici_model_calvetti_h #define _amici_model_calvetti_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -70,7 +70,7 @@ class Model_model_calvetti : public amici::Model_DAE { amici::Model* clone() const override { return new Model_model_calvetti(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; void fJSparse(SUNMatrixContent_Sparse JSparse, 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) override { JSparse_model_calvetti(JSparse, t, x, p, k, h, cj, dx, w, dwdx); diff --git a/models/model_dirac/model_dirac.h b/models/model_dirac/model_dirac.h index cc6ce2f222..0e3c2ed353 100644 --- a/models/model_dirac/model_dirac.h +++ b/models/model_dirac/model_dirac.h @@ -1,6 +1,6 @@ #ifndef _amici_model_dirac_h #define _amici_model_dirac_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -70,7 +70,7 @@ class Model_model_dirac : public amici::Model_ODE { amici::Model* clone() const override { return new Model_model_dirac(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; 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) override { JSparse_model_dirac(JSparse, t, x, p, k, h, w, dwdx); diff --git a/models/model_events/model_events.h b/models/model_events/model_events.h index 16272b1be0..706028c7ae 100644 --- a/models/model_events/model_events.h +++ b/models/model_events/model_events.h @@ -1,6 +1,6 @@ #ifndef _amici_model_events_h #define _amici_model_events_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -84,7 +84,7 @@ class Model_model_events : public amici::Model_ODE { amici::Model* clone() const override { return new Model_model_events(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; 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) override { JSparse_model_events(JSparse, t, x, p, k, h, w, dwdx); diff --git a/models/model_jakstat_adjoint/model_jakstat_adjoint.h b/models/model_jakstat_adjoint/model_jakstat_adjoint.h index ffd1e7fe34..2c0374e4eb 100644 --- a/models/model_jakstat_adjoint/model_jakstat_adjoint.h +++ b/models/model_jakstat_adjoint/model_jakstat_adjoint.h @@ -1,6 +1,6 @@ #ifndef _amici_model_jakstat_adjoint_h #define _amici_model_jakstat_adjoint_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -73,7 +73,7 @@ class Model_model_jakstat_adjoint : public amici::Model_ODE { amici::Model* clone() const override { return new Model_model_jakstat_adjoint(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; 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) override { JSparse_model_jakstat_adjoint(JSparse, t, x, p, k, h, w, dwdx); diff --git a/models/model_jakstat_adjoint_o2/model_jakstat_adjoint_o2.h b/models/model_jakstat_adjoint_o2/model_jakstat_adjoint_o2.h index bec91a063e..4c7a8b6c42 100644 --- a/models/model_jakstat_adjoint_o2/model_jakstat_adjoint_o2.h +++ b/models/model_jakstat_adjoint_o2/model_jakstat_adjoint_o2.h @@ -1,6 +1,6 @@ #ifndef _amici_model_jakstat_adjoint_o2_h #define _amici_model_jakstat_adjoint_o2_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -73,7 +73,7 @@ class Model_model_jakstat_adjoint_o2 : public amici::Model_ODE { amici::Model* clone() const override { return new Model_model_jakstat_adjoint_o2(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; 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) override { JSparse_model_jakstat_adjoint_o2(JSparse, t, x, p, k, h, w, dwdx); diff --git a/models/model_nested_events/model_nested_events.h b/models/model_nested_events/model_nested_events.h index b40ec0e0fc..22671533a9 100644 --- a/models/model_nested_events/model_nested_events.h +++ b/models/model_nested_events/model_nested_events.h @@ -1,6 +1,6 @@ #ifndef _amici_model_nested_events_h #define _amici_model_nested_events_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -73,7 +73,7 @@ class Model_model_nested_events : public amici::Model_ODE { amici::Model* clone() const override { return new Model_model_nested_events(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; 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) override { JSparse_model_nested_events(JSparse, t, x, p, k, h, w, dwdx); diff --git a/models/model_neuron/model_neuron.h b/models/model_neuron/model_neuron.h index 92a8e71262..e9311ef68e 100644 --- a/models/model_neuron/model_neuron.h +++ b/models/model_neuron/model_neuron.h @@ -1,6 +1,6 @@ #ifndef _amici_model_neuron_h #define _amici_model_neuron_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -87,7 +87,7 @@ class Model_model_neuron : public amici::Model_ODE { amici::Model* clone() const override { return new Model_model_neuron(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; 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) override { JSparse_model_neuron(JSparse, t, x, p, k, h, w, dwdx); diff --git a/models/model_neuron_o2/model_neuron_o2.h b/models/model_neuron_o2/model_neuron_o2.h index 800e76282a..37fbf158a6 100644 --- a/models/model_neuron_o2/model_neuron_o2.h +++ b/models/model_neuron_o2/model_neuron_o2.h @@ -1,6 +1,6 @@ #ifndef _amici_model_neuron_o2_h #define _amici_model_neuron_o2_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -89,7 +89,7 @@ class Model_model_neuron_o2 : public amici::Model_ODE { amici::Model* clone() const override { return new Model_model_neuron_o2(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; 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) override { JSparse_model_neuron_o2(JSparse, t, x, p, k, h, w, dwdx); diff --git a/models/model_robertson/model_robertson.h b/models/model_robertson/model_robertson.h index a1d9a3cfa8..9c5d7a1ab0 100644 --- a/models/model_robertson/model_robertson.h +++ b/models/model_robertson/model_robertson.h @@ -1,6 +1,6 @@ #ifndef _amici_model_robertson_h #define _amici_model_robertson_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -71,7 +71,7 @@ class Model_model_robertson : public amici::Model_DAE { amici::Model* clone() const override { return new Model_model_robertson(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; void fJSparse(SUNMatrixContent_Sparse JSparse, 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) override { JSparse_model_robertson(JSparse, t, x, p, k, h, cj, dx, w, dwdx); diff --git a/models/model_steadystate/model_steadystate.h b/models/model_steadystate/model_steadystate.h index 0238e21c52..964636ccb5 100644 --- a/models/model_steadystate/model_steadystate.h +++ b/models/model_steadystate/model_steadystate.h @@ -1,6 +1,6 @@ #ifndef _amici_model_steadystate_h #define _amici_model_steadystate_h -/* Generated by amiwrap (R2017b) ef4445d1c2b3241a8faa11a25a7729648b56300b */ +/* Generated by amiwrap (R2017b) fee0d5a069aba864a92ded7da50ae68ea7ea43cc */ #include #include #include "amici/defines.h" @@ -70,7 +70,7 @@ class Model_model_steadystate : public amici::Model_ODE { amici::Model* clone() const override { return new Model_model_steadystate(*this); }; - std::string getAmiciCommit() const override { return "ef4445d1c2b3241a8faa11a25a7729648b56300b"; }; + std::string getAmiciCommit() const override { return "fee0d5a069aba864a92ded7da50ae68ea7ea43cc"; }; 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) override { JSparse_model_steadystate(JSparse, t, x, p, k, h, w, dwdx); diff --git a/src/amici.cpp b/src/amici.cpp index ee7bb8fcd1..9ef30c68c5 100644 --- a/src/amici.cpp +++ b/src/amici.cpp @@ -124,8 +124,7 @@ AmiciApplication::runAmiciSimulation(Solver& solver, bool bwd_success = true; try { - if (solver.getPreequilibration() || - (edata && !edata->fixedParametersPreequilibration.empty())) { + if (edata && !edata->fixedParametersPreequilibration.empty()) { ConditionContext cc2( &model, edata, FixedParameterContext::preequilibration ); diff --git a/src/hdf5.cpp b/src/hdf5.cpp index 4a4ffdf388..025ca5c237 100644 --- a/src/hdf5.cpp +++ b/src/hdf5.cpp @@ -718,10 +718,6 @@ void writeSolverSettingsToHDF5(Solver const& solver, H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), "newton_maxsteps", &ibuffer, 1); - ibuffer = static_cast(solver.getPreequilibration()); - H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), - "newton_preeq", &ibuffer, 1); - ibuffer = static_cast(solver.getNewtonDampingFactorMode()); H5LTset_attribute_int(file.getId(), hdf5Location.c_str(), "newton_damping_factor_mode", &ibuffer, 1); @@ -884,11 +880,6 @@ void readSolverSettingsFromHDF5(H5::H5File const& file, Solver &solver, getIntScalarAttribute(file, datasetPath, "newton_maxsteps")); } - if(attributeExists(file, datasetPath, "newton_preeq")) { - solver.setPreequilibration( - getIntScalarAttribute(file, datasetPath, "newton_preeq")); - } - if(attributeExists(file, datasetPath, "newton_damping_factor_mode")) { solver.setNewtonDampingFactorMode( static_cast( diff --git a/src/interface_matlab.cpp b/src/interface_matlab.cpp index 168af5461c..f9d5025c4b 100644 --- a/src/interface_matlab.cpp +++ b/src/interface_matlab.cpp @@ -321,10 +321,6 @@ void setSolverOptions(const mxArray *prhs[], int nrhs, Solver &solver) solver.setStabilityLimitFlag(dbl2int(mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "stldet")))); } - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_preeq")) { - solver.setPreequilibration(dbl2int(mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_preeq")))); - } - if (mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_maxsteps")) { solver.setNewtonMaxSteps(dbl2int(mxGetScalar(mxGetProperty(prhs[RHS_OPTIONS], 0, "newton_maxsteps")))); } diff --git a/src/solver.cpp b/src/solver.cpp index 47cdc219a3..e3e8437204 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -26,7 +26,6 @@ Solver::Solver(const Solver &other) newton_maxsteps_(other.newton_maxsteps_), newton_damping_factor_mode_(other.newton_damping_factor_mode_), newton_damping_factor_lower_bound_(other.newton_damping_factor_lower_bound_), - requires_preequilibration_(other.requires_preequilibration_), linsol_(other.linsol_), atol_(other.atol_), rtol_(other.rtol_), atol_fsa_(other.atol_fsa_), rtol_fsa_(other.rtol_fsa_), atolB_(other.atolB_), rtolB_(other.rtolB_), quad_atol_(other.quad_atol_), @@ -493,7 +492,7 @@ bool operator==(const Solver &a, const Solver &b) { (a.newton_maxsteps_ == b.newton_maxsteps_) && (a.newton_damping_factor_mode_ == b.newton_damping_factor_mode_) && (a.newton_damping_factor_lower_bound_ == b.newton_damping_factor_lower_bound_) && - (a.requires_preequilibration_ == b.requires_preequilibration_) && (a.ism_ == b.ism_) && + (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_) && @@ -631,12 +630,6 @@ void Solver::setNewtonMaxSteps(const int newton_maxsteps) { newton_maxsteps_ = newton_maxsteps; } -bool Solver::getPreequilibration() const { return requires_preequilibration_; } - -void Solver::setPreequilibration(const bool require_preequilibration) { - requires_preequilibration_ = require_preequilibration; -} - NewtonDampingFactorMode Solver::getNewtonDampingFactorMode() const { return newton_damping_factor_mode_; } void Solver::setNewtonDampingFactorMode(NewtonDampingFactorMode dampingFactorMode) { diff --git a/tests/cpp/expectedResults.h5 b/tests/cpp/expectedResults.h5 index 4c406822138f20658567ab8748a5d782e2c185d5..c87ab70dccdf889f63dcdc2f2a48523deab9c198 100644 GIT binary patch delta 1810 zcmaKtYivtl7{}Lh*3;9|ZmXlCY^%C#Y8z^9onAJK)UXwaTZBb~hD8=1M10_6k#!T7 zS;adMaV)|TGndh)=F5DO`9PLQm`j>iWRamF5_}L(FYj5)>cj8b@A*IP)Au=V!&f9G zjI!YBdn`YY!X#XA`=wZnjw`4Xr5&N zEuSi3w234B`mZe1f6d&v8;z%%)W;c;9bx%sST~Ucu9t1k$RLkDu_eT&VgP+2`snN9otCFD9gbUj_XgG37GjY8ke_&OW!$ayc-eIoJ0D z-UPJS*dw-3!4<;zBlw_swXmXtjv5|Gy7-F50|pNqrkcr^_}IG1UulvlYx{4iMJ8pZY1(jEU#f-^mh59cscf2>LuIKT zXRNgcBy-N#3GMq7P2QiR@&394Ifwz7fQZOM#E9e~laR@X37LXSMe?BTK>pBadIImy ze0l=Qmh$8i#E$D9W(p89G7YgH(~%j-Ok@^Ph!i2S5i2qWhMD4E6K!y(rTD)MsshFS zsz3=+ip)jkA@dO%vH&SV79xv~#mEw<3M}2*OdIs&+2al75$}6@DETF96Fp$rTK-u> zx<~B-SgiK45H0hCX&GSIvazSp1J8%;0|%)W+Ekv12YGXOvggNWj9<3#6Lp(cIjGy< zaVRb^fWrrdYDd41nvr|dBxr_6YtTWTZ$P_k0~JHOlSaxqrxMvF4nkL!v*`x)JP#Dl z7E1M;y-qj2^05ybTJv4;ynnX1p~uyk338!pV+)n}+X*@4|0VjXqWnwQA!)fgo;jYj vBa^Iq##TXdwOcqzgF6SY;;+9X4;Ws#Mdfq1TPx^6<&uiz#gt1cD&PMBx@4;^ delta 1885 zcmb`IOH30{7=>qAT85#uSSwbkSYL?sh4?CMDPWWUqE=B*Vzg25fek)ld~GC{SWsz< z8m^ic(S;#O@B#AIx-oI53ysl4Ytl5)1q&q@^D=(dY5liYLef9`*0?w$C} zR!tmSw20!viI0t}*em7s$Bm~nBv(iUVpp%OjYSQfH_Z1-E1!v(gE6xRG!!#y4}DPV zwEK&e`|pVCb*H0>*^QnH%7n`?vm6Y3(^BAD#`c}!1bSJgJFQve6EXxr5Cx-9AV@+> z)A6GRTXq~e($sVq!-7?l&;%hz;!gpd%8J^$)fn5fadYAUfi0|%UUW6+BCIi6e~K<_ z(dm5%xZIQ00QP!7AE9WkZfL7$q33%2t8}ftL1$yJVQpWc1wsqY606VKrcc}U{2TB{ zOP1aG0H%^9#~Dmfodr$0x1FKFj&Ff)H0 z+WG!2{V{*q3HS5EB!#5Z!$*!wQtJT1?^R|6Qw}9XYqI=ajJrF=jyHE^wGJqy23N|d zl^DL_J=9()ue{BZ;zMKfL{db@%HL=UyJ}?%kOsfYZGGH)^;Y(nGd_4HyJkI?{68Qu zTo=R2adg};)RW(5Xq2c}W9Y8trlB>4q=|Pd`wT->T#~M-lCtNR|Na zS&iMJTpGz8mnt!r)p(tH^TyRDb?X$9s+UhSMaY$JQuCa9|49C3QzY1Uzyxx@cwhz- zfCWqhxnL5og2`YC$fL#swsHFfUli+lTbndg$dXQ`3x{_oS^4FKR#2`wb zjO*%GuD7ptt9ntD8*}egyNmX7ZRC-vF=%1t8+5lZ=zHh{xg8sl0g^{8;LrA;0RBE( zdJ@_ss($P_seBJDG(rij`H3fI7oT_0R?6oM^KZq`UpkyY-Lp$Kc5s7qABMkwHG-i$ z#HH2>@4wRR3|9L}yNcRtyz)8j>Uo=V;WzidUb=xMfAD4{&w#VAOilOCE5n68zwGR% E-%xlsg8%>k diff --git a/tests/cpp/unittests/testMisc.cpp b/tests/cpp/unittests/testMisc.cpp index 63197ef441..5b28a5f14c 100644 --- a/tests/cpp/unittests/testMisc.cpp +++ b/tests/cpp/unittests/testMisc.cpp @@ -408,10 +408,7 @@ testSolverGetterSetters(CVodeSolver solver, ASSERT_EQ(static_cast(solver.getLinearMultistepMethod()), static_cast(lmm)); - solver.setPreequilibration(true); - ASSERT_EQ(solver.getPreequilibration(), true); - - solver.setStabilityLimitFlag(true); + solver.setStabilityLimitFlag(true); ASSERT_EQ(solver.getStabilityLimitFlag(), true); ASSERT_THROW(solver.setNewtonMaxSteps(badsteps), AmiException); diff --git a/tests/cpp/unittests/testSerialization.cpp b/tests/cpp/unittests/testSerialization.cpp index 4a96c90c84..b1b5850dee 100644 --- a/tests/cpp/unittests/testSerialization.cpp +++ b/tests/cpp/unittests/testSerialization.cpp @@ -106,7 +106,6 @@ class SolverSerializationTest : public ::testing::Test { solver.setMaxSteps(1e1); solver.setMaxStepsBackwardProblem(1e2); solver.setNewtonMaxSteps(1e3); - solver.setPreequilibration(true); solver.setStateOrdering(static_cast(amici::SUNLinSolKLU::StateOrdering::COLAMD)); solver.setInterpolationType(amici::InterpolationType::polynomial); solver.setStabilityLimitFlag(false); diff --git a/tests/generateTestConfig/example.py b/tests/generateTestConfig/example.py index 03bbeebf0b..6fad77dfa9 100644 --- a/tests/generateTestConfig/example.py +++ b/tests/generateTestConfig/example.py @@ -56,7 +56,6 @@ def __init__(self): 'lmm': 2, 'maxsteps' : 1e4, 'newton_maxsteps' : 40, - 'newton_preeq' : 0, 'nmaxevent' : 10, 'ordering' : 0, 'rtol' : 1e-8, diff --git a/tests/generateTestConfig/example_steadystate.py b/tests/generateTestConfig/example_steadystate.py index fae863b3fc..517508f9b2 100755 --- a/tests/generateTestConfig/example_steadystate.py +++ b/tests/generateTestConfig/example_steadystate.py @@ -104,7 +104,6 @@ def writeSensiFwdNewtonPreeq(filename): ex.modelOptions['ts'] = np.linspace(0, 5, 10) ex.solverOptions['sensi'] = 1 ex.solverOptions['sensi_meth'] = 1 - ex.solverOptions['newton_preeq'] = True ex.solverOptions['atol'] = 10**-16 ex.solverOptions['rtol'] = 10**-12 ex.solverOptions['sens_ind'] = np.arange(0, ex.numP) @@ -140,7 +139,6 @@ def writeSensiAdjNewtonPreeq(filename): ex.modelOptions['ts'] = np.linspace(0, 5, 10) ex.solverOptions['sensi'] = 1 ex.solverOptions['sensi_meth'] = 2 - ex.solverOptions['newton_preeq'] = True ex.solverOptions['atol'] = 10**-16 ex.solverOptions['rtol'] = 10**-12 ex.solverOptions['sens_ind'] = np.arange(0, ex.numP) @@ -176,7 +174,6 @@ def writeSensiFwdSimPreeq(filename): ex.modelOptions['ts'] = np.linspace(0, 5, 10) ex.solverOptions['sensi'] = 1 ex.solverOptions['sensi_meth'] = 1 - ex.solverOptions['newton_preeq'] = True ex.solverOptions['atol'] = 10 ** -16 ex.solverOptions['rtol'] = 10 ** -12 ex.solverOptions['sens_ind'] = np.arange(0, ex.numP) @@ -213,7 +210,6 @@ def writeSensiFwdSimPreeqFSA(filename): ex.modelOptions['steadyStateSensitivityMode'] = 1 ex.solverOptions['sensi'] = 1 ex.solverOptions['sensi_meth'] = 1 - ex.solverOptions['newton_preeq'] = True ex.solverOptions['atol'] = 10 ** -16 ex.solverOptions['rtol'] = 10 ** -12 ex.solverOptions['sens_ind'] = np.arange(0, ex.numP) @@ -249,7 +245,6 @@ def writeSensiAdjSimPreeq(filename): ex.modelOptions['ts'] = np.linspace(0, 5, 10) ex.solverOptions['sensi'] = 1 ex.solverOptions['sensi_meth'] = 2 - ex.solverOptions['newton_preeq'] = True ex.solverOptions['atol'] = 10 ** -16 ex.solverOptions['rtol'] = 10 ** -12 ex.solverOptions['sens_ind'] = np.arange(0, ex.numP) @@ -286,7 +281,6 @@ def writeSensiAdjSimPreeqFSA(filename): ex.modelOptions['steadyStateSensitivityMode'] = 1 ex.solverOptions['sensi'] = 1 ex.solverOptions['sensi_meth'] = 2 - ex.solverOptions['newton_preeq'] = True ex.solverOptions['atol'] = 10 ** -16 ex.solverOptions['rtol'] = 10 ** -12 ex.solverOptions['sens_ind'] = np.arange(0, ex.numP) @@ -303,7 +297,6 @@ def writeSensiFwdByhandPreeq(filename): ex.modelOptions['ts'] = np.linspace(0, 5, 10) ex.solverOptions['sensi'] = 1 ex.solverOptions['sensi_meth'] = 1 - ex.solverOptions['newton_preeq'] = False ex.solverOptions['x0'] = np.array([0.532609637980272, 0.625849232840357, 0.066666666666667]) ex.solverOptions['sx0'] = np.array( [[-0.425457638009506, 0.499939012301881, 0], [-0.375463736779318, -0.999878024603762, -0.000000000000000], @@ -343,7 +336,6 @@ def writeSensiAdjByhandPreeq(filename): ex.modelOptions['ts'] = np.linspace(0, 5, 10) ex.solverOptions['sensi'] = 1 ex.solverOptions['sensi_meth'] = 2 - ex.solverOptions['newton_preeq'] = False ex.solverOptions['x0'] = np.array([0.532609637980272, 0.625849232840357, 0.066666666666667]) ex.solverOptions['sx0'] = np.array( [[-0.425457638009506, 0.499939012301881, 0], [-0.375463736779318, -0.999878024603762, -0.000000000000000], From 3683d3e7ad5cb9c4765f62450da2b01d52b9e2e8 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Fri, 25 Mar 2022 17:46:05 +0100 Subject: [PATCH 15/31] =?UTF-8?q?Add=20`ReturnData::cpu=5Ftime=5Ftotal`=20?= =?UTF-8?q?to=20track=20total=20time=20spent=20in=20runAmiciS=E2=80=A6=20(?= =?UTF-8?q?#1743)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `ReturnData::cpu_time_total` to track total time spent in `runAmiciSimulation` --- include/amici/rdata.h | 3 +++ include/amici/serialization.h | 1 + python/amici/numpy.py | 2 +- python/tests/test_pysb.py | 2 +- src/amici.cpp | 5 +++++ src/hdf5.cpp | 3 +++ 6 files changed, 14 insertions(+), 2 deletions(-) diff --git a/include/amici/rdata.h b/include/amici/rdata.h index a320418163..41d4ceef47 100644 --- a/include/amici/rdata.h +++ b/include/amici/rdata.h @@ -225,6 +225,9 @@ class ReturnData: public ModelDimensions { /** computation time of backward solve [ms] */ double cpu_timeB = 0.0; + /** total CPU time from entering runAmiciSimulation until exiting [ms] */ + double cpu_time_total = 0.0; + /** flags indicating success of steady state solver (preequilibration) */ std::vector preeq_status; diff --git a/include/amici/serialization.h b/include/amici/serialization.h index 8af3f0db86..6cd6e7865b 100644 --- a/include/amici/serialization.h +++ b/include/amici/serialization.h @@ -207,6 +207,7 @@ void serialize(Archive &ar, amici::ReturnData &r, const unsigned int /*version*/ ar &r.order; ar &r.cpu_time; ar &r.cpu_timeB; + ar &r.cpu_time_total; ar &r.preeq_cpu_time; ar &r.preeq_cpu_timeB; ar &r.preeq_status; diff --git a/python/amici/numpy.py b/python/amici/numpy.py index 1dde73aad5..00d6d9ebd8 100644 --- a/python/amici/numpy.py +++ b/python/amici/numpy.py @@ -158,7 +158,7 @@ class ReturnDataView(SwigPtrView): 'posteq_cpu_timeB', 'numsteps', 'numrhsevals', 'numerrtestfails', 'numnonlinsolvconvfails', 'order', 'cpu_time', 'numstepsB', 'numrhsevalsB', 'numerrtestfailsB', - 'numnonlinsolvconvfailsB', 'cpu_timeB' + 'numnonlinsolvconvfailsB', 'cpu_timeB', 'cpu_time_total' ] def __init__(self, rdata: Union[ReturnDataPtr, ReturnData]): diff --git a/python/tests/test_pysb.py b/python/tests/test_pysb.py index 0fd050b596..b55ed5e6ba 100644 --- a/python/tests/test_pysb.py +++ b/python/tests/test_pysb.py @@ -67,7 +67,7 @@ def test_compare_to_sbml_import(pysb_example_presimulation_module, skip_attrs = ['ptr', 'preeq_t', 'numsteps', 'preeq_numsteps', 'numrhsevals', 'numerrtestfails', 'order', 'J', 'xdot', 'preeq_wrms', 'preeq_cpu_time', 'cpu_time', - 'cpu_timeB', 'w'] + 'cpu_timeB', 'cpu_time_total', 'w'] for field in rdata_pysb: if field in skip_attrs: diff --git a/src/amici.cpp b/src/amici.cpp index 9ef30c68c5..0c962c8388 100644 --- a/src/amici.cpp +++ b/src/amici.cpp @@ -104,6 +104,7 @@ AmiciApplication::runAmiciSimulation(Solver& solver, Model& model, bool rethrow) { + auto start_time_total = clock(); solver.startTimer(); /* Applies condition-specific model settings and restores them when going @@ -233,6 +234,10 @@ AmiciApplication::runAmiciSimulation(Solver& solver, preeq.get(), fwd.get(), bwd_success ? bwd.get() : nullptr, posteq.get(), model, solver, edata); + + rdata->cpu_time_total = static_cast(clock() - start_time_total) + * 1000.0 / CLOCKS_PER_SEC; + return rdata; } diff --git a/src/hdf5.cpp b/src/hdf5.cpp index 025ca5c237..9c006dfa1f 100644 --- a/src/hdf5.cpp +++ b/src/hdf5.cpp @@ -452,6 +452,9 @@ void writeReturnDataDiagnosis(const ReturnData &rdata, H5LTset_attribute_double(file.getId(), hdf5Location.c_str(), "cpu_timeB", &rdata.cpu_timeB, 1); + H5LTset_attribute_double(file.getId(), hdf5Location.c_str(), + "cpu_time_total", &rdata.cpu_time_total, 1); + if (!rdata.J.empty()) createAndWriteDouble2DDataset(file, hdf5Location + "/J", rdata.J, rdata.nx, rdata.nx); From 82c5d22bca6794851a3c03573e1ae490f15199ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Fri, 25 Mar 2022 14:05:39 -0400 Subject: [PATCH 16/31] use pass by reference, add consts where possible (steadystateproblem) (#1745) * use pass by reference, add consts where possible * Apply suggestions from code review Co-authored-by: Daniel Weindl * fixup Co-authored-by: Daniel Weindl --- include/amici/newton_solver.h | 30 ++--- include/amici/solver.h | 2 +- include/amici/steadystateproblem.h | 40 +++---- src/amici.cpp | 8 +- src/newton_solver.cpp | 58 +++++----- src/solver.cpp | 2 +- src/steadystateproblem.cpp | 170 +++++++++++++++-------------- 7 files changed, 156 insertions(+), 154 deletions(-) diff --git a/include/amici/newton_solver.h b/include/amici/newton_solver.h index 926ea08842..42ef3a90fa 100644 --- a/include/amici/newton_solver.h +++ b/include/amici/newton_solver.h @@ -29,7 +29,7 @@ class NewtonSolver { * * @param model pointer to the model object */ - explicit NewtonSolver(const Model *model); + explicit NewtonSolver(const Model &model); /** * @brief Factory method to create a NewtonSolver based on linsolType @@ -39,7 +39,7 @@ class NewtonSolver { * @return solver NewtonSolver according to the specified linsolType */ static std::unique_ptr - getSolver(const Solver &simulationSolver, const Model *model); + getSolver(const Solver &simulationSolver, const Model &model); /** * @brief Computes the solution of one Newton iteration @@ -49,7 +49,7 @@ class NewtonSolver { * @param model pointer to the model instance * @param state current simulation state */ - void getStep(AmiVector &delta, Model *model, const SimulationState &state); + void getStep(AmiVector &delta, Model &model, const SimulationState &state); /** * @brief Computes steady state sensitivities @@ -58,7 +58,7 @@ class NewtonSolver { * @param model pointer to the model instance * @param state current simulation state */ - void computeNewtonSensis(AmiVectorArray &sx, Model *model, + void computeNewtonSensis(AmiVectorArray &sx, Model &model, const SimulationState &state); /** @@ -68,7 +68,7 @@ class NewtonSolver { * @param model pointer to the model instance * @param state current simulation state */ - virtual void prepareLinearSystem(Model *model, + virtual void prepareLinearSystem(Model &model, const SimulationState &state) = 0; /** @@ -78,7 +78,7 @@ class NewtonSolver { * @param model pointer to the model instance * @param state current simulation state */ - virtual void prepareLinearSystemB(Model *model, + virtual void prepareLinearSystemB(Model &model, const SimulationState &state) = 0; /** @@ -103,7 +103,7 @@ class NewtonSolver { * @return boolean indicating whether the linear system is singular * (condition number < 1/machine precision) */ - virtual bool is_singular(Model *model, + virtual bool is_singular(Model &model, const SimulationState &state) const = 0; virtual ~NewtonSolver() = default; @@ -133,7 +133,7 @@ class NewtonSolverDense : public NewtonSolver { * * @param model model instance that provides problem dimensions */ - explicit NewtonSolverDense(const Model *model); + explicit NewtonSolverDense(const Model &model); NewtonSolverDense(const NewtonSolverDense &) = delete; @@ -143,15 +143,15 @@ class NewtonSolverDense : public NewtonSolver { void solveLinearSystem(AmiVector &rhs) override; - void prepareLinearSystem(Model *model, + void prepareLinearSystem(Model &model, const SimulationState &state) override; - void prepareLinearSystemB(Model *model, + void prepareLinearSystemB(Model &model, const SimulationState &state) override; void reinitialize() override; - bool is_singular(Model *model, const SimulationState &state) const override; + bool is_singular(Model &model, const SimulationState &state) const override; private: /** temporary storage of Jacobian */ @@ -174,7 +174,7 @@ class NewtonSolverSparse : public NewtonSolver { * * @param model model instance that provides problem dimensions */ - explicit NewtonSolverSparse(const Model *model); + explicit NewtonSolverSparse(const Model &model); NewtonSolverSparse(const NewtonSolverSparse &) = delete; @@ -184,13 +184,13 @@ class NewtonSolverSparse : public NewtonSolver { void solveLinearSystem(AmiVector &rhs) override; - void prepareLinearSystem(Model *model, + void prepareLinearSystem(Model &model, const SimulationState &state) override; - void prepareLinearSystemB(Model *model, + void prepareLinearSystemB(Model &model, const SimulationState &state) override; - bool is_singular(Model *model, const SimulationState &state) const override; + bool is_singular(Model &model, const SimulationState &state) const override; void reinitialize() override; diff --git a/include/amici/solver.h b/include/amici/solver.h index d78176133c..edb761a82f 100644 --- a/include/amici/solver.h +++ b/include/amici/solver.h @@ -147,7 +147,7 @@ class Solver { * * @param model pointer to the model instance */ - void updateAndReinitStatesAndSensitivities(Model *model); + void updateAndReinitStatesAndSensitivities(Model *model) const; /** * getRootInfo extracts information which event occurred diff --git a/include/amici/steadystateproblem.h b/include/amici/steadystateproblem.h index ebeca17f88..e3c91b9644 100644 --- a/include/amici/steadystateproblem.h +++ b/include/amici/steadystateproblem.h @@ -29,7 +29,7 @@ class SteadystateProblem { * @param solver Solver instance * @param model Model instance */ - explicit SteadystateProblem(const Solver &solver, Model &model); + explicit SteadystateProblem(const Solver &solver, const Model &model); /** * @brief Handles steady state computation in the forward case: @@ -39,7 +39,7 @@ class SteadystateProblem { * @param model pointer to the model object * @param it integer with the index of the current time step */ - void workSteadyStateProblem(Solver *solver, Model *model, int it); + void workSteadyStateProblem(const Solver &solver, Model &model, int it); /** * Integrates over the adjoint state backward in time by solving a linear @@ -49,7 +49,7 @@ class SteadystateProblem { * @param model pointer to the model object * @param bwd backward problem */ - void workSteadyStateBackwardProblem(Solver *solver, Model *model, + void workSteadyStateBackwardProblem(const Solver &solver, Model &model, const BackwardProblem *bwd); /** @@ -166,14 +166,14 @@ class SteadystateProblem { * @param model pointer to the model object * @param it integer with the index of the current time step */ - void findSteadyState(const Solver *solver, Model *model, int it); + void findSteadyState(const Solver &solver, Model &model, int it); /** * @brief Tries to determine the steady state by using Newton's method * @param model pointer to the model object * @param newton_retry bool flag indicating whether being relaunched */ - void findSteadyStateByNewtonsMethod(Model *model, bool newton_retry); + void findSteadyStateByNewtonsMethod(Model &model, bool newton_retry); /** * @brief Tries to determine the steady state by using forward simulation @@ -181,7 +181,7 @@ class SteadystateProblem { * @param model pointer to the model object * @param it integer with the index of the current time step */ - void findSteadyStateBySimulation(const Solver *solver, Model *model, + void findSteadyStateBySimulation(const Solver &solver, Model &model, int it); /** @@ -190,14 +190,14 @@ class SteadystateProblem { * @param solver pointer to the solver object * @param model pointer to the model object */ - void computeSteadyStateQuadrature(const Solver *solver, Model *model); + void computeSteadyStateQuadrature(const Solver &solver, Model &model); /** * @brief Computes the quadrature in steady state backward mode by * solving the linear system defined by the backward Jacobian * @param model pointer to the model object */ - void getQuadratureByLinSolve(Model *model); + void getQuadratureByLinSolve(Model &model); /** * @brief Computes the quadrature in steady state backward mode by @@ -205,7 +205,7 @@ class SteadystateProblem { * @param solver pointer to the solver object * @param model pointer to the model object */ - void getQuadratureBySimulation(const Solver *solver, Model *model); + void getQuadratureBySimulation(const Solver &solver, Model &model); /** * @brief Stores state and throws an exception if equilibration failed @@ -230,7 +230,7 @@ class SteadystateProblem { * @param context SteadyStateContext giving the situation for the flag * @return flag telling how to process state sensitivities */ - bool getSensitivityFlag(const Model *model, const Solver *solver, int it, + bool getSensitivityFlag(const Model &model, const Solver &solver, int it, SteadyStateContext context); /** @@ -254,14 +254,14 @@ class SteadystateProblem { * @param sensi_method sensitivity method * @return weighted root mean squared residuals of the RHS */ - realtype getWrms(Model *model, SensitivityMethod sensi_method); + realtype getWrms(Model &model, SensitivityMethod sensi_method); /** * @brief Checks convergence for state sensitivities * @param model Model instance * @return weighted root mean squared residuals of the RHS */ - realtype getWrmsFSA(Model *model); + realtype getWrmsFSA(Model &model); /** * @brief Runs the Newton solver iterations and checks for convergence @@ -269,7 +269,7 @@ class SteadystateProblem { * @param model pointer to the model object * @param newton_retry flag indicating if Newton solver is rerun */ - void applyNewtonsMethod(Model *model, bool newton_retry); + void applyNewtonsMethod(Model &model, bool newton_retry); /** * @brief Simulation is launched, if Newton solver or linear system solve @@ -278,7 +278,7 @@ class SteadystateProblem { * @param model pointer to the model object * @param backward flag indicating adjoint mode (including quadrature) */ - void runSteadystateSimulation(const Solver *solver, Model *model, + void runSteadystateSimulation(const Solver &solver, Model &model, bool backward); /** @@ -289,8 +289,8 @@ class SteadystateProblem { * @param backward flag switching on quadratures computation * @return solver instance */ - std::unique_ptr createSteadystateSimSolver(const Solver *solver, - Model *model, + std::unique_ptr createSteadystateSimSolver(const Solver &solver, + Model &model, bool forwardSensis, bool backward) const; @@ -300,7 +300,7 @@ class SteadystateProblem { * @param solver pointer to the solver object * @param model pointer to the model object */ - void initializeForwardProblem(int it, const Solver *solver, Model *model); + void initializeForwardProblem(int it, const Solver &solver, Model &model); /** * @brief Initialize backward computation @@ -309,7 +309,7 @@ class SteadystateProblem { * @param bwd pointer to backward problem * @return flag indicating whether backward computation to be carried out */ - bool initializeBackwardProblem(Solver *solver, Model *model, + bool initializeBackwardProblem(const Solver &solver, Model &model, const BackwardProblem *bwd); /** @@ -319,7 +319,7 @@ class SteadystateProblem { * @param yQ vector to be multiplied with dxdotdp * @param yQB resulting vector after multiplication */ - void computeQBfromQ(Model *model, const AmiVector &yQ, + void computeQBfromQ(Model &model, const AmiVector &yQ, AmiVector &yQB) const; /** @@ -327,7 +327,7 @@ class SteadystateProblem { * check, if necessary * @param model pointer to the model object */ - bool makePositiveAndCheckConvergence(Model *model); + bool makePositiveAndCheckConvergence(Model &model); /** * @brief Updates the damping factor gamma that determines step size diff --git a/src/amici.cpp b/src/amici.cpp index 0c962c8388..5be90dad00 100644 --- a/src/amici.cpp +++ b/src/amici.cpp @@ -131,7 +131,7 @@ AmiciApplication::runAmiciSimulation(Solver& solver, ); preeq = std::make_unique(solver, model); - preeq->workSteadyStateProblem(&solver, &model, -1); + preeq->workSteadyStateProblem(solver, model, -1); } @@ -142,7 +142,7 @@ AmiciApplication::runAmiciSimulation(Solver& solver, if (fwd->getCurrentTimeIteration() < model.nt()) { posteq = std::make_unique(solver, model); - posteq->workSteadyStateProblem(&solver, &model, + posteq->workSteadyStateProblem(solver, model, fwd->getCurrentTimeIteration()); } @@ -151,7 +151,7 @@ AmiciApplication::runAmiciSimulation(Solver& solver, fwd->getAdjointUpdates(model, *edata); if (posteq) { posteq->getAdjointUpdates(model, *edata); - posteq->workSteadyStateBackwardProblem(&solver, &model, + posteq->workSteadyStateBackwardProblem(solver, model, bwd.get()); } @@ -165,7 +165,7 @@ AmiciApplication::runAmiciSimulation(Solver& solver, if (preeq) { ConditionContext cc2(&model, edata, FixedParameterContext::preequilibration); - preeq->workSteadyStateBackwardProblem(&solver, &model, + preeq->workSteadyStateBackwardProblem(solver, model, bwd.get()); } } diff --git a/src/newton_solver.cpp b/src/newton_solver.cpp index 5924649623..4e57e516f8 100644 --- a/src/newton_solver.cpp +++ b/src/newton_solver.cpp @@ -13,12 +13,12 @@ namespace amici { -NewtonSolver::NewtonSolver(const Model *model) - : xdot_(model->nx_solver), x_(model->nx_solver), - xB_(model->nJ * model->nx_solver), dxB_(model->nJ * model->nx_solver) {} +NewtonSolver::NewtonSolver(const Model &model) + : xdot_(model.nx_solver), x_(model.nx_solver), + xB_(model.nJ * model.nx_solver), dxB_(model.nJ * model.nx_solver) {} std::unique_ptr -NewtonSolver::getSolver(const Solver &simulationSolver, const Model *model) { +NewtonSolver::getSolver(const Solver &simulationSolver, const Model &model) { std::unique_ptr solver; @@ -63,7 +63,7 @@ NewtonSolver::getSolver(const Solver &simulationSolver, const Model *model) { return solver; } -void NewtonSolver::getStep(AmiVector &delta, Model *model, +void NewtonSolver::getStep(AmiVector &delta, Model &model, const SimulationState &state) { prepareLinearSystem(model, state); @@ -71,55 +71,55 @@ void NewtonSolver::getStep(AmiVector &delta, Model *model, solveLinearSystem(delta); } -void NewtonSolver::computeNewtonSensis(AmiVectorArray &sx, Model *model, +void NewtonSolver::computeNewtonSensis(AmiVectorArray &sx, Model &model, const SimulationState &state) { prepareLinearSystem(model, state); - model->fdxdotdp(state.t, state.x, state.dx); + model.fdxdotdp(state.t, state.x, state.dx); if (is_singular(model, state)) - model->app->warningF("AMICI:newton", + model.app->warningF("AMICI:newton", "Jacobian is singular at steadystate, " "sensitivities may be inaccurate"); - if (model->pythonGenerated) { - for (int ip = 0; ip < model->nplist(); ip++) { + if (model.pythonGenerated) { + for (int ip = 0; ip < model.nplist(); ip++) { N_VConst(0.0, sx.getNVector(ip)); - model->get_dxdotdp_full().scatter(model->plist(ip), -1.0, nullptr, + model.get_dxdotdp_full().scatter(model.plist(ip), -1.0, nullptr, gsl::make_span(sx.getNVector(ip)), 0, nullptr, 0); solveLinearSystem(sx[ip]); } } else { - for (int ip = 0; ip < model->nplist(); ip++) { - for (int ix = 0; ix < model->nx_solver; ix++) - sx.at(ix, ip) = -model->get_dxdotdp().at(ix, ip); + for (int ip = 0; ip < model.nplist(); ip++) { + for (int ix = 0; ix < model.nx_solver; ix++) + sx.at(ix, ip) = -model.get_dxdotdp().at(ix, ip); solveLinearSystem(sx[ip]); } } } -NewtonSolverDense::NewtonSolverDense(const Model *model) - : NewtonSolver(model), Jtmp_(model->nx_solver, model->nx_solver), +NewtonSolverDense::NewtonSolverDense(const Model &model) + : NewtonSolver(model), Jtmp_(model.nx_solver, model.nx_solver), linsol_(SUNLinSol_Dense(x_.getNVector(), Jtmp_.get())) { auto status = SUNLinSolInitialize_Dense(linsol_); if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolInitialize_Dense"); } -void NewtonSolverDense::prepareLinearSystem(Model *model, +void NewtonSolverDense::prepareLinearSystem(Model &model, const SimulationState &state) { - model->fJ(state.t, 0.0, state.x, state.dx, xdot_, Jtmp_.get()); + model.fJ(state.t, 0.0, state.x, state.dx, xdot_, Jtmp_.get()); Jtmp_.refresh(); auto status = SUNLinSolSetup_Dense(linsol_, Jtmp_.get()); if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_Dense"); } -void NewtonSolverDense::prepareLinearSystemB(Model *model, +void NewtonSolverDense::prepareLinearSystemB(Model &model, const SimulationState &state) { - model->fJB(state.t, 0.0, state.x, state.dx, xB_, dxB_, xdot_, Jtmp_.get()); + model.fJB(state.t, 0.0, state.x, state.dx, xB_, dxB_, xdot_, Jtmp_.get()); Jtmp_.refresh(); auto status = SUNLinSolSetup_Dense(linsol_, Jtmp_.get()); if (status != SUNLS_SUCCESS) @@ -140,7 +140,7 @@ void NewtonSolverDense::reinitialize(){ /* dense solver does not need reinitialization */ }; -bool NewtonSolverDense::is_singular(Model *model, +bool NewtonSolverDense::is_singular(Model &model, const SimulationState &state) const { // dense solver doesn't have any implementation for rcond/condest, so use // sparse solver interface, not the most efficient solution, but who is @@ -155,29 +155,29 @@ NewtonSolverDense::~NewtonSolverDense() { SUNLinSolFree_Dense(linsol_); } -NewtonSolverSparse::NewtonSolverSparse(const Model *model) +NewtonSolverSparse::NewtonSolverSparse(const Model &model) : NewtonSolver(model), - Jtmp_(model->nx_solver, model->nx_solver, model->nnz, CSC_MAT), + Jtmp_(model.nx_solver, model.nx_solver, model.nnz, CSC_MAT), linsol_(SUNKLU(x_.getNVector(), Jtmp_.get())) { auto status = SUNLinSolInitialize_KLU(linsol_); if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolInitialize_KLU"); } -void NewtonSolverSparse::prepareLinearSystem(Model *model, +void NewtonSolverSparse::prepareLinearSystem(Model &model, const SimulationState &state) { /* Get sparse Jacobian */ - model->fJSparse(state.t, 0.0, state.x, state.dx, xdot_, Jtmp_.get()); + model.fJSparse(state.t, 0.0, state.x, state.dx, xdot_, Jtmp_.get()); Jtmp_.refresh(); auto status = SUNLinSolSetup_KLU(linsol_, Jtmp_.get()); if (status != SUNLS_SUCCESS) throw NewtonFailure(status, "SUNLinSolSetup_KLU"); } -void NewtonSolverSparse::prepareLinearSystemB(Model *model, +void NewtonSolverSparse::prepareLinearSystemB(Model &model, const SimulationState &state) { /* Get sparse Jacobian */ - model->fJSparseB(state.t, 0.0, state.x, state.dx, xB_, dxB_, xdot_, + model.fJSparseB(state.t, 0.0, state.x, state.dx, xB_, dxB_, xdot_, Jtmp_.get()); Jtmp_.refresh(); auto status = SUNLinSolSetup_KLU(linsol_, Jtmp_.get()); @@ -203,8 +203,8 @@ void NewtonSolverSparse::reinitialize() { throw NewtonFailure(status, "SUNLinSol_KLUReInit"); } -bool NewtonSolverSparse::is_singular(Model * /*model*/, - const SimulationState & /*state*/) const { +bool NewtonSolverSparse::is_singular(Model& /*model*/, + const SimulationState& /*state*/) const { // adapted from SUNLinSolSetup_KLU in sunlinsol/klu/sunlinsol_klu.c auto content = (SUNLinearSolverContent_KLU)(linsol_->content); // first cheap check via rcond diff --git a/src/solver.cpp b/src/solver.cpp index e3e8437204..05e8d8d8b2 100644 --- a/src/solver.cpp +++ b/src/solver.cpp @@ -224,7 +224,7 @@ void Solver::setupSteadystate(const realtype t0, Model *model, const AmiVector & model->writeSteadystateJB(t0, 0, x0, dx0, xB0, dxB0, xB0); } -void Solver::updateAndReinitStatesAndSensitivities(Model *model) { +void Solver::updateAndReinitStatesAndSensitivities(Model *model) const { model->fx0_fixedParameters(x_); reInit(t_, x_, dx_); diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index 5e05864580..88eb69ad8f 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -20,7 +20,7 @@ constexpr realtype conv_thresh = 1.0; namespace amici { -SteadystateProblem::SteadystateProblem(const Solver &solver, Model &model) +SteadystateProblem::SteadystateProblem(const Solver &solver, const Model &model) : delta_(model.nx_solver), delta_old_(model.nx_solver), ewt_(model.nx_solver), ewtQB_(model.nplist()), x_old_(model.nx_solver), xdot_(model.nx_solver), @@ -39,7 +39,7 @@ SteadystateProblem::SteadystateProblem(const Solver &solver, Model &model) rtol_sensi_(solver.getRelativeToleranceSteadyStateSensi()), atol_quad_(solver.getAbsoluteToleranceQuadratures()), rtol_quad_(solver.getRelativeToleranceQuadratures()), - newton_solver_(NewtonSolver::getSolver(solver, &model)), + newton_solver_(NewtonSolver::getSolver(solver, model)), damping_factor_mode_(solver.getNewtonDampingFactorMode()), damping_factor_lower_bound_(solver.getNewtonDampingFactorLowerBound()), newton_step_conv_(solver.getNewtonStepSteadyStateCheck()), @@ -54,7 +54,8 @@ SteadystateProblem::SteadystateProblem(const Solver &solver, Model &model) "sensitivities during simulation"); } -void SteadystateProblem::workSteadyStateProblem(Solver *solver, Model *model, +void SteadystateProblem::workSteadyStateProblem(const Solver &solver, + Model &model, int it) { initializeForwardProblem(it, solver, model); @@ -79,7 +80,7 @@ void SteadystateProblem::workSteadyStateProblem(Solver *solver, Model *model, } void SteadystateProblem::workSteadyStateBackwardProblem( - Solver *solver, Model *model, const BackwardProblem *bwd) { + const Solver &solver, Model &model, const BackwardProblem *bwd) { if (!initializeBackwardProblem(solver, model, bwd)) return; @@ -90,7 +91,7 @@ void SteadystateProblem::workSteadyStateBackwardProblem( cpu_timeB_ = (double)((clock() - starttime) * 1000) / CLOCKS_PER_SEC; } -void SteadystateProblem::findSteadyState(const Solver *solver, Model *model, +void SteadystateProblem::findSteadyState(const Solver &solver, Model &model, int it) { steady_state_status_.resize(3, SteadyStateStatus::not_run); @@ -110,7 +111,7 @@ void SteadystateProblem::findSteadyState(const Solver *solver, Model *model, handleSteadyStateFailure(); } -void SteadystateProblem::findSteadyStateByNewtonsMethod(Model *model, +void SteadystateProblem::findSteadyStateByNewtonsMethod(Model &model, bool newton_retry) { int ind = newton_retry ? 2 : 0; try { @@ -139,8 +140,8 @@ void SteadystateProblem::findSteadyStateByNewtonsMethod(Model *model, } } -void SteadystateProblem::findSteadyStateBySimulation(const Solver *solver, - Model *model, int it) { +void SteadystateProblem::findSteadyStateBySimulation(const Solver &solver, + Model &model, int it) { try { if (it < 0) { /* Preequilibration? -> Create a new solver instance for sim */ @@ -148,7 +149,7 @@ void SteadystateProblem::findSteadyStateBySimulation(const Solver *solver, model, solver, it, SteadyStateContext::solverCreation); auto newtonSimSolver = createSteadystateSimSolver( solver, model, integrateSensis, false); - runSteadystateSimulation(newtonSimSolver.get(), model, false); + runSteadystateSimulation(*newtonSimSolver, model, false); } else { /* Solver was already created, use this one */ runSteadystateSimulation(solver, model, false); @@ -164,62 +165,63 @@ void SteadystateProblem::findSteadyStateBySimulation(const Solver *solver, SteadyStateStatus::failed_too_long_simulation; break; default: - model->app->warningF("AMICI:newton", + model.app->warningF("AMICI:newton", "AMICI newton method failed: %s\n", ex.what()); steady_state_status_[1] = SteadyStateStatus::failed; } } catch (AmiException const &ex) { - model->app->warningF("AMICI:equilibration", + model.app->warningF("AMICI:equilibration", "AMICI equilibration failed: %s\n", ex.what()); steady_state_status_[1] = SteadyStateStatus::failed; } } -void SteadystateProblem::initializeForwardProblem(int it, const Solver *solver, - Model *model) { +void SteadystateProblem::initializeForwardProblem(int it, const Solver &solver, + Model &model) { newton_solver_->reinitialize(); /* process solver handling for pre- or postequilibration */ if (it == -1) { /* solver was not run before, set up everything */ - model->initialize(state_.x, state_.dx, state_.sx, sdx_, - solver->getSensitivityOrder() >= + model.initialize(state_.x, state_.dx, state_.sx, sdx_, + solver.getSensitivityOrder() >= SensitivityOrder::first); - state_.t = model->t0(); - solver->setup(state_.t, model, state_.x, state_.dx, state_.sx, sdx_); + state_.t = model.t0(); + solver.setup(state_.t, &model, state_.x, state_.dx, state_.sx, sdx_); } else { /* solver was run before, extract current state from solver */ - solver->writeSolution(&state_.t, state_.x, state_.dx, state_.sx, xQ_); + solver.writeSolution(&state_.t, state_.x, state_.dx, state_.sx, xQ_); } /* overwrite starting timepoint */ if (it < 1) /* No previous time point computed, set t = t0 */ - state_.t = model->t0(); + state_.t = model.t0(); else /* Carry on simulating from last point */ - state_.t = model->getTimepoint(it - 1); + state_.t = model.getTimepoint(it - 1); - state_.state = model->getModelState(); + state_.state = model.getModelState(); flagUpdatedState(); } -bool SteadystateProblem::initializeBackwardProblem(Solver *solver, Model *model, +bool SteadystateProblem::initializeBackwardProblem(const Solver &solver, + Model &model, const BackwardProblem *bwd) { newton_solver_->reinitialize(); /* note that state_ is still set from forward run */ if (bwd) { /* preequilibration */ - if (solver->getSensitivityMethodPreequilibration() != + if (solver.getSensitivityMethodPreequilibration() != SensitivityMethod::adjoint) return false; /* if not adjoint mode, there's nothing to do */ /* If we need to reinitialize solver states, this won't work yet. */ - if (model->nx_reinit() > 0) + if (model.nx_reinit() > 0) throw NewtonFailure( AMICI_NOT_IMPLEMENTED, "Adjoint preequilibration with reinitialization of " "non-constant states is not yet implemented. Stopping."); - solver->reInit(state_.t, state_.x, state_.dx); - solver->updateAndReinitStatesAndSensitivities(model); + solver.reInit(state_.t, state_.x, state_.dx); + solver.updateAndReinitStatesAndSensitivities(&model); xB_.copy(bwd->getAdjointState()); } /* postequilibration does not need a reInit */ @@ -232,8 +234,8 @@ bool SteadystateProblem::initializeBackwardProblem(Solver *solver, Model *model, return true; } -void SteadystateProblem::computeSteadyStateQuadrature(const Solver *solver, - Model *model) { +void SteadystateProblem::computeSteadyStateQuadrature(const Solver &solver, + Model &model) { /* This routine computes the quadratures: xQB = Integral[ xB(x(t), t, p) * dxdot/dp(x(t), t, p) | dt ] As we're in steady state, we have x(t) = x_ss (x_steadystate), hence @@ -256,7 +258,7 @@ void SteadystateProblem::computeSteadyStateQuadrature(const Solver *solver, "and numerical integration did not equilibrate within maxsteps"); } -void SteadystateProblem::getQuadratureByLinSolve(Model *model) { +void SteadystateProblem::getQuadratureByLinSolve(Model &model) { /* Computes the integral over the adjoint state xB: If the Jacobian has full rank, this has an analytical solution, since d/dt[ xB(t) ] = JB^T(x(t), p) xB(t) = JB^T(x_ss, p) xB(t) @@ -286,14 +288,14 @@ void SteadystateProblem::getQuadratureByLinSolve(Model *model) { } } -void SteadystateProblem::getQuadratureBySimulation(const Solver *solver, - Model *model) { +void SteadystateProblem::getQuadratureBySimulation(const Solver &solver, + Model &model) { /* If the Jacobian is singular, the integral over xB must be computed by usual integration over time, but simplifications can be applied: x is not time dependent, no forward trajectory is needed. */ /* set starting timepoint for the simulation solver */ - state_.t = model->t0(); + state_.t = model.t0(); /* xQ was written in getQuadratureByLinSolve() -> set to zero */ xQ_.zero(); @@ -302,7 +304,7 @@ void SteadystateProblem::getQuadratureBySimulation(const Solver *solver, /* perform integration and quadrature */ try { - runSteadystateSimulation(simSolver.get(), model, true); + runSteadystateSimulation(*simSolver, model, true); hasQuadrature_ = true; } catch (NewtonFailure const &) { hasQuadrature_ = false; @@ -348,8 +350,8 @@ void SteadystateProblem::writeErrorString(std::string *errorString, } } -bool SteadystateProblem::getSensitivityFlag(const Model *model, - const Solver *solver, int it, +bool SteadystateProblem::getSensitivityFlag(const Model &model, + const Solver &solver, int it, SteadyStateContext context) { /* We need to check whether we need to compute forward sensitivities. Depending on the situation (pre-/postequilibration) and the solver @@ -362,9 +364,9 @@ bool SteadystateProblem::getSensitivityFlag(const Model *model, /* Have we maybe already computed forward sensitivities? */ bool forwardSensisAlreadyComputed = - solver->getSensitivityOrder() >= SensitivityOrder::first && + solver.getSensitivityOrder() >= SensitivityOrder::first && steady_state_status_[1] == SteadyStateStatus::success && - model->getSteadyStateSensitivityMode() == + model.getSteadyStateSensitivityMode() == SteadyStateSensitivityMode::simulationFSA; bool simulationStartedInSteadystate = @@ -374,15 +376,15 @@ bool SteadystateProblem::getSensitivityFlag(const Model *model, /* Do we need forward sensis for postequilibration? */ bool needForwardSensisPosteq = !preequilibration && !forwardSensisAlreadyComputed && - solver->getSensitivityOrder() >= SensitivityOrder::first && - solver->getSensitivityMethod() == SensitivityMethod::forward; + solver.getSensitivityOrder() >= SensitivityOrder::first && + solver.getSensitivityMethod() == SensitivityMethod::forward; /* Do we need forward sensis for preequilibration? */ bool needForwardSensisPreeq = preequilibration && !forwardSensisAlreadyComputed && - solver->getSensitivityMethodPreequilibration() == + solver.getSensitivityMethodPreequilibration() == SensitivityMethod::forward && - solver->getSensitivityOrder() >= SensitivityOrder::first; + solver.getSensitivityOrder() >= SensitivityOrder::first; /* Do we need to do the linear system solve to get forward sensitivities? */ bool needForwardSensisNewton = @@ -391,7 +393,7 @@ bool SteadystateProblem::getSensitivityFlag(const Model *model, /* When we're creating a new solver object */ bool needForwardSensiAtCreation = - needForwardSensisPreeq && model->getSteadyStateSensitivityMode() == + needForwardSensisPreeq && model.getSteadyStateSensitivityMode() == SteadyStateSensitivityMode::simulationFSA; /* Check if we need to store sensis */ @@ -430,7 +432,7 @@ realtype SteadystateProblem::getWrmsNorm(const AmiVector &x, ewt.getNVector()); } -realtype SteadystateProblem::getWrms(Model *model, +realtype SteadystateProblem::getWrms(Model &model, SensitivityMethod sensi_method) { realtype wrms = INFINITY; if (sensi_method == SensitivityMethod::adjoint) { @@ -449,16 +451,16 @@ realtype SteadystateProblem::getWrms(Model *model, /* If we're doing a forward simulation (with or without sensitivities: Get RHS and compute weighted error norm */ if (newton_step_conv_) - getNewtonStep(*model); + getNewtonStep(model); else - updateRightHandSide(*model); + updateRightHandSide(model); wrms = getWrmsNorm(state_.x, newton_step_conv_ ? delta_ : xdot_, atol_, rtol_, ewt_); } return wrms; } -realtype SteadystateProblem::getWrmsFSA(Model *model) { +realtype SteadystateProblem::getWrmsFSA(Model &model) { /* Forward sensitivities: Compute weighted error norm for their RHS */ realtype wrms = 0.0; @@ -467,8 +469,8 @@ realtype SteadystateProblem::getWrmsFSA(Model *model) { same jacobian */ xdot_updated_ = false; - for (int ip = 0; ip < model->nplist(); ++ip) { - model->fsxdot(state_.t, state_.x, state_.dx, ip, state_.sx[ip], + for (int ip = 0; ip < model.nplist(); ++ip) { + model.fsxdot(state_.t, state_.x, state_.dx, ip, state_.sx[ip], state_.dx, xdot_); if (newton_step_conv_) newton_solver_->solveLinearSystem(xdot_); @@ -494,14 +496,14 @@ bool SteadystateProblem::checkSteadyStateSuccess() const { }); } -void SteadystateProblem::applyNewtonsMethod(Model *model, bool newton_retry) { +void SteadystateProblem::applyNewtonsMethod(Model &model, bool newton_retry) { int &i_newtonstep = numsteps_.at(newton_retry ? 2 : 0); i_newtonstep = 0; gamma_ = 1.0; bool update_direction = true; bool step_successful = false; - if (model->nx_solver == 0) + if (model.nx_solver == 0) return; /* initialize output of linear solver for Newton step */ @@ -515,7 +517,7 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, bool newton_retry) { /* If Newton steps are necessary, compute the initial search direction */ if (update_direction) { - getNewtonStep(*model); + getNewtonStep(model); /* we store delta_ here as later convergence checks may update it */ delta_old_.copy(delta_); @@ -551,11 +553,11 @@ void SteadystateProblem::applyNewtonsMethod(Model *model, bool newton_retry) { throw NewtonFailure(AMICI_TOO_MUCH_WORK, "applyNewtonsMethod"); } -bool SteadystateProblem::makePositiveAndCheckConvergence(Model *model) { +bool SteadystateProblem::makePositiveAndCheckConvergence(Model &model) { /* Ensure positivity of the found state and recheck if the convergence still holds */ - auto nonnegative = model->getStateIsNonNegative(); - for (int ix = 0; ix < model->nx_solver; ix++) { + auto nonnegative = model.getStateIsNonNegative(); + for (int ix = 0; ix < model.nx_solver; ix++) { if (state_.x[ix] < 0.0 && nonnegative[ix]) { state_.x[ix] = 0.0; flagUpdatedState(); @@ -581,9 +583,9 @@ bool SteadystateProblem::updateDampingFactor(bool step_successful) { return step_successful; } -void SteadystateProblem::runSteadystateSimulation(const Solver *solver, - Model *model, bool backward) { - if (model->nx_solver == 0) +void SteadystateProblem::runSteadystateSimulation(const Solver &solver, + Model &model, bool backward) { + if (model.nx_solver == 0) return; /* Loop over steps and check for convergence NB: This function is used for forward and backward simulation, and may @@ -593,15 +595,15 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, /* Do we also have to check for convergence of sensitivities? */ SensitivityMethod sensitivityFlag = SensitivityMethod::none; - if (solver->getSensitivityOrder() > SensitivityOrder::none && - solver->getSensitivityMethod() == SensitivityMethod::forward) + if (solver.getSensitivityOrder() > SensitivityOrder::none && + solver.getSensitivityMethod() == SensitivityMethod::forward) sensitivityFlag = SensitivityMethod::forward; /* If flag for forward sensitivity computation by simulation is not set, disable forward sensitivity integration. Sensitivities will be computed - by newtonSolver->computeNewtonSensis then */ - if (model->getSteadyStateSensitivityMode() == + by newtonsolver.computeNewtonSensis then */ + if (model.getSteadyStateSensitivityMode() == SteadyStateSensitivityMode::newtonOnly) { - solver->switchForwardSensisOff(); + solver.switchForwardSensisOff(); sensitivityFlag = SensitivityMethod::none; } if (backward) @@ -619,11 +621,11 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, stable computation value is not important for AMICI_ONE_STEP mode, only direction w.r.t. current t */ - solver->step(std::max(state_.t, 1.0) * 10); + solver.step(std::max(state_.t, 1.0) * 10); if (backward) { - solver->writeSolution(&state_.t, xB_, state_.dx, state_.sx, xQ_); + solver.writeSolution(&state_.t, xB_, state_.dx, state_.sx, xQ_); } else { - solver->writeSolution(&state_.t, state_.x, state_.dx, state_.sx, + solver.writeSolution(&state_.t, state_.x, state_.dx, state_.sx, xQ_); flagUpdatedState(); } @@ -635,7 +637,7 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, system is prepared for newton type convergence check */ if (wrms_ < conv_thresh && check_sensi_conv_ && sensitivityFlag == SensitivityMethod::forward) { - updateSensiSimulation(*solver); + updateSensiSimulation(solver); wrms_ = getWrmsFSA(model); } @@ -649,7 +651,7 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, break; // converged /* increase counter, check for maxsteps */ sim_steps++; - if (sim_steps >= solver->getMaxSteps()) { + if (sim_steps >= solver.getMaxSteps()) { numsteps_.at(1) = sim_steps; throw NewtonFailure(AMICI_TOO_MUCH_WORK, "exceeded maximum number of steps"); @@ -664,7 +666,7 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, // if check_sensi_conv_ is deactivated, we still have to update sensis if (sensitivityFlag == SensitivityMethod::forward) - updateSensiSimulation(*solver); + updateSensiSimulation(solver); /* store information about steps and sensitivities, if necessary */ if (backward) { @@ -675,13 +677,13 @@ void SteadystateProblem::runSteadystateSimulation(const Solver *solver, } std::unique_ptr -SteadystateProblem::createSteadystateSimSolver(const Solver *solver, - Model *model, bool forwardSensis, +SteadystateProblem::createSteadystateSimSolver(const Solver &solver, + Model &model, bool forwardSensis, bool backward) const { /* Create new CVode solver object */ - auto sim_solver = std::unique_ptr(solver->clone()); + auto sim_solver = std::unique_ptr(solver.clone()); - switch (solver->getLinearSolver()) { + switch (solver.getLinearSolver()) { case LinearSolver::dense: break; case LinearSolver::KLU: @@ -700,35 +702,35 @@ SteadystateProblem::createSteadystateSimSolver(const Solver *solver, } /* use x and sx as dummies for dx and sdx (they wont get touched in a CVodeSolver) */ - sim_solver->setup(model->t0(), model, state_.x, state_.dx, state_.sx, sdx_); + sim_solver->setup(model.t0(), &model, state_.x, state_.dx, state_.sx, sdx_); if (backward) { - sim_solver->setup(model->t0(), model, xB_, xB_, state_.sx, sdx_); - sim_solver->setupSteadystate(model->t0(), model, state_.x, state_.dx, + sim_solver->setup(model.t0(), &model, xB_, xB_, state_.sx, sdx_); + sim_solver->setupSteadystate(model.t0(), &model, state_.x, state_.dx, xB_, xB_, xQ_); } else { - sim_solver->setup(model->t0(), model, state_.x, state_.dx, state_.sx, + sim_solver->setup(model.t0(), &model, state_.x, state_.dx, state_.sx, sdx_); } return sim_solver; } -void SteadystateProblem::computeQBfromQ(Model *model, const AmiVector &yQ, +void SteadystateProblem::computeQBfromQ(Model &model, const AmiVector &yQ, AmiVector &yQB) const { /* Compute the quadrature as the inner product: yQB = dxdotdp * yQ */ /* set to zero first, as multiplication adds to existing value */ yQB.zero(); /* multiply */ - if (model->pythonGenerated) { + if (model.pythonGenerated) { /* fill dxdotdp with current values */ - const auto &plist = model->getParameterList(); - model->fdxdotdp(state_.t, state_.x, state_.dx); - model->get_dxdotdp_full().multiply(yQB.getNVector(), yQ.getNVector(), + const auto &plist = model.getParameterList(); + model.fdxdotdp(state_.t, state_.x, state_.dx); + model.get_dxdotdp_full().multiply(yQB.getNVector(), yQ.getNVector(), plist, true); } else { - for (int ip = 0; ip < model->nplist(); ++ip) - yQB[ip] = dotProd(yQ, model->get_dxdotdp()[ip]); + for (int ip = 0; ip < model.nplist(); ++ip) + yQB[ip] = dotProd(yQ, model.get_dxdotdp()[ip]); } } @@ -770,7 +772,7 @@ void SteadystateProblem::getNewtonStep(Model &model) { return; updateRightHandSide(model); delta_.copy(xdot_); - newton_solver_->getStep(delta_, &model, state_); + newton_solver_->getStep(delta_, model, state_); delta_updated_ = true; } } // namespace amici From db0f323210b2e4b12b694da9320127be2d9c69ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Sat, 26 Mar 2022 10:11:22 -0400 Subject: [PATCH 17/31] speedup edata construction from petab problems (#1746) * speedup edata construction * fixup --- python/amici/petab_objective.py | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/python/amici/petab_objective.py b/python/amici/petab_objective.py index b1da55d865..0b57d2739e 100644 --- a/python/amici/petab_objective.py +++ b/python/amici/petab_objective.py @@ -481,12 +481,27 @@ def create_edatas( observable_ids = amici_model.getObservableIds() + measurement_groupvar = [petab.SIMULATION_CONDITION_ID] + if petab.PREEQUILIBRATION_CONDITION_ID in simulation_conditions: + measurement_groupvar.append(petab.PREEQUILIBRATION_CONDITION_ID) + measurement_dfs = dict(list( + petab_problem.measurement_df.groupby(measurement_groupvar) + )) + edatas = [] for _, condition in simulation_conditions.iterrows(): # Create amici.ExpData for each simulation + if petab.PREEQUILIBRATION_CONDITION_ID in condition: + measurement_index = ( + condition.get(petab.SIMULATION_CONDITION_ID), + condition.get(petab.PREEQUILIBRATION_CONDITION_ID) + ) + else: + measurement_index = condition.get(petab.SIMULATION_CONDITION_ID) edata = create_edata_for_condition( condition=condition, amici_model=amici_model, + measurement_df=measurement_dfs[measurement_index], petab_problem=petab_problem, observable_ids=observable_ids, ) @@ -497,6 +512,7 @@ def create_edatas( def create_edata_for_condition( condition: Union[Dict, pd.Series], + measurement_df: pd.DataFrame, amici_model: AmiciModel, petab_problem: petab.Problem, observable_ids: List[str], @@ -508,6 +524,8 @@ def create_edata_for_condition( :param condition: pandas.DataFrame row with preequilibrationConditionId and simulationConditionId. + :param measurement_df: + pandas.DataFrame with measurements for the given condition. :param amici_model: AMICI model :param petab_problem: @@ -518,10 +536,6 @@ def create_edata_for_condition( :return: ExpData instance. """ - # extract measurement table rows for condition - measurement_df = petab.get_rows_for_condition( - measurement_df=petab_problem.measurement_df, condition=condition) - if amici_model.nytrue != len(observable_ids): raise AssertionError("Number of AMICI model observables does not " "match number of PEtab observables.") From a74d8b56610694d93a8d96e949b243fb14b81e60 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 28 Mar 2022 08:46:56 +0200 Subject: [PATCH 18/31] CI: performance test, increase time for adjoint simulation to 2.5s --- tests/performance/reference.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/performance/reference.yml b/tests/performance/reference.yml index b123a95d50..9bf2eb1733 100644 --- a/tests/performance/reference.yml +++ b/tests/performance/reference.yml @@ -8,7 +8,7 @@ install_model_O1: 90 install_model_O2: 120 forward_simulation: 2 forward_sensitivities: 2 -adjoint_sensitivities: 2 +adjoint_sensitivities: 2.5 forward_simulation_non_optimal_parameters: 2 adjoint_sensitivities_non_optimal_parameters: 5 forward_steadystate_sensitivities_non_optimal_parameters: 5 From 7c7851cb5bdc10cfc104d08a66b1235bf13f58b8 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Tue, 29 Mar 2022 17:00:46 +0200 Subject: [PATCH 19/31] CI: Upgrade from deprecated codecov/codecov-action@v1 to v2 (#1749) see https://github.com/codecov/codecov-action --- .github/workflows/test_petab_test_suite.yml | 2 +- .github/workflows/test_python_cplusplus.yml | 4 ++-- .github/workflows/test_sbml_semantic_test_suite.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test_petab_test_suite.yml b/.github/workflows/test_petab_test_suite.yml index ddb277be3a..b9a6d1bd6e 100644 --- a/.github/workflows/test_petab_test_suite.yml +++ b/.github/workflows/test_petab_test_suite.yml @@ -81,7 +81,7 @@ jobs: --cov-report=xml --cov-append --cov=amici tests/petab_test_suite/ - name: Codecov - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.xml diff --git a/.github/workflows/test_python_cplusplus.yml b/.github/workflows/test_python_cplusplus.yml index 5998c9a4b7..d7fbb7d5ae 100644 --- a/.github/workflows/test_python_cplusplus.yml +++ b/.github/workflows/test_python_cplusplus.yml @@ -104,7 +104,7 @@ jobs: scripts/runNotebook.sh documentation/GettingStarted.ipynb - name: Codecov Python - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./build/coverage_py.xml @@ -122,7 +122,7 @@ jobs: && lcov -a coverage_cpp.info -a coverage_py.info -o coverage.info - name: Codecov CPP - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage.info diff --git a/.github/workflows/test_sbml_semantic_test_suite.yml b/.github/workflows/test_sbml_semantic_test_suite.yml index ed0e5d9553..04406d44d7 100644 --- a/.github/workflows/test_sbml_semantic_test_suite.yml +++ b/.github/workflows/test_sbml_semantic_test_suite.yml @@ -52,7 +52,7 @@ jobs: path: tests/amici-semantic-results - name: Codecov SBMLSuite - uses: codecov/codecov-action@v1 + uses: codecov/codecov-action@v2 with: token: ${{ secrets.CODECOV_TOKEN }} file: ./coverage_SBMLSuite.xml From da56a08bc19ce1e41a95854dfb2c6efdc70fecbf Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 30 Mar 2022 12:57:14 +0200 Subject: [PATCH 20/31] SBML import: Alternative algorithm for identifying conservation laws (#1748) ... without Monte Carlo mumbo-jumbo. Compute left nullspace of S via (numerical) Gaussian elimination. Going via SVD would be faster and numerically safer, but this seems to lead to weird coefficients. For the model in `test_compute_moiety_conservation_laws_demartino2014`, this algorithm is slower than the one already implemented (~1min vs ~10s), but at least it's deterministic and not highly dependent on RNG seeds. For another model of similar size, the new algorithm takes ~2.5min instead of previously 1h20. --- documentation/python_modules.rst | 3 +- ...s.py => conserved_quantities_demartino.py} | 0 python/amici/conserved_quantities_rref.py | 99 +++++++++++ python/amici/sbml_import.py | 163 ++++++++++++++---- python/sdist/amici/conserved_moieties.py | 1 - .../amici/conserved_quantities_demartino.py | 1 + .../sdist/amici/conserved_quantities_rref.py | 1 + python/tests/test_conserved_moieties_rref.py | 40 +++++ ...=> test_conserved_quantities_demartino.py} | 10 +- 9 files changed, 281 insertions(+), 37 deletions(-) rename python/amici/{conserved_moieties.py => conserved_quantities_demartino.py} (100%) create mode 100644 python/amici/conserved_quantities_rref.py delete mode 120000 python/sdist/amici/conserved_moieties.py create mode 120000 python/sdist/amici/conserved_quantities_demartino.py create mode 120000 python/sdist/amici/conserved_quantities_rref.py create mode 100644 python/tests/test_conserved_moieties_rref.py rename python/tests/{test_conserved_moieties.py => test_conserved_quantities_demartino.py} (97%) diff --git a/documentation/python_modules.rst b/documentation/python_modules.rst index 080e512a49..a6f4a2d1f3 100644 --- a/documentation/python_modules.rst +++ b/documentation/python_modules.rst @@ -23,4 +23,5 @@ AMICI Python API amici.logging amici.gradient_check amici.parameter_mapping - amici.conserved_moieties + amici.conserved_quantities_demartino + amici.conserved_quantities_rref diff --git a/python/amici/conserved_moieties.py b/python/amici/conserved_quantities_demartino.py similarity index 100% rename from python/amici/conserved_moieties.py rename to python/amici/conserved_quantities_demartino.py diff --git a/python/amici/conserved_quantities_rref.py b/python/amici/conserved_quantities_rref.py new file mode 100644 index 0000000000..4c401293cf --- /dev/null +++ b/python/amici/conserved_quantities_rref.py @@ -0,0 +1,99 @@ +"""Find conserved quantities deterministically""" + +from typing import List, Literal, Optional, Union + +import numpy as np + + +def rref( + mat: np.array, + round_ndigits: Optional[Union[Literal[False], int]] = None +) -> np.array: + """ + Bring matrix ``mat`` to reduced row echelon form + + see https://en.wikipedia.org/wiki/Row_echelon_form + + :param mat: Numpy float matrix to operate on (will be copied) + :param round_ndigits: Number of digits to round intermediary results to, + or ``False`` to disable rounding completely. + Helps to avoid numerical artifacts. + :returns: ``mat`` in rref form. + """ + # Rounding function + if round_ndigits is False: + # no-op + def _round(mat): + return mat + else: + if round_ndigits is None: + # drop the least significant digit (more or less) + round_ndigits = - int(np.ceil(np.log10(np.spacing(1)))) + + def _round(mat): + mat = np.round(mat, round_ndigits) + mat[np.abs(mat) <= 10**(-round_ndigits)] = 0 + return mat + + # create a copy that will be modified + mat = mat.copy() + + lead = 0 + n_rows, n_columns = mat.shape + for r in range(n_rows): + if n_columns <= lead: + return mat + + i = r + while mat[i, lead] == 0: + i += 1 + if n_rows == i: + i = r + lead += 1 + if n_columns == lead: + return mat + + if i != r: + # Swap rows + mat[[i, r]] = mat[[r, i]] + # Divide row + mat[r] /= mat[r, lead] + for i in range(n_rows): + if i != r: + # Subtract multiple + mat[i] -= mat[i, lead] * mat[r] + mat = _round(mat) + lead += 1 + return mat + + +def pivots(mat: np.array) -> List[int]: + """Get indices of pivot columns in ``mat``, assumed to be in reduced row + echelon form""" + pivot_cols = [] + last_pivot_col = -1 + for i in range(mat.shape[0]): + for j in range(last_pivot_col + 1, mat.shape[1]): + if mat[i, j] != 0: + pivot_cols.append(j) + last_pivot_col = j + break + return pivot_cols + + +def nullspace_by_rref(mat: np.array) -> np.array: + """Compute basis of the nullspace of ``mat`` based on the reduced row + echelon form""" + rref_mat = rref(mat) + pivot_cols = pivots(rref_mat) + rows, cols = mat.shape + + basis = [] + for i in range(cols): + if i in pivot_cols: + continue + vec = [1.0 if i == j else 0.0 for j in range(cols)] + for pivot_row, pivot_col in enumerate(pivot_cols): + vec[pivot_col] -= rref_mat[pivot_row][i] + basis.append(vec) + return np.array(basis) diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py index 4fd56d984e..6387708bd4 100644 --- a/python/amici/sbml_import.py +++ b/python/amici/sbml_import.py @@ -11,23 +11,22 @@ import os import re import warnings -from typing import (Any, Callable, Dict, Iterable, List, Optional, Union) +from typing import (Any, Callable, Dict, Iterable, List, Optional, Tuple, + Union) import libsbml as sbml import sympy as sp from . import has_clibs -from .conserved_moieties import compute_moiety_conservation_laws from .constants import SymbolId -from .import_utils import (CircularDependencyError, +from .import_utils import (CircularDependencyError, RESERVED_SYMBOLS, _check_unsupported_functions, _get_str_symbol_identifiers, _parse_special_functions, generate_measurement_symbol, noise_distribution_to_cost_function, noise_distribution_to_observable_transformation, - smart_subs, smart_subs_dict, toposort_symbols, - RESERVED_SYMBOLS) + smart_subs, smart_subs_dict, toposort_symbols) from .logging import get_logger, log_execution_time, set_log_level from .ode_export import ( ODEExporter, ODEModel, symbol_with_assumptions @@ -289,7 +288,13 @@ def sbml2amici(self, Conservation laws for constant species are enabled by default. Support for conservation laws for non-constant species is experimental and may be enabled by setting an environment variable - ``AMICI_EXPERIMENTAL_SBML_NONCONST_CLS`` to any value. + ``AMICI_EXPERIMENTAL_SBML_NONCONST_CLS`` to either ``demartino`` + to use the algorithm proposed by De Martino et al. (2014) + https://doi.org/10.1371/journal.pone.0100750, or to any other value + to use the deterministic algorithm implemented in + ``conserved_moieties2.py``. In some cases, the ``demartino`` may + run for a very long time. This has been observed for example in the + case of stoichiometric coefficients with many significant digits. :param simplify: see :attr:`ODEModel._simplify` @@ -1441,22 +1446,22 @@ def process_conservation_laws(self, ode_model) -> None: for cl in conservation_laws: ode_model.add_conservation_law(**cl) - def _add_conservation_for_non_constant_species( - self, - ode_model: ODEModel, - conservation_laws: List[ConservationLaw] - ) -> List[int]: - """Add non-constant species to conservation laws + def _get_conservation_laws_demartino( + self, + ode_model: ODEModel, + ) -> List[Tuple[int, List[int], List[float]]]: + """Identify conservation laws based on algorithm by DeMartino et al. + (see conserved_moieties.py). - :param ode_model: - ODEModel object with basic definitions - :param conservation_laws: - List of already known conservation laws - :returns: - List of species indices which later remain in the ODE solver + :param ode_model: Model for which to compute conserved quantities + :returns: List of one tuple per conservation law, each containing: + (0) the index of the (solver-)species to eliminate, + (1) (solver-)indices of all species engaged in the conserved + quantity (including the eliminated one) + (2) coefficients for the species in (1) """ - # indices of retained species - species_solver = list(range(ode_model.num_states_rdata())) + from .conserved_quantities_demartino \ + import compute_moiety_conservation_laws try: stoichiometric_list = [ @@ -1470,7 +1475,7 @@ def _add_conservation_for_non_constant_species( "combination with parameterized stoichiometric " "coefficients are not currently supported " "and will be turned off.") - return species_solver + return [] if any(rule.getTypeCode() == sbml.SBML_RATE_RULE for rule in self.sbml.getListOfRules()): @@ -1478,7 +1483,7 @@ def _add_conservation_for_non_constant_species( warnings.warn("Conservation laws for non-constant species in " "models with RateRules are not currently supported " "and will be turned off.") - return species_solver + return [] cls_state_idxs, cls_coefficients = compute_moiety_conservation_laws( stoichiometric_list, *self.stoichiometric_matrix.shape, @@ -1501,7 +1506,105 @@ def _add_conservation_for_non_constant_species( for i, c in zip(cl, coefficients): A[i_cl, i] = sp.Rational(c) rref, pivots = A.rref() - species_to_be_removed = set(pivots) + + raw_cls = [] + for i_cl, target_state_model_idx in enumerate(pivots): + # collect values for species engaged in the current CL + state_idxs = [i for i, coeff in enumerate(rref[i_cl, :]) + if coeff] + coefficients = [coeff for coeff in rref[i_cl, :] if coeff] + raw_cls.append((target_state_model_idx, state_idxs, + coefficients),) + return raw_cls + + def _get_conservation_laws_rref( + self + ) -> List[Tuple[int, List[int], List[float]]]: + """Identify conservation laws based on left nullspace of the + stoichiometric matrix, computed through (numeric) Gaussian elimination + + :returns: List of one tuple per conservation law, each containing: + (0) the index of the (solver-)species to eliminate, + (1) (solver-)indices of all species engaged in the conserved + quantity (including the eliminated one) + (2) coefficients for the species in (1) + """ + import numpy as np + from numpy.linalg import matrix_rank + from .conserved_quantities_rref import nullspace_by_rref, rref + + try: + S = np.asarray(self.stoichiometric_matrix, dtype=float) + except TypeError: + # Due to the numerical algorithm currently used to identify + # conserved quantities, we can't have symbols in the + # stoichiometric matrix + warnings.warn("Conservation laws for non-constant species in " + "combination with parameterized stoichiometric " + "coefficients are not currently supported " + "and will be turned off.") + return [] + + if any(rule.getTypeCode() == sbml.SBML_RATE_RULE + for rule in self.sbml.getListOfRules()): + # see SBML semantic test suite, case 33 for an example + warnings.warn("Conservation laws for non-constant species in " + "models with RateRules are not currently supported " + "and will be turned off.") + return [] + + # Determine rank via SVD + rank = matrix_rank(S) if S.shape[0] else 0 + if rank == S.shape[0]: + return [] + kernel = nullspace_by_rref(S.T) + # Check dimensions - due to numerical errors, nullspace_by_rref may + # fail in certain situations + if kernel.shape[0] != S.shape[0] - rank: + raise AssertionError( + "Failed to determine all conserved quantities " + f"(found {kernel.shape[0]}, expected {S.shape[0] - rank}). " + "Try another algorithm, disable detection of conservation " + "laws, or submit a bug report along with the model." + ) + kernel = rref(kernel) + raw_cls = [] + for row in kernel: + state_idxs = [i for i, coeff in enumerate(row) if coeff] + coefficients = [coeff for coeff in row if coeff] + print(state_idxs, coefficients) + raw_cls.append((state_idxs[0], state_idxs, coefficients),) + + return raw_cls + + def _add_conservation_for_non_constant_species( + self, + ode_model: ODEModel, + conservation_laws: List[ConservationLaw] + ) -> List[int]: + """Add non-constant species to conservation laws + + :param ode_model: + ODEModel object with basic definitions + :param conservation_laws: + List of already known conservation laws + :returns: + List of species indices which later remain in the ODE solver + """ + # indices of retained species + species_solver = list(range(ode_model.num_states_rdata())) + + algorithm = os.environ.get("AMICI_EXPERIMENTAL_SBML_NONCONST_CLS", "") + if algorithm.lower() == "demartino": + raw_cls = self._get_conservation_laws_demartino(ode_model) + else: + raw_cls = self._get_conservation_laws_rref() + + if not raw_cls: + # no conservation laws identified + return species_solver + + species_to_be_removed = {x[0] for x in raw_cls} # keep new conservations laws separate until we know everything worked new_conservation_laws = [] @@ -1510,22 +1613,20 @@ def _add_conservation_for_non_constant_species( all_state_ids = [x.get_id() for x in ode_model._states] all_compartment_sizes = [ - self.compartments[ - self.symbols[SymbolId.SPECIES][state_id]['compartment']] - if not self.symbols[SymbolId.SPECIES][state_id]['amount'] - else sp.Integer(1) + sp.Integer(1) + if self.symbols[SymbolId.SPECIES][state_id]['amount'] + else self.compartments[ + self.symbols[SymbolId.SPECIES][state_id]['compartment'] + ] for state_id in all_state_ids ] # iterate over list of conservation laws, create symbolic expressions, - for i_cl, target_state_model_idx in enumerate(pivots): + for target_state_model_idx, state_idxs, coefficients in raw_cls: if all_state_ids[target_state_model_idx] in eliminated_state_ids: # constants state, already eliminated continue # collect values for species engaged in the current CL - state_idxs = [i for i, coeff in enumerate(rref[i_cl, :]) - if coeff] - coefficients = [coeff for coeff in rref[i_cl, :] if coeff] state_ids = [all_state_ids[i_state] for i_state in state_idxs] compartment_sizes = [all_compartment_sizes[i] for i in state_idxs] diff --git a/python/sdist/amici/conserved_moieties.py b/python/sdist/amici/conserved_moieties.py deleted file mode 120000 index 8e96336ae0..0000000000 --- a/python/sdist/amici/conserved_moieties.py +++ /dev/null @@ -1 +0,0 @@ -../../amici/conserved_moieties.py \ No newline at end of file diff --git a/python/sdist/amici/conserved_quantities_demartino.py b/python/sdist/amici/conserved_quantities_demartino.py new file mode 120000 index 0000000000..270caa6f65 --- /dev/null +++ b/python/sdist/amici/conserved_quantities_demartino.py @@ -0,0 +1 @@ +../../amici/conserved_quantities_demartino.py \ No newline at end of file diff --git a/python/sdist/amici/conserved_quantities_rref.py b/python/sdist/amici/conserved_quantities_rref.py new file mode 120000 index 0000000000..e84ceab727 --- /dev/null +++ b/python/sdist/amici/conserved_quantities_rref.py @@ -0,0 +1 @@ +../../amici/conserved_quantities_rref.py \ No newline at end of file diff --git a/python/tests/test_conserved_moieties_rref.py b/python/tests/test_conserved_moieties_rref.py new file mode 100644 index 0000000000..d9145b8bc5 --- /dev/null +++ b/python/tests/test_conserved_moieties_rref.py @@ -0,0 +1,40 @@ +import numpy as np +import pytest +import sympy as sp + +from amici.conserved_quantities_rref import nullspace_by_rref, pivots, rref + + +def random_matrix_generator(min_dim, max_dim, count): + """Generate random 2D square matrix""" + rng = np.random.default_rng(12345) + for rows, cols in rng.integers(min_dim, max_dim, [count, 2]): + yield np.random.rand(rows, cols) + + +@pytest.mark.parametrize("mat", random_matrix_generator(0, 10, 200)) +def test_rref(mat): + """Create some random matrices and compare output of ``rref`` and + ``pivots`` to that of sympy""" + actual_rref = rref(mat) + expected_rref, expected_pivots = sp.Matrix(mat).rref() + expected_rref = np.asarray(expected_rref, dtype=float) + + assert list(expected_pivots) == pivots(actual_rref) + assert np.allclose(expected_rref, actual_rref) + + +@pytest.mark.parametrize("mat", random_matrix_generator(0, 50, 50)) +def test_nullspace_by_rref(mat): + """Test ``nullspace_by_rref`` on a number of random matrices and compare + to sympy results""" + actual = nullspace_by_rref(mat) + + if len(actual): + assert np.allclose(mat.dot(actual.T), 0) + + expected = sp.Matrix(mat).nullspace() + expected = np.hstack(np.asarray(expected, dtype=float)).T \ + if len(expected) else np.array([]) + + assert np.allclose(actual, expected, rtol=1e-8) diff --git a/python/tests/test_conserved_moieties.py b/python/tests/test_conserved_quantities_demartino.py similarity index 97% rename from python/tests/test_conserved_moieties.py rename to python/tests/test_conserved_quantities_demartino.py index e039fe9f9e..432fd88dd5 100644 --- a/python/tests/test_conserved_moieties.py +++ b/python/tests/test_conserved_quantities_demartino.py @@ -6,9 +6,11 @@ import pytest import sympy as sp -from amici.conserved_moieties import (_fill, _kernel, - compute_moiety_conservation_laws, - _output as output) +from amici.conserved_quantities_demartino import ( + _fill, _kernel, + _output as output, + compute_moiety_conservation_laws +) from amici.logging import get_logger, log_execution_time logger = get_logger(__name__) @@ -225,7 +227,7 @@ def test_cl_detect_execution_time(data_demartino2014): Only one has to succeed.""" max_tries = 5 # <5s on modern hardware, but leave some slack - max_time_seconds = 30 if "GITHUB_ACTIONS" in os.environ else 10 + max_time_seconds = 40 if "GITHUB_ACTIONS" in os.environ else 10 runtime = np.Inf From 347a86d8d1345fb706d421e75ed4196f923d38dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Sun, 3 Apr 2022 22:32:43 -0400 Subject: [PATCH 21/31] Speedup conservation law computation (#1754) * speedup * Apply suggestions from code review Co-authored-by: Daniel Weindl Co-authored-by: Daniel Weindl --- python/amici/pysb_import.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/amici/pysb_import.py b/python/amici/pysb_import.py index 6c78d39f59..8d201c2b75 100644 --- a/python/amici/pysb_import.py +++ b/python/amici/pysb_import.py @@ -1015,12 +1015,12 @@ def _construct_conservation_from_prototypes( # x_j = (T - sum_i≠j(a_i * x_i))/a_j # law: sum_i≠j(a_i * x_i))/a_j # state: x_j - target_expression = sum( + target_expression = sp.Add(*( sp.Symbol(f'__s{ix}') * extract_monomers(specie).count(monomer_name) for ix, specie in enumerate(pysb_model.species) if ix != target_index - ) / extract_monomers(pysb_model.species[ + )) / extract_monomers(pysb_model.species[ target_index ]).count(monomer_name) # normalize by the stoichiometry of the target species From 5634d37bd45dcc9b7521907f295032871566451d Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 4 Apr 2022 10:02:42 +0200 Subject: [PATCH 22/31] CI: Skip Python-only tests under valgrind (#1752) --- ...d_moieties_rref.py => test_conserved_quantities_rref.py} | 6 ++++++ 1 file changed, 6 insertions(+) rename python/tests/{test_conserved_moieties_rref.py => test_conserved_quantities_rref.py} (85%) diff --git a/python/tests/test_conserved_moieties_rref.py b/python/tests/test_conserved_quantities_rref.py similarity index 85% rename from python/tests/test_conserved_moieties_rref.py rename to python/tests/test_conserved_quantities_rref.py index d9145b8bc5..7a28f78b9a 100644 --- a/python/tests/test_conserved_moieties_rref.py +++ b/python/tests/test_conserved_quantities_rref.py @@ -1,3 +1,5 @@ +import os + import numpy as np import pytest import sympy as sp @@ -12,6 +14,8 @@ def random_matrix_generator(min_dim, max_dim, count): yield np.random.rand(rows, cols) +@pytest.mark.skipif(os.environ.get('GITHUB_JOB') == 'valgrind', + reason="Python-only") @pytest.mark.parametrize("mat", random_matrix_generator(0, 10, 200)) def test_rref(mat): """Create some random matrices and compare output of ``rref`` and @@ -24,6 +28,8 @@ def test_rref(mat): assert np.allclose(expected_rref, actual_rref) +@pytest.mark.skipif(os.environ.get('GITHUB_JOB') == 'valgrind', + reason="Python-only") @pytest.mark.parametrize("mat", random_matrix_generator(0, 50, 50)) def test_nullspace_by_rref(mat): """Test ``nullspace_by_rref`` on a number of random matrices and compare From 3d4975cd7fbffd9be588bc8b8f1e925590b3931f Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 4 Apr 2022 11:24:35 +0200 Subject: [PATCH 23/31] Fix get_model_settings for initial states + sensitivities (#1751) Fixes a couple of issues in `amici.{get,set}_model_settings` * Initial states and initial state sensitivities should only be included if custom values have been set. Otherwise, `set_model_settings(model, get_model_settings(model))` will permanently set the given values, preventing evaluation of `Model::fx0` / `Model::fsx0`. Fixes https://github.com/ICB-DCM/pyPESTO/issues/837 * If a model had custom initial state sensitivities set, those would not have been set correctly by `set_model_settings(model, get_model_settings(model))`, because `setParameter{List,Scale}` was called after `setInitialStateSensitivities` and cleared initial state sensitivities. (Even if they would have been set, they would have been wrong in case of scaled parameters, due to using `setInitialStateSensitivities` instead of `setUnscaledInitialStateSensitivities`). --- python/amici/swig_wrappers.py | 17 ++++++++-- python/tests/test_swig_interface.py | 52 ++++++++++++++++++++++++++--- 2 files changed, 61 insertions(+), 8 deletions(-) diff --git a/python/amici/swig_wrappers.py b/python/amici/swig_wrappers.py index 77008a3e31..394f91aea8 100644 --- a/python/amici/swig_wrappers.py +++ b/python/amici/swig_wrappers.py @@ -172,16 +172,18 @@ def writeSolverSettingsToHDF5( # is a tuple where the first and second elements are the getter and setter # methods, respectively. model_instance_settings = [ + # `setParameter{List,Scale}` will clear initial state sensitivities, so + # `setParameter{List,Scale}` has to be called first. + 'ParameterList', + 'ParameterScale', # getter returns a SWIG object 'AddSigmaResiduals', 'AlwaysCheckFinite', 'FixedParameters', 'InitialStates', - 'InitialStateSensitivities', + ('getInitialStateSensitivities', 'setUnscaledInitialStateSensitivities'), 'MinimumSigmaResiduals', ('nMaxEvent', 'setNMaxEvent'), 'Parameters', - 'ParameterList', - 'ParameterScale', # getter returns a SWIG object 'ReinitializationStateIdxs', 'ReinitializeFixedParameterInitialStates', 'StateIsNonNegative', @@ -203,6 +205,15 @@ def get_model_settings( settings = {} for setting in model_instance_settings: getter = setting[0] if isinstance(setting, tuple) else f'get{setting}' + + if getter == 'getInitialStates' and not model.hasCustomInitialStates(): + settings[setting] = [] + continue + if getter == 'getInitialStateSensitivities' \ + and not model.hasCustomInitialStateSensitivities(): + settings[setting] = [] + continue + settings[setting] = getattr(model, getter)() # TODO `amici.Model.getParameterScale` returns a SWIG object instead # of a Python list/tuple. diff --git a/python/tests/test_swig_interface.py b/python/tests/test_swig_interface.py index 63b0ae0aff..c23eaaad00 100644 --- a/python/tests/test_swig_interface.py +++ b/python/tests/test_swig_interface.py @@ -75,7 +75,7 @@ def test_copy_constructors(pysb_example_presimulation_module): (10.0, 9.0, 1.0, 0.0, 0.0, 0.0), tuple([.1]*6), ], - 'InitialStateSensitivities': [ + ('getInitialStateSensitivities', 'setUnscaledInitialStateSensitivities'): [ tuple([1.0] + [0.0]*35), tuple([.1]*36), ], @@ -143,7 +143,6 @@ def test_model_instance_settings(pysb_example_presimulation_module): if name != 'ReinitializeFixedParameterInitialStates' ) - # All default values are as expected. for name, (default, custom) in model_instance_settings.items(): getter = name[i_getter] if isinstance(name, tuple) else f'get{name}' @@ -165,8 +164,15 @@ def test_model_instance_settings(pysb_example_presimulation_module): # The new model has the default settings. model_default_settings = amici.get_model_settings(model) for name in model_instance_settings: - assert model_default_settings[name] == \ - model_instance_settings[name][i_default] + if (name == "InitialStates" and not model.hasCustomInitialStates())\ + or (name == ('getInitialStateSensitivities', + 'setUnscaledInitialStateSensitivities') + and not model.hasCustomInitialStateSensitivities()): + # Here the expected value differs from what the getter would return + assert model_default_settings[name] == [] + else: + assert model_default_settings[name] == \ + model_instance_settings[name][i_default], name # The grouped setter method works. custom_settings_not_none = { @@ -299,7 +305,7 @@ def test_unhandled_settings(pysb_example_presimulation_module): 'setParameterByName', 'setParametersByIdRegex', 'setParametersByNameRegex', - 'setUnscaledInitialStateSensitivities', + 'setInitialStateSensitivities', ] from amici.swig_wrappers import model_instance_settings handled = [ @@ -361,3 +367,39 @@ def set_val(obj, attr, val): ) else: setattr(obj, attr, val) + + +def test_model_instance_settings_custom_x0(pysb_example_presimulation_module): + """Check that settings are applied in the correct order, and only if + required""" + model = pysb_example_presimulation_module.getModel() + + # ensure no-custom-(s)x0 is restored + assert not model.hasCustomInitialStates() + assert not model.hasCustomInitialStateSensitivities() + settings = amici.get_model_settings(model) + model.setInitialStates(model.getInitialStates()) + model.setUnscaledInitialStateSensitivities( + model.getInitialStateSensitivities()) + amici.set_model_settings(model, settings) + assert not model.hasCustomInitialStates() + assert not model.hasCustomInitialStateSensitivities() + # ensure everything was set correctly, and there wasn't any problem + # due to, e.g. interactions of different setters + assert settings == amici.get_model_settings(model) + + # ensure custom (s)x0 is restored + model.setInitialStates(model.getInitialStates()) + model.setParameterScale(amici.ParameterScaling.log10) + sx0 = model.getInitialStateSensitivities() + model.setUnscaledInitialStateSensitivities(sx0) + assert model.hasCustomInitialStates() + assert model.hasCustomInitialStateSensitivities() + settings = amici.get_model_settings(model) + model2 = pysb_example_presimulation_module.getModel() + amici.set_model_settings(model2, settings) + assert model2.hasCustomInitialStates() + assert model2.hasCustomInitialStateSensitivities() + assert model2.getInitialStateSensitivities() == sx0 + assert settings == amici.get_model_settings(model2) + From 17289f742709147b8120ce596a97f311b971786a Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 4 Apr 2022 18:47:00 +0200 Subject: [PATCH 24/31] Fix recent cmake-based build issues due to changed sundials library directory (#1756) Some package changed on GitHub Actions, not sure which exactly. But now sundials libraries are expected in a different directory than before. Let CMake figure it out. Fixes errors of type ``` make[5]: *** No rule to make target '/home/runner/work/AMICI/AMICI/ThirdParty/sundials/build/lib/x86_64-linux-gnu/libsundials_nvecserial.a', needed by 'swig/__model_neuron.so'. ``` Previous setup still worked [here](https://github.com/AMICI-dev/AMICI/runs/5812654996?check_suite_focus=true), but failed [here](https://github.com/AMICI-dev/AMICI/runs/5813779752?check_suite_focus=true). The problem is, that on certain OSs, `CMAKE_INSTALL_LIBDIR` takes a different value, [depending](https://cmake.org/cmake/help/v3.23/module/GNUInstallDirs.html#result-variables) on `CMAKE_INSTALL_PREFIX`, and `CMAKE_INSTALL_LIBDIR` from AMICI does not necessarily match the one from sundials. --- CMakeLists.txt | 59 ++++++++++++++++++++--------------------- cmake/AmiciConfig.cmake | 2 ++ 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index f7f9d0ac2f..215054645c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,34 +54,7 @@ set(SUITESPARSE_LIBRARIES ${SUITESPARSE_DIR}/SuiteSparse_config/libsuitesparseconfig${CMAKE_STATIC_LIBRARY_SUFFIX} ) -set(SUNDIALS_LIB_DIR "${CMAKE_SOURCE_DIR}/ThirdParty/sundials/build/${CMAKE_INSTALL_LIBDIR}") -set(SUNDIALS_LIBRARIES - ${SUNDIALS_LIB_DIR}/libsundials_nvecserial${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunlinsolband${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunlinsolklu${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunlinsolpcg${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunlinsolspbcgs${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunlinsolspfgmr${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunmatrixband${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunmatrixdense${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunmatrixsparse${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunnonlinsolfixedpoint${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_sunnonlinsolnewton${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_cvodes${CMAKE_STATIC_LIBRARY_SUFFIX} - ${SUNDIALS_LIB_DIR}/libsundials_idas${CMAKE_STATIC_LIBRARY_SUFFIX} - ) -set(SUNDIALS_INCLUDE_DIRS "${CMAKE_SOURCE_DIR}/ThirdParty/sundials/build/include") - -option(SUNDIALS_SUPERLUMT_ENABLE "Enable sundials SuperLUMT?" OFF) -if(SUNDIALS_SUPERLUMT_ENABLE) - set(SUNDIALS_LIBRARIES ${SUNDIALS_LIBRARIES} - ${SUNDIALS_LIB_DIR}/libsundials_sunlinsolsuperlumt${CMAKE_STATIC_LIBRARY_SUFFIX} - ${CMAKE_SOURCE_DIR}/ThirdParty/SuperLU_MT_3.1/lib/libsuperlu_mt_PTHREAD${CMAKE_STATIC_LIBRARY_SUFFIX} - -lblas - ) - set(SUNDIALS_INCLUDE_DIRS ${SUNDIALS_INCLUDE_DIRS} - "${CMAKE_SOURCE_DIR}/ThirdParty/SuperLU_MT_3.1/SRC/") -endif() +find_package(SUNDIALS REQUIRED PATHS "${CMAKE_SOURCE_DIR}/ThirdParty/sundials/build/lib/cmake/sundials/") set(GSL_LITE_INCLUDE_DIR "${CMAKE_SOURCE_DIR}/ThirdParty/gsl") @@ -187,7 +160,6 @@ target_include_directories(${PROJECT_NAME} PUBLIC $ PUBLIC swig PUBLIC ${GSL_LITE_INCLUDE_DIR} - PUBLIC ${SUNDIALS_INCLUDE_DIRS} PUBLIC ${SUITESPARSE_INCLUDE_DIRS} PUBLIC ${HDF5_INCLUDE_DIRS} ) @@ -197,13 +169,40 @@ if(NOT "${BLAS_INCLUDE_DIRS}" STREQUAL "") endif() target_link_libraries(${PROJECT_NAME} - PUBLIC ${SUNDIALS_LIBRARIES} + PUBLIC SUNDIALS::generic_static + PUBLIC SUNDIALS::nvecserial_static + PUBLIC SUNDIALS::sunmatrixband_static + PUBLIC SUNDIALS::sunmatrixdense_static + PUBLIC SUNDIALS::sunmatrixsparse_static + PUBLIC SUNDIALS::sunlinsolband_static + PUBLIC SUNDIALS::sunlinsoldense_static + PUBLIC SUNDIALS::sunlinsolpcg_static + PUBLIC SUNDIALS::sunlinsolspbcgs_static + PUBLIC SUNDIALS::sunlinsolspfgmr_static + PUBLIC SUNDIALS::sunlinsolspgmr_static + PUBLIC SUNDIALS::sunlinsolsptfqmr_static + PUBLIC SUNDIALS::sunlinsolklu_static + PUBLIC SUNDIALS::sunnonlinsolnewton_static + PUBLIC SUNDIALS::sunnonlinsolfixedpoint_static + PUBLIC SUNDIALS::cvodes_static + PUBLIC SUNDIALS::idas_static PUBLIC ${SUITESPARSE_LIBRARIES} PUBLIC ${HDF5_LIBRARIES} PUBLIC ${BLAS_LIBRARIES} PUBLIC ${CMAKE_DL_LIBS} ) +option(SUNDIALS_SUPERLUMT_ENABLE "Enable sundials SuperLUMT?" OFF) +if(SUNDIALS_SUPERLUMT_ENABLE) + set(SUNDIALS_LIBRARIES ${SUNDIALS_LIBRARIES} + ${SUNDIALS_LIB_DIR}/libsundials_sunlinsolsuperlumt${CMAKE_STATIC_LIBRARY_SUFFIX} + ${CMAKE_SOURCE_DIR}/ThirdParty/SuperLU_MT_3.1/lib/libsuperlu_mt_PTHREAD${CMAKE_STATIC_LIBRARY_SUFFIX} + -lblas + ) + target_include_directories(${PROJECT_NAME} + PUBLIC "${CMAKE_SOURCE_DIR}/ThirdParty/SuperLU_MT_3.1/SRC/") +endif() + # Create targets to make the sources show up in IDEs for convenience # For matlab interface diff --git a/cmake/AmiciConfig.cmake b/cmake/AmiciConfig.cmake index 7db90266f3..5f56358e12 100644 --- a/cmake/AmiciConfig.cmake +++ b/cmake/AmiciConfig.cmake @@ -2,6 +2,8 @@ include(CMakeFindDependencyMacro) +find_package(SUNDIALS REQUIRED PATHS "@CMAKE_SOURCE_DIR@/ThirdParty/sundials/build/lib/cmake/sundials/") + include("${CMAKE_CURRENT_LIST_DIR}/AmiciTargets.cmake") check_required_components(Amici) From 742c464406e23d9c48028066edd20331949e3766 Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 4 Apr 2022 21:21:04 +0200 Subject: [PATCH 25/31] Remove ancient TRAVIS CI checks from build scripts (#1757) --- scripts/buildAmici.sh | 5 ++--- scripts/buildSundials.sh | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/scripts/buildAmici.sh b/scripts/buildAmici.sh index 2d40d3cb08..e49472c205 100755 --- a/scripts/buildAmici.sh +++ b/scripts/buildAmici.sh @@ -13,8 +13,7 @@ amici_build_dir="${amici_path}/build" mkdir -p "${amici_build_dir}" cd "${amici_build_dir}" -if [ "${TRAVIS:-}" = true ] || - [ "${GITHUB_ACTIONS:-}" = true ] || +if [ "${GITHUB_ACTIONS:-}" = true ] || [ "${ENABLE_AMICI_DEBUGGING:-}" = TRUE ]; then # Running on CI server build_type="Debug" @@ -32,7 +31,7 @@ if [ "${CI_SONARCLOUD:-}" = "TRUE" ]; then build-wrapper-linux-x86-64 \ --out-dir "${amici_path}/bw-output" \ cmake --build . --parallel -elif [ "${TRAVIS:-}" = "true" ]; then +elif [ "${GITHUB_ACTIONS:-}" = "true" ]; then cmake --build . ${make} python-sdist else diff --git a/scripts/buildSundials.sh b/scripts/buildSundials.sh index dfd71e69cc..d651d99040 100755 --- a/scripts/buildSundials.sh +++ b/scripts/buildSundials.sh @@ -14,7 +14,7 @@ sundials_build_path="${amici_path}/ThirdParty/sundials/build/" cmake=${CMAKE:-cmake} make=${MAKE:-make} -if [[ $TRAVIS = true ]]; then +if [[ $GITHUB_ACTIONS = true ]]; then # Running on CI server build_type="Debug" else From cf375b9c3f71cf763eea743c05d311b0d6c3a766 Mon Sep 17 00:00:00 2001 From: Polina Lakrisenko Date: Tue, 5 Apr 2022 10:53:58 +0200 Subject: [PATCH 26/31] Steady state sensitivity modes (#1727) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Settings for specifying sensitivities computation method at steady state have been implemented/reworked. Three `SteadyStateSensitivityMode` are available for both `SensitivityMethod`s (`forward` and `adjoint`): - `newtonOnly`: only Newton's method is used for sensitivities computation - `integrationOnly`: only integration is used for sensitivities computation, which in forward sensitivity analysis case means that numerical integration has to be used to find the steady state - `integrateIfNewtonFails`: more flexible option, where simulation is used if Newton's method fails Note: Previously available `SteadyStateSensitivityMode` `simulationFSA` has been removed. Co-authored-by: Fabian Fröhlich --- include/amici/defines.h | 3 +- .../ExampleEquilibrationLogic.ipynb | 10 +-- .../test_compare_conservation_laws_sbml.py | 74 +++++++++++-------- python/tests/test_preequilibration.py | 8 +- python/tests/test_pysb.py | 4 +- python/tests/test_sbml_import.py | 3 +- src/steadystateproblem.cpp | 40 +++++++--- tests/petab_test_suite/test_petab_suite.py | 4 +- 8 files changed, 87 insertions(+), 59 deletions(-) diff --git a/include/amici/defines.h b/include/amici/defines.h index 852b0c4370..8efeacf491 100644 --- a/include/amici/defines.h +++ b/include/amici/defines.h @@ -180,7 +180,8 @@ enum class NonlinearSolverIteration { /** Sensitivity computation mode in steadyStateProblem */ enum class SteadyStateSensitivityMode { newtonOnly, - simulationFSA + integrationOnly, + integrateIfNewtonFails }; /** State in which the steady state computation finished */ diff --git a/python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb b/python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb index f65005b1d0..1e24e62a56 100644 --- a/python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb +++ b/python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb @@ -496,9 +496,9 @@ } ], "source": [ - "# Call simulation with singular Jacobian and simulationFSA mode\n", + "# Call simulation with singular Jacobian and integrateIfNewtonFails mode\n", "model.setTimepoints(np.full(1, np.inf))\n", - "model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.simulationFSA)\n", + "model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.integrateIfNewtonFails)\n", "solver = model.getSolver()\n", "solver.setNewtonMaxSteps(10)\n", "solver.setSensitivityMethod(amici.SensitivityMethod.forward)\n", @@ -883,7 +883,7 @@ ], "source": [ "# Singluar Jacobian, use simulation\n", - "model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.simulationFSA)\n", + "model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.integrateIfNewtonFails)\n", "solver = model.getSolver()\n", "solver.setNewtonMaxSteps(10)\n", "solver.setSensitivityMethod(amici.SensitivityMethod.forward)\n", @@ -1135,7 +1135,7 @@ ], "source": [ "# Non-singular Jacobian, use simulaiton\n", - "model_reduced.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.simulationFSA)\n", + "model_reduced.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.integrateIfNewtonFails)\n", "solver_reduced = model_reduced.getSolver()\n", "solver_reduced.setNewtonMaxSteps(0)\n", "solver_reduced.setSensitivityMethod(amici.SensitivityMethod.forward)\n", @@ -1186,7 +1186,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.2" + "version": "3.8.10" }, "toc": { "base_numbering": 1, diff --git a/python/tests/test_compare_conservation_laws_sbml.py b/python/tests/test_compare_conservation_laws_sbml.py index 2c760ae014..fc0644ef0a 100644 --- a/python/tests/test_compare_conservation_laws_sbml.py +++ b/python/tests/test_compare_conservation_laws_sbml.py @@ -10,7 +10,7 @@ def edata_fixture(): """edata is generated to test pre- and postequilibration""" edata_pre = amici.ExpData(2, 0, 0, - np.array([0., 0.1, 0.2, 0.5, 1., 2., 5., 10.])) + np.array([0., 0.1, 0.2, 0.5, 1., 2., 5., 10.])) edata_pre.setObservedData([1.5] * 16) edata_pre.fixedParameters = np.array([5., 20.]) edata_pre.fixedParametersPreequilibration = np.array([0., 10.]) @@ -18,13 +18,15 @@ def edata_fixture(): # edata for postequilibration edata_post = amici.ExpData(2, 0, 0, - np.array([float('inf')] * 3)) + np.array([float('inf')] * 3)) edata_post.setObservedData([0.75] * 6) edata_post.fixedParameters = np.array([7.5, 30.]) # edata with both equilibrations edata_full = amici.ExpData(2, 0, 0, - np.array([0., 0., 0., 1., 2., 2., 4., float('inf'), float('inf')])) + np.array( + [0., 0., 0., 1., 2., 2., 4., float('inf'), + float('inf')])) edata_full.setObservedData([3.14] * 18) edata_full.fixedParameters = np.array([1., 2.]) edata_full.fixedParametersPreequilibration = np.array([3., 4.]) @@ -42,7 +44,7 @@ def models(): sbml_importer = amici.SbmlImporter(sbml_file) # Name of the model that will also be the name of the python module - model_name = model_output_dir ='model_constant_species' + model_name = model_output_dir = 'model_constant_species' model_name_cl = model_output_dir_cl = 'model_constant_species_cl' # Define constants, observables, sigmas @@ -67,9 +69,11 @@ def models(): compute_conservation_laws=False) # load both models - model_without_cl_module = amici.import_model_module(model_name, + model_without_cl_module = amici.import_model_module( + model_name, module_path=os.path.abspath(model_name)) - model_with_cl_module = amici.import_model_module(model_name_cl, + model_with_cl_module = amici.import_model_module( + model_name_cl, module_path=os.path.abspath(model_name_cl)) # get the models and return @@ -81,8 +85,8 @@ def models(): def get_results(model, edata=None, sensi_order=0, sensi_meth=amici.SensitivityMethod.forward, sensi_meth_preeq=amici.SensitivityMethod.forward, + stst_sensi_mode=amici.SteadyStateSensitivityMode.newtonOnly, reinitialize_states=False): - # set model and data properties model.setReinitializeFixedParameterInitialStates(reinitialize_states) @@ -92,6 +96,7 @@ def get_results(model, edata=None, sensi_order=0, solver.setSensitivityOrder(sensi_order) solver.setSensitivityMethodPreequilibration(sensi_meth_preeq) solver.setSensitivityMethod(sensi_meth) + model.setSteadyStateSensitivityMode(stst_sensi_mode) if edata is None: model.setTimepoints(np.linspace(0, 5, 101)) else: @@ -134,9 +139,6 @@ def test_compare_conservation_laws_sbml(models, edata_fixture): assert np.isclose(rdata[field], rdata_cl[field]).all(), field # ----- compare simulations wo edata, sensi = 0, states and sensis ------- - model_without_cl.setSteadyStateSensitivityMode( - amici.SteadyStateSensitivityMode.simulationFSA - ) # run simulations edata, _, _ = edata_fixture @@ -154,7 +156,10 @@ def test_compare_conservation_laws_sbml(models, edata_fixture): # run simulations rdata_cl = get_results(model_with_cl, edata=edata, sensi_order=1) assert rdata_cl['status'] == amici.AMICI_SUCCESS - rdata = get_results(model_without_cl, edata=edata, sensi_order=1) + rdata = get_results( + model_without_cl, edata=edata, sensi_order=1, + stst_sensi_mode=amici.SteadyStateSensitivityMode.integrateIfNewtonFails + ) assert rdata['status'] == amici.AMICI_SUCCESS # check that steady state computation succeeded only by sim in full model assert (rdata['preeq_status'] == np.array([-3, 1, 0])).all() @@ -178,43 +183,50 @@ def test_compare_conservation_laws_sbml(models, edata_fixture): def test_adjoint_pre_and_post_equilibration(edata_fixture): # get both models - model_module = amici.import_model_module('model_constant_species', + model_module = amici.import_model_module( + 'model_constant_species', module_path=os.path.abspath('model_constant_species')) model = model_module.getModel() - model_module_cl = amici.import_model_module('model_constant_species_cl', + model_module_cl = amici.import_model_module( + 'model_constant_species_cl', module_path=os.path.abspath('model_constant_species_cl')) model_cl = model_module_cl.getModel() # check gradient with and without state reinitialization for edata in edata_fixture: for reinit in [False, True]: - # --- compare different ways of preequilibration, full rank Jacobian --------- + # --- compare different ways of preequilibration, full rank Jacobian ------ # forward preequilibration, forward simulation - rff_cl = get_results(model_cl, edata=edata, sensi_order=1, - sensi_meth=amici.SensitivityMethod.forward, - sensi_meth_preeq=amici.SensitivityMethod.forward, - reinitialize_states=reinit) + rff_cl = get_results( + model_cl, edata=edata, sensi_order=1, + sensi_meth=amici.SensitivityMethod.forward, + sensi_meth_preeq=amici.SensitivityMethod.forward, + reinitialize_states=reinit) # forward preequilibration, adjoint simulation - rfa_cl = get_results(model_cl, edata=edata, sensi_order=1, - sensi_meth=amici.SensitivityMethod.adjoint, - sensi_meth_preeq=amici.SensitivityMethod.forward, - reinitialize_states=reinit) + rfa_cl = get_results( + model_cl, edata=edata, sensi_order=1, + sensi_meth=amici.SensitivityMethod.adjoint, + sensi_meth_preeq=amici.SensitivityMethod.forward, + reinitialize_states=reinit) # adjoint preequilibration, adjoint simulation - raa_cl = get_results(model_cl, edata=edata, sensi_order=1, - sensi_meth=amici.SensitivityMethod.adjoint, - sensi_meth_preeq=amici.SensitivityMethod.adjoint, - reinitialize_states=reinit) + raa_cl = get_results( + model_cl, edata=edata, sensi_order=1, + sensi_meth=amici.SensitivityMethod.adjoint, + sensi_meth_preeq=amici.SensitivityMethod.adjoint, + reinitialize_states=reinit) # assert all are close assert np.isclose(rff_cl['sllh'], rfa_cl['sllh']).all() assert np.isclose(rfa_cl['sllh'], raa_cl['sllh']).all() assert np.isclose(raa_cl['sllh'], rff_cl['sllh']).all() - # --- compare fully adjoint approach to simulation with singular Jacobian ---- - raa = get_results(model, edata=edata, sensi_order=1, - sensi_meth=amici.SensitivityMethod.adjoint, - sensi_meth_preeq=amici.SensitivityMethod.adjoint, - reinitialize_states=reinit) + # --- compare fully adjoint approach to simulation with singular Jacobian ---- + raa = get_results( + model, edata=edata, sensi_order=1, + sensi_meth=amici.SensitivityMethod.adjoint, + sensi_meth_preeq=amici.SensitivityMethod.adjoint, + stst_sensi_mode=amici.SteadyStateSensitivityMode.integrateIfNewtonFails, + reinitialize_states=reinit) # assert gradients are close (quadrature tolerances are laxer) assert np.isclose(raa_cl['sllh'], raa['sllh'], 1e-5, 1e-5).all() diff --git a/python/tests/test_preequilibration.py b/python/tests/test_preequilibration.py index 0aefd9dfcc..d87f10b8e1 100644 --- a/python/tests/test_preequilibration.py +++ b/python/tests/test_preequilibration.py @@ -286,7 +286,8 @@ def test_equilibration_methods_with_adjoints(preeq_fixture): rdatas = {} equil_meths = [amici.SteadyStateSensitivityMode.newtonOnly, - amici.SteadyStateSensitivityMode.simulationFSA] + amici.SteadyStateSensitivityMode.integrationOnly, + amici.SteadyStateSensitivityMode.integrateIfNewtonFails] sensi_meths = [amici.SensitivityMethod.forward, amici.SensitivityMethod.adjoint] settings = itertools.product(equil_meths, sensi_meths) @@ -333,7 +334,7 @@ def test_newton_solver_equilibration(preeq_fixture): edata.setObservedDataStdDev(np.hstack([stdy, stdy[0]])) rdatas = {} - settings = [amici.SteadyStateSensitivityMode.simulationFSA, + settings = [amici.SteadyStateSensitivityMode.integrationOnly, amici.SteadyStateSensitivityMode.newtonOnly] solver.setNewtonStepSteadyStateCheck(True) @@ -345,9 +346,6 @@ def test_newton_solver_equilibration(preeq_fixture): model.setSteadyStateSensitivityMode(equil_meth) if equil_meth == amici.SteadyStateSensitivityMode.newtonOnly: solver.setNewtonMaxSteps(10) - else: - solver.setSensiSteadyStateCheck(False) - solver.setNewtonMaxSteps(0) # add rdatas rdatas[equil_meth] = amici.runAmiciSimulation(model, solver, edata) diff --git a/python/tests/test_pysb.py b/python/tests/test_pysb.py index b55ed5e6ba..df8558fadd 100644 --- a/python/tests/test_pysb.py +++ b/python/tests/test_pysb.py @@ -201,7 +201,7 @@ def get_data(model): solver = model.getSolver() model.setTimepoints(np.linspace(0, 60, 61)) model.setSteadyStateSensitivityMode( - amici.SteadyStateSensitivityMode.simulationFSA + amici.SteadyStateSensitivityMode.integrateIfNewtonFails ) rdata = amici.runAmiciSimulation(model, solver) @@ -220,7 +220,7 @@ def get_results(model, edata): edata.reinitializeFixedParameterInitialStates = True model.setTimepoints(np.linspace(0, 60, 61)) model.setSteadyStateSensitivityMode( - amici.SteadyStateSensitivityMode.simulationFSA + amici.SteadyStateSensitivityMode.integrateIfNewtonFails ) return amici.runAmiciSimulation(model, solver, edata) diff --git a/python/tests/test_sbml_import.py b/python/tests/test_sbml_import.py index 4637e149ad..64bf2610f1 100644 --- a/python/tests/test_sbml_import.py +++ b/python/tests/test_sbml_import.py @@ -205,10 +205,9 @@ def test_presimulation(sbml_example_presimulation_module): """Test 'presimulation' test model""" model = sbml_example_presimulation_module.getModel() solver = model.getSolver() - solver.setNewtonMaxSteps(0) model.setTimepoints(np.linspace(0, 60, 61)) model.setSteadyStateSensitivityMode( - amici.SteadyStateSensitivityMode.simulationFSA + amici.SteadyStateSensitivityMode.integrationOnly ) solver.setSensitivityOrder(amici.SensitivityOrder.first) model.setReinitializeFixedParameterInitialStates(True) diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp index 88eb69ad8f..be16d56981 100644 --- a/src/steadystateproblem.cpp +++ b/src/steadystateproblem.cpp @@ -94,16 +94,22 @@ void SteadystateProblem::workSteadyStateBackwardProblem( void SteadystateProblem::findSteadyState(const Solver &solver, Model &model, int it) { steady_state_status_.resize(3, SteadyStateStatus::not_run); + bool turnOffNewton = model.getSteadyStateSensitivityMode() == + SteadyStateSensitivityMode::integrationOnly && + ((it == -1 && solver.getSensitivityMethodPreequilibration() == + SensitivityMethod::forward) || solver.getSensitivityMethod() == + SensitivityMethod::forward); /* First, try to run the Newton solver */ - findSteadyStateByNewtonsMethod(model, false); + if (!turnOffNewton) + findSteadyStateByNewtonsMethod(model, false); /* Newton solver didn't work, so try to simulate to steady state */ if (!checkSteadyStateSuccess()) findSteadyStateBySimulation(solver, model, it); /* Simulation didn't work, retry the Newton solver from last sim state. */ - if (!checkSteadyStateSuccess()) + if (!turnOffNewton && !checkSteadyStateSuccess()) findSteadyStateByNewtonsMethod(model, true); /* Nothing worked, throw an as informative error as possible */ @@ -243,11 +249,17 @@ void SteadystateProblem::computeSteadyStateQuadrature(const Solver &solver, We therefore compute the integral over xB first and then do a matrix-vector multiplication */ - /* Try to compute the analytical solution for quadrature algebraically */ - getQuadratureByLinSolve(model); + auto sensitivityMode = model.getSteadyStateSensitivityMode(); - /* Analytical solution didn't work, perform simulation instead */ - if (!hasQuadrature()) + /* Try to compute the analytical solution for quadrature algebraically */ + if (sensitivityMode == SteadyStateSensitivityMode::newtonOnly + || sensitivityMode == SteadyStateSensitivityMode::integrateIfNewtonFails) + getQuadratureByLinSolve(model); + + /* Perform simulation */ + if (sensitivityMode == SteadyStateSensitivityMode::integrationOnly || + (sensitivityMode == SteadyStateSensitivityMode::integrateIfNewtonFails + && !hasQuadrature())) getQuadratureBySimulation(solver, model); /* If analytic solution and integration did not work, throw an Exception */ @@ -366,8 +378,10 @@ bool SteadystateProblem::getSensitivityFlag(const Model &model, bool forwardSensisAlreadyComputed = solver.getSensitivityOrder() >= SensitivityOrder::first && steady_state_status_[1] == SteadyStateStatus::success && - model.getSteadyStateSensitivityMode() == - SteadyStateSensitivityMode::simulationFSA; + (model.getSteadyStateSensitivityMode() == + SteadyStateSensitivityMode::integrationOnly || + model.getSteadyStateSensitivityMode() == + SteadyStateSensitivityMode::integrateIfNewtonFails); bool simulationStartedInSteadystate = steady_state_status_[0] == SteadyStateStatus::success && @@ -392,9 +406,13 @@ bool SteadystateProblem::getSensitivityFlag(const Model &model, !simulationStartedInSteadystate; /* When we're creating a new solver object */ - bool needForwardSensiAtCreation = - needForwardSensisPreeq && model.getSteadyStateSensitivityMode() == - SteadyStateSensitivityMode::simulationFSA; + bool needForwardSensiAtCreation = + needForwardSensisPreeq && + (model.getSteadyStateSensitivityMode() == + SteadyStateSensitivityMode::integrationOnly || + model.getSteadyStateSensitivityMode() == + SteadyStateSensitivityMode::integrateIfNewtonFails + ); /* Check if we need to store sensis */ switch (context) { diff --git a/tests/petab_test_suite/test_petab_suite.py b/tests/petab_test_suite/test_petab_suite.py index 59c13c72b4..97d4bc6866 100755 --- a/tests/petab_test_suite/test_petab_suite.py +++ b/tests/petab_test_suite/test_petab_suite.py @@ -16,7 +16,7 @@ from amici.petab_import import import_petab_problem, PysbPetabProblem from amici.petab_objective import ( simulate_petab, rdatas_to_measurement_df, create_parameterized_edatas) -from amici import SteadyStateSensitivityMode_simulationFSA +from amici import SteadyStateSensitivityMode logger = get_logger(__name__, logging.DEBUG) set_log_level(get_logger("amici.petab_import"), logging.DEBUG) @@ -134,7 +134,7 @@ def check_derivatives(problem: petab.Problem, model: amici.Model) -> None: # Required for case 9 to not fail in # amici::NewtonSolver::computeNewtonSensis model.setSteadyStateSensitivityMode( - SteadyStateSensitivityMode_simulationFSA) + SteadyStateSensitivityMode.integrateIfNewtonFails) for edata in create_parameterized_edatas( amici_model=model, petab_problem=problem, From e4aaa0d32e2b17f91bc925fcc09c090bbbecbe20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Tue, 5 Apr 2022 10:40:38 -0400 Subject: [PATCH 27/31] Exploit stoichiometric matrix in pysb import (#1761) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * exploit stoichiometric matrix * add solver mapping * fixup * fixups * fixup * fixup conservation laws * Steady state sensitivity modes (#1727) * Steady State Sensitivity modes * integrationOnly and integrateIfNewtonFails in forward sensitivity case * turn off newton method in case integrationOnly and forward sensitivity * python tests * Apply suggestions from code review Co-authored-by: Fabian Fröhlich * turn off newtin in findSteadyState * fix test_compare_conservation_laws_sbml * take preequilibration into account * fix test_compare_conservation_laws_sbml * quick notebook fix Co-authored-by: Fabian Fröhlich * update according to reviews Co-authored-by: Polina Lakrisenko --- python/amici/ode_export.py | 16 ++++++ python/amici/pysb_import.py | 99 +++++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index dd96678e30..ec9172fa82 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -1952,6 +1952,22 @@ def state_has_conservation_law(self, ix: int) -> bool: """ return self._states[ix]._conservation_law is not None + def get_solver_indices(self) -> Dict[int, int]: + """ + Returns a mapping that maps rdata species indices to solver indices + + :return: + dictionary mapping rdata species indices to solver indices + """ + solver_index = {} + ix_solver = 0 + for ix in range(len(self._states)): + if self.state_has_conservation_law(ix): + continue + solver_index[ix] = ix_solver + ix_solver += 1 + return solver_index + def state_is_constant(self, ix: int) -> bool: """ Checks whether the temporal derivative of the state is zero diff --git a/python/amici/pysb_import.py b/python/amici/pysb_import.py index 8d201c2b75..f666d1bb4a 100644 --- a/python/amici/pysb_import.py +++ b/python/amici/pysb_import.py @@ -240,11 +240,110 @@ def ode_model_from_pysb_importer( for noise_distr in noise_distributions.values() ) + _process_stoichiometric_matrix(model, ode, constant_parameters) + ode.generate_basic_variables() return ode +@log_execution_time('processing PySB stoich. matrix', logger) +def _process_stoichiometric_matrix(pysb_model: pysb.Model, + ode_model: ODEModel, + constant_parameters: List[str]) -> None: + + """ + Exploits the PySB stoichiometric matrix to generate xdot derivatives + + :param pysb_model: + pysb model instance + + :param ode_model: + ODEModel instance + + :param constant_parameters: + list of constant parameters + """ + + x = ode_model.sym('x') + w = list(ode_model.sym('w')) + p = list(ode_model.sym('p')) + x_rdata = list(ode_model.sym('x_rdata')) + + n_x = len(x) + n_w = len(w) + n_p = len(p) + n_r = len(pysb_model.reactions) + + solver_index = ode_model.get_solver_indices() + dflux_dx_dict = {} + dflux_dw_dict = {} + dflux_dp_dict = {} + + w_idx = dict() + p_idx = dict() + wx_idx = dict() + + def get_cached_index(symbol, sarray, index_cache): + idx = index_cache.get(symbol, None) + if idx is not None: + return idx + idx = sarray.index(symbol) + index_cache[symbol] = idx + return idx + + for ir, rxn in enumerate(pysb_model.reactions): + for ix in np.unique(rxn['reactants']): + idx = solver_index.get(ix, None) + if idx is not None: + # species + values = dflux_dx_dict + else: + # conservation law + idx = get_cached_index(x_rdata[ix], w, wx_idx) + values = dflux_dw_dict + + values[(ir, idx)] = sp.diff(rxn['rate'], x_rdata[ix]) + + # typically <= 3 free symbols in rate, we already account for + # species above so we only need to account for propensity, which + # can only be a parameter or expression + for fs in rxn['rate'].free_symbols: + # dw + if isinstance(fs, pysb.Expression): + var = w + idx_cache = w_idx + values = dflux_dw_dict + # dp + elif isinstance(fs, pysb.Parameter): + if fs.name in constant_parameters: + continue + var = p + idx_cache = p_idx + values = dflux_dp_dict + else: + continue + + idx = get_cached_index(fs, var, idx_cache) + values[(ir, idx)] = sp.diff(rxn['rate'], fs) + + dflux_dx = sp.ImmutableSparseMatrix(n_r, n_x, dflux_dx_dict) + dflux_dw = sp.ImmutableSparseMatrix(n_r, n_w, dflux_dw_dict) + dflux_dp = sp.ImmutableSparseMatrix(n_r, n_p, dflux_dp_dict) + + # use dok format to convert numeric csc to sparse symbolic + S = sp.ImmutableSparseMatrix( + n_x, n_r, # don't use shape here as we are eliminating rows + pysb_model.stoichiometry_matrix[ + np.asarray(list(solver_index.keys())),: + ].todok() + ) + # don't use `.dot` since it's awfully slow + ode_model._eqs['dxdotdx_explicit'] = S*dflux_dx + ode_model._eqs['dxdotdw'] = S*dflux_dw + ode_model._eqs['dxdotdp_explicit'] = S*dflux_dp + + @log_execution_time('processing PySB species', logger) def _process_pysb_species(pysb_model: pysb.Model, ode_model: ODEModel) -> None: From 6669cfa01016ea71748c864443fa955dae1ae1cc Mon Sep 17 00:00:00 2001 From: Paul Lang <47160416+paulflang@users.noreply.github.com> Date: Wed, 6 Apr 2022 03:13:35 +0100 Subject: [PATCH 28/31] update windows installation (#1763) Co-authored-by: Daniel Weindl --- documentation/python_installation.rst | 22 +++++++++------------- scripts/compileBLAS.cmd | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/documentation/python_installation.rst b/documentation/python_installation.rst index 73bb25fc8f..b2171f8cbb 100644 --- a/documentation/python_installation.rst +++ b/documentation/python_installation.rst @@ -192,12 +192,12 @@ To build OpenBLAS, download the following scripts from the AMICI repository: The first script needs to be called in Powershell, and it needs to call ``compileBLAS.cmd``, so you will need to modify line 11: - C: \\Users\\travis\\build\\AMICI\\scripts\\compileBLAS.cmd + cmd /c "scripts\compileBLAS.cmd $version" so that it matches your directory structure. This will download OpenBLAS and compile it, creating - C:\\BLAS\lib\\openblas.lib + C:\\BLAS\\lib\\openblas.lib C:\\BLAS\\bin\\openblas.dll You will also need to define two environment variables: @@ -205,7 +205,7 @@ You will also need to define two environment variables: .. code-block:: text BLAS_LIBS="/LIBPATH:C:\BLAS\lib openblas.lib" - BLAS_CFLAGS="/IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12" + BLAS_CFLAGS="/IC:/BLAS/OpenBLAS-0.3.19/OpenBLAS-0.3.19" One way to do that is to run a PowerShell script with the following commands: @@ -213,8 +213,8 @@ One way to do that is to run a PowerShell script with the following commands: [System.Environment]::SetEnvironmentVariable("BLAS_LIBS", "/LIBPATH:C:/BLAS/lib openblas.lib", [System.EnvironmentVariableTarget]::User) [System.Environment]::SetEnvironmentVariable("BLAS_LIBS", "/LIBPATH:C:/BLAS/lib openblas.lib", [System.EnvironmentVariableTarget]::Process) - [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12", [System.EnvironmentVariableTarget]::User) - [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:/BLAS/OpenBLAS-0.3.12/OpenBLAS-0.3.12", [System.EnvironmentVariableTarget]::Process) + [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:/BLAS/OpenBLAS-0.3.19/OpenBLAS-0.3.19", [System.EnvironmentVariableTarget]::User) + [System.Environment]::SetEnvironmentVariable("BLAS_CFLAGS", "-IC:/BLAS/OpenBLAS-0.3.19/OpenBLAS-0.3.19", [System.EnvironmentVariableTarget]::Process) The call ending in ``Process`` sets the environment variable in the current process, and it is no longer in effect in the next process. The call ending in @@ -235,14 +235,6 @@ following error upon ``import amici``: ImportError: DLL load failed: The specified module could not be found. -This can be tested using the "where" command. For example - - where openblas.dll - -should return - - C:\\BLAS\\bin\\openblas.dll - Almost all of the DLLs are standard Windows DLLs and should be included in either Windows or Visual Studio. But, in case it is necessary to test this, here is a list of some DLLs required by AMICI (when compiled with MSVC): @@ -279,6 +271,10 @@ by MSVC (see Visual Studio above). ``KERNEL32.dll`` is part of Windows and in or via the environment variable ``AMICI_DLL_DIRS``. +If Python returns the following error upon ``import amici``, try updating to the latest Python version. + + OSError: [WinError 87] The parameter is incorrect: '' + Further topics ++++++++++++++ diff --git a/scripts/compileBLAS.cmd b/scripts/compileBLAS.cmd index f0f0d280d0..909d55a0b8 100644 --- a/scripts/compileBLAS.cmd +++ b/scripts/compileBLAS.cmd @@ -1,6 +1,6 @@ echo compileBLAS.cmd started for openBLAS version %1 cd /D "C:\BLAS\OpenBLAS-%1\OpenBLAS-%1" -call "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat" +call "C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Auxiliary\Build\vcvars64.bat" cmake -G "Ninja" ^ -DBUILD_DOUBLE=1 ^ -DBUILD_SHARED_LIBS=ON ^ From 64cad7136ea8b2916a665ad8698f2e0f1bf64e5d Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Wed, 6 Apr 2022 04:14:01 +0200 Subject: [PATCH 29/31] Use `amici.AmiciVersionError` to indicate version mismatch (#1764) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using a more specific exception will allow downstream packages to handle version mismatches better, by e.g. automatically re-importing such models. Co-authored-by: Fabian Fröhlich --- python/amici/__init__.py | 6 ++++++ python/amici/__init__.template.py | 13 +++++++------ 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/python/amici/__init__.py b/python/amici/__init__.py index 9b2d6158cc..9bf1f28855 100644 --- a/python/amici/__init__.py +++ b/python/amici/__init__.py @@ -178,3 +178,9 @@ def import_model_module(module_name: str, with add_path(module_path): return importlib.import_module(module_name) + + +class AmiciVersionError(RuntimeError): + """Error thrown if an AMICI model is loaded that is incompatible with + the installed AMICI base package""" + pass diff --git a/python/amici/__init__.template.py b/python/amici/__init__.template.py index 5c42765ec7..f8600d8fb6 100644 --- a/python/amici/__init__.template.py +++ b/python/amici/__init__.template.py @@ -4,12 +4,13 @@ # Ensure we are binary-compatible, see #556 if 'TPL_AMICI_VERSION' != amici.__version__: - raise RuntimeError('Cannot use model TPL_MODELNAME, generated with AMICI ' - 'version TPL_AMICI_VERSION, together with AMICI version' - f' {amici.__version__} which is present in your ' - 'PYTHONPATH. Install the AMICI package matching the ' - 'model version or regenerate the model with the AMICI ' - 'currently in your path.') + raise amici.AmiciVersionError( + 'Cannot use model `TPL_MODELNAME`, generated with amici==' + f'TPL_AMICI_VERSION, together with amici=={amici.__version__} ' + 'which is currently installed. To use this model, install ' + 'amici==TPL_AMICI_VERSION or re-import the model with the amici ' + 'version currently installed.' + ) from TPL_MODELNAME._TPL_MODELNAME import * From e003cba1cbb82fb93b7ed893029b21cf1f3309d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabian=20Fr=C3=B6hlich?= Date: Thu, 7 Apr 2022 07:48:16 -0400 Subject: [PATCH 30/31] Sparsify jacobian (#1766) --- .github/workflows/test_performance.yml | 2 +- python/amici/ode_export.py | 36 +++++++++++++++----------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/.github/workflows/test_performance.yml b/.github/workflows/test_performance.yml index 1d012ce958..1a7e12c53b 100644 --- a/.github/workflows/test_performance.yml +++ b/.github/workflows/test_performance.yml @@ -4,7 +4,7 @@ on: branches: - develop - master - - feature_1739_par_jac + - sparsify_jacobian pull_request: branches: diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py index ec9172fa82..e3da48b416 100644 --- a/python/amici/ode_export.py +++ b/python/amici/ode_export.py @@ -18,7 +18,7 @@ import subprocess import sys from dataclasses import dataclass -from itertools import chain +from itertools import chain, starmap from string import Template from typing import (Any, Callable, Dict, List, Optional, Sequence, Set, Tuple, Union) @@ -406,7 +406,7 @@ def var_in_function_signature(name: str, varname: str) -> bool: @log_execution_time('running smart_jacobian', logger) def smart_jacobian(eq: sp.MutableDenseMatrix, - sym_var: sp.MutableDenseMatrix) -> sp.MutableDenseMatrix: + sym_var: sp.MutableDenseMatrix) -> sp.MutableSparseMatrix: """ Wrapper around symbolic jacobian with some additional checks that reduce computation time for large matrices @@ -418,27 +418,35 @@ def smart_jacobian(eq: sp.MutableDenseMatrix, :return: jacobian of eq wrt sym_var """ + nrow = eq.shape[0] + ncol = sym_var.shape[0] if ( not min(eq.shape) or not min(sym_var.shape) or smart_is_zero_matrix(eq) or smart_is_zero_matrix(sym_var) ): - return sp.zeros(eq.shape[0], sym_var.shape[0]) + return sp.MutableSparseMatrix(nrow, ncol, dict()) + + # preprocess sparsity pattern + elements = ( + (i, j, a, b) + for i, a in enumerate(eq) + for j, b in enumerate(sym_var) + if a.has(b) + ) if (n_procs := int(os.environ.get("AMICI_IMPORT_NPROCS", 1))) == 1: # serial - return sp.Matrix([ - _jacobian_row(eq[i, :], sym_var) - for i in range(eq.shape[0]) - ]) + return sp.MutableSparseMatrix(nrow, ncol, + dict(starmap(_jacobian_element, elements)) + ) # parallel from multiprocessing import Pool with Pool(n_procs) as p: - mapped = p.starmap(_jacobian_row, - ((eq[i, :], sym_var) for i in range(eq.shape[0]))) - return sp.Matrix(mapped) + mapped = p.starmap(_jacobian_element, elements) + return sp.MutableSparseMatrix(nrow, ncol, dict(mapped)) @log_execution_time('running smart_multiply', logger) @@ -3352,8 +3360,6 @@ def _custom_pow_eval_derivative(self, s): ) -def _jacobian_row(eq_i, sym_var): - """Compute a row of a jacobian""" - if eq_i.has(*sym_var.flat()): - return eq_i.jacobian(sym_var) - return [0] * sym_var.shape[0] +def _jacobian_element(i, j, eq_i, sym_var_j): + """Compute a single element of a jacobian""" + return (i, j), eq_i.diff(sym_var_j) From d8f8ad679ace7adf23705c8f08481b240d27308a Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 4 Apr 2022 21:40:55 +0200 Subject: [PATCH 31/31] Bump version number, update changelog --- CHANGELOG.md | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++ version.txt | 2 +- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 207072c787..9823652959 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,63 @@ ## v0.X Series +### v0.11.27 (2022-04-04) + +New features: +* Checking condition number when computing sensitivities via Newton + by @FFroehlich in https://github.com/AMICI-dev/AMICI/pull/1730 +* Removed SPBCG solver by @FFroehlich in + https://github.com/AMICI-dev/AMICI/pull/1729 +* Added Newton step convergence checks to steadystate solver by @FFroehlich in + https://github.com/AMICI-dev/AMICI/pull/1737 +* Removed legacy options/members `amioption.newton_preeq` and `Solver::r… by + @dweindl in https://github.com/AMICI-dev/AMICI/pull/1744 +* Added `ReturnData::cpu_time_total` to track total time spent in + `runAmiciSimulation` by @dweindl in + https://github.com/AMICI-dev/AMICI/pull/1743 +* SBML import: Alternative algorithm for identifying conservation laws by + @dweindl in https://github.com/AMICI-dev/AMICI/pull/1748 +* Use `amici.AmiciVersionError` to indicate version mismatch by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1764 + +Performance: +* Optional parallel computation of derivatives during model import by @dweindl + in https://github.com/AMICI-dev/AMICI/pull/1740 +* Sparsify jacobian by @FFroehlich in https://github.com/AMICI-dev/AMICI/pull/1766 +* Speedup for models with conservation laws by @FFroehlich in https://github.com/AMICI-dev/AMICI/pull/1765 +* Speedup conservation law computation by @FFroehlich in + https://github.com/AMICI-dev/AMICI/pull/1754 +* Exploit stoichiometric matrix in pysb import by @FFroehlich in + https://github.com/AMICI-dev/AMICI/pull/1761 +* Speedup edata construction from petab problems by @FFroehlich in + https://github.com/AMICI-dev/AMICI/pull/1746 + +Fixes: +* Fixed `get_model_settings` that would to setting incorrect initial states and + initial state sensitivities for models with parameter-dependent initial + states by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1751 +* Use correct tolerances for convergence check in Newton solver by @FFroehlich + in https://github.com/AMICI-dev/AMICI/pull/1728 +* Harmonized convergence checks by @FFroehlich in + https://github.com/AMICI-dev/AMICI/pull/1731 +* Made sundials' KLU_INDEXTYPE match actual klu index type by @dweindl in + https://github.com/AMICI-dev/AMICI/pull/1733 +* Fixed `Model::setStateIsNonNegative` logic that would raise exceptions in + cases where it shouldn't by @dweindl in + https://github.com/AMICI-dev/AMICI/pull/1736 +* Fixed undefined reference to dladdr by @kristianmeyerr in + https://github.com/AMICI-dev/AMICI/pull/1738 +* Fixed HDF5 OSX intermediate group creation errors by @dweindl in + https://github.com/AMICI-dev/AMICI/pull/1741 +* Fixed recent cmake-based build issues due to changed sundials library + directory by @dweindl in https://github.com/AMICI-dev/AMICI/pull/1756 +* Updated Windows installation instructions by @paulflang in + https://github.com/AMICI-dev/AMICI/pull/1763 + +... and other contributions by @FFroehlich, @dweindl + +**Full Changelog**: +https://github.com/AMICI-dev/AMICI/compare/v0.11.26...v0.11.27 + ### v0.11.26 (2022-03-14) New features: diff --git a/version.txt b/version.txt index efb9e6c966..7d7d637254 100644 --- a/version.txt +++ b/version.txt @@ -1 +1 @@ -0.11.26 +0.11.27