r"""
Transport models connecting escape rates to effective diffusion.
This module implements models that map microscopic escape rates to
macroscopic transport coefficients, including jump diffusion and
Poisson-averaged escape rates.
Theoretical Foundation
----------------------
The transport model connects the microscopic Kramers escape rate to
macroscopic diffusion through several steps:
1. **Single bridge count escape rate** (Kramers):
.. math::
k_{\text{esc}}(n_b) = k_0 \exp[-\beta \Delta F^\ddagger(n_b)]
where the barrier height is:
.. math::
\Delta F^\ddagger(n_b) = \frac{1}{2}\kappa x_c^2 + n_b \epsilon_{\text{eff}}
2. **Poisson average over bridge distribution**:
.. math::
k_{\text{esc}}(\phi) = \sum_{n_b=0}^\infty P(n_b|\phi) k_{\text{esc}}(n_b)
For exponential ansatz :math:`k_{\text{esc}}(n_b) = \tilde{k}_0 e^{-\alpha n_b}`
with :math:`\alpha = \beta\epsilon_{\text{eff}}`, this yields closed form:
.. math::
k_{\text{esc}}(\phi) = \tilde{k}_0 \exp[-\lambda(\phi)(1 - e^{-\alpha})]
3. **Jump diffusion mapping**:
.. math::
D_{\text{eff}}(\phi) = \frac{\ell^2}{2d} k_{\text{esc}}(\phi)
where :math:`\ell` is the jump length and :math:`d=3` is spatial dimension.
Key Physical Insights
---------------------
**Re-entrant behavior:**
The concentration dependence enters through :math:`\lambda(\phi) = \lambda_0 \mathcal{G}(\phi)`
where :math:`\mathcal{G}(\phi)` is bell-shaped. This produces:
- **Suppression regime**: Diffusion minimum at :math:`\phi \approx K_d`
(where bridging is maximal)
- **Recovery at limits**: :math:`D_{\text{eff}} \to D_{\text{free}}` as
:math:`\phi \to 0` or :math:`\phi \to \infty`
**Two averaging protocols:**
1. **Rate-averaging** (controls long-time diffusion):
.. math::
k_{\text{eff}}(\phi) = \langle k_{\text{esc}} \rangle = \sum P(n_b) k_{\text{esc}}(n_b)
2. **Time-averaging** (conservative locking criterion):
.. math::
\langle \tau_{\text{res}} \rangle(\phi) = \langle 1/k_{\text{esc}} \rangle
For broad rate distributions, :math:`\langle 1/k \rangle \neq 1/\langle k \rangle`.
Examples
--------
Jump diffusion mapping:
>>> jd = JumpDiffusion(step_length=1.0, dim=3)
>>> jd.tau_res(0.5) # Residence time
2.0
>>> jd.D_eff(0.5) # Effective diffusion
0.0833...
Poisson-averaged escape rate:
>>> avg = PoissonEscapeAveraging()
>>> avg.kesc_closed_form(k_tilde0=1.0, lam=2.0, alpha=0.5) # doctest: +SKIP
0.243...
Concentration-dependent diffusion:
>>> cdd = ConcentrationDependentDiffusion(beta=1.0, D_free=1.0, epsilon_eff=1.0)
>>> cdd.D_eff(1.0) # doctest: +SKIP
0.531...
Parameters
----------
The transport model depends on these physical parameters:
==================== ============================================================
Parameter Physical Meaning
==================== ============================================================
:math:`\ell` Jump length (pore-to-pore distance)
:math:`d` Spatial dimension (typically 3)
:math:`\epsilon_{\text{eff}}` Effective bond free energy at escape coordinate
:math:`\beta` Inverse temperature :math:`(k_B T)^{-1}`
:math:`D_{\text{free}}` Baseline diffusion (no bridges)
==================== ============================================================
See Also
--------
KramersEscape : Microscopic escape rate calculation
EscapeAveraging : Alternative averaging with time-scale analysis
MicroscopicGatingModel : Provides :math:`\lambda(\phi)` input
References
----------
- Eq. (S38)-(S39): Jump diffusion mapping
- Eq. (S41): General Poisson average definition
- Eq. (S42): Exponential rate ansatz
- Eq. (S43): Closed-form Poisson average
- Eq. (S45): Concentration-dependent diffusion
- Eq. (S47)-(S49): Rate vs time averaging distinction
"""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
from .types import ArrayLike
[docs]@dataclass(frozen=True)
class JumpDiffusion:
r"""
Jump diffusion mapping between escape rate and effective diffusion.
Implements Eq. (S38)-(S39) for relating microscopic escape dynamics
to macroscopic diffusion:
.. math::
\tau_{\text{res}} &= 1/k_{\text{esc}}
D_{\text{eff}}(n_b) &= \frac{\ell^2}{2d}k_{\text{esc}}(n_b), \quad d=3
Parameters
----------
step_length : float
Jump length :math:`\ell` (typically pore-to-pore distance).
dim : int, optional
Spatial dimension :math:`d` (default: 3).
Examples
--------
>>> jd = JumpDiffusion(step_length=1.0, dim=3)
>>> jd.tau_res(0.5)
2.0
>>> jd.D_eff(0.5)
0.0833...
References
----------
- Eq. (S38): Residence time definition
- Eq. (S39): Effective diffusion from escape rate
"""
step_length: float
dim: int = 3
[docs] def tau_res(self, k_esc: ArrayLike) -> ArrayLike:
r"""
Calculate residence time.
Parameters
----------
k_esc : ArrayLike
Escape rate (1/time units).
Returns
-------
tau : ArrayLike
Residence time :math:`\tau_{\text{res}} = 1/k_{\text{esc}}`,
same shape as `k_esc`.
"""
k_esc = np.asarray(k_esc, dtype=float)
return 1.0 / k_esc
[docs] def D_eff(self, k_esc: ArrayLike) -> ArrayLike:
r"""
Calculate effective diffusion coefficient.
Parameters
----------
k_esc : ArrayLike
Escape rate (1/time units).
Returns
-------
D : ArrayLike
Effective diffusion coefficient:
:math:`D_{\text{eff}} = \ell^2 k_{\text{esc}} / (2d)`,
same shape as `k_esc`.
"""
k_esc = np.asarray(k_esc, dtype=float)
return (self.step_length**2) * k_esc / (2.0 * float(self.dim))
[docs]@dataclass(frozen=True)
class PoissonEscapeAveraging:
r"""
Average escape rate over Poisson-distributed bridge counts.
Implements Eq. (S41) and provides the closed-form Eq. (S43) under the
exponential-in-:math:`n_b` rate ansatz Eq. (S42).
For :math:`k_{\text{esc}}(n_b) = \tilde{k}_0 \exp(-\alpha n_b)` and
:math:`n_b \sim \text{Poisson}(\lambda)`:
.. math::
k_{\text{esc}}(\phi) = \tilde{k}_0 \exp[-\lambda(1-e^{-\alpha})]
Examples
--------
>>> averaging = PoissonEscapeAveraging()
>>> averaging.kesc_closed_form(k_tilde0=1.0, lam=2.0, alpha=0.5) # doctest: +SKIP
0.243...
References
----------
- Eq. (S41): General Poisson average
- Eq. (S42): Exponential rate ansatz
- Eq. (S43): Closed-form solution
"""
[docs] def kesc_sum(self, k_nb: ArrayLike, P_nb: ArrayLike) -> float:
r"""
Calculate generic discrete average :math:`\sum_n P(n) k(n)`.
Parameters
----------
k_nb : ArrayLike
Array of :math:`k(n)` values.
P_nb : ArrayLike
Array of probabilities :math:`P(n)`, same shape as `k_nb`.
Returns
-------
k_avg : float
Weighted average :math:`\sum_n P(n) k(n)`.
"""
k_nb = np.asarray(k_nb, dtype=float)
P_nb = np.asarray(P_nb, dtype=float)
return float(np.sum(P_nb * k_nb))
[docs]@dataclass(frozen=True)
class ConcentrationDependentDiffusion:
r"""
Concentration-dependent effective diffusion from gating-derived :math:`\lambda(\phi)`.
Implements Eq. (S45):
.. math::
D_{\text{eff}}(\phi) = D_{\text{free}} \exp[-\lambda(\phi)(1-e^{-\beta\epsilon_{\text{eff}}})]
Parameters
----------
beta : float
Inverse temperature :math:`\beta`.
D_free : float
Baseline diffusion scale :math:`D_{\text{free}} = \ell^2 \tilde{k}_0/6` (Eq. S45).
epsilon_eff : float
Effective bond free-energy penalty at exit coordinate.
Examples
--------
>>> cdd = ConcentrationDependentDiffusion(beta=1.0, D_free=1.0, epsilon_eff=1.0)
>>> cdd.D_eff(1.0) # doctest: +SKIP
0.531...
References
----------
- Eq. (S45): Concentration-dependent diffusion expression
"""
beta: float
D_free: float
epsilon_eff: float
[docs] def D_eff(self, lam: ArrayLike) -> ArrayLike:
r"""
Calculate effective diffusion coefficient.
Parameters
----------
lam : ArrayLike
Poisson intensity :math:`\lambda(\phi)`.
Returns
-------
D : ArrayLike
Concentration-dependent effective diffusion coefficient,
same shape as `lam`.
"""
lam = np.asarray(lam, dtype=float)
factor = 1.0 - np.exp(-float(self.beta) * float(self.epsilon_eff))
return float(self.D_free) * np.exp(-lam * factor)