Speeding up condition-specific parameters
Some models include parameters whose values differ across simulation conditions. For such problems, when gradients are computed with automatic differentiation (gradient_method = :ForwardDiff or gradient_method = :ForwardEquations), runtime can often be reduced substantially by setting split_over_conditions = true when constructing a PEtabODEProblem.
This page explains when and why this helps, and assumes familiarity with condition-specific parameters (see Simulation condition-specific parameters). As a working example, the published Beer model [27] available in the PEtab standard format is used (see Importing PEtab problems). Given the PEtab files (downloadable from here), the problem can be imported with:
using PEtab
# path_yaml depends on where the problem is saved
path_yaml = joinpath(@__DIR__, "beer", "Beer_MolBioSystems2014.yaml")
model = PEtabModel(path_yaml)Efficient handling of condition-specific parameters
The Beer problem has 4 species and 9 ODE parameters, but 72 parameters are estimated because many parameters are simulation condition-specific. For example, cond1 has τ_cond1 and cond2 has τ_cond2, which both map to the ODE parameter τ. This is reflected by the model statistics are:
using Catalyst
petab_prob = PEtabODEProblem(model)
println("Number of ODE model species = ", length(unknowns(model.sys)))
println("Number of ODE model parameters = ", length(parameters(model.sys)))
println("Number of parameters to estimate = ", length(petab_prob.xnames))Number of ODE model species = 4
Number of ODE model parameters = 9
Number of parameters to estimate = 72For small ODE systems, gradient_method = :ForwardDiff is typically fastest, and hessian_method = :ForwardDiff is often feasible. By default, PEtab.jl computes derivatives with a single ForwardDiff.gradient call over all simulation conditions. For condition-specific parameters this can be wasteful: if n directional passes are needed for the full parameter vector, condition i may only require n_i < n passes because many parameters are inactive in that condition.
To reduce this overhead, split_over_conditions = true computes derivatives per condition (one ForwardDiff call per simulation condition). Here, the effect on gradient runtime is noticeable:
using Printf
petab_prob1 = PEtabODEProblem(model; split_over_conditions = true)
petab_prob2 = PEtabODEProblem(model; split_over_conditions = false)
x = get_x(petab_prob1)
g1, g2 = similar(x), similar(x)
b1 = @elapsed petab_prob1.grad!(g1, x)
b2 = @elapsed petab_prob2.grad!(g2, x)
@printf("Runtime split_over_conditions = true: %.2fs\n", b1)
@printf("Runtime split_over_conditions = false: %.2fs\n", b2)Runtime split_over_conditions = true: 0.15s
Runtime split_over_conditions = false: 0.92sFor the Hessian, the difference is typically even larger:
h1, h2 = zeros(length(x), length(x)), zeros(length(x), length(x))
b1 = @elapsed petab_prob1.hess!(h1, x)
b2 = @elapsed petab_prob2.hess!(h2, x)
@printf("Runtime split_over_conditions = true: %.1fs\n", b1)
@printf("Runtime split_over_conditions = false: %.1fs\n", b2)Runtime split_over_conditions = true: 1.6s
Runtime split_over_conditions = false: 67.9sA natural question is why split_over_conditions = true is not always the default. The reason is overhead: evaluating ForwardDiff separately per condition can be slower when there are few or no condition-specific parameters due to overhead. PEtab.jl therefore uses a heuristic and enables split_over_conditions by default when the number of condition-specific parameters is at least twice the number of ODE parameters. This heuristic is conservative, so for models with many condition-specific parameters it is recommended to benchmark split_over_conditions = true vs false.
References
- R. Beer, K. Herbst, N. Ignatiadis, I. Kats, L. Adlung, H. Meyer, D. Niopek, T. Christiansen, F. Georgi, N. Kurzawa and others. Creating functional engineered variants of the single-module non-ribosomal peptide synthetase IndC by T domain exchange. Molecular BioSystems 10, 1709–1718 (2014).