The prozone effect#

The prozone effect, also called the hook effect, refers to when the abundance of a molecular complex first increases then decreases as the relative concentration of a constituent molecule E increases. In this example, we will say that a complex is composed of an L molecule, an M molecule, and an E molecule, which connects the former two together.

[2]:
from kappybara.system import System

system = System.from_ka(
    """
    %init: 100 L(e[.])
    %init: 100 M(e[.])

    %obs: 'E' |E()|
    %obs: 'Incomplete complexes' |E(l[_], m[.])| + |E(l[.], m[_])|
    %obs: 'Complete complexes' |E(l[_], m[_])|

    E(l[.]), L(e[.]) <-> E(l[1]), L(e[1]) @ 1, 1  // Bind L to E
    E(m[.]), M(e[.]) <-> E(m[1]), M(e[1]) @ 1, 1  // Bind M to E
    . -> E(l[.], m[.]) @ 1  // Inflow of E
    """
)

We’ve set up the system so that there’s no E to start but that it flows in at a constant stochastic rate. Let’s now simulate while tracking the corresponding number of complexes:

[3]:
while system.time < 5 * 10 ** 2:
    system.update()
system.monitor.plot();
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
Cell In[3], line 2
      1 while system.time < 5 * 10 ** 2:
----> 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'

As we introduce more E, at first more complexes can be formed but eventually there are so many E that the other components of the complex become more likely to bind separate E molecules. The result is that the full complex is less likely to form.