2a. Approximate solutions to autorepressive dynamics
[1]:
# Colab setup ------------------
import os, sys, subprocess
if "google.colab" in sys.modules:
cmd = "pip install --upgrade watermark"
process = subprocess.Popen(cmd.split(), stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = process.communicate()
# ------------------------------
import numpy as np
import bokeh.io
import bokeh.plotting
bokeh.io.output_notebook()
Dynamics of an autorepression circuit with strong ultrasensitivity
Recall that the differential equation describing the dynamics of autorepressive gene expression is
\begin{align} \frac{\mathrm{d}x}{\mathrm{d}t} = \frac{\beta}{1+(x/k)^n} - \gamma x. \end{align}
We begin our path toward an approximate solution by examining the steady state. It is found by setting the time derivative to zero, or
\begin{align} \frac{\beta}{1+(x_\mathrm{ss}/k)^n} - \gamma x_\mathrm{ss} = 0. \end{align}
This can be rearranged as
\begin{align} \left(\frac{x_\mathrm{ss}}{k}\right)^{n+1} + \frac{x_\mathrm{ss}}{k} - \frac{\beta}{\gamma k} = 0. \label{eq:polynomial_xss} \end{align}
As is often the case when reasoning about circuits, it is worthwhile to consider extreme cases. For very strong ultrasensitivity, that is for very large \(n\), we can find an approximate value for the steady state. If \(x_\mathrm{ss}/k\) is less than one, the first term in the above expression \eqref{eq:polynomial_xss} will be tiny, such that
\begin{align} \frac{x_\mathrm{ss}}{k} \approx \frac{\beta}{\gamma k}. \end{align}
Thus, we see that having \(x_\mathrm{ss} < k\) requires that \(\beta/\gamma k < 1\). This is the case for weak repression; the concentration of repressor necessary to invoke repression, \(k\), is less than the steady state level for unregulated expression, \(\beta/\gamma\). Conversely, if we have strong repression (\(\beta/\gamma k > 1\)), we must also have \(x_\mathrm{ss}/k > 1\). In this case, the first term in equation \eqref{eq:polynomial_xss} dominates over the second and
\begin{align} x_\mathrm{ss} \approx k\left(\frac{\beta}{\gamma k}\right)^{\frac{1}{n+1}} \end{align}
If \(n\) is very large, the n-plus-first root of \(\beta/\gamma k\) is close to one, even if \(\beta/\gamma k\) is large. Therefore, for very strong ultrasensitivity, the steady state is \(x_\mathrm{ss} \approx k\).
For very strong repression, \(\beta/\gamma k \gg 1\) (again along with strong autorepression), we expect the level of \(x\) to rise as if it is unregulated until it hits \(k\), at which point strong repression kicks in. The resulting approximate dynamics are described by
\begin{align} x(t) \approx \left\{\begin{array}{lll} \beta t & & t < k/\beta,\\[1em] k & & t \ge k/\beta \end{array} \right. \end{align}
A plot of the approximate \(x(t)\) is shown below.
[2]:
# Curve
t = [0, 1, 2]
x = [0, 1, 1]
# Set up plot
p = bokeh.plotting.figure(
frame_height=175,
frame_width=300,
x_axis_label="time",
y_axis_label="x(t)",
x_range=[0, 2],
)
# Custom axis labels
p.xaxis.ticker = bokeh.models.tickers.FixedTicker(ticks=[0, 1])
p.yaxis.ticker = bokeh.models.tickers.FixedTicker(ticks=[0, 1])
p.xaxis.major_label_overrides = {1: "$$k/\\beta$$"}
p.yaxis.major_label_overrides = {1: "$$k$$"}
# Populate plot
p.line(t, x, line_width=2)
# Label slope
p.line([0.2, 0.2, 0.3], [0.2, 0.3, 0.3], color='black')
p.text(
x=[0.125],
y=[0.25],
text=["β"],
text_color="black",
text_font_size="12pt",
)
bokeh.io.show(p)
Approximate solution with moderate \(n\)
If we now relax the assumption of strong ultrasensitivity. If \(n\) is not too large, we still have, for strong repression,
\begin{align} x_\mathrm{ss} \approx k\left(\frac{\beta}{\gamma k}\right)^{\frac{1}{n+1}}. \end{align}
For moderate \(n\) and strong repression,
\begin{align} \left(\frac{\beta}{\gamma k}\right)^{\frac{1}{n+1}} \gg 1, \end{align}
such that \(x_\mathrm{ss} \gg k\). Unlike the above case where \(n \gg 1\), now most of the change in \(x\) happens when \(x > k\), since the steady state value of \(x\) is so much bigger than \(k\). If we only consider these later dynamics, that is in the regime where \(x \gg k\), we have \(1 + (x/k)^n \approx (x/k)^n\) giving the approximate ODE
\begin{align} \frac{\mathrm{d}x}{\mathrm{d}t} \approx \frac{\beta}{(x/k)^n} - \gamma x. \end{align}
This is a separable differential equation such that
\begin{align} \frac{\mathrm{d}x}{\frac{\beta}{(x/k)^n} - \gamma x} = \mathrm{d}t. \end{align}
We can integrate both sides of this equation to solve. The right hand side is trivial.
\begin{align} \int_0^t\mathrm{d}t' = t. \end{align}
The left hand side is integrated as
\begin{align} \int_0^x\,\frac{\mathrm{d}x'}{\frac{\beta}{(x'/k)^n} - \gamma x'} &= \int_0^x\,\frac{\mathrm{d}x'}{\beta k^n - \gamma (x')^(n+1)} \\[1em] &= -\frac{1}{(n+1)\gamma}\int_{\beta k^n}^{\beta k^n - \gamma x^{n+1}}\,\frac{\mathrm{d}u}{u} \\[1em] &= -\frac{1}{(n+1)\gamma}\,\ln\left(\frac{\beta k^n - \gamma x^{n+1}}{\beta k^n}\right) \\[1em] &= -\frac{1}{(n+1)\gamma}\,\ln\left(1 - \frac{\gamma k}{\beta}\,(x/k)^{n+1}\right). \end{align}
Setting the two integrals equal and rearranging to solve for \(x(t)\) gives
\begin{align} x(t) \approx k\left(\frac{\beta}{\gamma k}\left(1 - \mathrm{e}^{-(n+1)\gamma t}\right)\right)^{\frac{1}{n+1}}, \end{align}
the approximate result given in Chapter 2.
Computing environment
[3]:
%load_ext watermark
%watermark -v -p numpy,bokeh,jupyterlab
Python implementation: CPython
Python version : 3.10.10
IPython version : 8.10.0
numpy : 1.23.5
bokeh : 3.1.0
jupyterlab: 3.5.3