Analysis of the Chemotaxis model described by Spiro et al., PNAS, 1999. The model describes the receptor state along 3 dimensions:
Key variables are:
Below is a figure from Spiro describing the state structure of receptors.
Issues
In [1]:
from IPython.display import Image, display
display(Image(filename='img/receptor_states.png'))
Initially, we consider a step response. Later, we repeat the same analysis for a ramp. These correspond to the analyses done by Spiro.
Most of the analysis is done using fractional concentrations (variables that begin with "f"). We begin by justifying this.
Next, we analyze the effects of a step response. "Perfect adaptation" is possible if the step is small enough.
In [2]:
import numpy as np
%matplotlib inline
import matplotlib.pyplot as plt
import tellurium as te
from chemotaxis_model import ChemotaxisModel, StateAggregationFactory
from data_plotter import DataPlotter
model = ChemotaxisModel()
In [3]:
# This is the templated model
print model.getModel()
In [4]:
# Runs simulation and creates global variables used in analysis
def sim(elements=None,end=400, concentrations={}):
"""
Runs the simulation and creates global names
:param list elements: additions to model
:param int end: simulation end time
:param dict concentrations: key is variable, value is assignment
Output: global variables - plotter, result
"""
if elements is None:
elements = []
global plotter, result, model, rr
model = ChemotaxisModel()
for element in elements:
model.appendToModel(element)
rr = model.initialize()
for k,v in concentrations.items():
rr[k] = v
result = model.run(end=end)
plotter = DataPlotter(model)
In [5]:
# Export the XML
#f_sbml = "chemotaxis.xml"
# export current model state
#rr.exportToSBML(f_sbml)
# to export the initial state when the model
The goal here is to understand the dynamics of the receptor state for a step response. From the foregoing, we established that it's sufficient to look at the fraction of receptors that are in the phosphorylated state (since this correlates with Yp). Now we want to understand what substates contribute to phosphorylated receptors.
Spiro shows a step response plot with L going from 0 to 11uM and "perfect adaption of Yp. We do not see this. Possibly, this is because he used a model with more methylation levels. Below is Spiro's step response curve.
In [6]:
# Spiro's plot for a step response with L=1mM
from IPython.display import Image, display
display(Image(filename='img/spiro_largestep.png'))
# The solid line is Yp
In [7]:
# The amount of ligand is 1000 times the amount of receptors
sim(elements=["at (time > 100): L = 1e-3"])
plotter.lines(["L", "Yp", "Bp"])
Observations
In [8]:
from IPython.display import Image, display
display(Image(filename='img/spiro_0.11.png'))
# The solid line is Yp;
In [9]:
sim(elements=["at (time > 10): L = 0.11e-6"], end=50)
plotter.lines(["Yp"])
Observations
In [10]:
sim(elements=["at (time > 100): L = 1.1e-6"])
plotter.lines(["Yp"])
Observations
In [11]:
sim(elements=["at (time > 100): L = 1.8e-6"])
plotter.lines(["Yp"])
In [12]:
sim(elements=["at (time > 100): L = 4e-6"])
plotter.lines(["Yp"])
In [13]:
# System is flooded with more ligand than there are receptors. Much more ligand than receptors.
sim(elements=["at (time > 100): L = 11e-6"])
plotter.lines(["Yp"])
In [14]:
# Breakdown the state phosphorylation state by methylation level
sim(elements=["at (time > 100): L = 11e-6"])
plotter.lines(["f_T__", "f_T_2", "f_T_3", "f_T_4"], yrange=[0,0.05])
Observations
In [15]:
# Analyze the methylation levels
sim(elements=["at (time > 100): L = 11e-6"])
plotter.lines(["f___2", "f___3", "f___4"], yrange=[0,1])
In [16]:
# Analyze the methylation levels for ligand bound receptors
sim(elements=["at (time > 100): L = 4.1e-6"])
plotter.lines(["fT___", "fT__2", "fT__3", "fT__4"], yrange=[0,0.5])
Observations
Questions
In [17]:
# Breakdown the state phosphorylations by ligand bound
sim(elements=["at (time > 100): L = 11e-6"])
plotter.lines(["f_T__", "fFT__", "fTT__"], yrange=[0, 0.2])
Observations
In [18]:
from IPython.display import Image, display
display(Image(filename='img/spiro_ramp.png'))
# The solid line is Yp; the long dashed line is L
In [19]:
# Ramp analysis for the same conditions as Spiro
elements = ["at (time > 200): k0 = 0.09e-6", "at (time > 300): k0 = 0, L=3e-6"]
sim(elements=elements)
plotter.lines(["L", "Yp"], yrange=[0,7e-6])
Observations
In [20]:
# Gradually add 1.1uM of ligand
elements = ["at (time > 200): k0 = 0.011e-6", "at (time > 300): k0 = 0, L=0.15e-6"]
sim(elements=elements)
plotter.lines(["L", "Yp"])
In [21]:
# See how much ligand I can add and still get the same steady state response. Added a total of 1.8uM.
elements = ["at (time > 200): k0 = 0.018e-6", "at (time > 300): k0 = 0, L=0.27e-6"]
sim(elements=elements)
plotter.lines(["L", "Yp"])
In [22]:
# Pushing the ramp a bit longer.
elements = ["at (time > 200): k0 = 0.018e-6", "at (time > 400): k0 = 0, L=0.6e-6"]
sim(elements=elements, end=500)
plotter.lines(["L", "Yp"])
This section provides an analytical validation of one quantity in the simulation, the fraction of ligand bound receptors. The analysis is based on a simple equilibrium analysis.
In [23]:
Kd = 1e-6 # 1 micromolar
TTOT = 8 # 8 micro molars
K = Kd/(TTOT*1e-6)
In [24]:
import numpy as np
def fBound(r, K=K):
"""
Using steady state analysis to compute the fraction of ligand bound to receptors
:param float r: ratio of total L to total T
:param float K: ratio of Kd to total T
:return float: fraction of ligand bound to receptors
"""
result = None
b = -(1 + r + K)
term1 = -b/2
term2 = np.sqrt(b**2-4*r)/2
result1 = term1 - term2
result2 = term1 + term2
if result1 <= 1.0 and result1 >= 0:
result = result1
if result2 <= 1.0 and result2 >= 0:
if result is not None:
raise RuntimeError("Two valid solutions")
else:
result = result1
if result is None:
raise RuntimeError("No valid solution.")
return result
def evaluateEstimateError(L):
"""
Returns the error of expected fraction of ligand bound compared with the values
obtained from simulation.
"""
sim(elements=["at (time > 100): L = %s" % (L*1e-6)])
actual = model.getVariable("fT___")[-1] # Get the last (steady state) value
expected = fBound(L/TTOT)
return (expected -actual)/actual
In [25]:
evaluateEstimateError(4.1)
Out[25]:
In [26]:
evaluateEstimateError(1.1)
Out[26]:
In [27]:
evaluateEstimateError(0.1)
Out[27]:
Observations
Questions