r"""
Adsorption isotherms for surface occupancy modeling.
This module implements adsorption isotherms that describe how surface
occupancy depends on bulk concentration. These form the foundation for
the gating function calculations.
Theoretical Foundation
----------------------
Derived from grand canonical ensemble for a single binding site with
two states (empty/occupied):
.. math::
Z = 1 + z e^{\beta\epsilon}
where :math:`z \simeq \phi/\phi^\circ` is the fugacity and :math:`\epsilon`
is the binding energy. The occupancy fraction is:
.. math::
\theta(\phi) = \frac{z e^{\beta\epsilon}}{1 + z e^{\beta\epsilon}}
= \frac{\phi/K_d}{1 + \phi/K_d}
with dissociation constant :math:`K_d = \phi^\circ e^{-\beta\epsilon}`.
Key Properties
--------------
- **Low concentration** (:math:`\phi \ll K_d`): :math:`\theta \approx \phi/K_d` (linear)
- **High concentration** (:math:`\phi \gg K_d`): :math:`\theta \to 1` (saturation)
- **Half-occupancy**: :math:`\theta = 0.5` at :math:`\phi = K_d`
References
----------
- Eq. (S3)-(S4): Langmuir isotherm derivation
- Eq. (S6)-(S7): Symmetric gating function
"""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
from .types import ArrayLike
[docs]@dataclass(frozen=True)
class LangmuirIsotherm:
r"""
Langmuir isotherm from grand canonical two-state site model.
Implements the standard Langmuir adsorption model (Eq. S3-S4):
.. math::
\theta(\phi) = \frac{\phi/K}{1+\phi/K}
This corresponds to a single-site partition function
:math:`Z = 1 + z e^{\beta \epsilon}`, with
:math:`z \simeq \phi/\phi^\circ` and :math:`K = \phi^\circ e^{-\beta\epsilon}`.
Parameters
----------
K : float
Dissociation constant (same unit as :math:`\phi`).
Smaller K indicates stronger binding.
See Also
--------
HillIsotherm : Extended isotherm with cooperativity parameter.
Examples
--------
>>> isotherm = LangmuirIsotherm(K=1.0)
>>> isotherm.theta(1.0)
0.5
>>> isotherm.theta([0.1, 1.0, 10.0])
array([0.0909..., 0.5, 0.9090...])
References
----------
- Eq. (S3)-(S4): Grand canonical two-state site model
"""
K: float
[docs] def theta(self, phi: ArrayLike) -> ArrayLike:
r"""
Calculate occupancy fraction.
Parameters
----------
phi : ArrayLike
Bulk concentration (same unit as K).
Returns
-------
theta : ArrayLike
Occupancy fraction in [0, 1], same shape as `phi`.
Notes
-----
At :math:`\phi = K`, the occupancy is exactly 0.5.
"""
phi = np.asarray(phi, dtype=float)
x = phi / self.K
return x / (1.0 + x)
[docs]@dataclass(frozen=True)
class HillIsotherm:
r"""
Hill isotherm with effective cooperativity.
Extends the Langmuir model with a Hill coefficient to capture
cooperative or multi-body effects:
.. math::
\theta(\phi) = \frac{(\phi/K)^n}{1+(\phi/K)^n}
Parameters
----------
K : float
Effective dissociation constant (same unit as :math:`\phi`).
n : float, optional
Hill coefficient (default: 1.0).
- n = 1: Reduces to Langmuir isotherm
- n > 1: Positive cooperativity (steeper transition)
- n < 1: Negative cooperativity (shallower transition)
See Also
--------
LangmuirIsotherm : Special case with n=1.
Notes
-----
Hill behavior is not required by the minimal model, but can represent
effective multi-body or cooperative binding effects.
Examples
--------
>>> isotherm = HillIsotherm(K=1.0, n=2.0)
>>> isotherm.theta(1.0)
0.5
>>> isotherm.theta([0.5, 1.0, 2.0])
array([0.2, 0.5, 0.8])
"""
K: float
n: float = 1.0
[docs] def theta(self, phi: ArrayLike) -> ArrayLike:
r"""
Calculate occupancy fraction with cooperativity.
Parameters
----------
phi : ArrayLike
Bulk concentration (same unit as K).
Returns
-------
theta : ArrayLike
Occupancy fraction in [0, 1], same shape as `phi`.
Notes
-----
The Hill coefficient :math:`n` controls the steepness of the
transition around :math:`\phi = K`.
"""
phi = np.asarray(phi, dtype=float)
x = (phi / self.K) ** self.n
return x / (1.0 + x)