Skip to content

models#

Ready-made model functions to fit, grouped by shape.

Pass any of these straight to fitting.fit(data, models.<name>). They cover the common decay/recovery curves (exponential, Gaussian, Lorentzian, stretched), general-purpose shapes (power law, sigmoid), and NMR relaxation dispersion models (CPMG two-/three-site exchange). See each function for its formula and parameters.

bi_exponential #

bi_exponential(
    x: ndarray,
    a1: float,
    tau1: float,
    a2: float,
    tau2: float,
    offset: float,
) -> ndarray

Two-component (bi-exponential) decay.

A sum of two single decays — the go-to model when a sample has two distinct pools relaxing at different rates (e.g. bound vs. free liquid), giving a fast initial drop followed by a slower tail.

\[y = A_1\,e^{-x/\tau_1} + A_2\,e^{-x/\tau_2} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable, usually time.

required
a1 float

Amplitude of the first (typically faster) component, \(A_1\).

required
tau1 float

Time constant of the first component, \(\tau_1\).

required
a2 float

Amplitude of the second (typically slower) component, \(A_2\).

required
tau2 float

Time constant of the second component, \(\tau_2\).

required
offset float

Baseline the curve decays towards, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

Notes

The two components are exchangeable: swapping \((A_1, \tau_1)\) with \((A_2, \tau_2)\) gives a bit-identical curve, so a fitter may return the same physical answer with the labels either way round. Pass the fitted vector through canonicalize_bi_exponential (or _utils.canonicalize_component_order) before reporting, so that "component 1" always means the faster one.

Both time constants are clamped away from zero (sign preserved) and the exponents are clipped, matching the rest of this module.

cpmg_sdr #

cpmg_sdr(
    nu_cpmg: ndarray, r2_0: float, gamma_g2_tau_d: float
) -> ndarray

Static Dephasing Regime (SDR) model for magnetic-particle solutions.

Applies when particles are large enough that the strong local field gradients around them are not averaged out by molecular diffusion. In this regime the relaxation rate no longer depends on the CPMG frequency, so the model returns a flat (frequency-independent) line — a useful null reference when checking whether a sample shows true dispersion.

\[R_2^{\text{eff}} = R_2^{0} + \tfrac{2}{3}\,\gamma^{2} G^{2} \tau_D\]

Parameters:

Name Type Description Default
nu_cpmg ndarray

CPMG frequency. Only its shape is used (the result is constant across it).

required
r2_0 float

Intrinsic relaxation rate, \(R_2^{0}\).

required
gamma_g2_tau_d float

The combined dephasing term \(\gamma^{2} G^{2} \tau_D\) (gyromagnetic ratio, local gradient, and correlation time rolled into one fitted constant).

required

Returns:

Type Description
ndarray

Effective \(R_2\) values (a constant float64 array matching the

ndarray

shape of nu_cpmg). The dtype is forced rather than inherited:

ndarray

np.full_like on an integer frequency array used to truncate the

ndarray

rate to a whole number.

cpmg_three_site #

cpmg_three_site(
    nu_cpmg: ndarray,
    r2_0: float,
    d_omega_ab: float,
    d_omega_ac: float,
    d_omega_bc: float,
    k_eff: float,
    p_a: float,
    p_b: float,
) -> ndarray

Three-site CPMG dispersion — empirical Lorentzian approximation.

Extends the two-site picture to a nucleus exchanging between three environments — for systems with an intermediate state, where each pair of sites contributes its own shift difference and population product.

\[R_2^{\text{eff}} = R_2^{0} + \frac{\Delta\omega_{AB}^{2}\,p_A p_B + \Delta\omega_{AC}^{2}\,p_A p_C + \Delta\omega_{BC}^{2}\,p_B p_C} {k_{\text{eff}} + \nu_{\text{CPMG}}^{2}/k_{\text{eff}}}, \qquad p_C = 1 - p_A - p_B\]
Warning

This is a phenomenological curve-shape, not a physical model, and it is retained under that understanding rather than as a derivation. There is no exact closed form for three-site CPMG dispersion in routine use: the secular equation is formally solvable for \(N \le 4\) sites but the resulting expressions are unusable in practice (Koss, Rance & Palmer, J. Magn. Reson. 321, 106846, 2020), so established software (relax NS models, ChemEx, CATIA) performs numerical Bloch–McConnell propagation instead. Published approximate three-site forms do exist — Koss, Rance & Palmer, Biochemistry 57, 4753–4763 (2018), Eq. 33 of the 2020 JMR paper, and the geometric approximation of Chao & Byrd, J. Magn. Reson. 277, 8–14 (2017) — and none of them is this expression.

Consequences for interpretation: the denominator \(k_{\text{eff}} + \nu^{2}/k_{\text{eff}}\) mixes a rate in s⁻¹ with a frequency in Hz and is not dimensionally coherent, so \(k_{\text{eff}}\) is a shape parameter with no defensible physical meaning. It does have the right qualitative behaviour — a plateau of \(R_2^{0} + \Sigma/k_{\text{eff}}\) at \(\nu \to 0\) decaying to \(R_2^{0}\) as \(\nu \to \infty\) — which makes it usable for empirical curve description and for deciding whether a dataset shows dispersion at all. Do not quote fitted \(k_{\text{eff}}\) or \(\Delta\omega\) values from it as exchange parameters; fit a Bloch–McConnell model for that.

Parameters:

Name Type Description Default
nu_cpmg ndarray

CPMG refocusing frequency \(\nu_{\text{CPMG}}\) (the swept variable).

required
r2_0 float

Intrinsic relaxation rate with no exchange, \(R_2^{0}\).

required
d_omega_ab float

Shift difference between sites A and B, \(\Delta\omega_{AB}\).

required
d_omega_ac float

Shift difference between sites A and C, \(\Delta\omega_{AC}\).

required
d_omega_bc float

Shift difference between sites B and C, \(\Delta\omega_{BC}\).

required
k_eff float

Effective overall exchange-rate shape parameter, \(k_{\text{eff}}\) (see the warning above).

required
p_a float

Fractional population of site A, \(p_A\).

required
p_b float

Fractional population of site B, \(p_B\) (site C is \(1 - p_A - p_B\)).

required

Returns:

Type Description
ndarray

Effective \(R_2\) values, one per element of nu_cpmg.

cpmg_tollinger #

cpmg_tollinger(
    nu_cpmg: ndarray,
    r2_0: float,
    delta_omega: float,
    k_ex: float,
    k_ba: float,
) -> ndarray

Two-site exchange dispersion for the slow-exchange limit (Tollinger).

An alternative to r2_effective_carver_richards that is accurate when exchange is slow compared with the shift difference. Use it for CPMG dispersion data where the populations are skewed and the simple fast-exchange form breaks down.

\[R_2^{\text{eff}} = R_2^{0} + k_{AB}\left(1 - \frac{\sin(\Delta\omega\,\tau_{cp})}{\Delta\omega\,\tau_{cp}}\right), \qquad \tau_{cp} = \frac{1}{2\,\nu_{\text{CPMG}}}, \quad k_{AB} = k_{\text{ex}} - k_{BA}\]

The bracket is \(1 - \operatorname{sinc}(\Delta\omega\tau_{cp})\), and \(\operatorname{sinc}(0) = 1\), so as \(\Delta\omega\,\tau_{cp} \to 0\) — very fast pulsing, or a vanishing shift difference — the model correctly returns \(R_2^{0}\): exchange is fully refocused and contributes nothing. (A previous implementation guarded only the denominator against zero and so returned \(R_2^{0} + k_{AB}\) in that limit, i.e. maximal broadening exactly where there should be none.)

Reference: Tollinger et al., J. Am. Chem. Soc. 2001, 123, 11341.

Parameters:

Name Type Description Default
nu_cpmg ndarray

CPMG refocusing frequency \(\nu_{\text{CPMG}}\) (the swept variable).

required
r2_0 float

Intrinsic relaxation rate with no exchange, \(R_2^{0}\).

required
delta_omega float

Chemical-shift difference between the two sites, \(\Delta\omega\) (rad/s).

required
k_ex float

Total exchange rate, \(k_{\text{ex}}\).

required
k_ba float

Reverse (B→A) exchange rate, \(k_{BA}\); the forward rate is \(k_{AB} = k_{\text{ex}} - k_{BA}\).

required

Returns:

Type Description
ndarray

Effective \(R_2\) values, one per element of nu_cpmg.

cpmg_two_site #

cpmg_two_site(
    nu_cpmg: ndarray,
    r2_0: float,
    delta_omega: float,
    k_ex: float,
    p_a: float,
) -> ndarray

Two-site exchange CPMG relaxation dispersion — the Carver–Richards closed form.

Fits how the effective transverse relaxation rate \(R_2^{\text{eff}}\) changes with the CPMG pulsing frequency when a nucleus hops between two environments (e.g. a protein switching between two conformations). The rate is highest when slow pulsing lets exchange dephase the signal and drops as fast pulsing refocuses it — the classic dispersion curve used to extract exchange rates and populations.

This is the general two-site solution: it makes no assumption about the exchange regime, unlike r2_effective_luz_meiboom (fast exchange only). The reduced form is used, i.e. both sites share one intrinsic rate \(R_2^{0}\):

\[R_2^{\text{eff}}(\nu) = R_2^{0} + \frac{k_{\text{ex}}}{2} - \nu\,\cosh^{-1}\!\big[D_+\cosh(\eta_+) - D_-\cos(\eta_-)\big]\]

with

\[\psi = k_{\text{ex}}^{2} - \Delta\omega^{2},\qquad \zeta = -2\,\Delta\omega\,(p_A k_{\text{ex}} - p_B k_{\text{ex}}),\]
\[D_\pm = \frac{1}{2}\left[\pm 1 + \frac{\psi + 2\Delta\omega^{2}}{\sqrt{\psi^{2} + \zeta^{2}}}\right], \qquad \eta_\pm = \frac{2^{-3/2}}{\nu}\sqrt{\pm\psi + \sqrt{\psi^{2}+\zeta^{2}}}.\]

Conventions (these matter — getting them wrong changes every fitted number): \(\nu_{\text{CPMG}} = 1/(4\tau_{cp})\) in Hz, where \(\tau_{cp}\) is the delay between the centres of successive \(180°\) pulses divided by two, i.e. the CPMG block is \([\tau_{cp} - 180° - 2\tau_{cp} - 180° - \tau_{cp}]^{n}\) and \(\nu_{\text{CPMG}} = n / T_{\text{rel}}\). \(\Delta\omega\) and \(k_{\text{ex}}\) are in rad/s; \(R_2\) in s⁻¹. The equation is not symmetric under swapping the sites — \(p_A\) must be the major population (\(p_A > 0.5\)).

References

Carver & Richards, J. Magn. Reson. 6, 89–105 (1972), doi:10.1016/0022-2364(72)90090-X; numerically tractable form from the appendix of Davis, Perlman & London, J. Magn. Reson. B 104, 266–275 (1994). Transcription cross-checked against the reference implementation lib/dispersion/cr72.py in relax (https://www.nmr-relax.com/manual/The_reduced_CR72_2_site_CPMG_model.html) and validated numerically against Bloch–McConnell propagation.

Warning

Prior versions of this function implemented an ad-hoc Lorentzian \(\Delta\omega^{2} k_{\text{ex}} p_A p_B / (2(k_{\text{ex}}^{2}+\nu^{2}))\) that is neither Carver–Richards nor any published limit of it, and which mixed rad/s with Hz in its denominator. Exchange parameters fitted with that expression are not comparable with these.

Parameters:

Name Type Description Default
nu_cpmg ndarray

CPMG refocusing frequency \(\nu_{\text{CPMG}}\) in Hz (the swept variable).

required
r2_0 float

Intrinsic relaxation rate with no exchange, \(R_2^{0}\) (s⁻¹).

required
delta_omega float

Chemical-shift difference between the two sites, \(\Delta\omega\) (rad/s).

required
k_ex float

Total exchange rate \(k_{\text{ex}} = k_{AB} + k_{BA}\) (s⁻¹).

required
p_a float

Fractional population of the major site A, \(p_A\) (site B is \(1 - p_A\)).

required

Returns:

Type Description
ndarray

Effective \(R_2\) values, one per element of nu_cpmg.

double_sigmoid #

double_sigmoid(
    x: ndarray,
    a1: float,
    c1: float,
    w1: float,
    a2: float,
    c2: float,
    w2: float,
    offset: float,
) -> ndarray

Two stacked sigmoids for a two-step transition.

Sums two S-shaped curves, so it can fit data that switches levels twice — e.g. a system with two sequential transitions or two unfolding events.

\[y = \frac{A_1}{1 + e^{-(x - c_1)/w_1}} + \frac{A_2}{1 + e^{-(x - c_2)/w_2}} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable.

required
a1 float

Height of the first step, \(A_1\).

required
c1 float

Midpoint of the first step, \(c_1\).

required
w1 float

Steepness of the first step, \(w_1\).

required
a2 float

Height of the second step, \(A_2\).

required
c2 float

Midpoint of the second step, \(c_2\).

required
w2 float

Steepness of the second step, \(w_2\).

required
offset float

Lower plateau the curve rises from, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

exponential_decay #

exponential_decay(
    x: ndarray,
    amplitude: float,
    time_constant: float,
    offset: float,
) -> ndarray

Single-rate exponential decay.

The workhorse for anything that falls from a starting level to a flat baseline at one characteristic rate: transverse (\(T_2\)) relaxation, a free-induction-decay envelope, or a generic intensity that dies away.

\[y = A\,e^{-x/\tau} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable, usually time.

required
amplitude float

Starting height above the baseline, \(A\).

required
time_constant float

Decay constant \(\tau\) (the value of \(x\) at which the signal falls to \(1/e \approx 37\%\) of \(A\)).

required
offset float

Baseline the curve decays towards, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

exponential_plateau #

exponential_plateau(
    x: ndarray,
    amplitude: float,
    time_constant: float,
    offset: float,
) -> ndarray

Exponential build-up to a plateau (saturation-recovery \(T_1\)).

Mirror image of a decay: the signal grows from the baseline and levels off at \(A + c\). This is the saturation-recovery longitudinal-relaxation curve, where magnetisation rebuilds along the field after being saturated.

\[y = A\left(1 - e^{-x/\tau}\right) + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable, usually the recovery delay.

required
amplitude float

Total rise from baseline to plateau, \(A\).

required
time_constant float

Recovery constant \(\tau\) (\(T_1\)); the signal reaches \(\approx 63\%\) of its final rise at \(x = \tau\).

required
offset float

Baseline the curve starts from, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

gaussian_decay #

gaussian_decay(
    x: ndarray,
    amplitude: float,
    time_constant: float,
    offset: float,
) -> ndarray

Gaussian (bell-shaped) decay.

Falls off faster than an exponential at first and then more gently — the shape produced by a spread of frequencies rather than a single rate. Typical of strongly-coupled solids and inhomogeneously broadened signals, where it gives a Gaussian line in the spectrum.

\[y = A\,e^{-(x/\tau)^{2}} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable, usually time.

required
amplitude float

Starting height above the baseline, \(A\).

required
time_constant float

Decay constant \(\tau\) (sets the width of the bell).

required
offset float

Baseline the curve decays towards, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

gaussian_lorentzian #

gaussian_lorentzian(
    x: ndarray,
    a_gauss: float,
    tau_gauss: float,
    a_lorentz: float,
    tau_lorentz: float,
    offset: float,
) -> ndarray

Combined Gaussian + Lorentzian decay (Voigt-like).

Adds a Gaussian and an exponential (Lorentzian) decay together, so it can capture signals that are partly inhomogeneously broadened (Gaussian) and partly homogeneously broadened (Lorentzian) — the time-domain analogue of a Voigt lineshape. Useful when neither pure shape fits the data alone.

\[y = A_g\,e^{-(x/\tau_g)^{2}} + A_l\,e^{-x/\tau_l} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable, usually time.

required
a_gauss float

Amplitude of the Gaussian part, \(A_g\).

required
tau_gauss float

Time constant of the Gaussian part, \(\tau_g\).

required
a_lorentz float

Amplitude of the Lorentzian (exponential) part, \(A_l\).

required
tau_lorentz float

Time constant of the Lorentzian part, \(\tau_l\).

required
offset float

Baseline the curve decays towards, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

lorentzian_decay #

lorentzian_decay(
    x: ndarray,
    amplitude: float,
    time_constant: float,
    offset: float,
) -> ndarray

Lorentzian decay (exponential in time, Lorentzian in the spectrum).

A plain exponential decay in the time domain Fourier-transforms into a Lorentzian line in the spectrum, so this is the model for homogeneously broadened signals with a single \(T_2\). It is identical in form to ExponentialModels.exponential_decay; the name reflects the lineshape it produces, not a different equation.

\[y = A\,e^{-x/\tau} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable, usually time.

required
amplitude float

Starting height above the baseline, \(A\).

required
time_constant float

Decay constant \(\tau\) (\(T_2\)); sets the linewidth of the resulting Lorentzian.

required
offset float

Baseline the curve decays towards, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

power_law #

power_law(
    x: ndarray,
    amplitude: float,
    exponent: float,
    offset: float,
) -> ndarray

Power-law relationship.

For quantities that scale as a power of \(x\) rather than rising or decaying exponentially — e.g. signal attenuation versus gradient strength, or any process that looks like a straight line on a log–log plot.

\[y = A\,x^{p} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable.

required
amplitude float

Overall scale factor, \(A\).

required
exponent float

Power \(p\) (negative for a falling curve, positive for a rising one).

required
offset float

Vertical offset, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

Notes

x is cast to float first. np.power on an integer array raises outright for a negative integer exponent ("Integers to negative integer powers are not allowed") and silently truncates for a positive one — the same class of dtype-inheritance defect that made the SDR relaxation model return integer rates.

sigmoid #

sigmoid(
    x: ndarray,
    amplitude: float,
    center: float,
    width: float,
    offset: float,
) -> ndarray

Sigmoid (S-shaped) transition.

Describes a smooth step from one level to another: titration and dose–response curves, melting/transition profiles, or any quantity that switches between two plateaus around a threshold.

\[y = \frac{A}{1 + e^{-(x - x_0)/w}} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable.

required
amplitude float

Total height of the step, \(A\).

required
center float

Midpoint \(x_0\) where the curve is halfway through the transition.

required
width float

Steepness \(w\); smaller is a sharper step, larger is more gradual.

required
offset float

Lower plateau the curve rises from, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

stretched_exponential #

stretched_exponential(
    x: ndarray,
    amplitude: float,
    time_constant: float,
    beta: float,
    offset: float,
) -> ndarray

Stretched exponential (Kohlrausch–Williams–Watts).

A decay that is not governed by a single rate but by a distribution of rates — typical of disordered, glassy, or porous systems where many environments relax at slightly different speeds. The stretch exponent \(\beta\) controls how broad that distribution is (\(\beta = 1\) recovers an ordinary single exponential).

\[y = A\,e^{-(x/\tau)^{\beta}} + c\]

Parameters:

Name Type Description Default
x ndarray

Independent variable, usually time.

required
amplitude float

Starting height above the baseline, \(A\).

required
time_constant float

Characteristic time \(\tau\).

required
beta float

Stretch exponent, \(0 < \beta \le 1\); smaller means a wider spread of underlying rates.

required
offset float

Baseline the curve decays towards, \(c\).

required

Returns:

Type Description
ndarray

Predicted \(y\) values, one per element of x.

Notes

\((x/\tau)^{\beta}\) is only real for \(x \ge 0\), and blows up at \(x = 0\) when \(\beta < 0\). Negative x is therefore clipped to zero, \(\tau\) is clamped away from zero, and the exponent is clipped before exp so the model returns finite numbers for every parameter an optimizer might try.