- 
          
 - 
                Notifications
    
You must be signed in to change notification settings  - Fork 82
 
Description
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.