2.2. Examples for symbolic calculations with CM
A SymbolicCompartmentalModel can operate in two modes: numeric (returning float values) and symbolic (returning closed-form analytical expressions via SymPy). In symbolic mode, every model property is expressed as a formula in terms of the named parameters, which is useful for gaining analytical insight, deriving limiting cases, and understanding model behaviour across the full parameter space without running numerical simulations.
[1]:
from symbolic_compartmental_model import SymbolicCompartmentalModel
import sympy
import matplotlib.pyplot as plt
from pathlib import Path
[2]:
cm3 = SymbolicCompartmentalModel(n_states=2)
k1 = cm3.add_parameter(symbol="k1", lb=0.0, ub=10.0)
k2 = cm3.add_parameter(symbol="k2", lb=0.0, ub=10.0)
cm3.contributed_turnovers = [[-k1, 0.0], [0.0, -k2]]
cm3.observed_pool_weights = [0.5, 0.5]
We define a two-state CM where each state has an independent turnover rate (k1 and k2), and both contribute equally to the observed signal. Because the off-diagonal entries are zero the two states do not exchange molecules — the labeling curve is a symmetric bi-exponential 0.5·exp(−k1·t) + 0.5·exp(−k2·t).
[3]:
print("\n\nmean age:")
display(sympy.simplify(cm3.as_symbolic("mean_age")))
print("\n\nmean residence time:")
display(sympy.simplify(cm3.as_symbolic("mean_residence_time")))
print("\n\nexpected decay rate:")
display(sympy.simplify(cm3.as_symbolic("expected_decay_rate")))
print("\n\nage CDF:")
display(sympy.simplify(cm3.as_symbolic("age_cdf")))
print("\n\nage PDF:")
display(sympy.simplify(cm3.as_symbolic("age_pdf")))
print("\n\nresidence time CDF:")
display(sympy.simplify(cm3.as_symbolic("residence_time_cdf")))
print("\n\nresidence time PDF:")
display(sympy.simplify(cm3.as_symbolic("residence_time_pdf")))
print("\n\ndecay rate:")
display(sympy.simplify(cm3.as_symbolic("decay_rate")))
mean age:
mean residence time:
expected decay rate:
age CDF:
age PDF:
residence time CDF:
residence time PDF:
decay rate:
[4]:
fig, ax = plt.subplots(1, 1, figsize=(5, 3), dpi=150)
cm3.plot("decay_rate", x=[1.0, 3.0], ax=ax)
fig.tight_layout()
if Path("../results").exists():
fig.savefig("../results/decay_rate.svg")
as_symbolic(key) returns a SymPy expression for any of the following model properties:
Key |
Meaning |
|---|---|
|
Expected age of a randomly chosen molecule (area under |
|
Expected remaining lifetime from now (forward-looking) |
|
Instantaneous turnover rate averaged over the age distribution |
|
Cumulative / probability density of molecular ages |
|
Distribution of remaining lifetimes |
|
Hazard function |
2.2.1. Growing system
[5]:
cm3.growth_rate = 0.0
print("\n\nmean age:")
display(sympy.simplify(cm3.as_symbolic("mean_age")))
print("\n\nmean residence time:")
display(sympy.simplify(cm3.as_symbolic("mean_residence_time")))
cm3.growth_rate = 1.5
print("\n\nmean age:")
display(sympy.simplify(cm3.as_symbolic("mean_age")))
print("\n\nmean residence time:")
display(sympy.simplify(cm3.as_symbolic("mean_residence_time")))
mean age:
mean residence time:
mean age:
mean residence time:
The decay_rate (hazard function) h(t) describes how the probability of clearance changes as a molecule ages. For a single exponential h(t) is constant; for a multi-state model it varies with time, reflecting the shifting composition of the surviving population. At t = 0 the fast-clearing state dominates, driving a high initial hazard; as fast-clearing molecules are depleted the hazard decreases toward the rate of the slower state.
In a growing cell population, cell division continuously dilutes all molecules, adding an apparent clearance term μ to every state. Setting growth_rate = μ incorporates this into the model.
This affects ``mean_residence_time`` (forward-looking — accounts for future dilution by growth) but not ``mean_age`` (retrospective — describes molecules that already exist). Setting growth_rate = 0 recovers the non-growing case.
Download notebook