Negative-muon analysis API (experimental)
Warning
Experimental — work in progress. The asymmetry.core.negmu
package is unvalidated against real μ⁻ data; see
Negative-muon capture-lifetime analysis (experimental) for the full disclaimer,
physics background, and worked example.
The asymmetry.core.negmu package provides a scriptable API for
μ⁻ capture-lifetime elemental analysis on raw single-histogram counts.
It is not registered in the GUI fit builders and has no plot-mode or
panel exposure.
Element lifetimes
EXPERIMENTAL — WORK IN PROGRESS. Negative-muon (μ⁻) capture-lifetime analysis.
This API is UNVALIDATED against real μ⁻ elemental-analysis data. No μ⁻ corpus exists in this project; every result here has been exercised only against synthetic histograms. The element lifetime values are literature-anchored (Suzuki, Measday & Roalsvig, Phys. Rev. C 35, 2212 (1987), via Blundell et al., Muon Spectroscopy: An Introduction, OUP 2022, Table C.1), but the fitting, capture-ratio, and background machinery have NOT been checked against an established tool (WiMDA, Mantid) on measured data. The API, parameter names, and return shapes MAY CHANGE without notice. Do not rely on results for publication without independent verification. This feature is deliberately NOT exposed in the GUI fit builders. Promotion trigger for a GUI: real ISIS μ⁻ data AND a user.
Negative-muon capture lifetimes: an element-keyed table of muonic-atom total disappearance lifetimes τ(Z) = 1/(Λ_capture + Λ_bound-decay), the seeds for the multi-exponential capture fit.
Source: Blundell, De Renzi, Lancaster & Pratt, Muon Spectroscopy: An Introduction (OUP, 2022), Appendix C, Table C.1 — values combined from T. Suzuki, D. F. Measday & J. P. Roalsvig, Phys. Rev. C 35, 2212 (1987). A few entries marked WiMDA-provisional are from WiMDA’s older table where Table C.1 has no value. WiMDA’s table is NOT trusted for values (see the porting study, comparison.md §5).
Provenance note: WiMDA’s mystrings table (NegMuAnalyse.pas:104–120) was
the workflow reference, not the value source. It has a 69-vs-67 length
mismatch, the 'Ti'``→’Tl’`` symbol bug, and several value divergences
(Ne 1.520→1.461, Zn 0.169→0.161, Sr 0.142→0.132, Ba 0.072→0.0949).
Adopt Table C.1; cite Suzuki/Measday/Roalsvig 1987.
- class asymmetry.core.negmu.lifetimes.ElementLifetime(symbol: str, z: int, name: str, tau_us: float, sigma_us: float | None, source: str)[source]
Literature capture lifetime for one element.
- asymmetry.core.negmu.lifetimes.DECAY_BACKGROUND_LABEL: str = 'decayBG'
Reserved label for the free-μ⁻ decay-background component (τ = MUON_LIFETIME_US).
- asymmetry.core.negmu.lifetimes.ELEMENT_LIFETIMES: dict[str, ElementLifetime] = {'Ag': ElementLifetime(symbol='Ag', z=47, name='Silver', tau_us=0.0906, sigma_us=0.0007, source='SuzukiMeasdayRoalsvig1987'), 'Al': ElementLifetime(symbol='Al', z=13, name='Aluminium', tau_us=0.864, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Ar': ElementLifetime(symbol='Ar', z=18, name='Argon', tau_us=0.537, sigma_us=0.032, source='SuzukiMeasdayRoalsvig1987'), 'As': ElementLifetime(symbol='As', z=33, name='Arsenic', tau_us=0.153, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Au': ElementLifetime(symbol='Au', z=79, name='Gold', tau_us=0.0728, sigma_us=0.0005, source='SuzukiMeasdayRoalsvig1987'), 'B': ElementLifetime(symbol='B', z=5, name='Boron', tau_us=2.097, sigma_us=0.003, source='SuzukiMeasdayRoalsvig1987'), 'Ba': ElementLifetime(symbol='Ba', z=56, name='Barium', tau_us=0.0949, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'Be': ElementLifetime(symbol='Be', z=4, name='Beryllium', tau_us=2.16747, sigma_us=0.00089, source='SuzukiMeasdayRoalsvig1987'), 'Bi': ElementLifetime(symbol='Bi', z=83, name='Bismuth', tau_us=0.0735, sigma_us=0.0004, source='SuzukiMeasdayRoalsvig1987'), 'Br': ElementLifetime(symbol='Br', z=35, name='Bromine', tau_us=0.133, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'C': ElementLifetime(symbol='C', z=6, name='Carbon', tau_us=2.03, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Ca': ElementLifetime(symbol='Ca', z=20, name='Calcium', tau_us=0.336, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Cd': ElementLifetime(symbol='Cd', z=48, name='Cadmium', tau_us=0.0906, sigma_us=0.0007, source='SuzukiMeasdayRoalsvig1987'), 'Ce': ElementLifetime(symbol='Ce', z=58, name='Cerium', tau_us=0.084, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'Cl': ElementLifetime(symbol='Cl', z=17, name='Chlorine', tau_us=0.561, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Co': ElementLifetime(symbol='Co', z=27, name='Cobalt', tau_us=0.186, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Cr': ElementLifetime(symbol='Cr', z=24, name='Chromium', tau_us=0.259, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Cs': ElementLifetime(symbol='Cs', z=55, name='Cesium', tau_us=0.088, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Cu': ElementLifetime(symbol='Cu', z=29, name='Copper', tau_us=0.164, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Er': ElementLifetime(symbol='Er', z=68, name='Erbium', tau_us=0.0749, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'F': ElementLifetime(symbol='F', z=9, name='Fluorine', tau_us=1.461, sigma_us=0.005, source='SuzukiMeasdayRoalsvig1987'), 'Fe': ElementLifetime(symbol='Fe', z=26, name='Iron', tau_us=0.206, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Ga': ElementLifetime(symbol='Ga', z=31, name='Gallium', tau_us=0.163, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Gd': ElementLifetime(symbol='Gd', z=64, name='Gadolinium', tau_us=0.0806, sigma_us=0.0008, source='SuzukiMeasdayRoalsvig1987'), 'Ge': ElementLifetime(symbol='Ge', z=32, name='Germanium', tau_us=0.167, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'H': ElementLifetime(symbol='H', z=1, name='Hydrogen', tau_us=2.1948, sigma_us=6e-05, source='SuzukiMeasdayRoalsvig1987'), 'He': ElementLifetime(symbol='He', z=2, name='Helium', tau_us=2.188, sigma_us=None, source='WiMDA-provisional'), 'Hf': ElementLifetime(symbol='Hf', z=72, name='Hafnium', tau_us=0.075, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Hg': ElementLifetime(symbol='Hg', z=80, name='Mercury', tau_us=0.076, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Ho': ElementLifetime(symbol='Ho', z=67, name='Holmium', tau_us=0.079, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'I': ElementLifetime(symbol='I', z=53, name='Iodine', tau_us=0.0856, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'Ir': ElementLifetime(symbol='Ir', z=77, name='Iridium', tau_us=0.074, sigma_us=None, source='WiMDA-provisional'), 'K': ElementLifetime(symbol='K', z=19, name='Potassium', tau_us=0.435, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Kr': ElementLifetime(symbol='Kr', z=36, name='Krypton', tau_us=0.136, sigma_us=None, source='WiMDA-provisional'), 'La': ElementLifetime(symbol='La', z=57, name='Lanthanum', tau_us=0.0899, sigma_us=0.0007, source='SuzukiMeasdayRoalsvig1987'), 'Li': ElementLifetime(symbol='Li', z=3, name='Lithium', tau_us=2.1869, sigma_us=0.0004, source='SuzukiMeasdayRoalsvig1987'), 'Mg': ElementLifetime(symbol='Mg', z=12, name='Magnesium', tau_us=1.069, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Mn': ElementLifetime(symbol='Mn', z=25, name='Manganese', tau_us=0.231, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Mo': ElementLifetime(symbol='Mo', z=42, name='Molybdenum', tau_us=0.104, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'N': ElementLifetime(symbol='N', z=7, name='Nitrogen', tau_us=1.92, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Na': ElementLifetime(symbol='Na', z=11, name='Sodium', tau_us=1.204, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Nb': ElementLifetime(symbol='Nb', z=41, name='Niobium', tau_us=0.092, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Nd': ElementLifetime(symbol='Nd', z=60, name='Neodymium', tau_us=0.0784, sigma_us=0.0007, source='SuzukiMeasdayRoalsvig1987'), 'Ne': ElementLifetime(symbol='Ne', z=10, name='Neon', tau_us=1.461, sigma_us=0.009, source='SuzukiMeasdayRoalsvig1987'), 'Ni': ElementLifetime(symbol='Ni', z=28, name='Nickel', tau_us=0.157, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Np': ElementLifetime(symbol='Np', z=93, name='Neptunium', tau_us=0.072, sigma_us=0.0007, source='SuzukiMeasdayRoalsvig1987'), 'O': ElementLifetime(symbol='O', z=8, name='Oxygen', tau_us=1.795, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Os': ElementLifetime(symbol='Os', z=76, name='Osmium', tau_us=0.078, sigma_us=None, source='WiMDA-provisional'), 'P': ElementLifetime(symbol='P', z=15, name='Phosphorus', tau_us=0.616, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Pb': ElementLifetime(symbol='Pb', z=82, name='Lead', tau_us=0.0747, sigma_us=0.0004, source='SuzukiMeasdayRoalsvig1987'), 'Pd': ElementLifetime(symbol='Pd', z=46, name='Palladium', tau_us=0.0885, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'Pr': ElementLifetime(symbol='Pr', z=59, name='Praseodymium', tau_us=0.0721, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'Pt': ElementLifetime(symbol='Pt', z=78, name='Platinum', tau_us=0.074, sigma_us=None, source='WiMDA-provisional'), 'Rb': ElementLifetime(symbol='Rb', z=37, name='Rubidium', tau_us=0.137, sigma_us=0.003, source='SuzukiMeasdayRoalsvig1987'), 'Re': ElementLifetime(symbol='Re', z=75, name='Rhenium', tau_us=0.076, sigma_us=None, source='WiMDA-provisional'), 'Rh': ElementLifetime(symbol='Rh', z=45, name='Rhodium', tau_us=0.096, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'Ru': ElementLifetime(symbol='Ru', z=44, name='Ruthenium', tau_us=0.0958, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'S': ElementLifetime(symbol='S', z=16, name='Sulfur', tau_us=0.555, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Sb': ElementLifetime(symbol='Sb', z=51, name='Antimony', tau_us=0.0924, sigma_us=0.0009, source='SuzukiMeasdayRoalsvig1987'), 'Sc': ElementLifetime(symbol='Sc', z=21, name='Scandium', tau_us=0.317, sigma_us=0.003, source='SuzukiMeasdayRoalsvig1987'), 'Se': ElementLifetime(symbol='Se', z=34, name='Selenium', tau_us=0.163, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Si': ElementLifetime(symbol='Si', z=14, name='Silicon', tau_us=0.759, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Sm': ElementLifetime(symbol='Sm', z=62, name='Samarium', tau_us=0.079, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Sn': ElementLifetime(symbol='Sn', z=50, name='Tin', tau_us=0.0907, sigma_us=0.0008, source='SuzukiMeasdayRoalsvig1987'), 'Sr': ElementLifetime(symbol='Sr', z=38, name='Strontium', tau_us=0.132, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'Ta': ElementLifetime(symbol='Ta', z=73, name='Tantalum', tau_us=0.0755, sigma_us=0.0006, source='SuzukiMeasdayRoalsvig1987'), 'Tb': ElementLifetime(symbol='Tb', z=65, name='Terbium', tau_us=0.0762, sigma_us=0.0007, source='SuzukiMeasdayRoalsvig1987'), 'Tc': ElementLifetime(symbol='Tc', z=43, name='Technetium', tau_us=0.095, sigma_us=None, source='WiMDA-provisional'), 'Te': ElementLifetime(symbol='Te', z=52, name='Tellurium', tau_us=0.104, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Th': ElementLifetime(symbol='Th', z=90, name='Thorium', tau_us=0.078, sigma_us=0.0003, source='SuzukiMeasdayRoalsvig1987'), 'Ti': ElementLifetime(symbol='Ti', z=22, name='Titanium', tau_us=0.329, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Tl': ElementLifetime(symbol='Tl', z=81, name='Thallium', tau_us=0.0704, sigma_us=0.0008, source='SuzukiMeasdayRoalsvig1987'), 'Tm': ElementLifetime(symbol='Tm', z=69, name='Thulium', tau_us=0.074, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'U': ElementLifetime(symbol='U', z=92, name='Uranium', tau_us=0.0775, sigma_us=0.0002, source='SuzukiMeasdayRoalsvig1987'), 'V': ElementLifetime(symbol='V', z=23, name='Vanadium', tau_us=0.28, sigma_us=0.002, source='SuzukiMeasdayRoalsvig1987'), 'W': ElementLifetime(symbol='W', z=74, name='Tungsten', tau_us=0.0765, sigma_us=0.0008, source='SuzukiMeasdayRoalsvig1987'), 'Y': ElementLifetime(symbol='Y', z=39, name='Yttrium', tau_us=0.12, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Zn': ElementLifetime(symbol='Zn', z=30, name='Zinc', tau_us=0.161, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987'), 'Zr': ElementLifetime(symbol='Zr', z=40, name='Zirconium', tau_us=0.11, sigma_us=0.001, source='SuzukiMeasdayRoalsvig1987')}
Element symbol → ElementLifetime. Transcribed from plan.md §2. ⚠confirm rows (period-5 transition cluster and lanthanides/actinides) are included at the plan’s stated values; they have not been independently verified against the textbook table layout and are NOT test-spot-check anchors.
- asymmetry.core.negmu.lifetimes.lifetime(symbol: str) ElementLifetime[source]
Return the table entry for
symbol(raisesKeyErrorif absent).
- asymmetry.core.negmu.lifetimes.tau_us(symbol: str) float[source]
Return the capture lifetime (µs) for
symbol.
Multi-exponential count model
EXPERIMENTAL — WORK IN PROGRESS. Negative-muon (μ⁻) capture-lifetime analysis.
This API is UNVALIDATED against real μ⁻ elemental-analysis data. No μ⁻ corpus exists in this project; every result here has been exercised only against synthetic histograms. The element lifetime values are literature-anchored (Suzuki, Measday & Roalsvig, Phys. Rev. C 35, 2212 (1987), via Blundell et al., Muon Spectroscopy: An Introduction, OUP 2022, Table C.1), but the fitting, capture-ratio, and background machinery have NOT been checked against an established tool (WiMDA, Mantid) on measured data. The API, parameter names, and return shapes MAY CHANGE without notice. Do not rely on results for publication without independent verification. This feature is deliberately NOT exposed in the GUI fit builders. Promotion trigger for a GUI: real ISIS μ⁻ data AND a user.
Multi-exponential μ⁻ capture count model: N(t) = Σ_i amp_i·exp(−t/τ_i) + bg. A raw-count model, NOT an asymmetry model — see the study comparison.md §3 for why the single-envelope count model used for μ⁺ cannot express this.
- class asymmetry.core.negmu.model.CaptureComponent(label: str, tau_us: float)[source]
One exponential component in the capture count model.
- asymmetry.core.negmu.model.build_capture_count_model(components: Sequence[CaptureComponent]) Callable[[...], ndarray[tuple[int, ...], dtype[float64]]][source]
Return
f(t, **params)→ raw counts for the fixed component order.Recognised params:
amp_<label>(per component), optionaltau_<label>(overrides the component seed when the lifetime is freed), andbackground(flat). Unknown params are ignored. Vectorised over t (µs).
Fitting
EXPERIMENTAL — WORK IN PROGRESS. Negative-muon (μ⁻) capture-lifetime analysis.
This API is UNVALIDATED against real μ⁻ elemental-analysis data. No μ⁻ corpus exists in this project; every result here has been exercised only against synthetic histograms. The element lifetime values are literature-anchored (Suzuki, Measday & Roalsvig, Phys. Rev. C 35, 2212 (1987), via Blundell et al., Muon Spectroscopy: An Introduction, OUP 2022, Table C.1), but the fitting, capture-ratio, and background machinery have NOT been checked against an established tool (WiMDA, Mantid) on measured data. The API, parameter names, and return shapes MAY CHANGE without notice. Do not rely on results for publication without independent verification. This feature is deliberately NOT exposed in the GUI fit builders. Promotion trigger for a GUI: real ISIS μ⁻ data AND a user.
Fit a μ⁻ capture-lifetime histogram (raw counts) to the multi-exponential model. Reuses the shared minimiser drive (drive_minuit), FitResult, and Parameter/ParameterSet. The Poisson (Cash) and Gaussian (√N) count costs are replicated locally (the fitting engine’s helpers are private API, off-limits). Lifetimes are FIXED at the table value by default; free any via spec.free_tau.
- class asymmetry.core.negmu.fit.CaptureModelSpec(elements: tuple[str, ...], include_decay_background: bool = True, free_tau: frozenset[str] = <factory>)[source]
Which elemental components are in the fit, with τ fixed by default.
- components() tuple[CaptureComponent, ...][source]
Element components (τ from table) + optional decayBG (τ_μ).
- asymmetry.core.negmu.fit.default_capture_parameters(spec: CaptureModelSpec, *, time: ndarray[tuple[int, ...], dtype[float64]], counts: ndarray[tuple[int, ...], dtype[float64]], seeds: Mapping[str, float] | None = None) ParameterSet[source]
Seeded ParameterSet for a capture fit.
amp_<label>(free, ≥0) — derived from the signal window integral using the exact telescoping normalisation so that seeds scale correctly with τ: amp_seed_i = (signal / n_comp) · (1 − exp(−Δt/τ_i)) / (1 − exp(−T/τ_i)).tau_<label>(fixed unless label in spec.free_tau; freed τ bounded to[0.5×, 2×]the table value, widened to include any explicit seed override).background(free, ≥0) — seeded from the late-time floor.seedsoverrides anyamp_/tau_/backgroundvalue before Parameter creation.
- asymmetry.core.negmu.fit.fit_capture_histogram(time: ndarray[tuple[int, ...], dtype[float64]], counts: ndarray[tuple[int, ...], dtype[float64]], spec: CaptureModelSpec, *, variance: ndarray[tuple[int, ...], dtype[float64]] | None = None, cost: str = 'poisson', parameters: ParameterSet | None = None, polarisation: str | None = None, minos: bool = False, cancel_callback: Callable[[], bool] | None = None) FitResult[source]
Fit raw counts to Σ_i amp_i·exp(−t/τ_i)+bg, with optional polarisation.
The array-level entry: testable without a dataset.
variance(if given) is used by the Gaussian cost; Poisson (Cash) ignores it. Returns aFitResultwithdofset soassess_fit_quality()applies.- Parameters:
polarisation –
None(no polarisation; result identical to the base model),"lorgau"(Lorentzian-damped oscillation), or"diamagnetic"(undamped precession). When notNone, the exponential sum is multiplied by(1 + P_pol(t))and extra parameterspol_a0,pol_freq,pol_phase(andpol_lamfor “lorgau”) are added to the fit. Pass them viaparametersor rely on the conservative default seeds (pol_freq = 0.5 MHz).
- asymmetry.core.negmu.fit.fit_capture_group(dataset, group_id: int, spec: CaptureModelSpec, *, t_min: float | None = None, t_max: float | None = None, cost: str = 'poisson', parameters: ParameterSet | None = None, polarisation: str | None = None, exclude: tuple[float, float] | None = None, minos: bool = False, cancel_callback: Callable[[], bool] | None = None) FitResult[source]
Build the raw (time, counts) trace for
group_idand fit.Uses
build_count_group()withlifetime_corrected=Falseto obtain raw counts on which the Poisson cost is exact, then delegates tofit_capture_histogram().parametersandpolarisationare forwarded verbatim.
- asymmetry.core.negmu.fit.fit_capture_fb_alpha(dataset, forward_group: int, backward_group: int, spec: CaptureModelSpec, *, cost: str = 'poisson', t_min: float | None = None, t_max: float | None = None, exclude: tuple[float, float] | None = None, alpha_seed: float = 1.0, minos: bool = False, cancel_callback: Callable[[], bool] | None = None) GroupedTimeDomainFitResult[source]
Simultaneously fit forward and backward capture histograms with shared per-element amplitudes amp_<label>, shared τ_i, and a free detector balance α:
N_F(t) = √α · Σ_i amp_i·exp(−t/τ_i) + bg_F N_B(t) = (1/√α) · Σ_i amp_i·exp(−t/τ_i) + bg_B
mirroring
build_count_groupsgeometry and the √α detector-balance split. Both banks built in ONE context (build_count_groups) for a common t0. Returns aGroupedTimeDomainFitResult:group_resultskeyed byforward_group/backward_group;shared_parametersholds α, amplitudes, and τ.DIVERGENCE from WiMDA: WiMDA fits independent per-side amplitudes (NF, NB); this shares
amp_i(isotropic capture populations), so per-side capture ratios are identical by construction. Usefit_capture_group()on each side independently when a genuine F/B amplitude difference is wanted.
Capture-ratio report
EXPERIMENTAL — WORK IN PROGRESS. Negative-muon (μ⁻) capture-lifetime analysis.
This API is UNVALIDATED against real μ⁻ elemental-analysis data. No μ⁻ corpus exists in this project; every result here has been exercised only against synthetic histograms. The element lifetime values are literature-anchored (Suzuki, Measday & Roalsvig, Phys. Rev. C 35, 2212 (1987), via Blundell et al., Muon Spectroscopy: An Introduction, OUP 2022, Table C.1), but the fitting, capture-ratio, and background machinery have NOT been checked against an established tool (WiMDA, Mantid) on measured data. The API, parameter names, and return shapes MAY CHANGE without notice. Do not rely on results for publication without independent verification. This feature is deliberately NOT exposed in the GUI fit builders. Promotion trigger for a GUI: real ISIS μ⁻ data AND a user.
Capture-ratio report: relative capture probabilities from fitted amplitude ratios amp_i/amp_ref, with covariance-aware uncertainties. Adapts WiMDA’s RatioButtonClick (NegMuAnalyse.pas:455–620) as a derived-quantities function — no new results framework.
- class asymmetry.core.negmu.ratio.CaptureRatio(numerator: str, denominator: str, ratio: float, sigma: float)[source]
One amplitude ratio and its uncertainty.
- class asymmetry.core.negmu.ratio.CaptureRatioReport(side: str, reference: str, ratios: tuple[CaptureRatio, ...], amplitudes: dict[str, float], amplitude_uncertainties: dict[str, float])[source]
Derived capture-probability ratios from a single fit result.
- ratios: tuple[CaptureRatio, ...]
- asymmetry.core.negmu.ratio.capture_ratio_report(fit: FitResult, spec: CaptureModelSpec, *, reference: str, side: str = 'forward') CaptureRatioReport[source]
Compute per-element capture-probability ratios amp_label/amp_reference.
For each element label ≠ reference (and ≠ decayBG, excluded by default):
R = amp_label / amp_reference σ_R = R · sqrt((σ_i/amp_i)² + (σ_ref/amp_ref)² − 2·cov(i,ref)/(amp_i·amp_ref))
where the covariance term is taken from
fit.covariance/fit.covariance_parameterswhen both parameters are present in the covariance matrix, and falls back to 0 (pure quadrature) otherwise.
- asymmetry.core.negmu.ratio.fb_capture_ratio_report(grouped: GroupedTimeDomainFitResult, spec: CaptureModelSpec, forward_group: int, backward_group: int, *, reference: str) dict[str, CaptureRatioReport][source]
Per-side capture-ratio reports from a
fit_capture_fb_alpha()result.Returns
{'forward': CaptureRatioReport, 'backward': CaptureRatioReport}. Since amplitudes are shared in the F+B fit, the two reports will have identical ratios by construction (they differ only in theirsidelabel).
Background subtraction
EXPERIMENTAL — WORK IN PROGRESS. Negative-muon (μ⁻) capture-lifetime analysis.
This API is UNVALIDATED against real μ⁻ elemental-analysis data. No μ⁻ corpus exists in this project; every result here has been exercised only against synthetic histograms. The element lifetime values are literature-anchored (Suzuki, Measday & Roalsvig, Phys. Rev. C 35, 2212 (1987), via Blundell et al., Muon Spectroscopy: An Introduction, OUP 2022, Table C.1), but the fitting, capture-ratio, and background machinery have NOT been checked against an established tool (WiMDA, Mantid) on measured data. The API, parameter names, and return shapes MAY CHANGE without notice. Do not rely on results for publication without independent verification. This feature is deliberately NOT exposed in the GUI fit builders. Promotion trigger for a GUI: real ISIS μ⁻ data AND a user.
Set-as-BG capture-component subtraction.
Adapts WiMDA’s SetBgButtonClick (NegMuAnalyse.pas) as a histogram-level
model subtraction: evaluate the unwanted components from the fitted parameters
and subtract from the counts, leaving the signal of interest. The derived
Run produced by
capture_background_run() can be re-fitted with
fit_capture_group().
- asymmetry.core.negmu.background.subtract_capture_background(time: ndarray[tuple[int, ...], dtype[float64]], counts: ndarray[tuple[int, ...], dtype[float64]], fit: FitResult, spec: CaptureModelSpec, *, unwanted: Sequence[str]) ndarray[tuple[int, ...], dtype[float64]][source]
Return
counts − Σ_{label in unwanted} amp_label·exp(−t/tau_label).The unwanted components are evaluated from the fitted parameters via
evaluate_capture_model(). The result is the residual signal of interest. Passing an emptyunwantedsequence is the identity — the counts are returned unchanged. (WiMDA SetBgButtonClick adapted as a histogram-level model subtraction, comparison.md §7.)Note
The unwanted components are evaluated from the non-polarised model (plain
amp·exp(−t/τ)). If the fit was run with a Phase-4polarisationmultiplier, that(1 + P_pol(t))modulation is not reproduced here, so the subtraction is only exact for the unpolarised capture model.- Parameters:
time – Time axis (µs) — the same array used for the fit.
counts – Raw count histogram on that time axis.
fit –
FitResultfrom a precedingfit_capture_group()/fit_capture_histogram()call.spec –
CaptureModelSpecused for the fit — provides the component ordering and lifetime seeds.unwanted – Labels of the components to subtract. Each must appear in
spec. Duplicates are silently ignored.
- asymmetry.core.negmu.background.capture_background_run(dataset: MuonDataset, group_id: int, fit: FitResult, spec: CaptureModelSpec, *, unwanted: Sequence[str], run_number: int | None = None) Run[source]
Derived
Runwith unwanted components subtracted.Builds the raw grouped histogram for
group_idviabuild_count_group()(lifetime_corrected=False), subtracts the unwanted components, and returns a minimalRunsuitable for re-fitting withfit_capture_group().The derived Run contains a single detector histogram (the grouped, subtracted counts) with
group_idmapped to that detector. This preserves the time-bin structure of the original fit exactly.- Parameters:
dataset – Source dataset with a
Runand grouping (same dataset passed to the original fit).group_id – Group whose histogram is to be cleaned.
fit –
FitResultfrom the preceding fit of this group.spec –
CaptureModelSpecused for that fit.unwanted – Labels of the components to subtract. Empty sequence → identity.
run_number – Run number for the derived Run. Defaults to the source run’s number.
Polarisation multipliers
EXPERIMENTAL — WORK IN PROGRESS. Negative-muon (μ⁻) capture-lifetime analysis.
This API is UNVALIDATED against real μ⁻ elemental-analysis data. No μ⁻ corpus exists in this project; every result here has been exercised only against synthetic histograms. The element lifetime values are literature-anchored (Suzuki, Measday & Roalsvig, Phys. Rev. C 35, 2212 (1987), via Blundell et al., Muon Spectroscopy: An Introduction, OUP 2022, Table C.1), but the fitting, capture-ratio, and background machinery have NOT been checked against an established tool (WiMDA, Mantid) on measured data. The API, parameter names, and return shapes MAY CHANGE without notice. Do not rely on results for publication without independent verification. This feature is deliberately NOT exposed in the GUI fit builders. Promotion trigger for a GUI: real ISIS μ⁻ data AND a user.
μ⁻SR polarisation functions and a polarisation-aware capture count model.
In μ⁻SR a surviving spin polarisation (≥5/6 is lost in the muonic cascade;
Blundell et al. 2022, §22.1) gives rise to an oscillatory modulation of the
muonic-atom count decay. The modulation multiplies the raw exponential sum by
(1 + P_pol(t)), leaving the flat background unchanged.
Two polarisation functions are provided:
lorentzian_gaussian_polarisation(WiMDA “LorGau”) — damped precessiondiamagnetic_polarisation(WiMDA “Diamagnetic”) — undamped precession
Both return P_pol(t) (the modulation depth × envelope × oscillation), not
the full multiplier (1 + P_pol(t)). The composite model builder
build_capture_count_model_with_polarisation wraps the multiplier.
Parameterisation conventions confirmed against WiMDA Analyse.pas
Polfunc() (RadioGroup1.itemindex = 2, lines 1322–1323):
LorGau forward:
(1 + a0·exp(−λ·t)·cos(2π·freq·t + phase))
where freq is in MHz, t in µs, λ in µs⁻¹, and phase in
radians. Diamagnetic is the undamped (λ = 0) limit. WiMDA’s Diamagnetic
RadioGroup mode (itemindex = 3) has no implementation in Polfunc (falls
through with undefined result — a WiMDA bug), so the Asymmetry formula is
derived from the textbook physics rather than the WiMDA source.
- asymmetry.core.negmu.polarisation.POLARISATION_MODES: tuple[str, ...] = ('lorgau', 'diamagnetic')
Recognised polarisation mode names.
- asymmetry.core.negmu.polarisation.lorentzian_gaussian_polarisation(t: ndarray[tuple[int, ...], dtype[float64]], a0: float, lam: float, freq: float, phase: float) ndarray[tuple[int, ...], dtype[float64]][source]
Lorentzian-damped oscillation — WiMDA “LorGau” polarisation function.
Returns
a0·exp(−λ·t)·cos(2π·freq·t + phase)(the modulation part P_pol(t) only; the full multiplier is1 + P_pol(t)).- Parameters:
t – Time array in µs.
a0 – Precession amplitude (dimensionless, typically ≤ 1/3 from muonic cascade geometry).
lam – Lorentzian damping rate λ in µs⁻¹.
freq – Precession frequency in MHz.
phase – Initial phase in radians.
- asymmetry.core.negmu.polarisation.diamagnetic_polarisation(t: ndarray[tuple[int, ...], dtype[float64]], a0: float, freq: float, phase: float) ndarray[tuple[int, ...], dtype[float64]][source]
Undamped diamagnetic precession — WiMDA “Diamagnetic” polarisation.
Returns
a0·cos(2π·freq·t + phase)(P_pol(t) only; full multiplier is1 + P_pol(t)). This is the λ → 0 limit oflorentzian_gaussian_polarisation().- Parameters:
t – Time array in µs.
a0 – Precession amplitude (dimensionless).
freq – Precession frequency in MHz.
phase – Initial phase in radians.
- asymmetry.core.negmu.polarisation.build_capture_count_model_with_polarisation(components: Sequence[CaptureComponent], polarisation: str | None = None) Callable[[...], ndarray[tuple[int, ...], dtype[float64]]][source]
Return
f(t, **params)→ raw counts with optional polarisation multiplier.When
polarisationisNonethe result is bit-identical tobuild_capture_count_model()on the same inputs — verified bynp.array_equal.When
polarisationis"lorgau"or"diamagnetic"the model is:N(t) = [Σ_i amp_i·exp(−t/τ_i)] · (1 + P_pol(t)) + background
where P_pol(t) is the polarisation function evaluated from the following named parameters in
**params:"lorgau":pol_a0,pol_lam,pol_freq,pol_phase"diamagnetic":pol_a0,pol_freq,pol_phase
Missing pol params default to 0. Unknown params are ignored.
- Parameters:
components – Capture components (same as
build_capture_count_model()).polarisation –
None(no polarisation),"lorgau", or"diamagnetic".