Source code for microscopic_gating.transport

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_closed_form(self, k_tilde0: float, lam: ArrayLike, alpha: float) -> ArrayLike: r""" Calculate closed-form Poisson average (Eq. S43). Parameters ---------- k_tilde0 : float Base rate including geometric barrier constant part (Eq. S44). lam : ArrayLike Poisson intensity :math:`\lambda(\phi)`. alpha : float Exponential factor :math:`\alpha = \beta \epsilon_{\text{eff}}`. Returns ------- k_esc : ArrayLike Averaged escape rate, same shape as `lam`. """ lam = np.asarray(lam, dtype=float) return float(k_tilde0) * np.exp(-lam * (1.0 - np.exp(-float(alpha))))
[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)