r"""
Gating functions for bridge probability modeling.
This module implements gating functions that determine how bridge
probability depends on surface occupancy. The gating function :math:`\mathcal{G}(\phi)`
is the key component linking adsorption to transport properties.
Theoretical Foundation
----------------------
Productive bridge formation requires:
1. Particle site occupied by TC: probability :math:`\theta_P`
2. Network site **unoccupied** (to avoid blocking): probability :math:`1 - \theta_N`
3. Geometric contact within capture volume: factor :math:`\langle\chi\rangle`
4. Successful closure given contact: probability :math:`\kappa_B`
For **symmetric binding** (:math:`K_P \approx K_N \approx K_d`):
.. math::
\mathcal{G}(\phi) = \theta(\phi)[1-\theta(\phi)]
= \frac{\phi/K_d}{(1+\phi/K_d)^2}
For **asymmetric binding** (different affinities):
.. math::
\mathcal{G}(\phi) = \theta_P(\phi)[1-\theta_N(\phi)]
Key Properties
--------------
**Bell-shaped curve:**
- **Peak position**: :math:`\phi^\ast = K_d` (for symmetric case)
- **Peak value**: :math:`\mathcal{G}_{\max} = 1/4`
- **Low concentration limit**: :math:`\mathcal{G} \sim \phi/K_d` (starvation)
- **High concentration limit**: :math:`\mathcal{G} \sim K_d/\phi` (blocking)
**Physical interpretation:**
- Low :math:`\phi`: Bridge formation limited by "starvation" (insufficient TC)
- High :math:`\phi`: Bridge formation limited by "blocking" (sites saturated)
- Intermediate :math:`\phi`: Optimal balance yields maximum bridging
This non-monotonic behavior is the origin of **re-entrant transport phenomena**
and **dome-shaped phase boundaries** in the model.
Examples
--------
Symmetric gating with Langmuir isotherm:
>>> from microscopic_gating.adsorption import LangmuirIsotherm
>>> isotherm = LangmuirIsotherm(K=1.0)
>>> gating = SymmetricGating(isotherm)
>>> gating.G(1.0) # Peak at phi = K_d
0.25
>>> gating.phi_star()
1.0
Asymmetric gating (different binding affinities):
>>> isotherm_P = LangmuirIsotherm(K=1.0) # Particle side
>>> isotherm_N = LangmuirIsotherm(K=2.0) # Network side
>>> gating = AsymmetricGating(isotherm_P, isotherm_N)
>>> gating.G(1.0) # doctest: +SKIP
0.1666...
See Also
--------
LangmuirIsotherm : Standard adsorption model
HillIsotherm : Cooperative adsorption
MicroscopicGatingModel : Full model integrating gating with statistics
References
----------
- Eq. (S5): Asymmetric gating (minimal blocking picture)
- Eq. (S6)-(S7): Symmetric gating function
- Eq. (S8): Peak position and maximum value
"""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
from .types import ArrayLike, Isotherm
[docs]@dataclass(frozen=True)
class SymmetricGating:
r"""
Symmetric gating function :math:`G(\phi) = \theta(1-\theta)`.
Implements Eq. (S6)-(S7), assuming :math:`K_P \approx K_N \approx K_d`.
This is the standard gating function for symmetric binding scenarios.
The gating function produces a bell-shaped curve with maximum at
:math:`\phi = K` where :math:`G_{max} = 1/4`.
Parameters
----------
isotherm : Isotherm
Any isotherm providing :math:`\theta(\phi)`, e.g.,
:class:`~microscopic_gating.adsorption.LangmuirIsotherm` or
:class:`~microscopic_gating.adsorption.HillIsotherm`.
See Also
--------
AsymmetricGating : Uses :math:`\theta_P(1-\theta_N)` for non-symmetric
particle/network binding.
Examples
--------
>>> from microscopic_gating.adsorption import LangmuirIsotherm
>>> isotherm = LangmuirIsotherm(K=1.0)
>>> gating = SymmetricGating(isotherm)
>>> gating.G(1.0)
0.25
>>> gating.phi_star()
1.0
References
----------
- Eq. (S6)-(S7): Symmetric gating function derivation
"""
isotherm: Isotherm
[docs] def G(self, phi: ArrayLike) -> ArrayLike:
r"""
Calculate symmetric gating function.
Parameters
----------
phi : ArrayLike
Bulk concentration.
Returns
-------
G : ArrayLike
Gating function value :math:`\theta(\phi)[1-\theta(\phi)]`,
same shape as `phi`. Values are in [0, 0.25].
Notes
-----
The function reaches its maximum of 0.25 when :math:`\theta = 0.5`,
which occurs at :math:`\phi = K` for Langmuir/Hill isotherms.
"""
theta = self.isotherm.theta(phi)
return theta * (1.0 - theta)
[docs] def phi_star(self) -> float:
r"""
Find peak location of the gating function.
Returns
-------
phi_star : float
Concentration at which :math:`G(\phi)` is maximized.
For standard Langmuir/Hill forms, :math:`\phi^\\ast = K`.
Raises
------
AttributeError
If the isotherm does not have a 'K' attribute.
Notes
-----
For Hill/Langmuir isotherms, :math:`G(\phi)` is maximized at
:math:`\phi = K` with maximum value 1/4.
"""
K = getattr(self.isotherm, "K", None)
if K is None:
raise AttributeError(
"isotherm has no attribute 'K'; phi_star undefined."
)
return float(K)
[docs]@dataclass(frozen=True)
class AsymmetricGating:
r"""
Asymmetric gating function: :math:`G(\phi) = \theta_P(\phi)[1-\theta_N(\phi)]`.
Implements Eq. (S5) in the minimal blocking picture. This gating
function accounts for different binding affinities on the particle
versus network sides.
Parameters
----------
isotherm_P : Isotherm
Particle-side isotherm giving :math:`\theta_P(\phi)`.
isotherm_N : Isotherm
Network-side isotherm giving :math:`\theta_N(\phi)`.
See Also
--------
SymmetricGating : Special case where :math:`\theta_P = \theta_N`.
Examples
--------
>>> from microscopic_gating.adsorption import LangmuirIsotherm
>>> isotherm_P = LangmuirIsotherm(K=1.0)
>>> isotherm_N = LangmuirIsotherm(K=2.0)
>>> gating = AsymmetricGating(isotherm_P, isotherm_N)
>>> gating.G(1.0) # doctest: +SKIP
0.1666...
References
----------
- Eq. (S5): Asymmetric gating in minimal blocking picture
"""
isotherm_P: Isotherm
isotherm_N: Isotherm
[docs] def G(self, phi: ArrayLike) -> ArrayLike:
r"""
Calculate asymmetric gating function.
Parameters
----------
phi : ArrayLike
Bulk concentration.
Returns
-------
G : ArrayLike
Gating function value :math:`\theta_P(\phi)[1-\theta_N(\phi)]`,
same shape as `phi`.
Notes
-----
Unlike symmetric gating, the maximum value and peak location depend
on both isotherm parameters.
"""
theta_P = self.isotherm_P.theta(phi)
theta_N = self.isotherm_N.theta(phi)
return theta_P * (1.0 - theta_N)