diff --git a/.github/workflows/test_petab_test_suite.yml b/.github/workflows/test_petab_test_suite.yml
index f089b544b9..4594317582 100644
--- a/.github/workflows/test_petab_test_suite.yml
+++ b/.github/workflows/test_petab_test_suite.yml
@@ -71,7 +71,8 @@ jobs:
- name: Run PEtab-related unit tests
run: |
source ./build/venv/bin/activate \
- && pytest --cov-report=xml --cov=./ python/tests/test_*petab*.py
+ && pytest --cov-report=xml:coverage.xml \
+ --cov=./ python/tests/test_*petab*.py
# run test models
- name: Run PEtab test suite
@@ -79,12 +80,15 @@ jobs:
run: |
source ./build/venv/bin/activate \
&& AMICI_PARALLEL_COMPILE=2 pytest -v \
- --cov-report=xml --cov-append --cov=amici tests/petab_test_suite/
+ --cov-report=xml:coverage.xml \
+ --cov-append \
+ --cov=amici \
+ tests/petab_test_suite/
- name: Codecov
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3.1.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
- file: ./coverage.xml
+ file: coverage.xml
flags: petab
fail_ci_if_error: true
diff --git a/.github/workflows/test_python_cplusplus.yml b/.github/workflows/test_python_cplusplus.yml
index d7fbb7d5ae..69b09e9dde 100644
--- a/.github/workflows/test_python_cplusplus.yml
+++ b/.github/workflows/test_python_cplusplus.yml
@@ -104,12 +104,13 @@ jobs:
scripts/runNotebook.sh documentation/GettingStarted.ipynb
- name: Codecov Python
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3.1.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
- file: ./build/coverage_py.xml
+ file: build/coverage_py.xml
flags: python
fail_ci_if_error: true
+ verbose: true
- name: lcov
run: |
@@ -117,15 +118,15 @@ jobs:
-d ${AMICI_DIR}/build/CMakeFiles/amici.dir/src \
-b ${AMICI_DIR} -c -o coverage_cpp.info \
&& lcov --compat-libtool --no-external \
- -d ${AMICI_DIR}/python/sdist/build/temp.linux-x86_64-$(python3 --version | sed -E 's/.*([0-9]+\.[0-9]+)\..*/\1/')/amici/src \
+ -d ${AMICI_DIR}/python/sdist/build/temp.linux-x86_64-$(python -c "import sys; print(sys.implementation.cache_tag)")/amici/src \
-b ${AMICI_DIR}/python/sdist -c -o coverage_py.info \
&& lcov -a coverage_cpp.info -a coverage_py.info -o coverage.info
- name: Codecov CPP
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3.1.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
- file: ./coverage.info
+ file: coverage.info
flags: cpp
fail_ci_if_error: true
diff --git a/.github/workflows/test_sbml_semantic_test_suite.yml b/.github/workflows/test_sbml_semantic_test_suite.yml
index 458f010fd0..7901ef8640 100644
--- a/.github/workflows/test_sbml_semantic_test_suite.yml
+++ b/.github/workflows/test_sbml_semantic_test_suite.yml
@@ -53,9 +53,9 @@ jobs:
path: tests/amici-semantic-results
- name: Codecov SBMLSuite
- uses: codecov/codecov-action@v2
+ uses: codecov/codecov-action@v3.1.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
- file: ./coverage_SBMLSuite.xml
+ file: coverage_SBMLSuite.xml
flags: sbmlsuite
fail_ci_if_error: true
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 4bde64a0b1..07e7c3c6f8 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,30 @@
## v0.X Series
+### v0.11.29 (2022-05-06)
+
+## What's Changed
+
+Features:
+* Performance: Limit newton step convergence check by @FFroehlich in
+ https://github.com/AMICI-dev/AMICI/pull/1780
+* More informative NaN/Inf warnings by @dweindl in
+ https://github.com/AMICI-dev/AMICI/pull/1640
+* SBML import can now handle initial events by @FFroehlich in
+ https://github.com/AMICI-dev/AMICI/pull/1789
+
+Fixes:
+* Avoid error if no measurements in PEtab problem; fixed type handling in
+ PEtab parameter mapping by @dilpath in
+ https://github.com/AMICI-dev/AMICI/pull/1783
+* Fixed substitution of expressions in root and stau by @dilpath in
+ https://github.com/AMICI-dev/AMICI/pull/1784
+* Workaround for PEtab problems with state-dependent noise models by @dweindl
+ in https://github.com/AMICI-dev/AMICI/pull/1791
+
+**Full Changelog**: https://github.com/AMICI-dev/AMICI/compare/v0.11.28...v0.11.29
+
+
### v0.11.28 (2022-04-08)
New features:
diff --git a/documentation/python_interface.rst b/documentation/python_interface.rst
index b4137c27e3..02cc22af35 100644
--- a/documentation/python_interface.rst
+++ b/documentation/python_interface.rst
@@ -26,7 +26,7 @@ AMICI can import :term:`SBML` models via the
Status of SBML support in Python-AMICI
++++++++++++++++++++++++++++++++++++++
-Python-AMICI currently **passes 1014 out of the 1821 (~56%) test cases** from
+Python-AMICI currently **passes 1030 out of the 1821 (~57%) test cases** from
the semantic
`SBML Test Suite `_
(`current status `_).
diff --git a/include/amici/forwardproblem.h b/include/amici/forwardproblem.h
index 712377d959..38c3ba1785 100644
--- a/include/amici/forwardproblem.h
+++ b/include/amici/forwardproblem.h
@@ -290,9 +290,11 @@ class ForwardProblem {
*
* @param tlastroot pointer to the timepoint of the last event
* @param seflag Secondary event flag
+ * @param initial_event initial event flag
*/
- void handleEvent(realtype *tlastroot,bool seflag);
+ void handleEvent(realtype *tlastroot, bool seflag,
+ bool initial_event);
/**
* @brief Extract output information for events
diff --git a/include/amici/misc.h b/include/amici/misc.h
index ee069b5224..329ca9a48d 100644
--- a/include/amici/misc.h
+++ b/include/amici/misc.h
@@ -187,6 +187,17 @@ class ContextManager{
ContextManager(ContextManager &&other) = delete;
};
+
+/**
+ * @brief Convert a flat index to a pair of row/column indices,
+ * assuming row-major order.
+ * @param flat_idx flat index
+ * @param num_cols number of columns of referred to matrix
+ * @return row index, column index
+ */
+auto unravel_index(size_t flat_idx, size_t num_cols)
+ -> std::pair;
+
} // namespace amici
#endif // AMICI_MISC_H
diff --git a/include/amici/model.h b/include/amici/model.h
index fac088af2c..1897f9b295 100644
--- a/include/amici/model.h
+++ b/include/amici/model.h
@@ -34,6 +34,42 @@ void serialize(Archive &ar, amici::Model &m, unsigned int version);
namespace amici {
+/**
+ * @brief Describes the various model quantities
+ */
+enum class ModelQuantity {
+ J,
+ JB,
+ Jv,
+ JvB,
+ JDiag,
+ sx,
+ sy,
+ ssigmay,
+ xdot,
+ sxdot,
+ xBdot,
+ x0_rdata,
+ x0,
+ x_rdata,
+ x,
+ dwdw,
+ dwdx,
+ dwdp,
+ y,
+ dydp,
+ dydx,
+ w,
+ root,
+ qBdot,
+ qBdot_ss,
+ xBdot_ss,
+ JSparseB_ss,
+};
+
+extern const std::map model_quantity_to_str;
+
+
/**
* @brief The Model class represents an AMICI ODE/DAE model.
*
@@ -61,7 +97,8 @@ class Model : public AbstractModel, public ModelDimensions {
SimulationParameters simulation_parameters,
amici::SecondOrderMode o2mode,
std::vector idlist,
- std::vector z2event, bool pythonGenerated = false,
+ std::vector z2event,
+ bool pythonGenerated = false,
int ndxdotdp_explicit = 0, int ndxdotdx_explicit = 0,
int w_recursion_depth = 0);
@@ -166,9 +203,11 @@ class Model : public AbstractModel, public ModelDimensions {
* @param sdx Reference to time derivative of state sensitivities (DAE only)
* @param computeSensitivities Flag indicating whether sensitivities are to
* be computed
+ * @param roots_found boolean indicators indicating whether roots were found at t0 by this fun
*/
void initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx,
- AmiVectorArray &sdx, bool computeSensitivities);
+ AmiVectorArray &sdx, bool computeSensitivities,
+ std::vector &roots_found);
/**
* @brief Initialize model properties.
@@ -200,8 +239,10 @@ class Model : public AbstractModel, public ModelDimensions {
*
* @param x Reference to state variables
* @param dx Reference to time derivative of states (DAE only)
+ * @param roots_found boolean indicators indicating whether roots were found at t0 by this fun
*/
- void initHeaviside(const AmiVector &x, const AmiVector &dx);
+ void initEvents(const AmiVector &x, const AmiVector &dx,
+ std::vector &roots_found);
/**
* @brief Get number of parameters wrt to which sensitivities are computed.
@@ -1201,18 +1242,43 @@ class Model : public AbstractModel, public ModelDimensions {
*/
void updateHeavisideB(const int *rootsfound);
+
+ /**
+ * @brief Check if the given array has only finite elements.
+ *
+ * For (1D) spans.
+ *
+ * @param array
+ * @param model_quantity The model quantity `array` corresponds to
+ * @return
+ */
+ int checkFinite(gsl::span array,
+ ModelQuantity model_quantity) const;
+ /**
+ * @brief Check if the given array has only finite elements.
+ *
+ * For flattened 2D arrays.
+ *
+ * @param array Flattened matrix
+ * @param model_quantity The model quantity `array` corresponds to
+ * @param num_cols Number of columns of the non-flattened matrix
+ * @return
+ */
+ int checkFinite(gsl::span array,
+ ModelQuantity model_quantity,
+ size_t num_cols) const;
+
/**
* @brief Check if the given array has only finite elements.
*
- * If not, try to give hints by which other fields this could be caused.
+ * For SUNMatrix.
*
- * @param array Array to check
- * @param fun Name of the function that generated the values (for more
- * informative messages).
- * @return `amici::AMICI_RECOVERABLE_ERROR` if a NaN/Inf value was found,
- * `amici::AMICI_SUCCESS` otherwise
+ * @param m Matrix to check
+ * @param model_quantity The model quantity `m` corresponds to
+ * @param t current timepoint
+ * @return
*/
- int checkFinite(gsl::span array, const char *fun) const;
+ int checkFinite(SUNMatrix m, ModelQuantity model_quantity, realtype t) const;
/**
* @brief Set whether the result of every call to `Model::f*` should be
@@ -1803,6 +1869,11 @@ class Model : public AbstractModel, public ModelDimensions {
/** vector of bools indicating whether state variables are to be assumed to
* be positive */
std::vector state_is_non_negative_;
+
+ /** Vector of booleans indicating the initial boolean value for every event trigger function. Events at t0
+ * can only trigger if the initial value is set to `false`. Must be specified during model compilation by
+ * setting the `initialValue` attribute of an event trigger. */
+ std::vector root_initial_values_;
/** boolean indicating whether any entry in stateIsNonNegative is `true` */
bool any_state_non_negative_ {false};
diff --git a/include/amici/sundials_matrix_wrapper.h b/include/amici/sundials_matrix_wrapper.h
index b94667e39e..52d6c3377e 100644
--- a/include/amici/sundials_matrix_wrapper.h
+++ b/include/amici/sundials_matrix_wrapper.h
@@ -532,6 +532,16 @@ class SUNMatrixWrapper {
bool ownmat = true;
};
+
+/**
+ * @brief Convert a flat index to a pair of row/column indices.
+ * @param i flat index
+ * @param m referred to matrix
+ * @return row index, column index
+ */
+auto unravel_index(sunindextype i, SUNMatrix m)
+ -> std::pair;
+
} // namespace amici
namespace gsl {
diff --git a/python/amici/ode_export.py b/python/amici/ode_export.py
index a1f40409b6..b40e025524 100644
--- a/python/amici/ode_export.py
+++ b/python/amici/ode_export.py
@@ -822,7 +822,7 @@ def transform_dxdt_to_concentration(species_id, dxdt):
else:
args += ['value']
if symbol_name == SymbolId.EVENT:
- args += ['state_update', 'event_observable']
+ args += ['state_update', 'event_observable', 'initial_value']
if symbol_name == SymbolId.OBSERVABLE:
args += ['transformation']
@@ -1297,7 +1297,7 @@ def parse_events(self) -> None:
expr.set_val(self._process_heavisides(expr.get_val(), roots))
# remove all possible Heavisides from roots, which may arise from
- # the substitution of `'w'` in `_get_unique_root`
+ # the substitution of `'w'` in `_collect_heaviside_roots`
for root in roots:
root.set_val(self._process_heavisides(root.get_val(), roots))
@@ -1589,34 +1589,43 @@ def _compute_equation(self, name: str) -> None:
elif name == 'stau':
self._eqs[name] = [
-self.eq('sroot')[ie, :] / self.eq('drootdt_total')[ie]
+ if not self.eq('drootdt_total')[ie].is_zero else
+ sp.zeros(*self.eq('sroot')[ie, :].shape)
for ie in range(self.num_events())
]
elif name == 'deltasx':
event_eqs = []
for ie, event in enumerate(self._events):
- if event._state_update is not None:
- # ====== chain rule for the state variables ===============
- # get xdot with expressions back-substituted
- tmp_eq = smart_multiply(
+
+ tmp_eq = sp.zeros(self.num_states_solver(), self.num_par())
+
+ # only add stau part if trigger is time-dependent
+ if not self.eq('drootdt_total')[ie].is_zero:
+ tmp_eq += smart_multiply(
(self.sym('xdot_old') - self.sym('xdot')),
self.eq('stau')[ie])
- # construct an enhanced state sensitivity, which accounts
- # for the time point sensitivity as well
+
+ # only add deltax part if there is state update
+ if event._state_update is not None:
+ # partial derivative for the parameters
+ tmp_eq += self.eq('ddeltaxdp')[ie]
+
+ # initial part of chain rule state variables
tmp_dxdp = self.sym('sx') * sp.ones(1, self.num_par())
- tmp_dxdp += smart_multiply(self.sym('xdot'),
- self.eq('stau')[ie])
+
+ # only add stau part if trigger is time-dependent
+ if not self.eq('drootdt_total')[ie].is_zero:
+ # chain rule for the time point
+ tmp_eq += smart_multiply(self.eq('ddeltaxdt')[ie],
+ self.eq('stau')[ie])
+
+ # additional part of chain rule state variables
+ tmp_dxdp += smart_multiply(self.sym('xdot'),
+ self.eq('stau')[ie])
+ # finish chain rule for the state variables
tmp_eq += smart_multiply(self.eq('ddeltaxdx')[ie],
tmp_dxdp)
- # ====== chain rule for the time point ====================
- tmp_eq += smart_multiply(self.eq('ddeltaxdt')[ie],
- self.eq('stau')[ie])
- # ====== partial derivative for the parameters ============
- tmp_eq += self.eq('ddeltaxdp')[ie]
- else:
- tmp_eq = smart_multiply(
- (self.eq('xdot_old') - self.eq('xdot')),
- self.eq('stau')[ie])
event_eqs.append(tmp_eq)
@@ -2066,15 +2075,6 @@ def _get_unique_root(
unique identifier for root, or ``None`` if the root is not
time-dependent
"""
- # substitute 'w' expressions into root expressions now, to avoid
- # rewriting '{model_name}_root.cpp' and '{model_name}_stau.cpp' headers
- # to include 'w.h'
- w_sorted = toposort_symbols(dict(zip(
- [expr.get_id() for expr in self._expressions],
- [expr.get_val() for expr in self._expressions],
- )))
- root_found = root_found.subs(w_sorted)
-
if not self._expr_is_time_dependent(root_found):
return None
@@ -2115,6 +2115,18 @@ def _collect_heaviside_roots(
elif arg.has(sp.Heaviside):
root_funs.extend(self._collect_heaviside_roots(arg.args))
+ # substitute 'w' expressions into root expressions now, to avoid
+ # rewriting '{model_name}_root.cpp' and '{model_name}_stau.cpp' headers
+ # to include 'w.h'
+ w_sorted = toposort_symbols(dict(zip(
+ [expr.get_id() for expr in self._expressions],
+ [expr.get_val() for expr in self._expressions],
+ )))
+ root_funs = [
+ r.subs(w_sorted)
+ for r in root_funs
+ ]
+
return root_funs
def _process_heavisides(
@@ -2934,6 +2946,11 @@ def _write_model_header_cpp(self) -> None:
'W_RECURSION_DEPTH': self.model._w_recursion_depth,
'QUADRATIC_LLH': 'true'
if self.model._has_quadratic_nllh else 'false',
+ 'ROOT_INITIAL_VALUES':
+ ', '.join([
+ 'true' if event.get_initial_value() else 'false'
+ for event in self.model._events
+ ])
}
for func_name, func_info in self.functions.items():
diff --git a/python/amici/ode_model.py b/python/amici/ode_model.py
index a5885216f3..ed494cf3ea 100644
--- a/python/amici/ode_model.py
+++ b/python/amici/ode_model.py
@@ -2,17 +2,7 @@
import sympy as sp
-import numpy as np
-import re
-import shutil
-import subprocess
-import sys
-import os
-import copy
import numbers
-import logging
-import itertools
-import contextlib
try:
import pysb
@@ -20,25 +10,11 @@
pysb = None
from typing import (
- Callable, Optional, Union, List, Dict, Tuple, SupportsFloat, Sequence,
- Set, Any
+ Optional, Union, Dict, SupportsFloat, Set
)
-from dataclasses import dataclass
-from string import Template
-from sympy.matrices.immutable import ImmutableDenseMatrix
-from sympy.matrices.dense import MutableDenseMatrix
-from sympy.logic.boolalg import BooleanAtom
-from itertools import chain
-from .cxxcodeprinter import AmiciCxxCodePrinter, get_switch_statement
-
-from . import (
- amiciSwigPath, amiciSrcPath, amiciModulePath, __version__, __commit__,
- sbml_import
-)
-from .logging import get_logger, log_execution_time, set_log_level
-from .constants import SymbolId
-from .import_utils import smart_subs_dict, toposort_symbols, \
- ObservableTransformation, generate_measurement_symbol, RESERVED_SYMBOLS
+
+from .import_utils import ObservableTransformation, \
+ generate_measurement_symbol, RESERVED_SYMBOLS
from .import_utils import cast_to_sym
__all__ = [
@@ -46,6 +22,7 @@
'ModelQuantity', 'Observable', 'Parameter', 'SigmaY', 'State'
]
+
class ModelQuantity:
"""
Base class for model components
@@ -166,14 +143,6 @@ def __init__(self,
self._ncoeff: sp.Expr = coefficients[state_id]
super(ConservationLaw, self).__init__(identifier, name, value)
- def get_state(self) -> sp.Symbol:
- """
- Get the identifier of the state that this conservation law replaces
-
- :return: identifier of the state
- """
- return self._state_id
-
def get_ncoeff(self, state_id) -> Union[sp.Expr, int, float]:
"""
Computes the normalized coefficient a_i/a_j where i is the index of
@@ -211,10 +180,6 @@ class State(ModelQuantity):
algebraic formula that defines the temporal derivative of this state
"""
-
- _dt: Union[sp.Expr, None] = None
- _conservation_law: Union[sp.Expr, None] = None
-
def __init__(self,
identifier: sp.Symbol,
name: str,
@@ -276,7 +241,7 @@ def get_dt(self) -> sp.Expr:
"""
return self._dt
- def get_free_symbols(self) -> Set[sp.Symbol]:
+ def get_free_symbols(self) -> Set[sp.Basic]:
"""
Gets the set of free symbols in time derivative and initial conditions
@@ -307,8 +272,9 @@ def get_x_rdata(self):
def get_dx_rdata_dx_solver(self, state_id):
"""
- Returns the expression that allows computation of ``dx_rdata_dx_solver`` for this
- state, accounting for conservation laws.
+ Returns the expression that allows computation of
+ ``dx_rdata_dx_solver`` for this state, accounting for conservation
+ laws.
:return: dx_rdata_dx_solver expression
"""
@@ -514,7 +480,8 @@ def __init__(self,
name: str,
value: sp.Expr,
state_update: Union[sp.Expr, None],
- event_observable: Union[sp.Expr, None]):
+ event_observable: Union[sp.Expr, None],
+ initial_value: Optional[bool] = True):
"""
Create a new Event instance.
@@ -534,15 +501,30 @@ def __init__(self,
:param event_observable:
formula a potential observable linked to the event
(None for Heaviside functions, empty events without observable)
+
+ :param initial_value:
+ initial boolean value of the trigger function at t0. If set to
+ `False`, events may trigger at ``t==t0``, otherwise not.
"""
super(Event, self).__init__(identifier, name, value)
# add the Event specific components
self._state_update = state_update
self._observable = event_observable
+ self._initial_value = initial_value
+
+ def get_initial_value(self) -> bool:
+ """
+ Return the initial value for the root function.
+
+ :return:
+ initial value formula
+ """
+ return self._initial_value
def __eq__(self, other):
"""
Check equality of events at the level of trigger/root functions, as we
need to collect unique root functions for ``roots.cpp``
"""
- return self.get_val() == other.get_val()
+ return self.get_val() == other.get_val() and \
+ (self.get_initial_value() == other.get_initial_value())
diff --git a/python/amici/petab_import.py b/python/amici/petab_import.py
index f1183b451f..bc520f9d20 100644
--- a/python/amici/petab_import.py
+++ b/python/amici/petab_import.py
@@ -40,6 +40,38 @@
PREEQ_INDICATOR_ID = 'preequilibration_indicator'
+def _add_global_parameter(sbml_model: libsbml.Model,
+ parameter_id: str,
+ parameter_name: str = None,
+ constant: bool = False,
+ units: str = 'dimensionless',
+ value: float = 0.0) -> libsbml.Parameter:
+ """Add new global parameter to SBML model
+
+ Arguments:
+ sbml_model: SBML model
+ parameter_id: ID of the new parameter
+ parameter_name: Name of the new parameter
+ constant: Is parameter constant?
+ units: SBML unit ID
+ value: parameter value
+
+ Returns:
+ The created parameter
+ """
+
+ if parameter_name is None:
+ parameter_name = parameter_id
+
+ p = sbml_model.createParameter()
+ p.setId(parameter_id)
+ p.setName(parameter_name)
+ p.setConstant(constant)
+ p.setValue(value)
+ p.setUnits(units)
+ return p
+
+
def get_fixed_parameters(
sbml_model: 'libsbml.Model',
condition_df: Optional[pd.DataFrame] = None,
@@ -501,11 +533,12 @@ def import_model_sbml(
key=lambda symbol: symbol.name)
for free_sym in free_syms:
sym = str(free_sym)
- if sbml_model.getElementBySId(sym) is None and sym != 'time':
+ if sbml_model.getElementBySId(sym) is None and sym != 'time' \
+ and sym not in observables:
output_parameters[sym] = None
logger.debug(f"Adding output parameters to model: {output_parameters}")
for par in output_parameters.keys():
- petab.add_global_parameter(sbml_model, par)
+ _add_global_parameter(sbml_model, par)
#
# TODO: to parameterize initial states or compartment sizes, we currently
@@ -624,10 +657,15 @@ def get_observation_model(
observables[oid] = {'name': name, 'formula': formula_obs}
sigmas[oid] = formula_noise
- # Replace observableIds occurring in error model definition
+ # PEtab does currently not allow observables in noiseFormula and AMICI
+ # cannot handle states in sigma expressions. Therefore, where possible,
+ # replace species occurring in error model definition by observableIds.
+ replacements = {
+ sp.sympify(observable['formula']): sp.Symbol(observable_id)
+ for observable_id, observable in observables.items()
+ }
for observable_id, formula in sigmas.items():
- repl = sp.sympify(formula).subs(
- observable_id, observables[observable_id]['formula'])
+ repl = sp.sympify(formula).subs(replacements)
sigmas[observable_id] = str(repl)
noise_distrs = petab_noise_distributions_to_amici(observable_df)
diff --git a/python/amici/petab_objective.py b/python/amici/petab_objective.py
index 1e6dfb0e7f..0cb8b4d9d2 100644
--- a/python/amici/petab_objective.py
+++ b/python/amici/petab_objective.py
@@ -154,8 +154,11 @@ def simulate_petab(
# Log results
sim_cond = petab_problem.get_simulation_conditions_from_measurement_df()
for i, rdata in enumerate(rdatas):
- logger.debug(f"Condition: {sim_cond.iloc[i, :].values}, status: "
- f"{rdata['status']}, llh: {rdata['llh']}")
+ sim_cond_id = "N/A" if sim_cond.empty else sim_cond.iloc[i, :].values
+ logger.debug(
+ f"Condition: {sim_cond_id}, status: {rdata['status']}, "
+ f"llh: {rdata['llh']}"
+ )
return {
LLH: llh,
@@ -230,7 +233,7 @@ def create_parameterized_edatas(
def create_parameter_mapping(
petab_problem: petab.Problem,
- simulation_conditions: Union[pd.DataFrame, Dict],
+ simulation_conditions: Union[pd.DataFrame, List[Dict]],
scaled_parameters: bool,
amici_model: AmiciModel,
) -> ParameterMapping:
@@ -254,6 +257,8 @@ def create_parameter_mapping(
if simulation_conditions is None:
simulation_conditions = \
petab_problem.get_simulation_conditions_from_measurement_df()
+ if isinstance(simulation_conditions, list):
+ simulation_conditions = pd.DataFrame(data=simulation_conditions)
# Because AMICI globalizes all local parameters during model import,
# we need to do that here as well to prevent parameter mapping errors
diff --git a/python/amici/sbml_import.py b/python/amici/sbml_import.py
index 616f82e102..8b38e06f87 100644
--- a/python/amici/sbml_import.py
+++ b/python/amici/sbml_import.py
@@ -484,13 +484,6 @@ def check_event_support(self) -> None:
if trigger_sbml.getMath() is None:
logger.warning(f'Event {event_id} trigger has no trigger '
'expression, so a dummy trigger will be set.')
- if not trigger_sbml.getInitialValue():
- # True: event not executed if triggered at time == 0
- # (corresponding to AMICI default). Raise if set to False.
- raise SBMLException(
- f'Event {event_id} has a trigger that has an initial '
- 'value of False. This is currently not supported in AMICI.'
- )
if not trigger_sbml.getPersistent():
raise SBMLException(
@@ -1001,10 +994,16 @@ def _convert_event_assignment_parameter_targets_to_species(self):
parameter_def = \
self.symbols[symbol_id].pop(parameter_target)
if parameter_def is None:
- raise AssertionError(
- 'Unexpected error. The parameter target of an event '
- 'assignment could not be found.'
+ # this happens for parameters that have initial assignments
+ # or are assignment rule targets
+ par = self.sbml.getElementBySId(str(parameter_target))
+ ia_init = self._get_element_initial_assignment(
+ par.getId()
)
+ parameter_def = {
+ 'name': par.getName() if par.isSetName() else par.getId(),
+ 'value': par.getValue() if ia_init is None else ia_init
+ }
# Fixed parameters are added as species such that they can be
# targets of events.
self.symbols[SymbolId.SPECIES][parameter_target] = {
@@ -1140,6 +1139,9 @@ def get_empty_bolus_value() -> sp.Float:
'value': trigger,
'state_update': sp.MutableDenseMatrix(bolus),
'event_observable': None,
+ 'initial_value':
+ trigger_sbml.getInitialValue() if trigger_sbml is not None
+ else True,
}
@log_execution_time('processing SBML observables', logger)
diff --git a/src/amici.cpp b/src/amici.cpp
index 5be90dad00..cc20bd5927 100644
--- a/src/amici.cpp
+++ b/src/amici.cpp
@@ -179,7 +179,7 @@ AmiciApplication::runAmiciSimulation(Solver& solver,
throw;
warningF("AMICI:simulation",
"AMICI forward simulation failed at t = %f: "
- "Maximum time exceeed.\n",
+ "Maximum time exceeded.\n",
ex.time);
} else {
rdata->status = ex.error_code;
@@ -199,7 +199,7 @@ AmiciApplication::runAmiciSimulation(Solver& solver,
warningF(
"AMICI:simulation",
"AMICI backward simulation failed when trying to solve until "
- "t = %f: Maximum time exceeed.\n",
+ "t = %f: Maximum time exceeded.\n",
ex.time);
} else {
@@ -305,6 +305,8 @@ AmiciApplication::errorF(const char* identifier, const char* format, ...) const
int
AmiciApplication::checkFinite(gsl::span array, const char* fun)
{
+ Expects(array.size()
+ <= static_cast(std::numeric_limits::max()));
for (int idx = 0; idx < (int)array.size(); idx++) {
if (isNaN(array[idx])) {
diff --git a/src/forwardproblem.cpp b/src/forwardproblem.cpp
index 8147edce05..773ffc5c4b 100644
--- a/src/forwardproblem.cpp
+++ b/src/forwardproblem.cpp
@@ -52,15 +52,21 @@ void ForwardProblem::workForwardProblem() {
if (!preequilibrated_)
model->initialize(x_, dx_, sx_, sdx_,
solver->getSensitivityOrder() >=
- SensitivityOrder::first);
- else if (model->ne)
- model->initHeaviside(x_, dx_);
+ SensitivityOrder::first,
+ roots_found_);
+ else if (model->ne) {
+ model->initEvents(x_, dx_, roots_found_);
+ }
/* compute initial time and setup solver for (pre-)simulation */
auto t0 = model->t0();
if (presimulate)
t0 -= edata->t_presim;
solver->setup(t0, model, x_, dx_, sx_, sdx_);
+
+ if (model->ne && std::any_of(roots_found_.begin(), roots_found_.end(),
+ [](int rf){return rf==1;}))
+ handleEvent(&t0, false, true);
/* perform presimulation if necessary */
if (presimulate) {
@@ -69,9 +75,14 @@ void ForwardProblem::workForwardProblem() {
" is currently not implemented.");
handlePresimulation();
t_ = model->t0();
- if (model->ne)
- model->initHeaviside(x_, dx_);
+ if (model->ne) {
+ model->initEvents(x_, dx_, roots_found_);
+ if (std::any_of(roots_found_.begin(), roots_found_.end(),
+ [](int rf){return rf==1;}))
+ handleEvent(&t0, false, true);
+ }
}
+
/* when computing adjoint sensitivity analysis with presimulation,
we need to store sx after the reinitialization after preequilibration
but before reinitialization after presimulation. As presimulation with ASA
@@ -92,7 +103,6 @@ void ForwardProblem::workForwardProblem() {
if (solver->computingFSA() || (solver->computingASA() && !presimulate ))
sx_ = solver->getStateSensitivity(model->t0());
-
/* store initial state and sensitivity*/
initial_state_ = getSimulationState();
@@ -114,7 +124,7 @@ void ForwardProblem::workForwardProblem() {
/* clustering of roots => turn off rootfinding */
solver->turnOffRootFinding();
} else if (status == AMICI_ROOT_RETURN) {
- handleEvent(&tlastroot_, false);
+ handleEvent(&tlastroot_, false, false);
}
}
}
@@ -138,7 +148,8 @@ void ForwardProblem::handlePresimulation()
}
-void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) {
+void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag,
+ const bool initial_event) {
/* store Heaviside information at event occurrence */
model->froot(t_, x_, dx_, rootvals_);
@@ -146,14 +157,14 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) {
discs_.push_back(t_);
/* extract and store which events occurred */
- if (!seflag) {
+ if (!seflag && !initial_event) {
solver->getRootInfo(roots_found_.data());
}
root_idx_.push_back(roots_found_);
rval_tmp_ = rootvals_;
- if (!seflag) {
+ if (!seflag && !initial_event) {
/* only check this in the first event fired, otherwise this will always
* be true */
if (t_ == *tlastroot) {
@@ -165,24 +176,24 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) {
*tlastroot = t_;
}
- if(model->nz>0)
+ if(model->nz > 0)
storeEvent();
/* if we need to do forward sensitivities later on we need to store the old
* x and the old xdot */
if (solver->getSensitivityOrder() >= SensitivityOrder::first) {
/* store x and xdot to compute jump in sensitivities */
- x_old_ = x_;
+ x_old_.copy(x_);
}
if (solver->computingFSA()) {
model->fxdot(t_, x_, dx_, xdot_);
- xdot_old_ = xdot_;
- dx_old_ = dx_;
+ xdot_old_.copy(xdot_);
+ dx_old_.copy(dx_);
/* compute event-time derivative only for primary events, we get
* into trouble with multiple simultaneously firing events here (but
* is this really well defined then?), in that case just use the
* last ie and hope for the best. */
- if (!seflag) {
+ if (!seflag && !initial_event) {
for (int ie = 0; ie < model->ne; ie++) {
if (roots_found_.at(ie) == 1) {
/* only consider transitions false -> true */
@@ -190,6 +201,8 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) {
}
}
}
+ if (initial_event) // t0 has no parameter dependency
+ std::fill(stau_.begin(), stau_.end(), 0.0);
} else if (solver->computingASA()) {
/* store x to compute jump in discontinuity */
x_disc_.push_back(x_);
@@ -197,7 +210,8 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) {
xdot_old_disc_.push_back(xdot_old_);
}
- model->updateHeaviside(roots_found_);
+ if (!initial_event)
+ model->updateHeaviside(roots_found_);
applyEventBolus();
@@ -239,7 +253,7 @@ void ForwardProblem::handleEvent(realtype *tlastroot, const bool seflag) {
"Secondary event was triggered. Depending on "
"the bolus of the secondary event, forward "
"sensitivities can be incorrect.");
- handleEvent(tlastroot, true);
+ handleEvent(tlastroot, true, false);
}
/* only reinitialise in the first event fired */
diff --git a/src/misc.cpp b/src/misc.cpp
index 60e9d3ea18..9b4c650cba 100644
--- a/src/misc.cpp
+++ b/src/misc.cpp
@@ -178,4 +178,9 @@ std::string printfToString(const char *fmt, va_list ap) {
return str;
}
+std::pair unravel_index(size_t flat_idx, size_t num_cols)
+{
+ return {flat_idx / num_cols, flat_idx % num_cols};
+}
+
} // namespace amici
diff --git a/src/model.ODE_template.cpp b/src/model.ODE_template.cpp
index c5fb93198d..049124881d 100644
--- a/src/model.ODE_template.cpp
+++ b/src/model.ODE_template.cpp
@@ -53,6 +53,10 @@ std::array stateIdxsSolver = {
TPL_STATE_IDXS_SOLVER_INITIALIZER_LIST
};
+std::array rootInitialValues = {
+ TPL_ROOT_INITIAL_VALUES
+};
+
} // namespace model_TPL_MODELNAME
} // namespace amici
diff --git a/src/model.cpp b/src/model.cpp
index 1edc211dde..e6097fa88f 100644
--- a/src/model.cpp
+++ b/src/model.cpp
@@ -15,6 +15,39 @@
namespace amici {
+/**
+ * @brief Maps ModelQuantity items to their string value
+ */
+const std::map model_quantity_to_str {
+ {ModelQuantity::J, "J"},
+ {ModelQuantity::JB, "JB"},
+ {ModelQuantity::Jv, "Jv"},
+ {ModelQuantity::JvB, "JvB"},
+ {ModelQuantity::JDiag, "JDiag"},
+ {ModelQuantity::sx, "sx"},
+ {ModelQuantity::sy, "sy"},
+ {ModelQuantity::ssigmay, "ssigmay"},
+ {ModelQuantity::xdot, "xdot"},
+ {ModelQuantity::sxdot, "sxdot"},
+ {ModelQuantity::xBdot, "xBdot"},
+ {ModelQuantity::x0, "x0"},
+ {ModelQuantity::x0_rdata, "x0_rdata"},
+ {ModelQuantity::x, "x"},
+ {ModelQuantity::x_rdata, "x_rdata"},
+ {ModelQuantity::dwdw, "dwdw"},
+ {ModelQuantity::dwdx, "dwdx"},
+ {ModelQuantity::dwdp, "dwdp"},
+ {ModelQuantity::y, "y"},
+ {ModelQuantity::dydp, "dydp"},
+ {ModelQuantity::dydx, "dydx"},
+ {ModelQuantity::w, "w"},
+ {ModelQuantity::root, "root"},
+ {ModelQuantity::qBdot, "qBdot"},
+ {ModelQuantity::qBdot_ss, "qBdot_ss"},
+ {ModelQuantity::xBdot_ss, "xBdot_ss"},
+ {ModelQuantity::JSparseB_ss, "JSparseB_ss"},
+};
+
/**
* @brief local helper function to get parameters
* @param ids vector of name/ids of (fixed)Parameters
@@ -97,7 +130,8 @@ static int setValueByIdRegex(std::vector const &ids,
Model::Model(ModelDimensions const & model_dimensions,
SimulationParameters simulation_parameters,
- SecondOrderMode o2mode, std::vector idlist, std::vector z2event,
+ SecondOrderMode o2mode, std::vector idlist,
+ std::vector z2event,
const bool pythonGenerated, const int ndxdotdp_explicit,
const int ndxdotdx_explicit, const int w_recursion_depth)
: ModelDimensions(model_dimensions), pythonGenerated(pythonGenerated),
@@ -120,6 +154,8 @@ Model::Model(ModelDimensions const & model_dimensions,
simulation_parameters_.pscale, state_.unscaledParameters);
state_.fixedParameters = simulation_parameters_.fixedParameters;
state_.plist = simulation_parameters_.plist;
+
+ root_initial_values_.resize(ne, true);
/* If Matlab wrapped: dxdotdp is a full AmiVector,
if Python wrapped: dxdotdp_explicit and dxdotdp_implicit are CSC matrices
@@ -208,7 +244,8 @@ bool operator==(const ModelDimensions &a, const ModelDimensions &b) {
void Model::initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx,
- AmiVectorArray & /*sdx*/, bool computeSensitivities) {
+ AmiVectorArray & /*sdx*/, bool computeSensitivities,
+ std::vector &roots_found) {
initializeStates(x);
if (computeSensitivities)
initializeStateSensitivities(sx, x);
@@ -218,7 +255,7 @@ void Model::initialize(AmiVector &x, AmiVector &dx, AmiVectorArray &sx,
fsdx0();
if (ne)
- initHeaviside(x, dx);
+ initEvents(x, dx, roots_found);
}
void Model::initializeB(AmiVector &xB, AmiVector &dxB, AmiVector &xQB,
@@ -265,14 +302,18 @@ void Model::initializeStateSensitivities(AmiVectorArray &sx,
}
}
-void Model::initHeaviside(AmiVector const &x, AmiVector const &dx) {
+void Model::initEvents(AmiVector const &x, AmiVector const &dx,
+ std::vector &roots_found) {
std::vector rootvals(ne, 0.0);
froot(simulation_parameters_.tstart_, x, dx, rootvals);
+ std::fill(roots_found.begin(), roots_found.end(), 0);
for (int ie = 0; ie < ne; ie++) {
if (rootvals.at(ie) < 0) {
state_.h.at(ie) = 0.0;
} else {
state_.h.at(ie) = 1.0;
+ if (!root_initial_values_.at(ie)) // only false->true triggers event
+ roots_found.at(ie) = 1;
}
}
}
@@ -843,7 +884,7 @@ void Model::getObservableSensitivity(gsl::span sy, const realtype t,
writeSlice(derived_state_.dydp_, sy);
if (always_check_finite_)
- checkFinite(sy, "sy");
+ checkFinite(sy, ModelQuantity::sy, nplist());
}
void Model::getObservableSigma(gsl::span sigmay, const int it,
@@ -875,7 +916,7 @@ void Model::getObservableSigmaSensitivity(gsl::span ssigmay,
}
if (always_check_finite_)
- checkFinite(ssigmay, "ssigmay");
+ checkFinite(ssigmay, ModelQuantity::ssigmay, nplist());
}
void Model::addObservableObjective(realtype &Jy, const int it,
@@ -1218,18 +1259,242 @@ void Model::updateHeavisideB(const int *rootsfound) {
}
}
+int Model::checkFinite(gsl::span array,
+ ModelQuantity model_quantity) const
+{
+ auto it = std::find_if(array.begin(), array.end(),
+ [](realtype x){return !std::isfinite(x);});
+ if(it == array.end()) {
+ return AMICI_SUCCESS;
+ }
+
+ // there is some issue - produce a meaningful message
+ auto flat_index = it - array.begin();
+
+ std::string msg_id;
+ std::string non_finite_type;
+ if (std::isnan(array[flat_index])) {
+ msg_id = "AMICI:NaN";
+ non_finite_type = "NaN";
+ } else if (std::isinf(array[flat_index])) {
+ msg_id = "AMICI:Inf";
+ non_finite_type = "Inf";
+ }
+ std::string element_id = std::to_string(flat_index);
+
+ switch (model_quantity) {
+ case ModelQuantity::xdot:
+ case ModelQuantity::xBdot:
+ case ModelQuantity::x0:
+ case ModelQuantity::x:
+ case ModelQuantity::x_rdata:
+ case ModelQuantity::x0_rdata:
+ case ModelQuantity::Jv:
+ case ModelQuantity::JvB:
+ case ModelQuantity::JDiag:
+ if(hasStateIds()) {
+ element_id = getStateIdsSolver()[flat_index];
+ }
+ break;
+ case ModelQuantity::y:
+ if(hasObservableIds()) {
+ element_id = getObservableIds()[flat_index];
+ }
+ break;
+ case ModelQuantity::w:
+ if(hasExpressionIds()) {
+ element_id = getExpressionIds()[flat_index];
+ }
+ break;
+ default:
+ break;
+ }
-int Model::checkFinite(gsl::span array, const char *fun) const {
- auto result = app->checkFinite(array, fun);
-
- if (result != AMICI_SUCCESS) {
- app->checkFinite(state_.fixedParameters, "k");
- app->checkFinite(state_.unscaledParameters, "p");
+ std::string model_quantity_str;
+ try {
+ model_quantity_str = model_quantity_to_str.at(model_quantity);
+ } catch (std::out_of_range const&) {
+ // Missing model quantity string - terminate if this is a debug build,
+ // but show the quantity number if non-debug.
+ gsl_ExpectsDebug(false);
+ model_quantity_str = std::to_string(static_cast(model_quantity));
+ }
+ app->warningF(msg_id.c_str(),
+ "AMICI encountered a %s value for %s[%i] (%s)",
+ non_finite_type.c_str(),
+ model_quantity_str.c_str(),
+ static_cast(flat_index),
+ element_id.c_str()
+ );
+
+ // check upstream
+ app->checkFinite(state_.fixedParameters, "k");
+ app->checkFinite(state_.unscaledParameters, "p");
+ app->checkFinite(simulation_parameters_.ts_, "t");
+ if(!always_check_finite_) {
+ // don't check twice if always_check_finite_ is true
app->checkFinite(derived_state_.w_, "w");
- app->checkFinite(simulation_parameters_.ts_, "t");
}
- return result;
+ return AMICI_RECOVERABLE_ERROR;
+}
+
+int Model::checkFinite(gsl::span array,
+ ModelQuantity model_quantity, size_t num_cols) const
+{
+ auto it = std::find_if(array.begin(), array.end(),
+ [](realtype x){return !std::isfinite(x);});
+ if(it == array.end()) {
+ return AMICI_SUCCESS;
+ }
+
+ // there is some issue - produce a meaningful message
+ auto flat_index = it - array.begin();
+ sunindextype row, col;
+ std::tie(row, col) = unravel_index(flat_index, num_cols);
+ std::string msg_id;
+ std::string non_finite_type;
+ if (std::isnan(array[flat_index])) {
+ msg_id = "AMICI:NaN";
+ non_finite_type = "NaN";
+ } else if (std::isinf(array[flat_index])) {
+ msg_id = "AMICI:Inf";
+ non_finite_type = "Inf";
+ }
+ std::string row_id = std::to_string(row);
+ std::string col_id = std::to_string(col);
+
+ switch (model_quantity) {
+ case ModelQuantity::sy:
+ case ModelQuantity::ssigmay:
+ case ModelQuantity::dydp:
+ row_id += " " + getObservableIds()[row];
+ col_id += " " + getParameterIds()[plist(col)];
+ break;
+ case ModelQuantity::dydx:
+ row_id += " " + getObservableIds()[row];
+ col_id += " " + getStateIdsSolver()[col];
+ break;
+ default:
+ break;
+ }
+
+ std::string model_quantity_str;
+ try {
+ model_quantity_str = model_quantity_to_str.at(model_quantity);
+ } catch (std::out_of_range const&) {
+ // Missing model quantity string - terminate if this is a debug build,
+ // but show the quantity number if non-debug.
+ gsl_ExpectsDebug(false);
+ model_quantity_str = std::to_string(static_cast(model_quantity));
+ }
+
+ app->warningF(msg_id.c_str(),
+ "AMICI encountered a %s value for %s[%i] (%s, %s)",
+ non_finite_type.c_str(),
+ model_quantity_str.c_str(),
+ static_cast(flat_index),
+ row_id.c_str(),
+ col_id.c_str()
+ );
+
+ // check upstream
+ app->checkFinite(state_.fixedParameters, "k");
+ app->checkFinite(state_.unscaledParameters, "p");
+ app->checkFinite(simulation_parameters_.ts_, "t");
+ app->checkFinite(derived_state_.w_, "w");
+
+ return AMICI_RECOVERABLE_ERROR;
+}
+
+int Model::checkFinite(SUNMatrix m, ModelQuantity model_quantity, realtype t) const
+{
+ // check flat array, to see if there are any issues
+ // (faster, in particular for sparse arrays)
+ auto m_flat = gsl::make_span(m);
+ auto it = std::find_if(m_flat.begin(), m_flat.end(),
+ [](realtype x){return !std::isfinite(x);});
+ if(it == m_flat.end()) {
+ return AMICI_SUCCESS;
+ }
+
+ // there is some issue - produce a meaningful message
+ auto flat_index = it - m_flat.begin();
+ sunindextype row, col;
+ std::tie(row, col) = unravel_index(flat_index, m);
+ std::string msg_id;
+ std::string non_finite_type;
+ if (std::isnan(m_flat[flat_index])) {
+ msg_id = "AMICI:NaN";
+ non_finite_type = "NaN";
+ } else if (std::isinf(m_flat[flat_index])) {
+ msg_id = "AMICI:Inf";
+ non_finite_type = "Inf";
+ } else {
+ throw std::runtime_error(
+ "Value is not finite, but neither infinite nor NaN.");
+ }
+ std::string row_id = std::to_string(row);
+ std::string col_id = std::to_string(col);
+
+ switch (model_quantity) {
+ case ModelQuantity::J:
+ case ModelQuantity::JB:
+ if(hasStateIds()) {
+ auto state_ids = getStateIdsSolver();
+ row_id += " " + state_ids[row];
+ col_id += " " + state_ids[col];
+ }
+ break;
+ case ModelQuantity::dwdx:
+ if(hasExpressionIds())
+ row_id += " " + getExpressionIds()[row];
+ if(hasStateIds())
+ col_id += " " + getStateIdsSolver()[col];
+ break;
+ case ModelQuantity::dwdw:
+ if(hasExpressionIds()) {
+ auto expr_ids = getExpressionIds();
+ row_id += " " + expr_ids[row];
+ col_id += " " + expr_ids[col];
+ }
+ break;
+ case ModelQuantity::dwdp:
+ if(hasExpressionIds())
+ row_id += " " + getExpressionIds()[row];
+ if(hasParameterIds())
+ col_id += " " + getParameterIds()[plist(col)];
+ break;
+ default:
+ break;
+ }
+
+ std::string model_quantity_str;
+ try {
+ model_quantity_str = model_quantity_to_str.at(model_quantity);
+ } catch (std::out_of_range const&) {
+ // Missing model quantity string - terminate if this is a debug build,
+ // but show the quantity number if non-debug.
+ gsl_ExpectsDebug(false);
+ model_quantity_str = std::to_string(static_cast(model_quantity));
+ }
+ app->warningF(msg_id.c_str(),
+ "AMICI encountered a %s value for %s[%i] (%s, %s) at t=%g",
+ non_finite_type.c_str(),
+ model_quantity_str.c_str(),
+ static_cast(flat_index),
+ row_id.c_str(),
+ col_id.c_str(),
+ t
+ );
+
+ // check upstream
+ app->checkFinite(state_.fixedParameters, "k");
+ app->checkFinite(state_.unscaledParameters, "p");
+ app->checkFinite(simulation_parameters_.ts_, "t");
+ app->checkFinite(derived_state_.w_, "w");
+
+ return AMICI_RECOVERABLE_ERROR;
}
void Model::setAlwaysCheckFinite(bool alwaysCheck) {
@@ -1250,8 +1515,8 @@ void Model::fx0(AmiVector &x) {
state_.fixedParameters.data());
if (always_check_finite_) {
- checkFinite(derived_state_.x_rdata_, "x0 x_rdata");
- checkFinite(x.getVector(), "x0 x");
+ checkFinite(derived_state_.x_rdata_, ModelQuantity::x0_rdata);
+ checkFinite(x.getVector(), ModelQuantity::x0);
}
}
@@ -1333,7 +1598,7 @@ void Model::fx_rdata(AmiVector &x_rdata, const AmiVector &x) {
fx_rdata(x_rdata.data(), computeX_pos(x), state_.total_cl.data(),
state_.unscaledParameters.data(), state_.fixedParameters.data());
if (always_check_finite_)
- checkFinite(x_rdata.getVector(), "x_rdata");
+ checkFinite(x_rdata.getVector(), ModelQuantity::x_rdata);
}
void Model::fsx_rdata(AmiVectorArray &sx_rdata, const AmiVectorArray &sx,
@@ -1411,7 +1676,7 @@ void Model::fy(const realtype t, const AmiVector &x) {
state_.h.data(), derived_state_.w_.data());
if (always_check_finite_) {
- app->checkFinite(gsl::make_span(derived_state_.y_.data(), ny), "y");
+ checkFinite(gsl::make_span(derived_state_.y_.data(), ny), ModelQuantity::y);
}
}
@@ -1441,7 +1706,7 @@ void Model::fdydp(const realtype t, const AmiVector &x) {
}
if (always_check_finite_) {
- app->checkFinite(derived_state_.dydp_, "dydp");
+ checkFinite(derived_state_.dydp_, ModelQuantity::dydp, nplist());
}
}
@@ -1461,7 +1726,7 @@ void Model::fdydx(const realtype t, const AmiVector &x) {
derived_state_.dwdx_.data());
if (always_check_finite_) {
- app->checkFinite(derived_state_.dydx_, "dydx");
+ checkFinite(derived_state_.dydx_, ModelQuantity::dydx, ny);
}
}
@@ -2076,7 +2341,7 @@ void Model::fw(const realtype t, const realtype *x) {
state_.fixedParameters.data(), state_.h.data(), state_.total_cl.data());
if (always_check_finite_) {
- app->checkFinite(derived_state_.w_, "w");
+ checkFinite(derived_state_.w_, ModelQuantity::w);
}
}
@@ -2116,7 +2381,7 @@ void Model::fdwdp(const realtype t, const realtype *x) {
}
if (always_check_finite_) {
- app->checkFinite(gsl::make_span(derived_state_.dwdp_.get()), "dwdp");
+ checkFinite(derived_state_.dwdp_.get(), ModelQuantity::dwdp, t);
}
}
@@ -2157,7 +2422,7 @@ void Model::fdwdx(const realtype t, const realtype *x) {
}
if (always_check_finite_) {
- app->checkFinite(gsl::make_span(derived_state_.dwdx_.get()), "dwdx");
+ checkFinite(derived_state_.dwdx_.get(), ModelQuantity::dwdx, t);
}
}
@@ -2172,7 +2437,7 @@ void Model::fdwdw(const realtype t, const realtype *x) {
derived_state_.w_.data(), state_.total_cl.data());
if (always_check_finite_) {
- app->checkFinite(gsl::make_span(dwdw_.get()), "dwdw");
+ checkFinite(dwdw_.get(), ModelQuantity::dwdw, t);
}
}
diff --git a/src/model_dae.cpp b/src/model_dae.cpp
index bed9b4c8c9..2e3d6d06ee 100644
--- a/src/model_dae.cpp
+++ b/src/model_dae.cpp
@@ -87,7 +87,7 @@ void Model_DAE::fJDiag(const realtype t, AmiVector &JDiag,
fJSparse(t, 0.0, x.getNVector(), dx.getNVector(), derived_state_.J_.get());
derived_state_.J_.refresh();
derived_state_.J_.to_diag(JDiag.getNVector());
- if (!checkFinite(JDiag.getVector(), "Jacobian"))
+ if (checkFinite(JDiag.getVector(), ModelQuantity::JDiag) != AMICI_SUCCESS)
throw AmiException("Evaluation of fJDiag failed!");
}
diff --git a/src/model_header.ODE_template.h b/src/model_header.ODE_template.h
index 27e664bd8f..8a885a61ba 100644
--- a/src/model_header.ODE_template.h
+++ b/src/model_header.ODE_template.h
@@ -27,6 +27,7 @@ extern std::array stateIds;
extern std::array observableIds;
extern std::array expressionIds;
extern std::array stateIdxsSolver;
+extern std::array rootInitialValues;
TPL_JY_DEF
TPL_DJYDSIGMA_DEF
@@ -129,7 +130,11 @@ class Model_TPL_MODELNAME : public amici::Model_ODE {
TPL_NDXDOTDP_EXPLICIT, // ndxdotdp_explicit
TPL_NDXDOTDX_EXPLICIT, // ndxdotdx_explicit
TPL_W_RECURSION_DEPTH // w_recursion_depth
- ) {}
+ ) {
+ root_initial_values_ = std::vector(
+ rootInitialValues.begin(), rootInitialValues.end()
+ );
+ }
/**
* @brief Clone this model instance.
diff --git a/src/model_ode.cpp b/src/model_ode.cpp
index 02905ef2c0..65552b8579 100644
--- a/src/model_ode.cpp
+++ b/src/model_ode.cpp
@@ -105,7 +105,7 @@ void Model_ODE::fJDiag(const realtype t, AmiVector &JDiag,
const realtype /*cj*/, const AmiVector &x,
const AmiVector & /*dx*/) {
fJDiag(t, JDiag.getNVector(), x.getNVector());
- if (checkFinite(JDiag.getVector(), "Jacobian") != AMICI_SUCCESS)
+ if (checkFinite(JDiag.getVector(), ModelQuantity::JDiag) != AMICI_SUCCESS)
throw AmiException("Evaluation of fJDiag failed!");
}
diff --git a/src/solver_cvodes.cpp b/src/solver_cvodes.cpp
index c37b1207cd..74998fdf66 100644
--- a/src/solver_cvodes.cpp
+++ b/src/solver_cvodes.cpp
@@ -807,7 +807,6 @@ const Model *CVodeSolver::getModel() const {
/**
* @brief Jacobian of xdot with respect to states x
- * @param N number of state variables
* @param t timepoint
* @param x Vector with the states
* @param xdot Vector with the right hand side
@@ -827,13 +826,12 @@ static int fJ(realtype t, N_Vector x, N_Vector xdot, SUNMatrix J,
Expects(model);
model->fJ(t, x, xdot, J);
- return model->checkFinite(gsl::make_span(J), "Jacobian");
+ return model->checkFinite(J, ModelQuantity::J, t);
}
/**
* @brief Jacobian of xBdot with respect to adjoint state xB
- * @param NeqBdot number of adjoint state variables
* @param t timepoint
* @param x Vector with the states
* @param xB Vector with the adjoint states
@@ -854,7 +852,7 @@ static int fJB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot,
Expects(model);
model->fJB(t, x, xB, xBdot, JB);
- return model->checkFinite(gsl::make_span(JB), "Jacobian");
+ return model->checkFinite(gsl::make_span(JB), ModelQuantity::JB);
}
@@ -879,7 +877,7 @@ static int fJSparse(realtype t, N_Vector x, N_Vector /*xdot*/,
Expects(model);
model->fJSparse(t, x, J);
- return model->checkFinite(gsl::make_span(J), "Jacobian");
+ return model->checkFinite(J, ModelQuantity::J, t);
}
@@ -905,7 +903,7 @@ static int fJSparseB(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot,
Expects(model);
model->fJSparseB(t, x, xB, xBdot, JB);
- return model->checkFinite(gsl::make_span(JB), "Jacobian");
+ return model->checkFinite(gsl::make_span(JB), ModelQuantity::JB);
}
@@ -967,7 +965,7 @@ static int fJv(N_Vector v, N_Vector Jv, realtype t, N_Vector x,
Expects(model);
model->fJv(v, Jv, t, x);
- return model->checkFinite(gsl::make_span(Jv), "Jacobian");
+ return model->checkFinite(gsl::make_span(Jv), ModelQuantity::Jv);
}
@@ -993,7 +991,7 @@ static int fJvB(N_Vector vB, N_Vector JvB, realtype t, N_Vector x,
Expects(model);
model->fJvB(vB, JvB, t, x, xB);
- return model->checkFinite(gsl::make_span(JvB), "Jacobian");
+ return model->checkFinite(gsl::make_span(JvB), ModelQuantity::JvB);
}
@@ -1014,7 +1012,7 @@ static int froot(realtype t, N_Vector x, realtype *root,
model->froot(t, x, gsl::make_span(root, model->ne));
return model->checkFinite(gsl::make_span(root, model->ne),
- "root function");
+ ModelQuantity::root);
}
@@ -1038,7 +1036,8 @@ static int fxdot(realtype t, N_Vector x, N_Vector xdot, void *user_data) {
return AMICI_MAX_TIME_EXCEEDED;
}
- if (t > 1e200 && !model->checkFinite(gsl::make_span(x), "fxdot")) {
+ if (t > 1e200
+ && !model->checkFinite(gsl::make_span(x), ModelQuantity::xdot)) {
/* when t is large (typically ~1e300), CVODES may pass all NaN x
to fxdot from which we typically cannot recover. To save time
on normal execution, we do not always want to check finiteness
@@ -1047,7 +1046,7 @@ static int fxdot(realtype t, N_Vector x, N_Vector xdot, void *user_data) {
}
model->fxdot(t, x, xdot);
- return model->checkFinite(gsl::make_span(xdot), "fxdot");
+ return model->checkFinite(gsl::make_span(xdot), ModelQuantity::xdot);
}
@@ -1074,7 +1073,7 @@ static int fxBdot(realtype t, N_Vector x, N_Vector xB, N_Vector xBdot,
}
model->fxBdot(t, x, xB, xBdot);
- return model->checkFinite(gsl::make_span(xBdot), "fxBdot");
+ return model->checkFinite(gsl::make_span(xBdot), ModelQuantity::xBdot);
}
@@ -1095,7 +1094,7 @@ static int fqBdot(realtype t, N_Vector x, N_Vector xB, N_Vector qBdot,
Expects(model);
model->fqBdot(t, x, xB, qBdot);
- return model->checkFinite(gsl::make_span(qBdot), "qBdot");
+ return model->checkFinite(gsl::make_span(qBdot), ModelQuantity::qBdot);
}
@@ -1116,7 +1115,7 @@ static int fxBdot_ss(realtype t, N_Vector xB, N_Vector xBdot,
Expects(model);
model->fxBdot_ss(t, xB, xBdot);
- return model->checkFinite(gsl::make_span(xBdot), "fxBdot_ss");
+ return model->checkFinite(gsl::make_span(xBdot), ModelQuantity::xBdot_ss);
}
@@ -1137,7 +1136,7 @@ static int fqBdot_ss(realtype t, N_Vector xB, N_Vector qBdot,
Expects(model);
model->fqBdot_ss(t, xB, qBdot);
- return model->checkFinite(gsl::make_span(qBdot), "qBdot_ss");
+ return model->checkFinite(gsl::make_span(qBdot), ModelQuantity::qBdot_ss);
}
/**
@@ -1161,7 +1160,8 @@ static int fJSparseB_ss(realtype /*t*/, N_Vector /*x*/, N_Vector xBdot,
Expects(model);
model->fJSparseB_ss(JB);
- return model->checkFinite(gsl::make_span(xBdot), "JSparseB_ss");
+ return model->checkFinite(gsl::make_span(xBdot),
+ ModelQuantity::JSparseB_ss);
}
@@ -1189,7 +1189,7 @@ static int fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector /*xdot*/,
Expects(model);
model->fsxdot(t, x, ip, sx, sxdot);
- return model->checkFinite(gsl::make_span(sxdot), "sxdot");
+ return model->checkFinite(gsl::make_span(sxdot), ModelQuantity::sxdot);
}
bool operator==(const CVodeSolver &a, const CVodeSolver &b) {
diff --git a/src/solver_idas.cpp b/src/solver_idas.cpp
index 0742f9c095..0039f6de0a 100644
--- a/src/solver_idas.cpp
+++ b/src/solver_idas.cpp
@@ -817,7 +817,7 @@ int fJ(realtype t, realtype cj, N_Vector x, N_Vector dx,
auto model = dynamic_cast(typed_udata->first);
Expects(model);
model->fJ(t, cj, x, dx, xdot, J);
- return model->checkFinite(gsl::make_span(J), "Jacobian");
+ return model->checkFinite(J, ModelQuantity::J, t);
}
/**
@@ -846,7 +846,8 @@ int fJB(realtype t, realtype cj, N_Vector x, N_Vector dx,
Expects(model);
model->fJB(t, cj, x, dx, xB, dxB, JB);
- return model->checkFinite(gsl::make_span(JB), "Jacobian");
+ return model->checkFinite(JB, ModelQuantity::JB, t);
+
}
/**
@@ -873,7 +874,7 @@ int fJSparse(realtype t, realtype cj, N_Vector x, N_Vector dx,
Expects(model);
model->fJSparse(t, cj, x, dx, J);
- return model->checkFinite(gsl::make_span(J), "Jacobian");
+ return model->checkFinite(J, ModelQuantity::J, t);
}
/**
@@ -902,7 +903,7 @@ int fJSparseB(realtype t, realtype cj, N_Vector x, N_Vector dx,
Expects(model);
model->fJSparseB(t, cj, x, dx, xB, dxB, JB);
- return model->checkFinite(gsl::make_span(JB), "Jacobian");
+ return model->checkFinite(JB, ModelQuantity::JB, t);
}
/**
@@ -973,7 +974,7 @@ int fJv(realtype t, N_Vector x, N_Vector dx, N_Vector /*xdot*/,
Expects(model);
model->fJv(t, x, dx, v, Jv, cj);
- return model->checkFinite(gsl::make_span(Jv), "Jacobian");
+ return model->checkFinite(gsl::make_span(Jv), ModelQuantity::Jv);
}
/**
@@ -1003,7 +1004,7 @@ int fJvB(realtype t, N_Vector x, N_Vector dx, N_Vector xB,
Expects(model);
model->fJvB(t, x, dx, xB, dxB, vB, JvB, cj);
- return model->checkFinite(gsl::make_span(JvB), "Jacobian");
+ return model->checkFinite(gsl::make_span(JvB), ModelQuantity::JvB);
}
/**
@@ -1024,7 +1025,7 @@ int froot(realtype t, N_Vector x, N_Vector dx, realtype *root,
model->froot(t, x, dx, gsl::make_span(root, model->ne));
return model->checkFinite(gsl::make_span(root, model->ne),
- "root function");
+ ModelQuantity::root);
}
/**
@@ -1058,7 +1059,7 @@ int fxdot(realtype t, N_Vector x, N_Vector dx, N_Vector xdot,
}
model->fxdot(t, x, dx, xdot);
- return model->checkFinite(gsl::make_span(xdot), "fxdot");
+ return model->checkFinite(gsl::make_span(xdot), ModelQuantity::xdot);
}
/**
@@ -1086,7 +1087,7 @@ int fxBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB,
}
model->fxBdot(t, x, dx, xB, dxB, xBdot);
- return model->checkFinite(gsl::make_span(xBdot), "xBdot");
+ return model->checkFinite(gsl::make_span(xBdot), ModelQuantity::xBdot);
}
/**
@@ -1109,7 +1110,7 @@ int fqBdot(realtype t, N_Vector x, N_Vector dx, N_Vector xB,
Expects(model);
model->fqBdot(t, x, dx, xB, dxB, qBdot);
- return model->checkFinite(gsl::make_span(qBdot), "qBdot");
+ return model->checkFinite(gsl::make_span(qBdot), ModelQuantity::qBdot);
}
@@ -1132,7 +1133,7 @@ static int fxBdot_ss(realtype t, N_Vector xB, N_Vector dxB, N_Vector xBdot,
Expects(model);
model->fxBdot_ss(t, xB, dxB, xBdot);
- return model->checkFinite(gsl::make_span(xBdot), "xBdot_ss");
+ return model->checkFinite(gsl::make_span(xBdot), ModelQuantity::xBdot_ss);
}
@@ -1154,7 +1155,7 @@ static int fqBdot_ss(realtype t, N_Vector xB, N_Vector dxB, N_Vector qBdot,
Expects(model);
model->fqBdot_ss(t, xB, dxB, qBdot);
- return model->checkFinite(gsl::make_span(qBdot), "qBdot_ss");
+ return model->checkFinite(gsl::make_span(qBdot), ModelQuantity::qBdot_ss);
}
/**
@@ -1181,7 +1182,8 @@ static int fqBdot_ss(realtype t, N_Vector xB, N_Vector dxB, N_Vector qBdot,
Expects(model);
model->fJSparseB_ss(JB);
- return model->checkFinite(gsl::make_span(xBdot), "JSparseB_ss");
+ return model->checkFinite(gsl::make_span(xBdot),
+ ModelQuantity::JSparseB_ss);
}
/**
@@ -1212,7 +1214,7 @@ int fsxdot(int /*Ns*/, realtype t, N_Vector x, N_Vector dx,
for (int ip = 0; ip < model->nplist(); ip++) {
model->fsxdot(t, x, dx, ip, sx[ip], sdx[ip], sxdot[ip]);
- if (model->checkFinite(gsl::make_span(sxdot[ip]), "sxdot")
+ if (model->checkFinite(gsl::make_span(sxdot[ip]), ModelQuantity::sxdot)
!= AMICI_SUCCESS)
return AMICI_RECOVERABLE_ERROR;
}
diff --git a/src/steadystateproblem.cpp b/src/steadystateproblem.cpp
index df5104dd9e..a2acec9596 100644
--- a/src/steadystateproblem.cpp
+++ b/src/steadystateproblem.cpp
@@ -188,9 +188,11 @@ void SteadystateProblem::initializeForwardProblem(int it, const Solver &solver,
/* process solver handling for pre- or postequilibration */
if (it == -1) {
/* solver was not run before, set up everything */
+ auto roots_found = std::vector(model.ne, 0);
model.initialize(state_.x, state_.dx, state_.sx, sdx_,
- solver.getSensitivityOrder() >=
- SensitivityOrder::first);
+ solver.getSensitivityOrder() >=
+ SensitivityOrder::first,
+ roots_found);
state_.t = model.t0();
solver.setup(state_.t, &model, state_.x, state_.dx, state_.sx, sdx_);
} else {
@@ -629,7 +631,8 @@ void SteadystateProblem::runSteadystateSimulation(const Solver &solver,
/* If run after Newton's method checks again if it converged */
wrms_ = getWrms(model, sensitivityFlag);
- int sim_steps = 0;
+
+ int &sim_steps = backward ? numstepsB_ : numsteps_.at(1);
int convergence_check_frequency = 1;
@@ -653,22 +656,16 @@ void SteadystateProblem::runSteadystateSimulation(const Solver &solver,
flagUpdatedState();
}
- try {
/* Check for convergence */
- wrms_ = getWrms(model, sensitivityFlag);
- /* 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 &&
- sim_steps % convergence_check_frequency == 0) {
- updateSensiSimulation(solver);
- wrms_ = getWrmsFSA(model);
- }
-
- } catch (NewtonFailure const &) {
- /* linear solves in getWrms failed */
- numsteps_.at(1) = sim_steps;
- throw;
+ if (sim_steps % convergence_check_frequency == 0) {
+ wrms_ = getWrms(model, sensitivityFlag);
+ /* 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) {
+ updateSensiSimulation(solver);
+ wrms_ = getWrmsFSA(model);
+ }
}
if (wrms_ < conv_thresh)
@@ -676,12 +673,10 @@ void SteadystateProblem::runSteadystateSimulation(const Solver &solver,
/* increase counter, check for maxsteps */
sim_steps++;
if (sim_steps >= solver.getMaxSteps()) {
- numsteps_.at(1) = sim_steps;
throw NewtonFailure(AMICI_TOO_MUCH_WORK,
"exceeded maximum number of steps");
}
if (state_.t >= 1e200) {
- numsteps_.at(1) = sim_steps;
throw NewtonFailure(AMICI_NO_STEADY_STATE,
"simulated to late time"
" point without convergence of RHS");
@@ -691,13 +686,6 @@ void SteadystateProblem::runSteadystateSimulation(const Solver &solver,
// 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) {
- numstepsB_ = sim_steps;
- } else {
- numsteps_.at(1) = sim_steps;
- }
}
std::unique_ptr
diff --git a/src/sundials_matrix_wrapper.cpp b/src/sundials_matrix_wrapper.cpp
index bd3fda4fb8..f3515c357a 100644
--- a/src/sundials_matrix_wrapper.cpp
+++ b/src/sundials_matrix_wrapper.cpp
@@ -747,5 +747,45 @@ void SUNMatrixWrapper::refresh() {
SUNMatrix SUNMatrixWrapper::get() const { return matrix_; }
+std::pair unravel_index(sunindextype i, SUNMatrix m)
+{
+ gsl_ExpectsDebug(i >= 0);
+ auto mat_id = SUNMatGetID(m);
+ if(mat_id == SUNMATRIX_DENSE) {
+ gsl_ExpectsDebug(i < SM_COLUMNS_D(m) * SM_ROWS_D(m));
+
+ auto num_rows = SM_ROWS_D(m);
+ // col-major
+ sunindextype row = i % num_rows;
+ sunindextype col = i / num_rows;
+
+ gsl_EnsuresDebug(row >= 0);
+ gsl_EnsuresDebug(row < SM_ROWS_D(m));
+ gsl_EnsuresDebug(col >= 0);
+ gsl_EnsuresDebug(col < SM_COLUMNS_D(m));
+
+ return {row, col};
+ }
+
+ if(mat_id == SUNMATRIX_SPARSE) {
+ gsl_ExpectsDebug(i < SM_NNZ_S(m));
+ sunindextype row = SM_INDEXVALS_S(m)[i];
+ sunindextype i_colptr = 0;
+ while(SM_INDEXPTRS_S(m)[i_colptr] < SM_NNZ_S(m)) {
+ if(SM_INDEXPTRS_S(m)[i_colptr + 1] > i) {
+ sunindextype col = i_colptr;
+ gsl_EnsuresDebug(row >= 0);
+ gsl_EnsuresDebug(row < SM_ROWS_S(m));
+ gsl_EnsuresDebug(col >= 0);
+ gsl_EnsuresDebug(col < SM_COLUMNS_S(m));
+ return {row, col};
+ }
+ ++i_colptr;
+ }
+ }
+
+ throw amici::AmiException("Unimplemented SUNMatrix type for unravel_index");
+}
+
} // namespace amici
diff --git a/swig/amici.i b/swig/amici.i
index a754cd61b8..ccfbf65933 100644
--- a/swig/amici.i
+++ b/swig/amici.i
@@ -120,6 +120,7 @@ wrap_unique_ptr(ExpDataPtr, amici::ExpData)
%ignore amici::ContextManager;
%ignore amici::ModelState;
%ignore amici::ModelStateDerived;
+%ignore amici::unravel_index;
// Include before any other header which uses enums defined there
%include "amici/defines.h"
diff --git a/swig/model.i b/swig/model.i
index 81466a5c3b..d2c9f2eabd 100644
--- a/swig/model.i
+++ b/swig/model.i
@@ -32,7 +32,7 @@ using namespace amici;
%ignore getObservable;
%ignore getObservableSensitivity;
%ignore getExpression;
-%ignore initHeaviside;
+%ignore initEvents;
%ignore initialize;
%ignore initializeB;
%ignore initializeStateSensitivities;
diff --git a/tests/cpp/unittests/testMisc.cpp b/tests/cpp/unittests/testMisc.cpp
index 278e93b2bc..c689090c8c 100644
--- a/tests/cpp/unittests/testMisc.cpp
+++ b/tests/cpp/unittests/testMisc.cpp
@@ -6,6 +6,7 @@
#include
#include
#include
+#include
#include
#include
@@ -646,4 +647,62 @@ TEST_F(SunMatrixWrapperTest, BlockTranspose)
SM_INDEXPTRS_S(B_sparse.get())[icol]);
}
+TEST(UnravelIndex, UnravelIndex)
+{
+ EXPECT_EQ(unravel_index(0, 2), std::make_pair((size_t) 0, (size_t) 0));
+ EXPECT_EQ(unravel_index(1, 2), std::make_pair((size_t) 0, (size_t) 1));
+ EXPECT_EQ(unravel_index(2, 2), std::make_pair((size_t) 1, (size_t) 0));
+ EXPECT_EQ(unravel_index(3, 2), std::make_pair((size_t) 1, (size_t) 1));
+ EXPECT_EQ(unravel_index(4, 2), std::make_pair((size_t) 2, (size_t) 0));
+ EXPECT_EQ(unravel_index(5, 2), std::make_pair((size_t) 2, (size_t) 1));
+ EXPECT_EQ(unravel_index(6, 2), std::make_pair((size_t) 3, (size_t) 0));
+}
+
+TEST(UnravelIndex, UnravelIndexSunMatDense)
+{
+ SUNMatrixWrapper A = SUNMatrixWrapper(3, 2);
+
+ A.set_data(0, 0, 0);
+ A.set_data(1, 0, 1);
+ A.set_data(2, 0, 2);
+ A.set_data(0, 1, 3);
+ A.set_data(1, 1, 4);
+ A.set_data(2, 1, 5);
+
+ for(int i = 0; i < 6; ++i) {
+ auto idx = unravel_index(i, A.get());
+ EXPECT_EQ(A.get_data(idx.first, idx.second), i);
+ }
+}
+
+TEST(UnravelIndex, UnravelIndexSunMatSparse)
+{
+ SUNMatrixWrapper D = SUNMatrixWrapper(4, 2);
+
+ // [0, 3]
+ // [0, 0]
+ // [1, 0]
+ // [2, 0]
+ // data [1, 2, 3]
+ // colptrs [0, 2, 3]
+ // rowidxs [2, 3, 1]
+ D.set_data(0, 0, 0);
+ D.set_data(1, 0, 0);
+ D.set_data(2, 0, 1);
+ D.set_data(3, 0, 2);
+ D.set_data(0, 1, 3);
+ D.set_data(1, 1, 0);
+ D.set_data(2, 1, 0);
+ D.set_data(3, 1, 0);
+
+ auto S = SUNSparseFromDenseMatrix(D.get(), 1e-15, CSC_MAT);
+
+ EXPECT_EQ(unravel_index(0, S), std::make_pair((sunindextype) 2, (sunindextype) 0));
+ EXPECT_EQ(unravel_index(1, S), std::make_pair((sunindextype) 3, (sunindextype) 0));
+ EXPECT_EQ(unravel_index(2, S), std::make_pair((sunindextype) 0, (sunindextype) 1));
+
+ SUNMatDestroy(S);
+}
+
+
} // namespace
diff --git a/tests/testSBMLSuite.py b/tests/testSBMLSuite.py
index 3187720a8f..767c4287fb 100755
--- a/tests/testSBMLSuite.py
+++ b/tests/testSBMLSuite.py
@@ -60,6 +60,8 @@ def test_sbml_testsuite_case(
sensitivity_check_cases = {
# parameter-dependent conservation laws
'00783': 1.5e-2,
+ # initial events
+ '00995': 1e-3,
}
try:
diff --git a/version.txt b/version.txt
index 2b71711fca..36e40e13e0 100644
--- a/version.txt
+++ b/version.txt
@@ -1 +1 @@
-0.11.28
+0.11.29