Chemostat#
A chemostat can maintain a concentration of nutrients in a container by flowing in and flowing out nutrients each at a constant rate. Here we will set up a simulatable chemostat with a clonal and unevolving unicellular organism, and adjust the flow rate to observe the effect on the growth rate of the cells. More specifically, we will assume that a cell C
replicates following metabolism of three units of the nutrient N
, and that cells die memorylessly at a constant stochastic rate.
[2]:
from kappybara.system import System
system = System.from_ka(
"""
%init: 10 C(n{0}) // Start with a few cells
%obs: 'C' |C()|
. -> N() @ 1 // Nutrient inflow
N() -> . @ 0.1 // Nutrient outflow
// Metabolism
C(n{0}), N() -> C(n{1}), . @ 0.5
C(n{1}), N() -> C(n{2}), . @ 0.5
C(n{2}), N() -> C(n{3}), . @ 0.5
C(n{3}), . -> C(n{0}), C(n{0}) @ 0.1 // Cell division
C() -> . @ 0.01 // Cell death
"""
)
If we started with only, say, one cell, it would be reasonably likely that the population would die out before replicating sufficiently to virtually preclude extinction.
Let’s start the chemostat and observe the population size:
[3]:
while system.time < 10 ** 3:
system.update()
system.monitor.plot();
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[3], line 2
1 while system.time < 10 ** 3:
----> 2 system.update()
3 system.monitor.plot();
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'
Now let’s increase inflow of the nutrient without changing the outflow rate and observe that the population size increases:
[4]:
system.add_rule(". -> N() @ 1")
while system.time < 5 * 10 ** 3:
system.update()
system.monitor.plot();
---------------------------------------------------------------------------
AttributeError Traceback (most recent call last)
Cell In[4], line 4
1 system.add_rule(". -> N() @ 1")
3 while system.time < 5 * 10 ** 3:
----> 4 system.update()
5 system.monitor.plot();
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'