11. Time-based regulation in cells


Design principle

  • Time-based regulation enables coordinated regulation of target genes

  • Frequency Modulated Pulsing can implement time-based regulation

Concepts

  • In time-based regulation, as opposed to concentration-based regulation, inputs control the fraction of time a regulator is active, rather than the concentration of the active species.

Techniques

  • Autocorrelation and cross-correlation analysis


[1]:
import numpy as np
import scipy.stats as st

import bokeh.io
import bokeh.plotting

bokeh.io.output_notebook()
Loading BokehJS ...

Biological circuits can be strikingly dynamic, even in constant conditions

Even in a constant environment, biological circuits exhibit surprisingly dynamic behaviors. One can observe proteins activating and deactivating in repetitive pulses or or periodic oscillations across a wide range of timescales. Indeed, we have already seen how just a few genes can produce robust oscillations. In many cases, dynamics are coherent, involving simultaneous activation and deactivation of many molecules of a given species.

Today we will ask what functional benefits dynamics can provide for a signaling system compared to non-dynamic alternatives. We will see that dynamics allows cells to control targets “in time” rather than in concentration, and that this time-based regulation can allow coordination of diverse target genes within the cell.

Electrical systems also use dynamics

Electrical circuit design utilizes dynamics for numerous operations. One of the simplest, and most familiar, examples is a classic “dimmer” switch. Dimmers control light intensity by rapidly chopping the voltage on and off, varying the fraction of time that the light is on.

Our electrical devices are a mix of AC (alternating current) and DC (direct current) designs. The complementary benefits of the two paradigms are sometimes associated with the legendary conflict between Edison and Tesla, who advocated competing visions for DC and AC power distribution, respectively. The AC model prevailed for power distribution, while DC power is better for many digital electronic systems, such as laptop computers.

Edison_Tesla

AM and FM represent distinct signal encoding paradigms

In communications, signals can be encoded either in the amplitude or frequency of oscillations. Car stereos usually offer both Amplitude Modulation (AM) and Frequency Modulation (FM) radio bands. In either case, one can tune the radio to a particular frequency or channel. AM radios encode signals in the amplitude of that frequency, while FM encodes signals via small shifts in frequency. The code below shows two very simple ways of encoding the same information:

[2]:
t = np.linspace(0, 25, 1001)

# Frequency of carrier signal
f = 1

# Carrier signal
yc = np.sin(2 * np.pi * f * t)

# Modulation signal
m = np.sin(2 * np.pi * 0.1 * f * t)

# Phase multiplier
pm = 4

# Amplitude modulation
AM = m * yc

# Modulate phase to give frequency modulated signal
FM = np.sin(2 * np.pi * (f * t + pm * m))

# Collect signals for convenience
signals = dict(carrier=yc, signal=m, AM=AM, FM=FM)

# Set up plots
colors = bokeh.palettes.d3["Category10"][4]
plots = [
    bokeh.plotting.figure(
        height=150, width=400, x_axis_label="time" if key == "FM" else None, title=key
    )
    for key in signals
]

# Link axes
for p in plots[1:]:
    p.x_range = plots[0].x_range
    p.y_range = plots[0].y_range

# Populate glyphs
for i, (key, sig) in enumerate(signals.items()):
    plots[i].line(t, sig, line_width=2, color=colors[i])

bokeh.io.show(bokeh.layouts.gridplot(plots, ncols=1))

Transcription factors can be regulated in concentration or in time

Cellular signaling systems can similarly encode inputs in qualitatively different ways:

  • In an AM-like concentration-based encoding system, the input signals controls the concentration of an effector inside the cell. This is how we typically assume most systems work.

  • In a time-based regulation system, the input signal can modulate dynamic aspects of a signal, such as its oscillatory period, or, as we will discuss today, the fraction of time that the regulator is active.

These two modes are illustrated schematically here:

concentration_vs_time_based_regulation

Note that in this schematic example of time-based regulation, we allow the input to modulate both the frequency and duration of pulses to change the fraction of time that the factor is active. But there are many ways to implement time-based regulation. And any given system may mix both of these or other types of regulation at the same time.

Dynamic pulsing is ubiquitous

A large, and ever-growing, list of regulatory systems and pathways exhibit repetitive pulses of activity in constant conditions. In bacteria, sigma factors activate in pulses. In yeast, key transcription factors mediating stress response or glucose regulation, such as Msn2, Crz1, and Mig1, exhibit repetitive pulses of nuclear localization. In mammalian cells, core signaling pathways including NF-AT, NF-\(\kappa\)B, Erk, p53, and others all exhibit different types of pulsing even when cells are in constant conditions. Examples of these systems are summarized in this figure, which shows pulses from a broad variety of different cell types:

pulsing_is_ubiquitous

Image from Levine, Lin, and Elowitz (Science, 2013)

Detecting pulsing is not easy, since it requires making movies of specific regulators in individual cells. For this reason, this list is almost certainly an underestimate of the number and diversity of pulsatile factors.

In fact, a systematic movie-based survey of all yeast proteins detected pulsatile dynamics in at least some conditions for about 10% of transcription factors. This work took advantage of a convenient property of many transcription factors. They localize to the nucleus when they are active, and to the cytoplasm when they are inactive.

nuclear_cytoplasmic_localization

This enables one to infer total transcription factor activity from the degree of nuclear localization, as shown in these movies:

Movie from C. Dalal et al (Current Biology, 2014).

Crz1 mediates the response to calcium through nuclear localization

One of the best examples of this mode of control is the yeast transcription factor Crz1, whose name stands for calcineurin-responsive zinc finger 1, and is pronounced “crazy one.”

In this system, calcium activates the phosphatase calcineurin, which dephosphorylates Crz1. The desporphorylated form localizes to the nucleus, where it can activae a large regulon of target genes. Crz1 is functionaly analogous to the NF-AT transcription factors that play critical roles in the immune system, among many other texts. (See Stathopoulos, et al. (Genes Dev., 1997, *Genes Dev., 1999; and Matheos et al. (Genes Dev., 1997).)

Crz1_diagram

Crz1 pulses

In the late 2000s, Crz1 earned its name. Here at Caltech, Long Cai and Chiraj Dalal, working in Elowitz lab, were making movies of individual yeast cells expressing Crz1 fused to a fluorescent protein. After addition of calcium to the media, they were struck by the mesmerizing and unexpected “twinkling” of individual cells. These twinkles represented events in which many Crz1 molecules simultaneously transited to the nucleus, concentrating their fluorescent signal. They lasted for a couple of minutes and then ended as the Crz1 proteins returned to the cytoplasm. These cycles of coherent, repetitive pulses of nuclear localization continued for hours. The following movie shows a typical example.

Pulses appear stochastic and (sometimes) repetitive

To get a better idea of what is going on in this system, one can plot traces of Crz1 nuclear localization over time. For example, here are traces from two neighboring cells:

Crz1_pulse_examples

Analysis of Crz1 pulse dynamics in two cells, from L. Cai et al. (Nature, 2008)

These traces show a number of interesting features:

  • Nuclear localization occurs in brief pulses (sometimes called spikes or bursts), typically lasting just a couple of minutes. These pulses occur only after calcium is added to the media.

  • Pulses are unsynchronized between different cells, indicating they are generated in a cell-autonomous way. In fact, pulsing is uncorrelated even between mother-daughter cell pairs.

  • In many, but not all, cells, one sees an immediate response to sudden addition of calcium. This occurs in the blue trace, above.

  • One also can frequently observe extended episodes of elevated pulsing rate, termed pulse “trains” or “clusters.”

These features could only be discerned because we looked at individual cells over time. Averaging over many of these traces reveals a very different population average behavior, in which there is an initial response, evidently due to synchronized response to calcium addition, followed by partial adaptation to an (apparently) constant level. Of course, this seemingly steady behavior conceals the wild pulsing going on in each individual cell.

Crz1_averaged_traces

Average of Crz1 pulse dynamics in 42 cells, from L. Cai et al. (Nature, 2008).

Dynamic correlation analysis allows extraction of key features of stochastic dynamics

These observations present us with a frequently encountered challenge: How to condense a huge number of distinct, individual behaviors, each occurring in a different cell, into a small number of comprehensible parameters.

One of the most versatile tools is correlation analysis.

Consider the following two signals, which are generated synthetically by creating pulses at different points, and adding noise.

[3]:
t = np.linspace(0, 20, 2001)

mu_1 = np.array([0.3, 1.4, 6.4, 8.9, 11.4, 15.3])
sigma_1 = np.array([0.1, 0.2, 0.15, 0.3, 0.4, 0.1])
amp_1 = np.array([0.3, 0.5, 0.3, 0.1, 0.3, 0.34])
bg_1 = 0.07
noise_1 = 1.5

mu_2 = mu_1 + 1.7
sigma_2 = sigma_1
amp_2 = np.array([0.4, 0.3, 0.2, 0.2, 0.24, 0.1])
bg_2 = 0.1
noise_2 = 1.0

sig_1 = np.zeros_like(t)
for i, mu in enumerate(mu_1):
    sig_1 += amp_1[i] * np.exp(-((t - mu) ** 2) / sigma_1[i] ** 2)
sig_1 += bg_1
sig_1 += noise_1 * sig_1 * np.random.rand(len(t))

sig_2 = np.zeros_like(t)
for i, mu in enumerate(mu_2):
    sig_2 += amp_2[i] * np.exp(-((t - mu) ** 2) / sigma_2[i] ** 2)
sig_2 += bg_2
sig_2 += noise_2 * sig_2 * np.random.rand(len(t))


p = bokeh.plotting.figure(
    width=600, height=250, x_axis_label="time", y_axis_label="signal"
)
p.line(t, sig_1, legend_label="signal 1")
p.line(t, sig_2, color="orange", legend_label="signal 2")

bokeh.io.show(p)

Look at these traces. Do you notice any relationship between them?

Two interesting features we might want to extract are the typical durations of the pulses, and the timing relationship between the two signals. To do so, we can calculate the auto-correlation and cross-correlation functions, respectively.

Autocorrelation:

\begin{align} (f \star f)(\tau) = \int_{-\infty}^{\infty} {f(t)f(t+\tau)} dt \end{align}

Cross-correlation:

\begin{align} (f \star g)(\tau) = \int_{-\infty}^{\infty} {f(t)g(t+\tau)} dt \end{align}

For the signals we generated above, these functions can be easily computed. We can adjust the np.correlate() function to compute correlations between two signals (assumed to be sampled at uniform time intervals) and also give the corresponding values of the time lag in units of the time between samples.

[4]:
def correlate(x, y, half=False):
    n = len(x)

    if n != len(y):
        raise RuntimeError("Can only correlate arrays of equal length.")

    if n % 2:
        lengths = np.concatenate(
            (np.arange(n // 2 + 1, n), np.arange(n, n // 2, -1))
        )
        lags = np.concatenate(
            (np.arange(-n // 2 + 1, 0), np.arange(n // 2 + 1))
        )
    else:
        lengths = np.concatenate(
            (np.arange(n // 2, n), np.arange(n, n // 2, -1))
        )
        lags = np.concatenate((np.arange(-n // 2, 0), np.arange(n // 2)))

    # Compute correlation with max correlation being unity
    corr = np.correlate(x, y, "same") / lengths
    corr /= corr.max()

    if half:
        return lags[n // 2 :], corr[n // 2 :]
    else:
        return lags, corr

With this function in hand, we can compute the autocorrelation function for signal 1, and also the cross-correlation for the two signals (stored respectively as sig_1 and sig_2).

[5]:
lags, auto_corr = correlate(sig_1, sig_1)
lags, cross_corr = correlate(sig_1, sig_2)

# Compuet tau in units of time
tau = lags * (t[1] - t[0])

p = bokeh.plotting.figure(
    width=600, height=250, x_axis_label="τ", y_axis_label="correlation"
)
p.line(tau, auto_corr, legend_label="signal 1 autocorrelation")
p.line(tau, cross_corr, color="orange", legend_label="cross-correlation")

bokeh.io.show(p)

Many features can be extracted from these functions. Here we highlight:

  • In the autocorrelation, the width of the central peak indicates the typical pulse duration.

  • The offset of the central cross-correlation peak reveals the typical separation between a peak in one trace and a peak in the other. Here we can see that the orange pulses typically follow the blue traces after a couple time units.

As a reality check, look back at the original traces and see if those two conclusions make sense. It’s worth playing with these functions on real and synthetic data to get a feeling for what they look like for different types of functions.

Calcium modulates pulse frequency but not duration

We can now see how calcium modulates the dynamics. First, we see that it strongly modulates mean pulse frequency but not mean pulse duration:

Crz1_pulse_duration_frequency

Average of Crz1 pulse dynamics in 42 cells, from L. Cai et al. (Nature, 2008)

In fact, even as pulse frequencies change dramatically, the entire distribution of pulse durations is apparently unaffected by the level of calcium:

Crz1_duration_distribution

Distribution of pulse durations, from L. Cai et al. (Nature, 2008).

And we see a similar behavior at the level of pulse clusters, with calcium modulating their frequency, but not their mean duration. To extract these features, one first notes that the autocorrelation function is well fit by a sum of two exponentials with different timescales, corresponding to individual pulses and pulse clusters:

\begin{align} C_a = A_1 \mathrm{e}^{- \left(\frac{t}{\tau_{pulse}}\right)} + A_2 \mathrm{e}^{-\left(\frac{t}{\tau_{cluster}}\right)} \end{align}

Crz1_pulse_train_analysis

Analysis of pulse trains, from L. Cai et al. (Nature, 2008).

Finally, the initial response to calcium does not occur in all cells, but the fraction of cells that exhibit that response is also modulated by calcium:

Crz1_initial_response

Analysis of pulse trains, from L. Cai et al. (Nature, 2008).

To summarize, calcium seems to modulate the frequency of individual pulses, pulse clusters, and even the appearance of initial responses, while not affecting the amplitude or duration of the same features!

How do we get the observed autocorrelation?

To show how pulse trains might give the observed biexponential mean autocorrelation function, we can build a simulated signal. We use the following simplified model. A cell can either be in a cluster state, in which it may have pulsed localization, or out of a cluster state, in which no localization occurs. Entry into a cluster state is a modeled as a Poisson process with time constant \(\tau_1\), and exit from a cluster state is modeled as a Poisson process with time constant \(\tau_2\). While in a cluster state, we may get localization events, also modeled as a Poisson process, with time constant \(\tau_3\). We model each pulse as a Gaussian-like peak in time with width \(2\sigma\). Finally, there is also noise in the signal.

Crz1_initial_response

First, we’ll code up some function to make our signal.

[6]:
def pulse_times(tau_1, tau_2, tau_3, t_max):
    t_pulse = []
    t = 0

    while t < t_max:
        # Wait for pulsing to turn on
        t += np.random.exponential(tau_1)

        # How long will pulsing last?
        t_end = t + np.random.exponential(tau_2)

        # Make pulses
        while t < t_end:
            t += np.random.exponential(tau_3)
            t_pulse.append(t)

    return np.array(t_pulse)


def pulse_signal(
    pulse_times,
    base_signal,
    pulse_amplitude,
    pulse_amplitude_sigma,
    noise_amplitude,
    sigma,
    t_max,
    n_samples,
):
    t = np.linspace(0, t_max * (1 + 0.02), n_samples)
    signal = base_signal + np.random.normal(0, noise_amplitude, len(t))
    for tp in pulse_times:
        signal += (
            pulse_amplitude
            * np.random.normal(1, pulse_amplitude_sigma)
            * st.norm.pdf(t, tp, sigma)
        )

    return t, signal


def make_signal(
    tau_1,
    tau_2,
    tau_3,
    base_signal,
    pulse_amplitude,
    pulse_amplitude_sigma,
    noise_amplitude,
    sigma,
    t_max,
    n_samples,
):
    t_pulse = pulse_times(tau_1, tau_2, tau_3, t_max)

    return pulse_signal(
        t_pulse,
        base_signal,
        pulse_amplitude,
        pulse_amplitude_sigma,
        noise_amplitude,
        sigma,
        t_max,
        n_samples,
    )

Let’s generate a typical signal and see how it looks.

[7]:
t, signal = make_signal(
    tau_1=10,
    tau_2=12,
    tau_3=1,
    base_signal=0.5,
    pulse_amplitude=1.5,
    pulse_amplitude_sigma=0.1,
    noise_amplitude=0.1,
    sigma=0.25,
    t_max=5000,
    n_samples=65536,
)

p = bokeh.plotting.figure(
    width=500,
    height=250,
    x_axis_label="time (min)",
    y_axis_label="nuclear fluorescent intensity (a.u.)",
)
p.line(t, signal, line_join="bevel")
bokeh.io.show(p)