Lecture 2. 化学反応回路

海津一成

化学反応を制御する機構


In [1]:
%matplotlib inline
from ecell4 import *

In [2]:
import matplotlib.pylab as plt
import numpy as np

In [3]:
import seaborn
seaborn.set(font_scale=1.5)

In [4]:
import matplotlib as mpl
mpl.rc("figure", figsize=(6, 4))

ブール演算

ANDゲート、ORゲート、NOTゲートといった基本論理回路が遺伝子発現系にもある

ANDゲート

例えばANDゲートとは

ABA AND B
000
100
010
111

ふたつの入力のうち両方が活性でないと出力は活性化しない

ふたつの制御を組み合せてみる

$$\frac{\mathrm{d}[X]}{\mathrm{d}t}=f(A,B)-k[X]=\frac{[A]^{n_1}}{K_1^{n_1}+[A]^{n_1}}\times\frac{[B]^{n_2}}{K_2^{n_2}+[B]^{n_2}}-k[X]$$

AとBふたつのスイッチが入らなければ発現しない


In [5]:
def Hill(E, Km, nH):
    return E ** nH / (Km ** nH + E ** nH)

In [6]:
data = np.array([[Hill(A, 0.5, 8) * Hill(B, 0.5, 8)
                  for B in np.linspace(0, 1, 21)]
                 for A in np.linspace(0, 1, 21)])
plt.imshow(data, cmap='coolwarm')
plt.xlabel('A')
plt.ylabel('B')
plt.colorbar()
plt.clim(0, 1)
plt.show()


試すと


In [7]:
with reaction_rules():
    ~A > A | 0.2
    ~B > B | 0.2
    ~X > X | Hill(A, 0.2, 8) * Hill(B, 0.6, 8)
    X > ~X | 1.0
    ~Y > Y | Hill(A, 0.2, 8)
    Y > ~Y | 1.0

In [8]:
run_simulation(5, species_list=['X', 'Y', 'A'],
               opt_args=['-', lambda t: 0.2, '--', lambda t: 0.6, '--'])


ORゲート

ORゲートは

ABA OR B
000
101
011
111

入力のうちどちらか一方があれば良い

これは例えば

$$\frac{\mathrm{d}[X]}{\mathrm{d}t}=f(A,B)-k[X]=\frac{\left([A]/K_1\right)^{n_1}+\left([B]/K_2\right)^{n_2}}{1+\left([A]/K_1\right)^{n_1}+\left([B]/K_2\right)^{n_2}}-k[X]$$

In [9]:
def f(A, B, K1, K2, n1, n2):
    term1 = (A / K1) ** n1
    term2 = (B / K2) ** n1
    return (term1 + term2) / (1 + term1 + term2)

複雑に見えるが、$K_1=K_2$かつ$n_1=n_2$の場合を考えればヒル式と同じ


In [10]:
data = np.array([[f(A, B, 0.5, 0.5, 8, 8) for B in np.linspace(0, 1, 21)]
                 for A in np.linspace(0, 1, 21)])
plt.imshow(data, cmap='coolwarm')
plt.xlabel('A')
plt.ylabel('B')
plt.colorbar()
plt.clim(0, 1)
plt.show()



In [11]:
with reaction_rules():
    ~A > A | 0.2
    ~B > B | 0.2
    ~X > X | f(A, B, 0.2, 0.6, 8, 8)
    X > ~X | 1.0
    ~Y > Y | Hill(B, 0.6, 8)
    Y > ~Y | 1.0

In [12]:
run_simulation(5, species_list=['X', 'Y', 'A'],
               opt_args=['-', lambda t: 0.2, '--', lambda t: 0.6, '--'])


実は分解を制御しても似たようなことはできる

$$\frac{\mathrm{d}[X]}{\mathrm{d}t}=f(A)-g(B)[X]$$$$=\frac{[A]^{n_1}}{K_1^{n_1}+[A]^{n_1}}-\left[k_1+k_2\frac{K_2^{n_2}}{K_2^{n_2}+[B]^{n_2}}\right][X]$$

In [13]:
def Hill_compl(E, Km, nH):
    return Km ** nH / (Km ** nH + E ** nH)

上の式においてA、Bがそれぞれ0もしくは十分に存在する場合の定常状態を考えると

ABA OR B
000
$\infty$0$\frac{1}{k1+k2}$
0$\infty$0
$\infty$$\infty$$\frac{1}{k1}$

$k_2$が十分大きければ、ANDゲートが実現できる


In [14]:
data = np.array([[Hill(A, 0.5, 8) / (1 + 100 * Hill_compl(B, 0.3, 8))
                  for B in np.linspace(0, 1, 21)]
                 for A in np.linspace(0, 1, 21)])
plt.imshow(data, cmap='coolwarm')
plt.xlabel('A')
plt.ylabel('B')
plt.colorbar()
plt.clim(0, 1)
plt.show()



In [15]:
with reaction_rules():
    ~A > A | 0.2
    ~B > B | 0.2
    ~X > X | Hill(A, 0.2, 8)
    X > ~X | (1.0 + 100 * Hill_compl(B, 0.36, 8)) * X
    ~Y > Y | Hill(A, 0.2, 8)
    Y > ~Y | 1.0

In [16]:
run_simulation(5, species_list=['X', 'Y', 'A'],
               opt_args=['-', lambda t: 0.2, '--', lambda t: 0.36, '--'],
               opt_kwargs={'ylim': (0, 1)})


NOTゲート

先ほどみたようにヒル式$\frac{[A]^n}{K^n+[A]^n}$の相補関数$\frac{K^n}{K^n+[A]^n}$ $\left(=1-\frac{[A]^n}{K^n+[A]^n}\right)$はちょうどNOTゲートのようなことができる


In [17]:
x = np.linspace(0, 1, 101)
nH = 8
plt.plot(x, [Hill(xi, 0.5, nH) for xi in x], label='Hill eq.')
plt.plot(x, [Hill_compl(xi, 0.5, nH) for xi in x],
         label='Complementary Hill eq.')
plt.legend(loc='best')
plt.xlabel('INPUT')
plt.ylabel('OUTPUT')
plt.show()


フィードバック制御

これまでは一方向の制御だったが回路のようにすると時間的な挙動を制御できるようになる

例えば

$$A{\rightarrow}B{\rightarrow}C$$

のような反応だとAからBに流れてさらに下流のCへと蓄積していく


In [18]:
with reaction_rules():
    A > B | 1
    B > C | 1
    
run_simulation(5, {'A': 1})


ここにCの量が多くなると$A{\rightarrow}B$の反応を抑制するように制御を加えてみると


In [19]:
with reaction_rules():
    A > B | 1
    B > C | 1
    
obs = run_simulation(5, {'A': 1}, return_type='observer')

In [20]:
with reaction_rules():
    A > B | 1 * Hill_compl(C, 0.1, 8) * A
    B > C | 1
    
run_simulation(5, {'A': 1}, opt_args=('-', obs, '--'))


今度はさきほどのものに発現と分解を加えてみる

$${\emptyset}{\rightarrow}A{\rightarrow}B{\rightarrow}C{\rightarrow}{\emptyset}$$

Aだけが発現して、Cだけが分解する


In [21]:
with reaction_rules():
    ~A > A | 1
    A > B | 1 > C | 1 > ~C | 1
    
run_simulation(10)


発現にCの量に応じた抑制を加えると


In [22]:
with reaction_rules():
    ~A > A | Hill_compl(C, 0.5, 8)
    A > B | 1 > C | 1 > ~C | 1
    
run_simulation(16, opt_args=['-', lambda t: 0.5, '--'],
               opt_kwargs={'ylim': (0, 1)})


ネガティブフィードバック制御

さきほどのものは減衰していたが、ネガティブフィードバックを繰り返していくと安定な振動系にもできる

所謂、Repressilatorのようなもの(Wikipediaより)


In [23]:
with reaction_rules():
    ~A > A | Hill_compl(C, 0.3, 8) > ~A | 1
    ~B > B | Hill_compl(A, 0.5, 8) > ~B | 1
    ~C > C | Hill_compl(B, 0.7, 8) > ~C | 1

run_simulation(np.linspace(0, 14, 201))


この場合はヒル係数が小さくなる(スイッチがゆるくなる)と振動は消えてしまう


In [24]:
with reaction_rules():
    ~A > A | Hill_compl(C, 0.3, 4) > ~A | 1
    ~B > B | Hill_compl(A, 0.5, 4) > ~B | 1
    ~C > C | Hill_compl(B, 0.7, 4) > ~C | 1

run_simulation(np.linspace(0, 14, 201))


フィードフォワード制御

逆に下流の反応を上流の分子によって制御することもある

また単純な一連の反応を考える

$$A{\rightarrow}B{\rightarrow}C$$

In [25]:
with reaction_rules():
    A > B | 1 > C | 1

run_simulation(8, {'A': 1})


ここにAの量が多いと$B{\rightarrow}C$の反応を抑制するように制御を加えてみると


In [26]:
with reaction_rules():
    A > B | 1 > C | 1

obs = run_simulation(8, {'A': 1}, return_type='observer')

In [27]:
with reaction_rules():
    A > B | 1 > C | Hill_compl(A, 0.05, 8) * B

run_simulation(8, {'A': 1}, opt_args=('-', obs, '--'))


制御の仕方を変えてみると

Incoherent Feedforward Loop (FFL)


In [28]:
with reaction_rules():
    ~A > A | 1 > ~A | 1
    ~B > B | Hill(A, 0.5, 8) > ~B | 1
    ~C > C | Hill(A, 0.5, 8) * Hill_compl(B, 0.5, 8) > ~C | 1

run_simulation(5, opt_args=('-', lambda t: 0.5, '--'))


Coherent Feedforward Loop (FFL)


In [29]:
with reaction_rules():
    ~B > B | Hill(A, 0.2, 8) > ~B | 1
    ~C > C | Hill(A, 0.2, 8) * Hill(B, 0.5, 8) > ~C | 1

In [30]:
from ecell4_base.core import *
from ecell4_base import ode

m = get_model()
w = ode.World()
sim = ode.Simulator(w, m)
obs = FixedIntervalNumberObserver(0.01, ['A', 'B', 'C'])

sim.run(1, obs)
w.set_value(Species('A'), 1); sim.initialize()
sim.run(0.3, obs)
w.set_value(Species('A'), 0)
sim.initialize()
sim.run(4, obs)
w.set_value(Species('A'), 1)
sim.initialize()
sim.run(1.5, obs)
w.set_value(Species('A'), 0)
sim.initialize()
sim.run(3.2, obs)

In [31]:
viz.plot_number_observer(obs, '-', lambda t: 0.5, '--')