API: Exchange1D

NMRAnalysis.Exchange1D — Module
Exchange1D

Module for 1D chemical exchange analysis using Bloch-McConnell equations.

Supports R1ρ relaxation dispersion and CEST experiments with:

  • NoExchange (null model), TwoState, and TwoStateBinding exchange models
  • Full Bloch-McConnell Liouvillian construction
  • Joint fitting of multiple experiment types
source
NMRAnalysis.Exchange1D._OVERLAY_MARKER_COLORS — Constant

Solid, lightened marker fill colours for the data panel of an overlay plot, one per curve (indexed the same way as the full-strength color=i used for fit lines and for the residual panel) — not transparency: alpha fades the error bars along with the marker fill, and overlapping semi-transparent error bars from several experiments composite into a muddy mess. A flat lighter colour keeps error bars crisp (their stroke colour is still forced to black — see the markerstrokecolor on each data scatter!) while letting the full-strength fit line read as the most prominent element on the plot. The residual panel has no competing fit line, so its markers use the regular full-strength color=i instead.

source
NMRAnalysis.Exchange1D.CESTExperiment — Type
CESTExperiment <: AbstractExperiment

Chemical Exchange Saturation Transfer experiment. Stores saturation offsets, B1 field strength, saturation time, and observed/predicted intensity profiles.

Loading, integration, and simulation are not yet implemented.

source
NMRAnalysis.Exchange1D.ExchangeProblem — Type
ExchangeProblem

A set of experiments to be fitted jointly to a shared exchange model.

integration records the peak-picking parameters (peakppm, noiseppm, ppmwidth) used to integrate all experiments in the problem, as a NamedTuple, or nothing before integrate! has been called. Kept here (rather than discarded once used) so it can be saved alongside the fit results for traceability back to the source spectra.

savecalibration is how a B₁ calibration fitted for this problem is kept: a function writing that analysis into a folder, or nothing where the calibration was given ready-made or not at all. Saving it is deferred to _save_results, so the calibration lands inside the output folder of the fit that used it, and a fit that is never saved leaves nothing behind.

source
NMRAnalysis.Exchange1D.ExchangeProblem — Method
ExchangeProblem(filenames::Vector{String}, model::AbstractModel; calibration=nothing)

Construct an ExchangeProblem by loading experiments from filenames. Each file is classified and loaded via load_experiment.

calibration gives the B₁ field strengths and inhomogeneity every experiment is simulated with: a B1Calibration, or the nutation calibration experiment(s) to fit for one. It is resolved here rather than per experiment, so calibration experiments are fitted once. Without it each experiment falls back to its own reference pulse and an assumed 5% inhomogeneity.

source
NMRAnalysis.Exchange1D.FitResult — Type
FitResult

Result of a joint fit of exchange experiments. Fields are accessible via dot syntax.

Fields

  • params: fitted parameters with uncertainties (ComponentArray{Measurement})
  • params_value: fitted parameters as plain Float64, for simulation/plotting
  • params0: initial parameters used for the fit
  • chi2: chi-squared statistic
  • reduced_chi2: chi-squared / degrees of freedom
  • cov: covariance matrix of the fitted (non-fixed) parameters
  • cor: correlation matrix of the fitted (non-fixed) parameters, derived from cov
  • nobs: number of observations
  • nparams: number of fitted parameters — non-fixed ComponentArray entries plus any profiled out of residuals analytically (see nprofiledparams, e.g. R1ρ's I₀ per condition), which never appear in params but still cost a degree of freedom
  • dof: degrees of freedom
  • fixed: flat indices of parameters held fixed during the fit (see fit)
  • atbound: flat indices of fitted parameters that converged onto a bound (e.g. a relaxation rate pinned at zero) rather than settling to an interior optimum — their reported uncertainty still holds, but treat the value itself as a limit, not an estimate
  • freeidx: flat indices of the fitted (non-fixed) parameters, in the order corresponding to the rows/columns of cov and cor
  • prob: the ExchangeProblem that was fitted
source
NMRAnalysis.Exchange1D._ParamItem — Type

A flattened parameter item: individual scalar elements, with array elements expanded.

  • label: internal name (e.g. "spin.delta[1]")
  • flat_index: index into the underlying flat data array of the ComponentArray
  • value: the scalar value
  • section: top-level section name (e.g. "model", "spin", "nuisance")
source
Base.show — Method

Pretty multi-line display for FitResult with parameter tables and fit statistics.

source
NMRAnalysis.Exchange1D._dgpopulation — Method
_dgpopulation(pmodel::ComponentArray, key::Symbol)

Population fraction for key (e.g. :dGB → pB), reconstructed from all dG* values currently stored in pmodel (the model parameter section), using the same reparametrization as TwoStateModel/ThreeStateModel's populations (population of the implicit reference state, e.g. A, is 1 / (1 + Σ exp(-dG))).

source
NMRAnalysis.Exchange1D._dgvalue_for_population — Method
_dgvalue_for_population(pmodel::ComponentArray, key::Symbol, pnew)

Inverse of _dgpopulation: the dG value for key that reproduces the population fraction pnew, holding all other dG* values in pmodel fixed at their current values.

source
NMRAnalysis.Exchange1D._displayvalue — Method

Convert a parameter item's stored value to linear (display/entry) space. params is the enclosing top-level ComponentArray (with .model/.spin/ .nuisance sections), needed to reconstruct dG-parametrized populations.

source
NMRAnalysis.Exchange1D._format_concrange — Method
_format_concrange(values::Vector{Float64}) -> String

Format the concentration(s) at which a molecule appears across experiments: a single value if consistent, otherwise the range observed (which usually signals a titration series or inconsistently-entered sample metadata).

source
NMRAnalysis.Exchange1D._isdgparam — Method
_isdgparam(item::_ParamItem) -> Bool

Whether a parameter follows the dG<X> convention (model.dGB, model.dGC, ...): an internal, unconstrained free energy difference (relative to a reference state, e.g. dGB = G_B - G_A) standing in for a population fraction. Unlike _islogparam, the user-facing value (pB, pC, ...) is not a function of this one parameter alone — it also depends on the other dG* values currently stored in the same model section, since the underlying population fractions must sum to 1 (see _dgpopulation / _dgvalue_for_population).

source
NMRAnalysis.Exchange1D._islogparam — Method
_islogparam(item::_ParamItem) -> Bool

Whether a parameter is stored internally as its natural logarithm. By convention this is signalled by a log prefix on the parameter name (e.g. model.logKd, model.logkoff) — any positive-only parameter can be switched to log-space storage (to keep it from going negative during fitting) simply by naming it this way in defaultparams/ default_spin_params/default_nuisance_params and exponentiating it where it's used. Users always see and enter the linear (exponentiated) value — the internal representation is invisible to them either way.

source
NMRAnalysis.Exchange1D._pretty_label — Method
_pretty_label(item, state_labels, unique_fields) -> String

Convert an internal parameter label like spin.R2_22p31T[1] to a human-readable label like R₂ (A) or R₂ (A) [22.31 T].

source
NMRAnalysis.Exchange1D._prompt_concentrations! — Method
_prompt_concentrations!(model, prob::ExchangeProblem)

For each molecule role required by model, check whether every experiment carries sample concentration metadata for it. If some (or all) experiments lack it — e.g. because they weren't matched to a sample containing this molecule — prompt the user for a fallback concentration value and store it in model.concentrations, which moleculeconcentration (and hence modelconcentrations) falls back to for experiments missing metadata.

Warning about a partial mismatch here, rather than only about a total one, is what turns a KeyError buried deep in the fit (see issue #36) into an actionable prompt at setup time.

source
NMRAnalysis.Exchange1D._prompt_moleculemap_manual! — Method
_prompt_moleculemap_manual!(model, prob, roles) -> Bool

Fallback used by _prompt_moleculemap! when no experiment carries sample concentration metadata: since there is no list of molecule names to pick from, ask directly for a name and concentration for each role and populate both model.moleculemap and model.concentrations.

source
NMRAnalysis.Exchange1D._prompt_params — Method
_prompt_params(p0, prob, fixed) -> ComponentArray or nothing

Interactive parameter editor. Returns edited parameters, or nothing if cancelled.

fixed is a Set{Int} of flat parameter indices (mutated in place) that are held constant during fitting. Enter fix when editing a parameter to add it to this set (freezing it at its current value), or free to remove it.

Parameters stored internally in log space (see _islogparam) are displayed and entered here in linear space.

source
NMRAnalysis.Exchange1D._ratelowerbound — Method
_ratelowerbound(item) -> Float64

Lower bound for a raw (linear, un-fixed) parameter passed to the optimiser. Relaxation rates (spin.R1_*, spin.R2_*) are physically non-negative but, unlike Kd/koff (see _islogparam) or populations (see _isdgparam), are stored and fitted directly in linear space — so unlike those, nothing stops the optimiser from stepping into negative territory mid-search, which makes the Bloch-McConnell matrix exponential used to simulate CEST/R1ρ experiments numerically unstable (a relaxation rate < 0 is a growing, not decaying, mode). Bounding these at zero keeps the search in the physical region without changing how they're entered, displayed, or stored. Takes an _ParamItem (from _flatten_params_items, defined in interface.jl); left untyped here since interface.jl is included after this file.

source
NMRAnalysis.Exchange1D.b1calibration — Method
b1calibration(given, spec, nuc) -> B1Calibration

The calibration one experiment should use: the one resolved for the problem, or the nominal calibration read from that spectrum's own reference pulse, with an assumed B₁ inhomogeneity.

A calibration measured on another channel is a warning rather than an error, since exchange1d may be given experiments on more than one nucleus.

source
NMRAnalysis.Exchange1D.combineplots — Method
combineplots(result) -> Figure

Every experiment of a fit on one grid, at most four columns wide, with the fonts and the figure scaled so that a fit of many experiments stays legible.

source
NMRAnalysis.Exchange1D.correlationheatmap — Method
correlationheatmap(result::FitResult) -> Figure

Heatmap of the correlation matrix of the fitted (non-fixed) parameters, axes labelled with the same display names as the parameter tables — the picture version of correlation.csv, for spotting at a glance which parameters trade off against each other.

source
NMRAnalysis.Exchange1D.covariancematrixtable — Method
covariancematrixtable(result) -> (header, rows)

Full-precision covariance matrix of the fitted (non-fixed) parameters: one row and one column per parameter, in FitResult.freeidx order, keyed by the same flat parameter label global.csv uses in its parameter column.

source
NMRAnalysis.Exchange1D.default_nuisance_params — Method
default_nuisance_params(expt::CESTExperiment) -> Vector{Pair{Symbol,Any}}

Return flat nuisance parameter entries for this CEST experiment: an overall intensity scale I0, tagged with the experiment type and field, e.g. :CEST_14p1T_I0. Shared across all CEST experiments at the same field — see simulate!.

source
NMRAnalysis.Exchange1D.default_nuisance_params — Method
default_nuisance_params(expt::R1Experiment) -> Vector{Pair{Symbol,Any}}

Return flat nuisance parameter entries for this R1 experiment, tagged with the experiment type and field, e.g. :R1_14p1T_I0, :R1_14p1T_inv_factor.

source
NMRAnalysis.Exchange1D.default_spin_params — Method
default_spin_params(expt::CESTExperiment, nstates) -> Vector{Pair{Symbol,Any}}

Return spin parameter entries needed by this CEST experiment: R1, R2 for the experiment's field, and chemical shifts (delta).

source
NMRAnalysis.Exchange1D.defaultparams — Method
defaultparams(prob::ExchangeProblem) -> ComponentArray

Assemble a default parameter ComponentArray from the model and experiments.

Each experiment contributes spin parameters (via default_spin_params) and nuisance parameters (via default_nuisance_params). Both return flat Vector{Pair{Symbol,Any}} with self-describing keys (e.g. :R2_14p1T, :R1_14p1T_I0). Duplicate entries are merged automatically.

The returned ComponentArray has three sections:

  • model: exchange model parameters (kex, pB, Kd, koff, etc.)
  • spin: chemical shifts (delta, ppm) and field-dependent relaxation rates
  • nuisance: per-experiment amplitude and fitting parameters
source
NMRAnalysis.Exchange1D.detail — Method

A quieter status line for routine progress/confirmation messages (e.g. per-file loading) that don't need @info's logging prefix or full emphasis — @info/@warn are reserved here for things that genuinely need to stand out.

source
NMRAnalysis.Exchange1D.exchange1d — Method
exchange1d(filenames::Vector{String}; calibration=nothing)

Interactive text-based interface for 1D chemical exchange analysis.

Guides the user through:

  1. Model selection
  2. Molecule mapping (if required by model)
  3. Peak integration parameters
  4. Parameter review and editing
  5. Fitting and display of results
  6. Saving results

Arguments

  • filenames: Vector of paths to NMR experiment directories
  • calibration: the B₁ field strengths and inhomogeneity to simulate with, as a B1Calibration or as the nutation calibration experiment(s) to fit for one. Without it, each experiment's own reference pulse gives the field strengths on the assumption of a perfectly linear amplifier, and the B₁ inhomogeneity is taken to be 5%.

Example

# spin-lock fields and B₁ inhomogeneity from a set of nutation calibrations
exchange1d(["cest/11", "r1rho/12"]; calibration=["cal/1", "cal/2", "cal/3"])
source
NMRAnalysis.Exchange1D.exchangeexperiments — Method

The experiments an exchange analysis can be run on, or nothing where the selection holds nothing to fit. A CEST or off-resonance R1ρ experiment is what makes an exchange analysis worth offering; R1 and on-resonance R1ρ experiments join a fit but do not trigger one, and a calibration only ever joins.

source
NMRAnalysis.Exchange1D.exchangematrix — Method
exchangematrix(model::InducedFitModel, params, expt) -> Matrix{Float64}

Build the 3×3 kinetic exchange matrix for the induced fit mechanism A + L ⇌ B ⇌ C. There is no direct A ⇌ C exchange. The pseudo-first-order on-rate A → B is kon * Lfree, with free ligand concentration obtained from the ligand mass balance Lfree = X0 - (B + C).

source
NMRAnalysis.Exchange1D.exchangematrix — Method
exchangematrix(model::ThreeStateBindingModel, params, expt)

Build the 3×3 exchange matrix for three-state parallel binding. B and C do not interconvert directly (kbc = kcb = 0).

source
NMRAnalysis.Exchange1D.exchangematrix — Method
exchangematrix(model::TwoStateBindingModel, params, expt) -> Matrix{Float64}

Build the 2×2 kinetic exchange matrix for two-state binding. Populations are derived from Kd and the total concentrations of the observed species (:A) and binding partner (:X) via the quadratic binding equation. Molecule names are looked up in sampleconcentrations using model.moleculemap.

source
NMRAnalysis.Exchange1D.exchangematrix — Method
exchangematrix(model::TwoStateModel, params, expt) -> Matrix{Float64}

Build the 2×2 kinetic exchange matrix for two-state intramolecular exchange. K[i,j] is the rate from state j to state i; column sums are zero.

source
NMRAnalysis.Exchange1D.experimentdirectory — Method
experimentdirectory(expt::AbstractExperiment) -> String

Full experiment directory (the parent of the pdata/N folder spec[:filename] points into) — i.e. the path that was, or could be, passed to exchange1d.

source
NMRAnalysis.Exchange1D.experimentgroups — Method
experimentgroups(key, experiments) -> Vector{Vector}

Group experiments by the value of key(experiment), preserving the order in which each group's key was first encountered. Used by overlayplots to find sets of experiments that share a saturation power or saturation time (and therefore differ only in the other one), so they can be drawn together.

source
NMRAnalysis.Exchange1D.experimentinfo — Method
experimentinfo(expt::CESTExperiment) -> Vector{Pair{String,String}}

Acquisition parameters worth recording alongside a saved fit (see _save_results): saturation field strength, saturation time, and the saturation offset range.

source
NMRAnalysis.Exchange1D.experimentinfo — Method
experimentinfo(expt::R1Experiment) -> Vector{Pair{String,String}}

Acquisition parameters worth recording alongside a saved fit (see _save_results): field strength, fitting model, and relaxation delay range.

source
NMRAnalysis.Exchange1D.experimentinfo — Method
experimentinfo(expt::R1rhoOffResExperiment) -> Vector{Pair{String,String}}

Acquisition parameters worth recording alongside a saved fit (see _save_results): field strength, spin-lock strength, and offset range.

source
NMRAnalysis.Exchange1D.experimentinfo — Method
experimentinfo(expt::R1rhoOnResExperiment) -> Vector{Pair{String,String}}

Acquisition parameters worth recording alongside a saved fit (see _save_results): field strength and spin-lock strength range.

source
NMRAnalysis.Exchange1D.field_label — Method
field_label(expt) -> Symbol

Convert a magnetic field strength in Tesla to a Symbol for use as a ComponentArray key. E.g. field_label(14.1) → :14p1T.

source
NMRAnalysis.Exchange1D.fit — Method
fit(prob::ExchangeProblem, params0::ComponentArray; fixed=Set{Int}()) -> FitResult

Fit all experiments jointly using least-squares optimisation.

fixed is a set of flat indices (as produced by _flatten_params_items) into params0 that are held constant at their params0 value rather than being optimised. This lets, e.g., a chemical shift be pinned by the user while the rest of the model is fitted.

Returns a FitResult containing fitted parameters (with uncertainties), fit statistics, and a reference to the problem for display and plotting.

The objective minimised is residuals(prob, params) — the concatenation of each experiment's own weighted residuals — evaluated against a zero target, so a per-experiment residuals override (as R1ρ uses to eliminate I₀ by variable projection; see residuals(::Union{R1rhoOnResExperiment,R1rhoOffResExperiment})) changes what the fit itself minimises, not just what is reported afterwards.

source
NMRAnalysis.Exchange1D.globaltable — Method
globaltable(result) -> (header, rows)

Column names and rows for global.csv: every fitted parameter, flat, with the value it started at, the value it reached, its uncertainty, whether it was held fixed, and whether it converged onto a bound (see FitResult.atbound) rather than an interior optimum. This is where an exchange fit's actual results are, a joint fit having nothing that belongs to one experiment alone.

source
NMRAnalysis.Exchange1D.inducedfitequilibrium — Method
inducedfitequilibrium(model::InducedFitModel, params, expt) -> (Afree, B, C)

Equilibrium concentrations of free observed species (Afree), the initial ("open") complex (B) and the conformationally-selected ("closed") complex (C) for a two-step induced fit mechanism

A + L ⇌ B ⇌ C

with dissociation constant Kd and off-rate koff for the bimolecular step, and forward/backward rates kclose/kopen for the unimolecular conformational step. Solved analytically from the ligand/observed-species mass balance equations (Waudby, TITAN bmInducedFit). Shared by populations and exchangematrix.

source
NMRAnalysis.Exchange1D.integrate! — Method
integrate!(prob::ExchangeProblem, peakppm, noiseppm, ppmwidth)

Integrate all experiments in the problem at the given peak and noise positions, and record them on prob.integration for later reference (e.g. when saving results).

source
NMRAnalysis.Exchange1D.isatbound — Method
isatbound(value, bound; tol=1e-6) -> Bool

Whether a fitted parameter has converged onto a finite bound rather than settling to an interior optimum near it. tol is relative to the larger of value and bound in magnitude (floored at 1).

source
NMRAnalysis.Exchange1D.iscalibration — Method

A nutation calibration among the selected experiments is not something to fit: it is where the spin-lock field strengths and the B₁ inhomogeneity come from. Matched alongside the experiments so that a folder holding both is analysed with measured fields rather than nominal ones.

source
NMRAnalysis.Exchange1D.liouvillian — Method
liouvillian(model, params, expt::AbstractExperiment, offset_hz, spinlock_hz)

Form the Liouvillian matrix for a given model, parameters, and experiment.

source
NMRAnalysis.Exchange1D.load_experiment — Method
load_experiment(filename) -> AbstractExperiment

Load an NMR experiment file and return the appropriate concrete experiment type based on its annotations.

Dispatches on annotations(spec, :experiment_type) and annotations(spec, :features):

  • "relaxation" + "R1" → R1Experiment
  • "cest" → CESTExperiment
  • "r1rho" + "on_resonance"/"off_resonance" → R1rhoOnResExperiment/R1rhoOffResExperiment
source
NMRAnalysis.Exchange1D.measured! — Method
measured!(ax, x, y; color, label=nothing, strokecolor=nothing)

Plot measurements carrying uncertainties: an error bar and a marker per point. Makie draws neither from a Measurement on its own.

source
NMRAnalysis.Exchange1D.modelconcentrations — Method
modelconcentrations(model::InducedFitModel, expt) -> (A0, X0)

Look up total concentrations of the observed species and binding partner from the experiment sample concentrations dict, using the model's moleculemap to translate role symbols (:A, :X) to molecule names. Falls back to model.concentrations (populated interactively) when sample metadata is unavailable.

source
NMRAnalysis.Exchange1D.modelconcentrations — Method
modelconcentrations(model::ThreeStateBindingModel, expt) -> (A0, X0)

Look up total concentrations of the observed species and binding partner from the experiment sample concentrations dict, using the model's moleculemap to translate role symbols (:A, :X) to molecule names. Falls back to model.concentrations (populated interactively) when sample metadata is unavailable.

source
NMRAnalysis.Exchange1D.modelconcentrations — Method
modelconcentrations(model::TwoStateBindingModel, expt) -> (A0, X0)

Look up total concentrations of the observed species and binding partner from the experiment sample concentrations dict, using the model's moleculemap to translate role symbols (:A, :X) to molecule names. Falls back to model.concentrations (populated interactively) when sample metadata is unavailable.

source
NMRAnalysis.Exchange1D.modelorder — Method
modelorder(model::AbstractModel) -> Int

Sort key controlling where model appears in the model selection menu (lowest first). Defaults to placing a model without an explicit ordering after every model that has one, so a custom model added by a package extension (see docs/src/advanced/extending_exchange1d.md) still appears automatically without needing to touch this function — just define modelorder too if a particular position matters.

source
NMRAnalysis.Exchange1D.moleculeconcentration — Method
moleculeconcentration(model, expt::AbstractExperiment, role::Symbol) -> Float64

Look up the concentration of the molecule assigned to role (e.g. :A, :X) for expt: first from the experiment's own sample metadata, falling back to model.concentrations (populated interactively when metadata is missing).

Raises an informative ArgumentError naming the molecule, its role, and the experiment when neither source has a value — e.g. because only some of the loaded experiments were matched to a sample containing this molecule — rather than letting a bare KeyError propagate from deep inside the fitting or plotting code.

source
NMRAnalysis.Exchange1D.nprofiledparams — Method
nprofiledparams(expt::AbstractExperiment) -> Int

Number of parameters residuals(expt) fits implicitly by analytic profiling (variable projection) rather than exposing them in the ComponentArray fit optimises. Zero by default. R1ρ experiments override this to one I₀ per condition (see residuals above): each is a genuine fitted parameter, so fit must count it towards dof and the parameter covariance even though the optimiser never sees it directly.

source
NMRAnalysis.Exchange1D.observable — Method
observable(expt) -> (name, unit)

What expt.observed_intensities actually holds. CEST and R1 experiments observe an integrated intensity, normalised to its own maximum and so dimensionless. Both R1ρ experiments observe a rate, already fitted from the decay over the spin-lock delays, so calling its column I would mislabel it.

source
NMRAnalysis.Exchange1D.overlaycest — Method
overlaycest(experiments, varyby::Symbol, params_value) -> Plot

Overlay CEST experiments that share every acquisition parameter except varyby (:saturation_time or :ν1) on shared axes, sorted by varyby, one colour per curve. Data, fit, and residuals for a given experiment share a colour; residual bands are shaded grey rather than green, as in overlayr1rhooffres. The fitted chemical shift(s) (params_value.spin.delta) are marked with dashed vertical lines, as on the individual per-experiment plot; observed/predicted intensities are normalised by the fitted I0 (see CESTExperiment's plotresult!) so the baseline sits at 1.0.

The legend is drawn inset (:bottomright, where the CEST dip is not) rather than outside the axes, for the same alignment reason as overlayr1rhooffres.

Draw order (bottom to top): resonance-position vlines, then residual bands and zero-line, then data (markers + error bars) for every experiment, then every fit line in a second pass. See _OVERLAY_MARKER_COLORS.

source
NMRAnalysis.Exchange1D.overlayplots — Method
overlayplots(result::FitResult) -> Vector

Additional plots overlaying similar experiments on shared axes, so that trends across a series are easy to compare directly (issue #39):

  • all off-resonance R1ρ experiments together, one curve per spin-lock power
  • CEST experiments sharing a saturation power (ν1), one curve per saturation time
  • CEST experiments sharing a saturation time, one curve per saturation power

These are kept separate from combineplots(result) — the per-experiment grid — rather than folded into it, so that grid doesn't grow denser (and illegible) as overlays are added; save or display them as their own figure(s) instead. Returns an empty vector if there is nothing to usefully overlay (fewer than two experiments in a group).

source
NMRAnalysis.Exchange1D.overlayr1rhooffres — Method
overlayr1rhooffres(experiments, params_value) -> Plot

Overlay all off-resonance R1ρ experiments on shared axes, one colour per spin-lock power, sorted by offset. Data, fit, and residuals for a given experiment share a colour; residual bands are shaded grey (rather than the green used for a single experiment's plot) so they read clearly with multiple overlapping colours. The fitted chemical shift(s) (params_value.spin.delta) are marked with dashed vertical lines, as on the individual per-experiment plot.

The legend is drawn inset (:topright), not outside the axes: an outer legend only widens the data panel, not the residual panel below it, which throws off their alignment under the shared link=:x.

Draw order (bottom to top): resonance-position vlines, then residual bands and zero-line, then data (markers + error bars) for every experiment, then every fit line in a second pass — so the fit lines render on top of every experiment's data rather than only their own, and the data markers render above the residual bands. See _OVERLAY_MARKER_COLORS.

source
NMRAnalysis.Exchange1D.parameterunit — Method
parameterunit(item) -> String

ASCII unit for a fitted parameter, matched on its ComponentArray key with any log prefix, section and state index already stripped by _paramkey.

Deliberately conservative: a key this does not recognise gets no unit rather than a guessed one. Kd is the notable blank - it comes out in whatever units the sample metadata gives concentrations in, which this module never learns, so summary.txt says so instead.

source
NMRAnalysis.Exchange1D.plotresult! — Method
plotresult!(gl, expt::R1Experiment, fitresult; axiskw=(;))

Draw an R1 experiment into gl: intensities against delay with the fitted curve above, weighted residuals below.

source
NMRAnalysis.Exchange1D.populations — Method
populations(model::InducedFitModel, params, expt) -> [pA, pB, pC]

Equilibrium populations of free (A), open-complex (B) and closed-complex (C) species, normalised to their sum (rather than to A0 directly) to stay exact under floating-point roundoff.

source
NMRAnalysis.Exchange1D.populations — Method
populations(model::ThreeStateBindingModel, params, expt)

Calculate equilibrium populations for three-state parallel binding:

A + L ⇌ B  (Kd1, koff1)
A + L ⇌ C  (Kd2, koff2)

Free ligand concentration is solved analytically from the quadratic binding equation for two parallel binding sites (n1=n2=1).

source
NMRAnalysis.Exchange1D.populations — Method
populations(::ThreeStateModel, params, expt) -> [pA, pB, pC]

Populations are derived from the free energy differences dGB = G_B - G_A and dGC = G_C - G_A (in units of RT), relative to reference state A. This keeps all three populations in (0, 1) summing to 1 for any real dGB, dGC. This internal dG representation is just a fitting convenience — the user only ever sees/enters pB/pC themselves (see _isdgparam in interface.jl).

source
NMRAnalysis.Exchange1D.populations — Method
populations(::TwoStateModel, params, expt) -> [pA, pB]

Population of state B is derived from the free energy difference dGB = G_B - G_A (in units of RT): pB = exp(-dGB)/(1+exp(-dGB)), rearranged as 1/(1+exp(dGB)) to avoid overflow for very negative dGB. This internal dGB representation is just a fitting convenience (it keeps pB in (0, 1) for any real value, unlike a raw population fraction) — the user only ever sees/enters pB itself (see _isdgparam in interface.jl).

source
NMRAnalysis.Exchange1D.prepare_outputfolder — Method
prepare_outputfolder(outputfolder)

Make outputfolder ready to be written into, moving any existing one aside to <name>_previous rather than deleting it. Starting from an empty folder is what keeps stale plots and data from a previous fit out of the current results; keeping the old one is what stops a mistyped folder name destroying its contents.

source
NMRAnalysis.Exchange1D.ratewres — Method
ratewres(expt::Union{R1rhoOnResExperiment,R1rhoOffResExperiment})

Weighted residual of the rate independently fitted per condition (observed_intensities) against the Bloch-McConnell prediction (predicted_intensities) — what the R1ρ result plots actually draw. Distinct from residuals(expt), which compares raw intensities directly and is what the joint fit minimises.

source
NMRAnalysis.Exchange1D.reportcalibration — Method
reportcalibration(prob)

Print the B₁ calibration each experiment will be simulated with, so an assumed 5% inhomogeneity is visible rather than silent. The keyword is easy to miss, so the analysis says what it is using either way.

source
NMRAnalysis.Exchange1D.residualbands! — Method
residualbands!(ax; color=:limegreen)

Shade ±1σ and ±2σ on a residual panel and mark zero. Overlay plots pass color=:grey, several curves of different colours reading badly against green.

source
NMRAnalysis.Exchange1D.residuals — Method
residuals(expt::AbstractExperiment)

Return weighted residuals (observed - predicted) / uncertainty for an experiment. Default implementation using the observed_intensities and predicted_intensities fields.

source
NMRAnalysis.Exchange1D.residuals — Method
residuals(prob::ExchangeProblem, params::ComponentArray)

Simulate all experiments then return concatenated weighted residuals. This is the objective the joint fit minimises, so a per-experiment residuals override (e.g. R1ρ's, above) changes what the fit itself compares, not just what is reported afterwards.

source
NMRAnalysis.Exchange1D.residuals — Method
residuals(expt::Union{R1rhoOnResExperiment,R1rhoOffResExperiment})

Weighted residuals comparing the raw spin-lock decay intensities directly to the Bloch-McConnell model, rather than to the rate independently fitted per condition into observed_intensities (which exists only for display — see plotresult! and ratewres).

Each condition (one νSL on resonance, one offset off resonance) has its own equilibrium intensity I₀. This is a linear parameter, separable from the nonlinear relaxation rate that simulate! has just written into predicted_intensities, so rather than adding one I₀ per condition to the fit, each is eliminated analytically by variable projection (Golub & Pereyra, 1973, SIAM J. Numer. Anal. 10, 413–432): for a fixed rate R the weighted least-squares I₀ minimising Σ (I_obs - I₀ exp(-R t))² has the closed form used below, leaving only the exchange and relaxation parameters for the nonlinear optimiser.

source
NMRAnalysis.Exchange1D.resolvecalibration — Method
resolvecalibration(given) -> (calibration, save)

Resolve the calibration argument an entry point was given, once per problem.

A ready-made B1Calibration is passed through and nothing stays nothing; anything else is taken to be the nutation calibration experiment(s) to fit, which are fitted here so that several experiments share one calibration rather than each refitting it. The second return value writes that fit's own analysis into a folder when there is one to write it into (see calibrationanalysis), and is nothing where nothing was fitted.

source
NMRAnalysis.Exchange1D.resultpanels! — Method
resultpanels!(gl; xlabel, ylabel, title="", xreversed=false, axiskw=(;))

The two panels of a result plot: data above, weighted residuals below at a third the height, sharing an x-axis. Returns both axes.

source
NMRAnalysis.Exchange1D.resultstable — Method
resultstable(prob) -> (header, rows)

Column names and rows for results.csv: one row per experiment, giving its label, its type and field, how many points it contributed, and the settings constant across it. Keys and no fitted parameters, every fitted parameter being shared and so global - see the note at the top of this file.

source
NMRAnalysis.Exchange1D.sectionheader — Method

Bold, cyan section heading for a stage of the interactive workflow — consistent with the styling FitResult's show method already uses for its own section titles.

source
NMRAnalysis.Exchange1D.seriescoordinates — Method
seriescoordinates(expt) -> Vector{Pair{Symbol,Any}}

The coordinates of each of expt's data points, as name => values. A vector value gives one entry per data point; a scalar is a setting constant across the experiment but varying between experiments in the same problem (a saturation field, a spin-lock power), and is repeated down the rows.

Coordinates are named per quantity rather than reduced to one x column, because a joint fit mixes experiment types: a problem containing CEST and R1 experiments has both an offset (ppm) and a delay (s) column, and each row fills in the ones that apply to it.

source
NMRAnalysis.Exchange1D.seriestable — Method
seriestable(prob) -> (header, rows)

Column names and rows for series.csv: the measurements, one row per experiment per data point, with the observed value, its uncertainty and the value the fitted model predicts there.

Experiments observing different quantities (an intensity, a relaxation rate) get a column pair each, so no row is ever labelled with a quantity it does not hold.

source
NMRAnalysis.Exchange1D.simulate! — Method
simulate!(expt::CESTExperiment, model, params)

Simulate the CEST saturation profile, then scale it by the fitted I0 intensity (params.nuisance.CEST_<field>_I0) to match the observed, integration-normalised intensities.

The profile is averaged over the B₁ distribution of the saturation field. Mz is what is measured, so the weighted mean of the profiles from separate parts of the sample is exactly what the spectrum reports (unlike a rate, which reaches the data through a fit - see b1rate). With no inhomogeneity the distribution is the single nominal field and this reduces to one Liouvillian per offset.

source
NMRAnalysis.Exchange1D.simulate! — Method
simulate!(expt::R1Experiment, model::AbstractModel, params::ComponentArray)

Simulate predicted intensities for an R1 experiment. The model argument is accepted for interface consistency but is not used (R1 decay is independent of chemical exchange).

Reads R1 from params.spin.R1_<field> and nuisance parameters (e.g. params.nuisance.R1_14p1T_I0) directly.

source
NMRAnalysis.Exchange1D.simulate! — Method
simulate!(expt::R1rhoOffResExperiment, model, params)

Simulate R₁ρ at each spin-lock offset, averaged over the B₁ distribution the same way as the on-resonance experiment: over the decays rather than the rates (see b1rate), matched at the mean of the sampled spin-lock durations, and for the same reason - residuals compares the raw decay against a single exponential of this rate.

source
NMRAnalysis.Exchange1D.simulate! — Method
simulate!(expt::R1rhoOnResExperiment, model, params)

Simulate R₁ρ at each spin-lock strength, averaged over the B₁ distribution.

A spread of B₁ gives a spread of rates, so the decay is a sum of exponentials, and what residuals compares the data against is a single exponential of this rate with I₀ profiled out. The rate handed to it is therefore the apparent one a monoexponential fit would report, obtained by averaging the decays and converting back (see b1rate) rather than by averaging the rates. It is matched at the mean of the sampled spin-lock durations, which makes it a first-order match: now that the residual sees the raw decay, building the sum of exponentials there instead would be exact.

source
NMRAnalysis.Exchange1D.strongcorrelations — Method

Off-diagonal (i, j, r) triples of cor with |r| ≥ threshold and i < j, indexed into cor's own row/column order (the fitted, non-fixed parameters — see FitResult.freeidx), not the flat parameter indices used elsewhere in FitResult.

source
NMRAnalysis.Exchange1D.warncorrelations — Method
warncorrelations(cor, freeitems, state_labels, fields)

Emit an @warn for each pair of fitted parameters correlated at or above strongcorrelations's threshold: this strong a correlation usually means the two parameters trade off against each other rather than being separately identifiable from this data, so their individually-reported uncertainties understate that. freeitems are the _ParamItems of the fitted (non-fixed) parameters in cor's row/column order. _pretty_label is defined in interface.jl, included after this file — see _ratelowerbound above for why that's fine.

source
NMRAnalysis.Exchange1D.writeexperimentsummary — Method
writeexperimentsummary(io::IO, prob::ExchangeProblem)

Write a plain-text summary of the experiments underlying prob: the model, its molecule-to-sample mapping (if any), and — for each experiment — its source filename, key acquisition parameters (via experimentinfo), and sample concentrations. Saved alongside fit results by _save_results so an analysis can always be traced back to its source data (issue #37).

source
NMRAnalysis.Exchange1D.writeresults! — Method
writeresults!(result, folder) -> String

Write results.csv, series.csv, global.csv, covariance.csv, correlation.csv and one experiments/<label>.csv per experiment into folder, and return the path of results.csv. The per-experiment files hold that experiment's own rows of series.csv, so the data behind each plot sits beside it under the same basename.

source
NMRAnalysis.Exchange1D.writesummary — Method
writesummary(filepath, result) -> String

Write summary.txt: the parameter tables and fit statistics as the REPL shows them, then the experiment-by-experiment provenance, then the call that repeats the analysis. Replaces the former pair of exchange1d_params.txt and exchange1d_experiments.txt, which split one record across two files.

source