Skip to content

Commit cf375b9

Browse files
plakrisenkoFabian Fröhlich
authored andcommitted
Steady state sensitivity modes (#1727)
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 <fabian_froehlich@hms.harvard.edu>
1 parent 742c464 commit cf375b9

File tree

8 files changed

+87
-59
lines changed

8 files changed

+87
-59
lines changed

include/amici/defines.h

+2-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ enum class NonlinearSolverIteration {
180180
/** Sensitivity computation mode in steadyStateProblem */
181181
enum class SteadyStateSensitivityMode {
182182
newtonOnly,
183-
simulationFSA
183+
integrationOnly,
184+
integrateIfNewtonFails
184185
};
185186

186187
/** State in which the steady state computation finished */

python/examples/example_constant_species/ExampleEquilibrationLogic.ipynb

+5-5
Original file line numberDiff line numberDiff line change
@@ -496,9 +496,9 @@
496496
}
497497
],
498498
"source": [
499-
"# Call simulation with singular Jacobian and simulationFSA mode\n",
499+
"# Call simulation with singular Jacobian and integrateIfNewtonFails mode\n",
500500
"model.setTimepoints(np.full(1, np.inf))\n",
501-
"model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.simulationFSA)\n",
501+
"model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.integrateIfNewtonFails)\n",
502502
"solver = model.getSolver()\n",
503503
"solver.setNewtonMaxSteps(10)\n",
504504
"solver.setSensitivityMethod(amici.SensitivityMethod.forward)\n",
@@ -883,7 +883,7 @@
883883
],
884884
"source": [
885885
"# Singluar Jacobian, use simulation\n",
886-
"model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.simulationFSA)\n",
886+
"model.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.integrateIfNewtonFails)\n",
887887
"solver = model.getSolver()\n",
888888
"solver.setNewtonMaxSteps(10)\n",
889889
"solver.setSensitivityMethod(amici.SensitivityMethod.forward)\n",
@@ -1135,7 +1135,7 @@
11351135
],
11361136
"source": [
11371137
"# Non-singular Jacobian, use simulaiton\n",
1138-
"model_reduced.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.simulationFSA)\n",
1138+
"model_reduced.setSteadyStateSensitivityMode(amici.SteadyStateSensitivityMode.integrateIfNewtonFails)\n",
11391139
"solver_reduced = model_reduced.getSolver()\n",
11401140
"solver_reduced.setNewtonMaxSteps(0)\n",
11411141
"solver_reduced.setSensitivityMethod(amici.SensitivityMethod.forward)\n",
@@ -1186,7 +1186,7 @@
11861186
"name": "python",
11871187
"nbconvert_exporter": "python",
11881188
"pygments_lexer": "ipython3",
1189-
"version": "3.8.2"
1189+
"version": "3.8.10"
11901190
},
11911191
"toc": {
11921192
"base_numbering": 1,

python/tests/test_compare_conservation_laws_sbml.py

+43-31
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,23 @@
1010
def edata_fixture():
1111
"""edata is generated to test pre- and postequilibration"""
1212
edata_pre = amici.ExpData(2, 0, 0,
13-
np.array([0., 0.1, 0.2, 0.5, 1., 2., 5., 10.]))
13+
np.array([0., 0.1, 0.2, 0.5, 1., 2., 5., 10.]))
1414
edata_pre.setObservedData([1.5] * 16)
1515
edata_pre.fixedParameters = np.array([5., 20.])
1616
edata_pre.fixedParametersPreequilibration = np.array([0., 10.])
1717
edata_pre.reinitializeFixedParameterInitialStates = True
1818

1919
# edata for postequilibration
2020
edata_post = amici.ExpData(2, 0, 0,
21-
np.array([float('inf')] * 3))
21+
np.array([float('inf')] * 3))
2222
edata_post.setObservedData([0.75] * 6)
2323
edata_post.fixedParameters = np.array([7.5, 30.])
2424

2525
# edata with both equilibrations
2626
edata_full = amici.ExpData(2, 0, 0,
27-
np.array([0., 0., 0., 1., 2., 2., 4., float('inf'), float('inf')]))
27+
np.array(
28+
[0., 0., 0., 1., 2., 2., 4., float('inf'),
29+
float('inf')]))
2830
edata_full.setObservedData([3.14] * 18)
2931
edata_full.fixedParameters = np.array([1., 2.])
3032
edata_full.fixedParametersPreequilibration = np.array([3., 4.])
@@ -42,7 +44,7 @@ def models():
4244
sbml_importer = amici.SbmlImporter(sbml_file)
4345

4446
# Name of the model that will also be the name of the python module
45-
model_name = model_output_dir ='model_constant_species'
47+
model_name = model_output_dir = 'model_constant_species'
4648
model_name_cl = model_output_dir_cl = 'model_constant_species_cl'
4749

4850
# Define constants, observables, sigmas
@@ -67,9 +69,11 @@ def models():
6769
compute_conservation_laws=False)
6870

6971
# load both models
70-
model_without_cl_module = amici.import_model_module(model_name,
72+
model_without_cl_module = amici.import_model_module(
73+
model_name,
7174
module_path=os.path.abspath(model_name))
72-
model_with_cl_module = amici.import_model_module(model_name_cl,
75+
model_with_cl_module = amici.import_model_module(
76+
model_name_cl,
7377
module_path=os.path.abspath(model_name_cl))
7478

7579
# get the models and return
@@ -81,8 +85,8 @@ def models():
8185
def get_results(model, edata=None, sensi_order=0,
8286
sensi_meth=amici.SensitivityMethod.forward,
8387
sensi_meth_preeq=amici.SensitivityMethod.forward,
88+
stst_sensi_mode=amici.SteadyStateSensitivityMode.newtonOnly,
8489
reinitialize_states=False):
85-
8690
# set model and data properties
8791
model.setReinitializeFixedParameterInitialStates(reinitialize_states)
8892

@@ -92,6 +96,7 @@ def get_results(model, edata=None, sensi_order=0,
9296
solver.setSensitivityOrder(sensi_order)
9397
solver.setSensitivityMethodPreequilibration(sensi_meth_preeq)
9498
solver.setSensitivityMethod(sensi_meth)
99+
model.setSteadyStateSensitivityMode(stst_sensi_mode)
95100
if edata is None:
96101
model.setTimepoints(np.linspace(0, 5, 101))
97102
else:
@@ -134,9 +139,6 @@ def test_compare_conservation_laws_sbml(models, edata_fixture):
134139
assert np.isclose(rdata[field], rdata_cl[field]).all(), field
135140

136141
# ----- compare simulations wo edata, sensi = 0, states and sensis -------
137-
model_without_cl.setSteadyStateSensitivityMode(
138-
amici.SteadyStateSensitivityMode.simulationFSA
139-
)
140142

141143
# run simulations
142144
edata, _, _ = edata_fixture
@@ -154,7 +156,10 @@ def test_compare_conservation_laws_sbml(models, edata_fixture):
154156
# run simulations
155157
rdata_cl = get_results(model_with_cl, edata=edata, sensi_order=1)
156158
assert rdata_cl['status'] == amici.AMICI_SUCCESS
157-
rdata = get_results(model_without_cl, edata=edata, sensi_order=1)
159+
rdata = get_results(
160+
model_without_cl, edata=edata, sensi_order=1,
161+
stst_sensi_mode=amici.SteadyStateSensitivityMode.integrateIfNewtonFails
162+
)
158163
assert rdata['status'] == amici.AMICI_SUCCESS
159164
# check that steady state computation succeeded only by sim in full model
160165
assert (rdata['preeq_status'] == np.array([-3, 1, 0])).all()
@@ -178,43 +183,50 @@ def test_compare_conservation_laws_sbml(models, edata_fixture):
178183

179184
def test_adjoint_pre_and_post_equilibration(edata_fixture):
180185
# get both models
181-
model_module = amici.import_model_module('model_constant_species',
186+
model_module = amici.import_model_module(
187+
'model_constant_species',
182188
module_path=os.path.abspath('model_constant_species'))
183189
model = model_module.getModel()
184-
model_module_cl = amici.import_model_module('model_constant_species_cl',
190+
model_module_cl = amici.import_model_module(
191+
'model_constant_species_cl',
185192
module_path=os.path.abspath('model_constant_species_cl'))
186193
model_cl = model_module_cl.getModel()
187194

188195
# check gradient with and without state reinitialization
189196
for edata in edata_fixture:
190197
for reinit in [False, True]:
191-
# --- compare different ways of preequilibration, full rank Jacobian ---------
198+
# --- compare different ways of preequilibration, full rank Jacobian ------
192199
# forward preequilibration, forward simulation
193-
rff_cl = get_results(model_cl, edata=edata, sensi_order=1,
194-
sensi_meth=amici.SensitivityMethod.forward,
195-
sensi_meth_preeq=amici.SensitivityMethod.forward,
196-
reinitialize_states=reinit)
200+
rff_cl = get_results(
201+
model_cl, edata=edata, sensi_order=1,
202+
sensi_meth=amici.SensitivityMethod.forward,
203+
sensi_meth_preeq=amici.SensitivityMethod.forward,
204+
reinitialize_states=reinit)
197205
# forward preequilibration, adjoint simulation
198-
rfa_cl = get_results(model_cl, edata=edata, sensi_order=1,
199-
sensi_meth=amici.SensitivityMethod.adjoint,
200-
sensi_meth_preeq=amici.SensitivityMethod.forward,
201-
reinitialize_states=reinit)
206+
rfa_cl = get_results(
207+
model_cl, edata=edata, sensi_order=1,
208+
sensi_meth=amici.SensitivityMethod.adjoint,
209+
sensi_meth_preeq=amici.SensitivityMethod.forward,
210+
reinitialize_states=reinit)
202211
# adjoint preequilibration, adjoint simulation
203-
raa_cl = get_results(model_cl, edata=edata, sensi_order=1,
204-
sensi_meth=amici.SensitivityMethod.adjoint,
205-
sensi_meth_preeq=amici.SensitivityMethod.adjoint,
206-
reinitialize_states=reinit)
212+
raa_cl = get_results(
213+
model_cl, edata=edata, sensi_order=1,
214+
sensi_meth=amici.SensitivityMethod.adjoint,
215+
sensi_meth_preeq=amici.SensitivityMethod.adjoint,
216+
reinitialize_states=reinit)
207217

208218
# assert all are close
209219
assert np.isclose(rff_cl['sllh'], rfa_cl['sllh']).all()
210220
assert np.isclose(rfa_cl['sllh'], raa_cl['sllh']).all()
211221
assert np.isclose(raa_cl['sllh'], rff_cl['sllh']).all()
212222

213-
# --- compare fully adjoint approach to simulation with singular Jacobian ----
214-
raa = get_results(model, edata=edata, sensi_order=1,
215-
sensi_meth=amici.SensitivityMethod.adjoint,
216-
sensi_meth_preeq=amici.SensitivityMethod.adjoint,
217-
reinitialize_states=reinit)
223+
# --- compare fully adjoint approach to simulation with singular Jacobian ----
224+
raa = get_results(
225+
model, edata=edata, sensi_order=1,
226+
sensi_meth=amici.SensitivityMethod.adjoint,
227+
sensi_meth_preeq=amici.SensitivityMethod.adjoint,
228+
stst_sensi_mode=amici.SteadyStateSensitivityMode.integrateIfNewtonFails,
229+
reinitialize_states=reinit)
218230

219231
# assert gradients are close (quadrature tolerances are laxer)
220232
assert np.isclose(raa_cl['sllh'], raa['sllh'], 1e-5, 1e-5).all()

python/tests/test_preequilibration.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -286,7 +286,8 @@ def test_equilibration_methods_with_adjoints(preeq_fixture):
286286

287287
rdatas = {}
288288
equil_meths = [amici.SteadyStateSensitivityMode.newtonOnly,
289-
amici.SteadyStateSensitivityMode.simulationFSA]
289+
amici.SteadyStateSensitivityMode.integrationOnly,
290+
amici.SteadyStateSensitivityMode.integrateIfNewtonFails]
290291
sensi_meths = [amici.SensitivityMethod.forward,
291292
amici.SensitivityMethod.adjoint]
292293
settings = itertools.product(equil_meths, sensi_meths)
@@ -333,7 +334,7 @@ def test_newton_solver_equilibration(preeq_fixture):
333334
edata.setObservedDataStdDev(np.hstack([stdy, stdy[0]]))
334335

335336
rdatas = {}
336-
settings = [amici.SteadyStateSensitivityMode.simulationFSA,
337+
settings = [amici.SteadyStateSensitivityMode.integrationOnly,
337338
amici.SteadyStateSensitivityMode.newtonOnly]
338339

339340
solver.setNewtonStepSteadyStateCheck(True)
@@ -345,9 +346,6 @@ def test_newton_solver_equilibration(preeq_fixture):
345346
model.setSteadyStateSensitivityMode(equil_meth)
346347
if equil_meth == amici.SteadyStateSensitivityMode.newtonOnly:
347348
solver.setNewtonMaxSteps(10)
348-
else:
349-
solver.setSensiSteadyStateCheck(False)
350-
solver.setNewtonMaxSteps(0)
351349

352350
# add rdatas
353351
rdatas[equil_meth] = amici.runAmiciSimulation(model, solver, edata)

python/tests/test_pysb.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -201,7 +201,7 @@ def get_data(model):
201201
solver = model.getSolver()
202202
model.setTimepoints(np.linspace(0, 60, 61))
203203
model.setSteadyStateSensitivityMode(
204-
amici.SteadyStateSensitivityMode.simulationFSA
204+
amici.SteadyStateSensitivityMode.integrateIfNewtonFails
205205
)
206206

207207
rdata = amici.runAmiciSimulation(model, solver)
@@ -220,7 +220,7 @@ def get_results(model, edata):
220220
edata.reinitializeFixedParameterInitialStates = True
221221
model.setTimepoints(np.linspace(0, 60, 61))
222222
model.setSteadyStateSensitivityMode(
223-
amici.SteadyStateSensitivityMode.simulationFSA
223+
amici.SteadyStateSensitivityMode.integrateIfNewtonFails
224224
)
225225
return amici.runAmiciSimulation(model, solver, edata)
226226

python/tests/test_sbml_import.py

+1-2
Original file line numberDiff line numberDiff line change
@@ -205,10 +205,9 @@ def test_presimulation(sbml_example_presimulation_module):
205205
"""Test 'presimulation' test model"""
206206
model = sbml_example_presimulation_module.getModel()
207207
solver = model.getSolver()
208-
solver.setNewtonMaxSteps(0)
209208
model.setTimepoints(np.linspace(0, 60, 61))
210209
model.setSteadyStateSensitivityMode(
211-
amici.SteadyStateSensitivityMode.simulationFSA
210+
amici.SteadyStateSensitivityMode.integrationOnly
212211
)
213212
solver.setSensitivityOrder(amici.SensitivityOrder.first)
214213
model.setReinitializeFixedParameterInitialStates(True)

src/steadystateproblem.cpp

+29-11
Original file line numberDiff line numberDiff line change
@@ -94,16 +94,22 @@ void SteadystateProblem::workSteadyStateBackwardProblem(
9494
void SteadystateProblem::findSteadyState(const Solver &solver, Model &model,
9595
int it) {
9696
steady_state_status_.resize(3, SteadyStateStatus::not_run);
97+
bool turnOffNewton = model.getSteadyStateSensitivityMode() ==
98+
SteadyStateSensitivityMode::integrationOnly &&
99+
((it == -1 && solver.getSensitivityMethodPreequilibration() ==
100+
SensitivityMethod::forward) || solver.getSensitivityMethod() ==
101+
SensitivityMethod::forward);
97102

98103
/* First, try to run the Newton solver */
99-
findSteadyStateByNewtonsMethod(model, false);
104+
if (!turnOffNewton)
105+
findSteadyStateByNewtonsMethod(model, false);
100106

101107
/* Newton solver didn't work, so try to simulate to steady state */
102108
if (!checkSteadyStateSuccess())
103109
findSteadyStateBySimulation(solver, model, it);
104110

105111
/* Simulation didn't work, retry the Newton solver from last sim state. */
106-
if (!checkSteadyStateSuccess())
112+
if (!turnOffNewton && !checkSteadyStateSuccess())
107113
findSteadyStateByNewtonsMethod(model, true);
108114

109115
/* Nothing worked, throw an as informative error as possible */
@@ -243,11 +249,17 @@ void SteadystateProblem::computeSteadyStateQuadrature(const Solver &solver,
243249
We therefore compute the integral over xB first and then do a
244250
matrix-vector multiplication */
245251

246-
/* Try to compute the analytical solution for quadrature algebraically */
247-
getQuadratureByLinSolve(model);
252+
auto sensitivityMode = model.getSteadyStateSensitivityMode();
248253

249-
/* Analytical solution didn't work, perform simulation instead */
250-
if (!hasQuadrature())
254+
/* Try to compute the analytical solution for quadrature algebraically */
255+
if (sensitivityMode == SteadyStateSensitivityMode::newtonOnly
256+
|| sensitivityMode == SteadyStateSensitivityMode::integrateIfNewtonFails)
257+
getQuadratureByLinSolve(model);
258+
259+
/* Perform simulation */
260+
if (sensitivityMode == SteadyStateSensitivityMode::integrationOnly ||
261+
(sensitivityMode == SteadyStateSensitivityMode::integrateIfNewtonFails
262+
&& !hasQuadrature()))
251263
getQuadratureBySimulation(solver, model);
252264

253265
/* If analytic solution and integration did not work, throw an Exception */
@@ -366,8 +378,10 @@ bool SteadystateProblem::getSensitivityFlag(const Model &model,
366378
bool forwardSensisAlreadyComputed =
367379
solver.getSensitivityOrder() >= SensitivityOrder::first &&
368380
steady_state_status_[1] == SteadyStateStatus::success &&
369-
model.getSteadyStateSensitivityMode() ==
370-
SteadyStateSensitivityMode::simulationFSA;
381+
(model.getSteadyStateSensitivityMode() ==
382+
SteadyStateSensitivityMode::integrationOnly ||
383+
model.getSteadyStateSensitivityMode() ==
384+
SteadyStateSensitivityMode::integrateIfNewtonFails);
371385

372386
bool simulationStartedInSteadystate =
373387
steady_state_status_[0] == SteadyStateStatus::success &&
@@ -392,9 +406,13 @@ bool SteadystateProblem::getSensitivityFlag(const Model &model,
392406
!simulationStartedInSteadystate;
393407

394408
/* When we're creating a new solver object */
395-
bool needForwardSensiAtCreation =
396-
needForwardSensisPreeq && model.getSteadyStateSensitivityMode() ==
397-
SteadyStateSensitivityMode::simulationFSA;
409+
bool needForwardSensiAtCreation =
410+
needForwardSensisPreeq &&
411+
(model.getSteadyStateSensitivityMode() ==
412+
SteadyStateSensitivityMode::integrationOnly ||
413+
model.getSteadyStateSensitivityMode() ==
414+
SteadyStateSensitivityMode::integrateIfNewtonFails
415+
);
398416

399417
/* Check if we need to store sensis */
400418
switch (context) {

tests/petab_test_suite/test_petab_suite.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from amici.petab_import import import_petab_problem, PysbPetabProblem
1717
from amici.petab_objective import (
1818
simulate_petab, rdatas_to_measurement_df, create_parameterized_edatas)
19-
from amici import SteadyStateSensitivityMode_simulationFSA
19+
from amici import SteadyStateSensitivityMode
2020

2121
logger = get_logger(__name__, logging.DEBUG)
2222
set_log_level(get_logger("amici.petab_import"), logging.DEBUG)
@@ -134,7 +134,7 @@ def check_derivatives(problem: petab.Problem, model: amici.Model) -> None:
134134
# Required for case 9 to not fail in
135135
# amici::NewtonSolver::computeNewtonSensis
136136
model.setSteadyStateSensitivityMode(
137-
SteadyStateSensitivityMode_simulationFSA)
137+
SteadyStateSensitivityMode.integrateIfNewtonFails)
138138

139139
for edata in create_parameterized_edatas(
140140
amici_model=model, petab_problem=problem,

0 commit comments

Comments
 (0)