The lac operon#
The E. coli lac operon controls the production of enzymes needed to metabolize lactose and is regulated in large part by lactose availability. This example model includes
LacI (
I
) repression of the lac operon via blocking of the operator (O
),lactose (
L
) transport by LacY (Y
) permease,conversion of lactose to allolactose (
A
) by LacZ β-galactosidase (Z
),and allolactose-mediated derepression of the operon.
[2]:
from kappybara.system import System
system = System.from_ka(
"""
%init: 1 O(i[.])
%init: 1 I(o[.])
%init: 10 Y()
%init: 10 Z(l[.])
%obs: 'Extracellular lactose' |L(loc{out})| / 100
%obs: 'LacY' |Y()|
%obs: 'Free Lac operon' |O(i[.])|
I(o[.]), O(i[.]) <-> I(o[1]), O(i[1]) @ 1, 0.1 // Repress the lac operon
Y(), L(loc{out}) -> Y(), L(loc{in}) @ 0.01 // Transport lactose into the cell
Z(l[.]), L(loc{in}) -> Z(l[.]), A(z[.], i[.]) @ 1 // Convert lactose into allolactose
A(i[.]), I(o[.]) <-> A(i[1]), I(o[1]) @ 1, 0.1 // Deactivate the repressor by allolactose
O(i[.]), ., . -> O(i[.]), Z(l[.]), Y() @ 1 // Express the lac operon
L(loc{in}) -> E() @ 1 // Metabolize lactose
// Degradation
A() -> . @ 0.1
Y() -> . @ 0.5
Z() -> . @ 0.5
"""
)
We simulate the system first without lactose, then add extracellular lactose to observe the regulatory response.
[3]:
# Simulate a while with no extracellular lactose
while system.time < 300:
system.update()
# Add extracellular lactose and continue simulating
system.mixture.instantiate("L(loc{out})", 1000)
while system.time < 800:
system.update()
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[3], line 3
1 # Simulate a while with no extracellular lactose
2 while system.time < 300:
----> 3 system.update()
5 # Add extracellular lactose and continue simulating
6 system.mixture.instantiate("L(loc{out})", 1000)
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/kappybara/system.py:475, in System.update(self)
473 def update(self) -> None:
474 """Perform one simulation step."""
--> 475 self.wait()
476 if (rule := self.choose_rule()) is not None:
477 self.apply_rule(rule)
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/kappybara/system.py:439, in System.wait(self)
433 """Advance simulation time according to exponential distribution.
434
435 Raises:
436 RuntimeWarning: If system has no reactivity (infinite wait time).
437 """
438 try:
--> 439 self.time += random.expovariate(self.reactivity)
440 except ZeroDivisionError:
441 warnings.warn(
442 "system has no reactivity: infinite wait time", RuntimeWarning
443 )
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/kappybara/system.py:430, in System.reactivity(self)
423 @property
424 def reactivity(self) -> float:
425 """The total reactivity of the system.
426
427 Returns:
428 Sum of all rule reactivities.
429 """
--> 430 return sum(self.rule_reactivities)
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/functools.py:998, in cached_property.__get__(self, instance, owner)
996 val = cache.get(self.attrname, _NOT_FOUND)
997 if val is _NOT_FOUND:
--> 998 val = self.func(instance)
999 try:
1000 cache[self.attrname] = val
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/kappybara/system.py:421, in System.rule_reactivities(self)
414 @cached_property
415 def rule_reactivities(self) -> list[float]:
416 """The reactivity of each rule in the system.
417
418 Returns:
419 List of reactivities corresponding to system rules.
420 """
--> 421 return [rule.reactivity(self) for rule in self.rules.values()]
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/kappybara/rule.py:186, in KappaRule.reactivity(self, system)
175 def reactivity(self, system: "System") -> float:
176 """Calculate the total reactivity of this rule in the given system.
177
178 Args:
(...) 183 for rule symmetry.
184 """
185 return (
--> 186 self.n_embeddings(system.mixture) // self.n_symmetries * self.rate(system)
187 )
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/functools.py:998, in cached_property.__get__(self, instance, owner)
996 val = cache.get(self.attrname, _NOT_FOUND)
997 if val is _NOT_FOUND:
--> 998 val = self.func(instance)
999 try:
1000 cache[self.attrname] = val
File /opt/hostedtoolcache/Python/3.12.11/x64/lib/python3.12/site-packages/kappybara/rule.py:218, in KappaRule.n_symmetries(self)
215 r_site.agent = r
216 r_site.partner = l_site
--> 218 l.interface["__temp__"] = l_site
219 r.interface["__temp__"] = r_site
221 pattern = Pattern(left_agents + right_agents)
AttributeError: 'NoneType' object has no attribute 'interface'
[4]:
system.monitor.plot();

Without lactose, the operon remains mostly repressed. When extracellular lactose is added,
existing LacY transports some lactose into the cell,
intracellular lactose is converted to allolactose by LacZ,
allolactose inactivates the LacI repressor,
derepressing the operon and leading to increased LacY and LacZ production.
As lactose is metabolized the operon is again repressed.