Skip to content

Better support for modular models #1278

@elbert5770

Description

@elbert5770

Is your feature request related to a problem? Please describe.

The current modularization methods are ambiguous and produce unneeded parameters. This is a complex issue and the point of this post is to capture some thoughts on how a bottom-up approach might help. The tables below will show the scope of information that needs to be handled in QSP or PBPK models.

Let's start with an example from the docs:

using Catalyst

function repressed_gene(; R, name)
  @network_component $name begin
    hillr($R,α,K,n), ∅ --> m
    (δ,γ), m <--> ∅
    β, m --> m + P
    μ, P --> ∅
  end
end

t = default_t()
@species G3₊P(t)
@named G1 = repressed_gene(; R=ParentScope(G3₊P))
@named G2 = repressed_gene(; R=ParentScope(G1.P))
@named G3 = repressed_gene(; R=ParentScope(G2.P))
@named repressilator = ReactionSystem(t; systems=[G1,G2,G3])

@show parameters(repressilator)

t = default_t()
@species P₁(t) P₂(t) P₃(t)
rxs = [(@reaction hillr($P₃,α,K,n), ∅ --> m₁),
       (@reaction hillr($P₁,α,K,n), ∅ --> m₂),
       (@reaction hillr($P₂,α,K,n), ∅ --> m₃),
       (@reaction δ, m₁ --> ∅),
       (@reaction γ, ∅ --> m₁),
       (@reaction δ, m₂ --> ∅),
       (@reaction γ, ∅ --> m₂),
       (@reaction δ, m₃ --> ∅),
       (@reaction γ, ∅ --> m₃),
       (@reaction β, m₁ --> m₁ + P₁),
       (@reaction β, m₂ --> m₂ + P₂),
       (@reaction β, m₃ --> m₃ + P₃),
       (@reaction μ, P₁ --> ∅),
       (@reaction μ, P₂ --> ∅),
       (@reaction μ, P₃ --> ∅)]
@named repressilator2 = ReactionSystem(rxs, t)

@show parameters(repressilator2)

produces 21 parameters in the modular method and 7 in the standard method:

parameters(repressilator) = Any[G1₊α, G1₊K, G1₊n, G1₊δ, G1₊γ, G1₊β, G1₊μ, G2₊α, G2₊K, G2₊n, G2₊δ, G2₊γ, G2₊β, G2₊μ, G3₊α, G3₊K, G3₊n, G3₊δ, G3₊γ, G3₊β, G3₊μ]
parameters(repressilator2) = Any[α, n, K, δ, γ, β, μ]

Describe the solution you’d like

Rather than removing redundant parameters posthoc, it would be better to never have them in the model. This requires a way to designate when a parameter is species-specific.

There are a large number of edge cases in QSP, PBPK, etc. that are difficult to anticipate. However, I think the tables below capture many of these cases. To implement a bottom up strategy, the wildcard character '@#' is replaced by building blocks that are used to construct equations and parameters via loops over building_blocks entries. I show the repressilator and a more complex PBPK model with flows, compartments and isotopes.

reaction_table = [
    # Reactant,    Product,        Rate_type,           Rate_eqtn_prototype,                    Building_blocks,                        Isotopes            Compartment     
    (None,        "m_@1",          "HillR",             ["P_@2","alpha","K","n"],               [["1","3"],["2","1"],["3","2"]],         None,              None),
    ("m@1",        None,            "mass_action",         "delta",                             ["1","2","3"],                           None,              None),
    ("m@1",        ["m@1","P_@1"],   "mass_action",       "beta",                               ["1","2","3"],                           None,              None),
    ("P_@1",        None,           "mass_action",          "mu",                               ["1","2","3"],                           None,              None)
]
reaction_table = [
    # Reactant,    Product,        Rate_type,           Rate_eqtn_prototype,                    Building_blocks,                        Isotopes            Compartment     
    ("Leu",        "APP",          "mass_action",          "k_f",                                  None,                           ["U", "L"],          ["ISF"]),
    ("APP",        "C99",          "MM",                   ["V_M2","K_M2"],                         None,                           ["U", "L"],          ["ISF"]),
    ("APP",        "C83",          "MM",                   ["V_M1","K_M1","CI:[C99,KM5]"],          None,                           ["U", "L"],          ["ISF"]),
    ("C99",        "@1",            "MM",                   ["V_gamma_@1","K_M4","CI:[C83,KM3]"],     None,                           ["U", "L"],          ["ISF"]),
    ("C99",        "C83",          "MM",                   ["V_M5","K_M5","CI:[APP,KM1]"],          None,                           ["U", "L"],          ["ISF"]),
    ("C83",        None,            "MM",                   ["V_M3","K_M3","CI:[C99,KM4]"],        None,                           ["U", "L"],          ["ISF"]),
    ("@1",          "@1_ex",         "reversible_mass_action",          ["k_ex_@1","k_ret_@1"],         ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["ISF","ISF"]),
    ("@1",          None,           "mass_action",          "k_BPD_@1",                               ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["ISF"]),
    ("@1",          "@1_cranial",    "bidirectional_flow",   "Q_glymph",                             ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["ISF","cranial"]),
    ("@1_cranial",   "@1_CV",         "bidirectional_flow",   "Q_osc",                             ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["cranial","CV"]),
    ("@1_cranial",   None,           "unidirectional_flow",   "Q_cranial",                             ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["cranial","0"]),
    ("@1_CV",        "@1_cranial",    "unidirectional_flow",   "Q_cranial",                             ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["CV","cranial"]),
    ("@1_CV",        "@1_SP1",        "unidirectional_flow",    "Q_LP",                             ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["CV","SP1"]),
    ("@1_SP1",       "@1_SP2",     "unidirectional_flow",          "Q_LP",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP1","SP2"]),
    ("@1_SP2",       "@1_SP3",     "unidirectional_flow",          "Q_LP",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP2","SP3"]),
    ("@1_SP3",       None,     "unidirectional_flow",          "Q_LP",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP3","0"]),
    ("@1_CV",        "@1_SP1",        "unidirectional_flow",    "Q_SN1",                               ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["CV","SP1"]),
    ("@1_SP1",       "@1_SP2",     "unidirectional_flow",          "Q_SN2",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP1","SP2"]),
    ("@1_SP1",       None,     "unidirectional_flow",          "Q_SN3",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP1","0"]),
    ("@1_SP2",       "@1_SP3",     "unidirectional_flow",          "Q_SN3",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP2","SP3"]),
    ("@1_SP2",       None,     "unidirectional_flow",          "Q_SN3",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP2","0"]),
    ("@1_SP3",       None,     "unidirectional_flow",          "Q_SN3",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP3","0"]),
    ("@1_CV",        "@1_SP1",        "bidirectional_flow",    "Q_osc",                                   ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["CV","SP1"]),
    ("@1_SP1",       "@1_SP2",     "bidirectional_flow",          "Q_osc",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP1","SP2"]),
    ("@1_SP2",       "@1_SP3",     "bidirectional_flow",          "Q_osc",                                 ["Ab38", "Ab40", "Ab42"],       ["U", "L"],          ["SP2","SP3"])
]

Isotopes are interesting because they should have identical kinetics, so they should be handled separately from the Building_blocks. Compartments need some nomenclature to distinguish reactions within a compartment and reactions (or flows) that cause movement between compartments.

Conceptually, what is in the tables above is not that different from normal Catalyst. In fact I think Catalyst could be written like this:

function repressed_gene(; R, m, P, name)
  @network_component $name begin
    hillr($R,α,K,n), ∅ --> $m
    (δ,γ), $m <--> ∅
    β, $m --> $m + $P
    μ, $P --> ∅
  end
end

where the default is that parameters are NOT species-specific unless designated with some wildcard (not shown here) but could be something like δ_$m.

Describe alternatives you’ve considered

Antimony has the same behavior when constructing modular models.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions