mixle.analysis package¶
Applied statistical methods that are not probability-distribution families.
These are estimators / analysis routines (extreme-value, kernel density, species coverage, geostatistics,
rank aggregation, spatial mixtures, max-stable processes, covariance shrinkage, mine economics,
health/exposure monitoring) that operate on data but are not SequenceEncodableProbabilityDistribution
families and are not used by any distribution. They were previously scattered under mixle.stats;
collected here so the distributions package stays focused on distribution families.
This package’s __all__ is the union of every submodule’s own declared __all__ (MXR-080-1593).
The façade previously omitted seven names – including result types returned by exported functions
(TransitionRiskResult, SmithMaxStableFit, VoiEstimate) and argument protocols needed to
call them (WaterBudget, GaussianObservationModel) – so from mixle.analysis import ...
could reach a function but not the type it hands back or the type it demands. A submodule’s
__all__ is that module’s declaration of its intentional stable surface; re-exporting anything less
here makes the package a lossy view of it rather than the entry point it is documented to be.
mixle/tests/api_consistency_test.py asserts the two stay in agreement.
- class GPDFit(shape, scale, threshold, n_exceedances, n_total, method, n_dropped_nonpositive=0)[source]
Bases:
objectFitted Generalized Pareto Distribution for exceedances over a threshold.
- Parameters:
- shape
tail index
xi(> 0heavy/Pareto tail,= 0exponential,< 0bounded).- Type:
- scale
scale
beta(> 0).- Type:
- threshold
the threshold
uthe exceedances were measured over.- Type:
- n_exceedances / n_total
exceedance and full-sample sizes (for return levels).
- method
"mle"or"pwm".- Type:
- n_dropped_nonpositive
count of finite, non-positive (
<= 0) input values excluded because they are not actually above the threshold (a receipt forgpd_fit()’s filtering; NaN and non-finite input are rejected outright rather than silently dropped, so they never contribute to this count).- Type:
- property endpoint: float
Right endpoint of the support (finite when
shape < 0, elseinf).
- gpd_fit(exceedances, *, threshold=0.0, method='mle', n_total=None)[source]
Fit a Generalized Pareto Distribution to threshold exceedances.
- Parameters:
exceedances (ndarray) – the excesses
x - ufor observations above the threshold (all positive). NaN or non-finite entries are invalid data, not sub-threshold observations, and raise. Finite entries<= 0are legitimately not above the threshold, so they are dropped and the count is receipted on the returnedGPDFit(n_dropped_nonpositive) rather than silently vanishing. If you have raw data, usepeaks_over_threshold()instead.threshold (float) – the threshold
u(stored for return-level computation).method (str) –
"mle"(Nelder-Mead on the GPD likelihood) or"pwm"(probability-weighted moments, closed form, robust forxi < 0.5).n_total (int | None) – full sample size before thresholding (defaults to the number of valid, positive exceedances; if given explicitly it must be at least that many).
- Returns:
A
GPDFit.- Raises:
ValueError –
exceedancescontains NaN/non-finite values; fewer than two positive exceedances remain after filtering;n_totalis smaller than the exceedance count;methodis not"mle"/"pwm"; the MLE optimizer fails to converge; or the fitted(shape, scale)is not a valid GPD (see_validate_gpd_params()).- Return type:
GPDFit
- peaks_over_threshold(data, threshold, *, method='mle')[source]
Peaks-over-threshold: select exceedances above
thresholdand fit a GPD to the excesses.
- return_level(fit, period)[source]
POT return level: the level exceeded on average once per
periodobservations.x_m = u + (beta/xi) [ (m zeta_u)^xi - 1 ]withzeta_u = n_exceed/n_totalthe exceedance rate (period = m). Forxi = 0it reduces tou + beta log(m zeta_u).- Raises:
ValueError –
periodis not positive (a non-positive return period is not meaningful –m = 0sends the level to +/-infinity and negativemis undefined for non-integerxi).- Parameters:
fit (GPDFit)
period (float)
- Return type:
- hill_estimator(data, k)[source]
Hill estimator of the tail index
xi = 1/alphafrom the topkorder statistics.xi_hat = (1/k) sum_{i=1}^{k} log(X_(n-i+1) / X_(n-k))– consistent for heavy (Pareto-type,xi > 0) tails. For a Pareto tail with exponentalphathis estimates1/alpha.
- moment_estimator(data, k)[source]
Dekkers–Einmahl–de Haan moment estimator of the extreme-value index (any tail sign).
Generalises Hill to
xiof either sign by combining the first two log-moments of the topkexceedances; works for heavy, light, and bounded (xi < 0) tails. Needsk >= 2: with a single log-spacing the second moment is identically the square of the first (zero variance), which sends the estimator’s denominator to zero.
- mean_residual_life(data, thresholds)[source]
Mean-excess (mean-residual-life) function for POT threshold selection.
e(u) = mean(X - u | X > u). Over a range where the GPD fits,e(u)is approximately linear inu(slopexi/(1-xi)); the lowest threshold from which the plot is linear is the choice.
- endpoint_estimator(data, k, *, method='gpd')[source]
Estimate the finite right endpoint of a bounded support (frontier / boundary estimation).
Fits a GPD to the top
kexceedances overX_(n-k); when the tail indexxiis negative the support is bounded and the endpoint isx+ = X_(n-k) - beta/xi(which, by the GPD support constraint, necessarily exceeds the observed maximum). Generic to econometric frontier analysis, reliability limits, and image-edge localisation. Returnsinfif the estimated tail is unbounded (xi >= 0).- Parameters:
- Returns:
The estimated right endpoint (
infif unbounded).- Raises:
ValueError –
methodis not"gpd",kis out of range, or the upper order statistics used are non-finite.- Return type:
- n_records(data)[source]
Number of upper records. For an i.i.d. sequence of length
nthe expectation isH_n.
- record_times(data)[source]
Indices at which a new running maximum (upper record) occurs, including the first observation.
datamust be a finite one-dimensional series, like the tail estimators above. A single NaN used to silently truncate the answer rather than raise:np.maximum.accumulatepropagates the NaN forward, every laterx[i] > running[i-1]comparison against it is false, and every record after the NaN disappears –record_times([1, nan, 2, 3])returned just[0], whichn_records()then reported as an authoritative count of one.An empty
datahas no observations and so, vacuously, no records: returns an empty index array rather than raising.
- class BMDResult(bmd, bmdl, bmr, model, dof, status='ok', bmd_se=nan, _coef=<factory>)[source]
Bases:
objectA fitted benchmark-dose analysis: the BMD, its lower confidence bound (BMDL), and fit metadata.
statusis one of:"ok": the curve fit converged, the BMD was found and bracketed, and the BMDL lower bound was established.bmdandbmdlare real doses."unidentifiable": the curve fit itself did not converge, or the fitted curve never reaches the benchmark target within a bounded search (flat, wrong-signed, or requiring an implausibly large dose).bmdandbmdlarenan– never a search boundary or other placeholder dressed up as a real dose."bmdl_unavailable": the BMD itself was identified (bmdis real), but the BMDL computation did not converge to a valid bound.bmdlisnan.
bmd_seis the delta-method standard error of the BMD (nanunlessstatus == "ok");bmdl = bmd - z * bmd_seclipped at 0,zthe one-sided normal quantile for the requested confidence level.- Parameters:
- property converged: bool
Trueonly when both the BMD and the BMDL were genuinely established.
- benchmark_dose(dose, n_affected, n_total, *, bmr=0.10, model='loglogistic', risk='extra', ci_level=0.95)[source]
Fit a quantal dose-response and report the benchmark dose (BMD) and its lower bound (BMDL).
dose/n_affected/n_totalare per-dose-group arrays (n_affected <= n_total). The curve is fit by maximum likelihood (DR-ALG K8); the BMD solves for the dose givingbmrextra (or added) risk over the fitted background rate. The BMDL is the one-sidedci_levellower confidence bound on the BMD by the delta method: the gradient of the BMD with respect to the fitted coefficients is obtained by implicit differentiation of the BMD-defining equation, the coefficient covariance by inverting the observed Fisher information at the MLE, andVar(BMD)by propagating one through the other (grad @ Cov @ grad);BMDL = BMD - z * SE(BMD)withzthe one-sided normal quantile forci_level, clipped at 0 (dose cannot be negative). SeeBMDResult.statusfor what happens when any step of that chain fails to converge.
- rfd_exceedance(exposure, bmd, *, uf=100.0, n=2000, rng=None)[source]
P(exposure > RfD) as an IC-8 DerivedQuantity, where RfD = BMDL / uf (EPA convention).
exposuremay be an IC-1 Posterior (the pushforward runs through its ownderived_quantityso prior_dominated propagates), an array of exposure draws (resampled tonif its length differs), or a bare scalar (a degenerate point mass).ufmust be finite and strictly positive,na positive exact sample count, andexposuredraws finite and nonnegative – all rejected up front, before any division or resampling.bmd.bmdlmust also be finite: aBMDResultwithstatus != "ok"(seeBMDResult) has no real BMDL to divide by an uncertainty factor, and a NaN silently compared withdraws > nanwould quietly evaluate to all-False– a fabricated-looking “0% exceedance” result – rather than surfacing the underlying unidentifiability. The finite/nonnegative exposure check applies uniformly to a Posterior’s own draws too, not only to a plain array/scalar: it lives insidefnbelow (the pushforward handed toPosterior.derived_quantity), not in a separate top-level check on the rawexposure– otherwise a mis-specified exposure posterior could emit a negative or NaN draw that flowed straight through draws > rfd (silentlyFalsefor NaN) into a confident-looking “not exceeding” result with no warning.
- class DoseResponse(model, params=<factory>)[source]
Bases:
objectA named dose-response model:
modelselects the functional form,paramsits coefficients.model in {"loglinear", "logit", "hill", "threshold_linear"}:loglinear:P = 1 - exp(-beta * dose)(params: {"beta"}) – the EPA-style linear low-dose cancer/chronic form.logit:P = sigmoid(a * dose + b)(params: {"a", "b"}, both optional).hill:P = emax * dose^n / (ec50^n + dose^n)(params: {"ec50"},"emax"/"n"optional) – saturating receptor-occupancy form.threshold_linear:P = clip(slope * (dose - threshold), 0, 1)(params: {"slope"},"threshold"optional) – no response belowthreshold.
No regulatory dose-response table ships here –
paramsare supplied by the caller (see the module Non-goals).Frozen, holding its own copy of
params. The eager domain validation below is only meaningful if the validated coefficients are the ones actually used: with a mutable record and a shared dict, a caller could construct a valid model and then swapmodelor editparams– and every laterresponse_fn()would rebuild the closure from coefficients that never passed the construction-time gate.- response_fn()[source]
The elementwise dose -> outcome-probability function for this model + params.
- probability(dose, *, n=2000, rng)[source]
Push
dosethrough the dose-response model into an outcome-probability DerivedQuantity.dosemay be an IC-1 Posterior over exposure (the pushforward runs through the posterior’s ownderived_quantity, so prior_dominated propagates from the exposure uncertainty), an array of dose draws/ensemble members (“array-with-UQ”, resampled tonif its length differs), or a bare scalar (a degenerate point mass – the returned quantity still carries a (trivial) credible interval). Bare ensembles are one-dimensional. Posterior draws explicitly use shape(n,)for scalar dose or(n, n_receptors)for a receptor vector. Every dose must be finite and nonnegative, andnis an exact non-Boolean positive integer.- Parameters:
dose (Any)
n (int)
rng (np.random.Generator)
- Return type:
DerivedQuantity
- cumulative_exposure(series, dt, *, decay=0.0)[source]
Time-integrated exposure (trapezoidal rule), with optional first-order biological decay.
decay=0is the plain trapezoidal integral ofseriesover itsdt-spaced timesteps (area under the exposure-rate curve).decay > 0discounts each sample toward the final timestep byexp(-decay * (t_end - t))before integrating – the way a biological half-life would – so a spike long ago contributes less to the current cumulative body burden than an equally large spike near the end of the series. Feeds a chronic dose-response evaluation (e.g. viaDoseResponse.probability()).seriesmust be a one-dimensional finite nonnegative time series; multidimensional receptor/time data require an explicit aggregation before this scalar integration.dtmust be finite and positive (MXR-080-0098: a zero or negative timestep makes the integral either trivially zero or sign-flipped, silently), anddecayfinite and non-negative (a negative “decay” would amplify, not discount, older readings without bound).
- population_risk(exposure, dr, *, n, rng)[source]
Aggregate per-receptor dose-response probabilities into an expected-case-count DerivedQuantity.
exposureis a per-receptor dose: an IC-1 Posterior whose draws are(n, n_receptors)dose vectors (K1/K2 transport output propagated through UQ), or a plain(n_receptors,)array of point doses. Each posterior draw is pushed throughdr’s response function and summed over receptors, so the returned quantity carries the expected-case-count distribution (not just its mean); a bare array has no exposure uncertainty and yields a degenerate (constant) distribution.- Parameters:
exposure (Posterior | np.ndarray)
dr (DoseResponse)
n (int)
rng (np.random.Generator)
- Return type:
DerivedQuantity
- health_liability(risk, *, cost_per_case, discount=0.0)[source]
Price a K3/K4 risk DerivedQuantity into an expected-liability DerivedQuantity (K6, work-plan §7-K).
riskis any IC-1-shaped DerivedQuantity over an expected case count or exceedance probability (population_risk()’s output, or K4’ssafety_risk_surface); every draw inrisk.samplesis multiplied bycost_per_case(dollars per case/incident) and divided by(1 + discount)– a single-period present-value factor (discount=0is undiscounted; multi-period accounting applies its own per-period factor before summing across periods, this function prices one period/one risk term at a time). Pricing does not change how uncertain the underlying risk is: the returned quantity keepsrisk’s sample count/shape and its prior_dominated flag unchanged, so a liability that is prior-dominated upstream is still honestly flagged as such downstream. Handed to J6’spriced_liabilities/risk_adjusted_plan(analysis/valuation.py) as thehealth_costcallable’s output.risk.samplesmust be a nonempty finite nonnegative(n,)case-count series or(n, n_receptors)risk surface; negative harm is never interpreted as a benefit.cost_per_casemust be finite and non-negative (a dollar price cannot be negative), anddiscountfinite and strictly greater than-1.0(MXR-080-0098: the1 / (1 + discount)present-value factor is only well-defined for a denominator that is positive;discount == -1divides by zero, and anything below it silently flips the liability’s sign).
- exposure_constraints(options, limits, *, treat_unmodeled_as_safe=False)[source]
Screen candidate operating
optionsagainst named exposure/exceedancelimits(K6).limitsmaps an occupational/community exposure metric name (e.g."silica_pm4", an 8-hour TWA, or an exceedance probability) to the regulatory/policy limit for that metric.optionsis a list of plain dicts – each one candidate operating configuration, carrying (among whatever other plan data the caller needs, e.g. block cost or grade) a value for zero or more of those metric keys.Returns a new list (the input dicts are never mutated), one entry per option, each the original option’s key/value pairs plus:
"status": one of"safe","violating", or"unknown"."violating"means at least one limited metric the option DOES carry a finite value for breaches its (finite) limit."unknown"means no limit was confirmed breached, but at least one limited metric could not be evaluated for this option – missing from the option entirely, a non-finite (NaN/Inf) measurement, or a non-finite/missing limit – “not modeled” and “safe” are never the same outcome here, because this is a hard occupational/community exposure screen, not a soft optimization preference (MXR-080-0095). A confirmed violation always dominates an unknown: one metric’s missing data never erases a different metric’s real breach."feasible":Trueiffstatus == "safe"– unlesstreat_unmodeled_as_safe=True, in which casestatus == "unknown"also counts as feasible (see below). Kept as a plain boolean for the existing contract H4’stwo_stage_stochastic_planfiltering relies on."binding": the sorted list of limit names actually confirmed breached (empty unlessstatus == "violating") – naming exactly which limit made the option infeasible."unmodeled": the sorted list of limited metric names that could not be evaluated for this option (empty unlessstatus == "unknown") – naming exactly which evidence was missing.
This is a HARD safety screen, so it FAILS CLOSED by default: an option that breaches no limit it was evaluated against, but leaves one or more limits unevaluated, is
"unknown"and thereforefeasible=False– unmodeled evidence is never silently treated as compliant, and a NaN measurement (which would otherwise compare False, and so silently pass, against> limit) is treated the same as a missing one. Passtreat_unmodeled_as_safe=Trueto explicitly opt into the old permissive behavior; this is a deliberate policy override the caller must request, and it defaults to off. A caller filters the returned list down to the feasible options before handing the survivors’ blocks to H4’stwo_stage_stochastic_plan(stochastic_opt.py) – an infeasible (violating OR, by default, unknown) option is dropped from the candidate set entirely, so the optimizer never has the chance to select it (see the K6 DoD).
- class KDE(data, *, bandwidth='silverman', bounds=None, adaptive=False)[source]
Bases:
objectA fitted kernel density estimate, 1-D or multivariate.
Use
kde()to construct. Evaluate withevaluate()(or call the instance). Supports a Gaussian kernel – an axis-aligned product kernel with a per-dimension bandwidth for multivariate(n, d)data, rows treated as observations and never reshaped – adaptive bandwidths (any dimension), and, for 1-D data only, normalized boundary correction (bounds; MXR-080-0099).- data
the fitted sample as
(n, d)observations (d == 1for a 1-D sample).
- n
number of observations.
- d
dimensionality (
1for a 1-D sample).
- bandwidth
the resolved bandwidth – a scalar
floatwhend == 1, else a length-dnumpy.ndarray(one bandwidth per dimension).
- kde(data, *, bandwidth='silverman', bounds=None, adaptive=False)[source]
Construct a kernel density estimate (Gaussian kernel; product kernel for multivariate data).
- Parameters:
data (ndarray) –
(n,)sample, or(n, d)for ad-dimensional sample – rows are observations and must stay paired (never flatten an(n, d)array before calling this; MXR-080-0099).bandwidth –
"silverman","scott", a single positive finite number (used in every dimension), or – for(n, d)data – a length-dsequence of positive finite numbers (one bandwidth per dimension). Automatic selection needs >= 2 observations with nonzero variation in every dimension.bounds –
(lo, hi)support limits for normalized boundary correction; either may beNonefor an unbounded side (e.g.(0.0, None)for a positive variable). Half-lines use an exactly normalized reflected image; finite intervals normalize every truncated kernel. Only supported for 1-D (d == 1) data.adaptive (bool) – use Abramson variable bandwidths (wider where the pilot density is low); supported for any dimension.
- Returns:
A
KDE.- Raises:
ValueError –
datais empty, non-finite, or more than 2-D;bandwidthis an unrecognized method name, the wrong length, or not strictly positive and finite; automatic bandwidth selection is requested on fewer than 2 observations or a constant sample in some dimension (MXR-080-0100); orboundsis given together withd > 1, out of order, or non-finite (MXR-080-0099 / MXR-080-0100).- Return type:
KDE
- kde_mode(data, *, bandwidth='silverman', bounds=None, grid=None, ci=False, n_boot=500, ci_level=0.95, seed=0)[source]
Estimate the mode (peak location) of a 1-D density, optionally with a bootstrap CI.
Univariate only: a dense grid search over
ddimensions is exponential indand is not implemented here (unlikeKDE, this never accepted multivariate data as a documented feature, so an(n, d)input is now rejected explicitly instead of being silently flattened with.ravel()– the same silent-flattening defectKDEitself had; MXR-080-0099).- Parameters:
data (ndarray) –
(n,)sample.bandwidth – passed to
kde().bounds – passed to
kde().grid (ndarray | None) – evaluation grid; defaults to 512 points spanning the data range.
ci (bool) – if True return a percentile bootstrap interval for the mode.
n_boot (int) – bootstrap controls.
ci_level (float) – bootstrap controls.
seed (int | RandomState | None) – bootstrap controls.
- Return type:
Bootstrap policy for degenerate resamples (MXR-080-1587): a resample of a perfectly valid, nonconstant sample can itself come out constant –
[0, 0, 1]resamples to[0, 0, 0]roughly one draw in four – and an automatic bandwidth selector has no spread to estimate from, so it raises. That made the interval fail at random, depending on the seed, for a fit that had already succeeded. A degenerate resample is now scored at the ORIGINAL fit’s bandwidth (the sample the bandwidth was selected from is the full one, and a bootstrap replicate is a resample of it, not a new dataset), rather than skipped – skipping would quietly narrow the interval by dropping exactly the least-dispersed replicates. The count of replicates that needed the fallback is reported asn_degenerate_resamplesso a caller can see when the interval rests largely on them.- Returns:
The mode (float), or
{'mode', 'ci_low', 'ci_high', 'n_boot', 'n_degenerate_resamples'}whenciis True.- Raises:
ValueError –
datais empty or not 1-D; or, whenciis True,n_bootis not a positive integer orci_levelis not in the open interval(0, 1)(MXR-080-0100). Also propagates anyKDEconstruction error fromkde()on the ORIGINAL sample (e.g. automatic bandwidth selection on a constant input) – that is a genuine property of the data, unlike a degenerate resample, which is handled by the policy above.- Parameters:
- Return type:
- intensity(events, grid, *, bandwidth='silverman', domain=None, edge_correct=True)[source]
Kernel intensity
lambda(t)of a 1-D inhomogeneous Poisson / point process.Unlike a density (which integrates to 1), the intensity integrates to the expected number of events:
lambda_hat(t) = sum_i K_h(t - t_i). Withedge_correctthe estimate is divided by the fraction of the kernel falling insidedomain, removing the downward bias near the boundary.Univariate only: like
kde_mode(), this never accepted multivariate data as a documented feature, so an(m, d)input is now rejected explicitly instead of being silently flattened with.ravel()(MXR-080-0099).- Parameters:
events (ndarray) –
(m,)event locations.grid (ndarray) – nonempty finite one-dimensional points
tat which to evaluate the intensity.bandwidth –
"silverman","scott", or a float.domain (tuple[float, float] | None) –
(lo, hi)observation window (defaults to the event range); used for edge correction.edge_correct (bool) – divide by the in-window kernel mass at each
t.
- Returns:
The intensity evaluated on
grid.- Raises:
ValueError –
eventsis empty, non-finite, or not 1-D;bandwidthis an unrecognized method name or a non-positive/non-finite number; automatic bandwidth selection is requested on fewer than 2 events or constant events; or (whenedge_correctis True) the effectivedomain– explicitly passed, or defaulted to the event range – is non-finite or haslo >= hi(MXR-080-0100; a collapsed-to-a-point domain, e.g. from constant events with an explicit numeric bandwidth, previously produced a silent divide-by-near-zero blowup).- Return type:
- silverman_bandwidth(data, *, d=1)[source]
Silverman’s rule-of-thumb bandwidth
0.9 min(sd, IQR/1.34) n^{-1/(d+4)}for a 1-D sample.datais always treated as one 1-D sample (e.g. a single column of a multivariate dataset).donly sets the exponent: it is the dimensionality of the joint sample this bandwidth will be used in a product kernel for – pass it explicitly when selecting one dimension’s bandwidth for a multivariateKDE, so then^{-1/(d+4)}rate reflects the true joint dimension rather than the univariate defaultd=1(n^{-1/5}) rate (MXR-080-0099).- Raises:
ValueError –
datahas fewer than 2 observations, non-finite entries, or zero variation (see_require_variation(); MXR-080-0100).- Parameters:
- Return type:
- scott_bandwidth(data, *, d=1)[source]
Scott’s rule-of-thumb bandwidth
sd * n^{-1/(d+4)}for a 1-D sample.datais always treated as one 1-D sample;donly sets the exponent – seesilverman_bandwidth()(whosedparameter has exactly the same meaning here).- Raises:
ValueError –
datahas fewer than 2 observations, non-finite entries, or zero variation (see_require_variation(); MXR-080-0100).- Parameters:
- Return type:
- exception CoverageInsufficientDataError[source]
Bases:
ValueErrorThe requested coverage/diversity estimand is not identified by an empty sample.
- turing_coverage(counts)[source]
Turing’s sample-coverage estimate and the complementary unseen probability mass.
C = 1 - f1 / nwheref1is the number of singletons andnthe total count: the estimated probability that the next observation is a previously seen species.1 - C = f1/nis the Good–Turing estimate of the total probability of all unseen species.
- good_turing(counts)[source]
Simple Good–Turing smoothed probabilities (Gale & Sampson 1995).
Reallocates probability from seen to unseen items using the frequencies of frequencies. Empirical Turing estimates
r* = (r+1) N_{r+1}/N_rare used for smallrand a smoothed log-linear fitS(r)takes over once the two diverge (the Gale switch), giving stable discounts in the sparse tail.The log-linear fit needs at least two distinct abundance classes to be identifiable. With exactly one – e.g. an all-singleton sample like
[1, 1, 1], a canonical Good–Turing input, not a malformed one – there is nothing to fit a slope against, so smoothing is skipped and the seen mass is reallocated by raw frequency instead (the Simple Good–Turing small-support fallback; previously this raised aLinAlgErrorout ofnp.polyfit). With no observed counts at all, coverage is not identifiable and the commonCoverageInsufficientDataErroris raised.- Parameters:
counts (ndarray) – per-species abundances (zeros ignored).
- Returns:
{'p0', 'proba', 'r_star', 'r', 'insufficient_evidence', 'reason'}–p0is the total probability assigned to unseen species;probaare the smoothed probabilities of the input species (aligned to the positive entries ofcounts, summing to1 - p0);r_star/rare the discounted and raw frequencies for the distinct abundance classes.insufficient_evidenceis alwaysFalsefor a returned estimate; empty samples raiseCoverageInsufficientDataError.- Return type:
- chao1(counts, *, ci_level=0.95)[source]
Chao1 nonparametric richness estimator from abundance data (bias-corrected).
S_chao1 = S_obs + f1 (f1 - 1) / (2 (f2 + 1))(Chao 1984, bias-corrected form), a lower bound on total richness driven by the singleton (f1) and doubleton (f2) counts. Returns a standard error and a log-normal confidence interval for the number of undetected species (Chao 1987), so the interval respectsS_chao1 >= S_obs.
- chao2(incidence, *, ci_level=0.95)[source]
Chao2 richness estimator from replicated incidence (presence/absence) data.
- Parameters:
incidence (ndarray) – finite binary
(n_species, n_sites)0/1 matrix (validated by_incidence(); every entry must be exactly 0 or 1). At least_MIN_REPLICATED_SITESsampling units are required – Chao2 estimates unseen richness from replication across units, so one unit does not identify it (CoverageInsufficientDataError).ci_level (float) – confidence level for the log-normal interval.
- Returns:
{'estimate', 'observed', 'q1', 'q2', 'se', 'ci_low', 'ci_high', 'sites'}whereq1/q2are the numbers of species found in exactly one / two sites.- Return type:
- ace(counts, *, rare_threshold=10)[source]
ACE: Abundance-based Coverage Estimator of richness (Chao & Lee 1992).
Splits species into abundant (
> rare_threshold) and rare, estimates sample coverage from the rare group’s singletons, and corrects for the coefficient of variation of the rare abundances.- Returns:
{'estimate', 'observed', 's_rare', 's_abund', 'c_ace'}.- Raises:
CoverageInsufficientDataError – when the estimated sample coverage
c_aceis zero (every rare individual is a singleton) – the1 / c_acecorrection is undefined there and unseen richness is unbounded, so no finite estimate is reported.- Parameters:
- Return type:
- ice(incidence, *, rare_threshold=10)[source]
ICE: Incidence-based Coverage Estimator of richness (the Chao–Lee estimator for incidence data).
- Parameters:
incidence (ndarray) – finite binary
(n_species, n_sites)0/1 matrix (validated by_incidence(); every entry must be exactly 0 or 1). At least_MIN_REPLICATED_SITESsampling units are required, for the same identifiability reason aschao2().rare_threshold (int) – species found in
<= rare_thresholdsites are treated as infrequent.
- Returns:
{'estimate', 'observed', 's_infreq', 's_freq', 'c_ice'}.- Raises:
CoverageInsufficientDataError – for a single-unit design, or when the estimated incidence coverage
c_iceis zero (every infrequent species seen in exactly one unit) – the correction terms_infreq / c_iceis undefined there.- Return type:
- hill_numbers(counts, q=(0.0, 1.0, 2.0))[source]
Hill numbers (effective number of species) of order
q.The unified diversity profile:
q=0is observed richness,q=1is the exponential of Shannon entropy,q=2is the inverse Simpson concentration. Largerqweights common species more, so the profileD(q)summarises evenness as well as richness.
- rarefaction_curve(counts, sizes=None)[source]
Individual-based rarefaction: expected richness when subsampling
mindividuals (Hurlbert).E[S(m)] = sum_i (1 - C(n - x_i, m) / C(n, m))– the expected number of species seen in a random subsample ofmof thenindividuals. Used to compare richness between samples at a common sample size (or coverage).- Parameters:
- Returns:
{'sizes', 'expected_richness'}.- Return type:
- class CohortAttribution(hazard_ratio, hr_ci, attributable_fraction, af_ci, cif, provenance)[source]
Bases:
objectCox-PH hazard ratio + attributable fraction for one exposure, with intervals and provenance.
- Parameters:
- hazard_ratio
exp(coef[exposure_col])from the fitted Cox model.- Type:
- hr_ci
Wald
(lo, hi)confidence interval onhazard_ratio(log scale, fromCoxResult.se).
- attributable_fraction
fraction of hazard among the exposed attributable to exposure,
(hazard_ratio - 1) / hazard_ratio.- Type:
- cif
cause-specific Aalen-Johansen cumulative incidence,
{cause: array}(empty unlesscompeting=True).- Type:
- provenance
fit diagnostics, the RNG’s full reproducible state (
rng_state), andaf_distribution– an_AFDistribution(IC-1DerivedQuantity-shaped) when the bootstrap produced adequate evidence, or an_InsufficientBootstrapEvidencewhen it did not (see_MIN_BOOTSTRAP_DRAWS).- Type:
The record is frozen and owns its cumulative-incidence curves (read-only copies), so the point estimates, the intervals and the
provenancethat documents how they were fitted cannot drift apart after the fact: rebindinghazard_ratiowithouthr_ci– or editing acifarray in place through the caller’s own handle – left a record whose parts described different fits.Construction now also enforces the record’s own domain (MXR-080-1900). It previously enforced none: a
hazard_ratioof-2.0(impossible – it isexp(coef)), anattributable_fractionof4.2(impossible –(HR - 1) / HRapproaches but never exceeds 1), and an invertedhr_ciof(9.0, 1.0)all constructed silently and were indistinguishable downstream from a real fit. The invariants, each one the producer already guarantees:hazard_ratiofinite and strictly positive.hr_cifinite, strictly positive, and orderedlo <= hi.attributable_fractionfinite and<= 1. It is NOT bounded below by 0: a protective exposure hasHR < 1and a legitimately negative attributable fraction.af_cieither two finite values orderedlo <= hi, or(nan, nan)– the documented marker for the insufficient-bootstrap-evidence case (see_InsufficientBootstrapEvidence), which is a real state the producer emits. A half-NaN interval is not.
Deliberately NOT checked: that
attributable_fractionequals(hazard_ratio - 1) / hazard_ratioexactly, or that either interval brackets its point estimate. Both are true ofcohort_attribution()’s own output, but a bootstrap percentile interval is not required to contain the point estimate in general, and pinning the AF identity would turn one documented parameterization into the only constructible one.provenanceis copied DEEPLY: the previousdict(...)detached only the top level, so nested entries –coef/selists, therng_statemapping that exists precisely so a fit can be replayed – stayed aliased to the caller’s own objects and could be rewritten after the attribution was reported.
- cohort_attribution(covariates, time, event, *, exposure_col=0, competing=False, latency=0.0, n_boot=1000, rng=None)[source]
Attribute a cohort’s hazard to one exposure via Cox PH, with an Aalen-Johansen competing-risks CIF.
- Parameters:
covariates (ndarray) –
(n, p)covariates (finite); columnexposure_colis the exposure of interest.time (ndarray) –
(n,)event/censoring times (finite, non-negative).event (ndarray) –
(n,)event indicator. Binary (0/1) whencompeting=False; an integer cause label (0= censored,1= the outcome of interest,2..K= competing causes) whencompeting=True. Either way, the Cox fit is cause-specific for cause1: competing-cause events are censored at their time for the hazard-ratio fit (only the CIF, when requested, reports the competing causes’ own cumulative incidence). Values must be exact integers – fractional codes are rejected outright, not truncated (a truncated1.5would silently relabel a subject from “the event of interest” to “censored”).exposure_col (int) – column of
covariatestreated as the exposure (must be a valid column index).competing (bool) – if True, also run aalen_johansen for cause-specific cumulative incidence.
latency (float) – exposure-to-effect lag (left-truncation, via cox_ph’s start);
0disables it. Must be non-negative – negative latency is rejected, not silently treated as0.n_boot (int) – cohort-resample bootstrap draws for the attributable-fraction interval. If fewer than
_MIN_BOOTSTRAP_DRAWSconverge (or too small a fraction ofn_boot, see_MIN_BOOTSTRAP_FRACTION),af_ciis(nan, nan)andprovenance["af_distribution"]is an_InsufficientBootstrapEvidencerather than a real interval.rng – seed, numpy.random.Generator, or None.
- Returns:
A
CohortAttribution.- Raises:
ValueError – if the cohort is malformed – see
_validate_cohort()for the exact checks (mismatched array shapes, non-finite values, negative time, an out-of-vocabulary or fractional event code, negative latency, or an out-of-boundsexposure_col).- Return type:
CohortAttribution
- class Variogram(model, nugget, psill, rng, nu=1.5, anisotropy=None)[source]
Bases:
objectA fitted variogram model
gamma(h) = nugget + psill * shape(h).- model
"spherical","exponential","gaussian"(aka"squared_exponential"/"rbf"– covariancepsill * exp(-(h/rng)**2)), or"matern".- Type:
- nugget
discontinuity at
h=0(measurement error / micro-scale variance).- Type:
- psill
partial sill (correlated variance);
nugget + psillis the total sill.- Type:
- rng
range (correlation length).
- Type:
- nu
Matern smoothness (ignored by other models).
- Type:
- anisotropy
optional
(angle_rad, ratio)geometric anisotropy – coordinates are rotated byangleand the minor axis scaled by1/ratiobefore distances are taken.
- Raises:
ValueError – if
modelis not implemented;nugget/psillare not finite and>= 0;rng/nuare not finite and> 0; oranisotropyis set with a non-finite angle or a ratio that is not finite and> 0(a zero or negative ratio collapses or flips the minor axis, which previously produced NaN predictions and variances downstream instead of an error).- Parameters:
Frozen. Every kriging solver reads these fields directly and trusts the construction-time validation below without re-checking; while the record was mutable, setting
rng = 0, a negative sill, an unimplementedmodel, or a collapsed anisotropy ratio after construction bypassed the whole check and put the invalid parameter straight into the solve. Freezing keeps the validated state true for the variogram’s lifetime, which is what those solvers assume.anisotropyis normalized to a(angle, ratio)tuple so it cannot be a mutable pair either.- gamma(h)[source]
Evaluate the semivariogram at lag distances.
- exception VarianceCalibrationError[source]
Bases:
ValueErrorBase error for a variance-calibration request that has no publishable finite factor.
- exception VarianceCalibrationUnidentifiable[source]
Bases:
VarianceCalibrationErrorRaised when the requested coverage holds without any identifiable positive scale.
- exception VarianceCalibrationOutOfRange[source]
Bases:
VarianceCalibrationErrorRaised when the required scale is outside the finite floating-point domain.
- empirical_variogram(coords, values, *, n_bins=15, max_dist=None)[source]
Binned empirical (semi-)variogram: mean
0.5 (z_i - z_j)^2by separation distance.max_distdefaults to half the largest pairwise distance. Bins aren_binsequal-width intervals, left-closed and right-open ([edges[b], edges[b+1])) except the last, which is closed on both ends – so a pair separated by exactlymax_dist(routine, not an edge case: e.g. the endpoints of an evenly spaced line always sit exactly on the default cutoff) lands in the last bin instead of being classified as “beyond the last bin” and silently dropped.- Returns:
{'lag', 'semivariance', 'count', 'insufficient_evidence', 'reason'}–lag/semivariance/countcover each non-empty distance bin.insufficient_evidenceisTrue(withreasonset and the other fields empty) only when no valid distinct pair falls within[0, max_dist]– e.g. amax_distnarrower than every pairwise distance – so no bin is identifiable; it isFalse(withreasonempty) whenever at least one bin is populated. Invalid geometry, fewer than two observations, or no distinct locations raise before distance computation.- Parameters:
- Return type:
- fit_variogram(coords, values, *, model='spherical', n_bins=15, nu=1.5)[source]
Fit a variogram model to data by least squares on the empirical variogram.
- Returns:
A fitted
Variogram(nugget, partial sill, range).- Raises:
ValueError – if the empirical variogram has fewer than
_MIN_POPULATED_BINS_FOR_FITpopulated bins, so nugget/partial sill/range are not identifiable from the data (seeempirical_variogram()).- Parameters:
- Return type:
Variogram
- ordinary_kriging(coords, values, variogram, query, *, noise=None)[source]
Ordinary kriging: BLUP of the field at
queryunder an unknown constant mean.- Parameters:
coords (ndarray) –
(n, d)data locations.values (ndarray) –
(n,)measured field.variogram (Variogram) – a fitted
Variogram.query (ndarray) –
(q, d)prediction locations.noise (ndarray | None) – optional
(n,)per-observation measurement variance (heteroscedastic nugget); if None the homoscedasticvariogram.nuggetis used on the diagonal.
- Returns:
{'prediction', 'variance'}arrays of lengthq.- Raises:
ValueError – if
coords/values/queryare empty, mismatched in shape, or contain non-finite values, or ifnoiseis provided and is mismatched in shape, non-finite, or negative.- Return type:
- universal_kriging(coords, values, variogram, query, *, degree=1, noise=None)[source]
Universal kriging: kriging with a polynomial spatial trend (drift) of the given
degree.degree=0uses a constant drift,degree=1removes a linear trend, anddegree=2a quadratic one. No other degree is implemented. Use when the field has a large-scale drift on top of the stationary residual the variogram describes. The selected drift basis must be full-rank with at least one observation per coefficient.- Returns:
{'prediction', 'variance'}.- Raises:
ValueError – if
coords/values/queryare empty, mismatched in shape, or contain non-finite values, or ifnoiseis provided and is mismatched in shape, non-finite, or negative.- Parameters:
- Return type:
- calibrate_variance(predicted_var, residuals, *, target=0.9)[source]
Scale factor that makes kriging predictive intervals hit a target coverage.
Finds the smallest observed order-statistic scale
csuch that standardised residualsresidual / sqrt(c * predicted_var)achieve at least thetargetempirical central coverage under a Gaussian predictive. Returns the variance multiplierc; multiplypredicted_varby it to recalibrate (generic GP/kriging variance recalibration). No fixed search caps are used.- Parameters:
- Returns:
The variance multiplier
c(> 0).- Raises:
ValueError – if
targetis not finite and strictly in(0, 1)– a target<= 0,>= 1, or NaN previously converged to a silent boundary scale factor (1e-6or1e6) instead of raising; ifpredicted_varandresidualsdo not have the same nonempty one-dimensional shape (a valid paired held-out sample is required); or if either contains non-finite values, orpredicted_varis not strictly positive everywhere.VarianceCalibrationUnidentifiable – if zero residuals already meet the target for every positive multiplier.
VarianceCalibrationOutOfRange – if the required factor is outside finite representable range.
- Return type:
- borda_count(rankings)[source]
Borda positional aggregation: each item scores
(m - 1 - position)summed over voters.
- copeland(rankings)[source]
Copeland pairwise-majority aggregation.
Each ordered pair contributes a win/loss by majority across voters; an item’s score is wins minus losses. Closely tracks the Condorcet winner when one exists.
- kemeny_consensus(rankings, *, exact_max_items=8)[source]
Kemeny median ranking: minimise the total Kendall-tau distance to all input orderings.
The Kemeny consensus is the maximum-likelihood aggregation under the Mallows–Kendall model and the Condorcet-consistent choice. Exact by enumeration when
m <= exact_max_items; otherwise a local search (adjacent transpositions) from the Borda ordering. Exact enumeration evaluatesm!permutations, soexact_max_itemsis an explicit factorial resource boundary; it must be an exact non-Boolean nonnegative integer.
- mallows_fit(rankings, *, exact_max_items=8)[source]
Fit a Mallows model (Kendall): central ranking + dispersion
theta.The central ranking is the
kemeny_consensus();thetais the MLE concentration, found by matching the observed mean Kendall distance to its expectation under the model. Largerthetameans tighter agreement (theta -> 0is uniform/no-consensus).thetaisnanwhen fewer than two items are ranked (MXR-080-1594): withm < 2there is exactly one possible ordering, so every ranking has Kendall distance zero under everythetaand the likelihood is flat – the dispersion is unidentifiable, not infinite. Reportinginfthere (the perfect-agreement branch below) would claim maximal evidence for concentration from data that carries no evidence at all. The trivial center is still returned, since it is correct.infremains the honest answer for genuine perfect agreement among two or more items, where the observed zero distance really was one of many attainable outcomes.
- kendall_distance(a, b)[source]
Kendall-tau distance: the number of item pairs ordered oppositely by
aandb.
- spearman_footrule(a, b)[source]
Spearman footrule distance: the sum of absolute position differences across items.
- cayley_distance(a, b)[source]
Cayley distance: the minimum number of transpositions turning
aintob(m - #cycles).
- class SpatialMixture(shape, n_components, emission, beta=1.0)[source]
Bases:
objectA grid-structured mixture with a Potts prior on the latent labels and pluggable mixle emissions.
- Parameters:
shape – grid shape, e.g.
(nx, ny)or(nx, ny, nz)– defines the neighbour structure.n_components (int) – number of mixture components (latent classes).
emission – a mixle
ParameterEstimatorfor the per-component family, e.g.MultivariateGaussianEstimator()– this is what makes the class domain-agnostic.beta (float) – Potts coupling (
>= 0); larger smooths the labels more.0is an ordinary mixture.
- Raises:
ValueError – if any
shapedimension is not an exact positive integer, if the in-memory grid exceeds_MAX_GRID_CELLS, ifn_componentsis not in[1, prod(shape)](MXR-080-0115: more components than grid cells guarantees at least one permanently empty component by pigeonhole – rejected here rather than left to fail unpredictably during fitting), or ifbetais not finite and non-negative.
- fit(observations, *, max_iter=40, mf_iter=3, seed=0)[source]
Fit by mean-field variational EM.
observationsis a length-prod(shape)sequence of per-cell observations (row order matchesshape.ravel()); each is a single emission datum.Robustness: components are initialized by a short hard-assignment pass and the Potts coupling is annealed from 0 to
betaover the first iterations, so components form before the smoothness prior is applied (a strong prior on a degenerate init otherwise collapses every cell into one). Every partition is repaired to be nonempty (see_repair_empty_components()) BEFORE it is re-estimated (MXR-080-0115).- Raises:
ValueError – if
max_iterormf_iteris not a positive integer, or ifobservationsdoes not have exactlyprod(shape)entries (MXR-080-0116: a mismatched count would otherwise let responsibilities, neighbors, and encoded data describe different numbers of cells, surfacing later as an unrelated shape-mismatch crash deep inside the M-step instead of a clear error here).- Parameters:
- Return type:
SpatialMixture
- responsibilities()[source]
The posterior label probabilities,
(prod(shape), n_components)– a simplex per cell.- Return type:
- entropy()[source]
Per-cell posterior entropy (label uncertainty), reshaped to
shape.- Return type:
- class SmithMaxStable(sigma)[source]
Bases:
objectThe Smith max-stable process with Gaussian storm-profile covariance
sigma(d x d, SPD).A spatial process, not an i.i.d. leaf distribution: its full likelihood is intractable, so it is not a
SequenceEncodableProbabilityDistribution– it exposes the things that do have closed forms (extremal_coefficient,bivariate_cdf) plus asampler, and is fitted by the module-levelfit_smith_maxstable()(composite/madogram estimation), mirroring the functional fit style of the other non-leaf spatial models. Margins are unit Frechet; spatial dependence grows withsigma.- Parameters:
sigma (np.ndarray)
- property sigma: ndarray
Owned read-only covariance used consistently by dependence formulas and simulation.
- extremal_coefficient(h)[source]
theta(h) = 2 * Phi(a/2)withathe Mahalanobis lag length – 1 at h=0 (full dependence) rising to 2 as the lag grows (independence).
- bivariate_cdf(z1, z2, h)[source]
P(Z(s) <= z1, Z(s+h) <= z2) = exp(-V(z1, z2))– the Smith bivariate distribution.z1/z2are thresholds on unit-Frechet margins and so must be strictly positive (the unit-Frechet support is(0, inf)); the closed form below divides byz1/z2and takeslog(z2/z1), which silently producesnan/infrather than a real probability at or below 0.
- class SmithMaxStableFit(model, n_locations, n_replicates, n_pairs, residual, status='ok')[source]
Bases:
objectA fitted isotropic Smith max-stable process, plus a diagnostic of whether the pairwise dependence-scale match actually identified an interior optimum.
statusis one of:"ok": the fitted scale is an interior point of the search bracket – a genuine stationary point of the F-madogram least-squares objective."boundary": the fit landed at (or numerically indistinguishable from) the edge of the search bracket – the signature of an objective with no interior minimum. This happens, for example, when replicates carry almost no independent information (every location’s rank collapses together), which pushes the empirical extremal coefficient to the same value for every pair and drives the least-squares match monotonically toward one edge instead of a genuine optimum.modelis still populated with the boundary value, but it is a search artifact, not a dependence-scale estimate, and should not be trusted.
- Parameters:
- model
the fitted
SmithMaxStable.- Type:
mixle.analysis.max_stable.SmithMaxStable
- n_locations
distinct spatial locations used.
- Type:
- n_replicates
replicated fields used.
- Type:
- n_pairs
location pairs the least-squares match was computed over (
n_locations choose 2).- Type:
- residual
the F-madogram objective’s value at the fitted scale (mean squared error between the empirical and model extremal coefficients across pairs; exactly 0 for a 2-location fit, which has one pair and one parameter).
- Type:
- status
see above.
- Type:
Construction validates the record against its own documented domain (MXR-080-1900). It previously validated nothing at all, so
SmithMaxStableFit(model=None, n_locations=-1, n_replicates=0, n_pairs=-5, residual=nan)constructed cleanly and reportedconverged is True– a record claiming a converged fit that had no model, no data and no objective value. The invariants are exactly whatfit_smith_maxstable()produces: a realSmithMaxStable; at least two locations (a dependence scale needs a pair) and one replicate;n_pairsthen_locations choose 2the docstring above states; a finite, non-negativeresidual(it is a mean of squares); and astatusthis class actually defines.Deliberately NOT changed: the record stays a MUTABLE dataclass. Its frozen siblings in this package were converted under a separate finding, and freezing this one is a broader API change than the shape invariants this validates – the fields can still be rebound after construction and escape the checks below.
- property converged: bool
Trueonly when the fit is a genuine interior optimum, not a search-bound artifact.
- class SmithMaxStableSampler(dist, locations, seed=None)[source]
Bases:
objectSampler for a fitted Smith max-stable process at fixed locations.
sample()truncates the (in principle unbounded, infinite-storm) Smith spectral representation to a finite spatial box and a tolerance-controlled number of storms; see its docstring for exactly what that truncation does and does not rigorously control, andapproximation_diagnostic()for an empirical check of the result.- Parameters:
dist (SmithMaxStable)
locations (np.ndarray)
seed (int | None)
- sample(size=None, *, n_storms=20_000, tol=1e-3, box_sigma=5.0)[source]
Draw max-stable field(s) at the locations via a boxed, tolerance-truncated storm sum.
The true Smith representation is a max over an infinite Poisson process of storms
(xi_i, U_i)spread over the whole of R^d; only then are margins exactly unit Frechet. This sampler approximates that in two distinct ways, one rigorously controlled and one not:Storm-count truncation (rigorously controlled by ``tol``). Storms are drawn in decreasing order of their Poisson intensity mark
xi(xi_i = 1/Gamma_i, the natural arrival order of this construction). Because the Gaussian storm kernel is bounded by its peak densityphi_Sigma(0), a storm with markxican contribute at mostxi * vol * phi_Sigma(0)to any site, no matter where its centerU_ilands (volthe sampling box’s volume). Sincexiis strictly decreasing, once that bound drops belowtolfor one storm it has also dropped belowtolfor every later one – so it is exact, not heuristic, to stop there: every dropped storm’s true contribution is provably belowtolat every site, which bounds the total storm-count truncation error (relative to the boxed field’s own true value) bytol.n_stormsis a safety cap on iterations, not the primary control; aUserWarningis raised if that cap binds beforetolis reached for some replicate, since then thetolguarantee was not actually achieved. Every storm drawn is used before its owntolcheck is applied, so at least one storm always contributes to the field regardless of how loosetolis – an all-zero field (like the previousn_storms=0behavior) is just as invalid a “draw” as a negative one, since 0 is outside unit Frechet’s(0, inf)support.Spatial truncation (NOT rigorously controlled). Storm centers are drawn only from a finite box around the query locations, expanded by
box_sigmastorm-kernel standard deviations in each dimension, rather than the true unbounded domain. This introduces a bias that empirically shrinks asbox_sigmagrows, but is not exactly bounded here – useapproximation_diagnostic()to check it for your particular locations/sigma.
Net effect: simulated margins are only approximately, not exactly, unit Frechet. The storm-count half of that approximation is controlled to within
tol(whenn_stormsdoesn’t bind first); the box half is not formally bounded and should be checked empirically.- Parameters:
size (int | None) – number of independent replicate fields (
Nonefor a single field).n_storms (int) – safety cap on storms drawn per replicate; must be a positive integer. Storms stop earlier, as soon as
tolis satisfied. Must be a positive integer – withn_storms=0no storms are drawn at all, which cannot produce a valid unit-Frechet draw (0 is outside its(0, inf)support), so that case is rejected outright rather than silently returned as if it were one.tol (float) – stop once every remaining storm’s max-possible contribution to every site is below this (absolute, field-scale) tolerance. Must be finite and strictly positive.
box_sigma (float) – half-width, in storm-kernel standard deviations, of the box storm centers are drawn from beyond the query locations’ own extent. Must be finite and strictly positive.
- Returns:
Field(s) at the locations. Shape
(len(locations),)ifsizeisNone, else(size, len(locations)).- Return type:
size(when supplied) andn_stormsare exact non-Boolean positive integers; zero does not represent a valid request for a field or a storm process.
- approximation_diagnostic(n=2000, probability_levels=(0.25, 0.5, 0.75, 0.9), **sample_kwargs)[source]
Empirically estimate how close this sampler’s truncated simulation is to true unit-Frechet margins.
Draws
nreplicate fields (withsample_kwargsforwarded tosample(), so a specificn_storms/tol/box_sigmasetting can be checked) and compares their empirical CDF, at each site and each requested probability level, against the true unit-Frechet CDF – seeFrechetApproximationDiagnostic.nis an exact non-Boolean positive integer andprobability_levelsis a nonempty finite one-dimensional sequence strictly inside(0, 1).
- class FrechetApproximationDiagnostic(n_replicates, probability_levels, max_abs_error, mean_abs_error, per_site_max_abs_error)[source]
Bases:
objectEmpirical check of how close a truncated
SmithMaxStableSamplerdraw is to true unit Frechet margins.At each site and each requested probability level
p, compares the empirical CDF of simulated draws (evaluated at the true unit-Frechet quantile forp) againstpitself – a QQ/PP-plot-style discrepancy. Both summary statistics should shrink toward 0 as the sampler’s truncation is loosened (smallertol, largerbox_sigma/n_storms) and should NOT shrink just from raisingnalone (nonly reduces Monte Carlo noise in the estimate of a possibly nonzero bias).- Parameters:
- n_replicates
number of simulated draws the diagnostic was computed from.
- Type:
- max_abs_error
worst-case
|empirical_cdf(q_p) - p|over every (site, level) pair.- Type:
- mean_abs_error
mean
|empirical_cdf(q_p) - p|over every (site, level) pair.- Type:
- per_site_max_abs_error
worst-case error at each site, shape
(n_locations,).- Type:
- fit_smith_maxstable(locations, fields)[source]
Fit an isotropic Smith max-stable process (
sigma = s^2 I) to replicated spatial extremes.locationsis(n_locations, d)andfieldsis(n_replicates, n_locations)of block maxima. Estimation matches the binned empirical extremal coefficient (from the F-madogram) to the model2 Phi(|h| / (2 s)).This is a single-parameter (
s) fit estimated from pairwise statistics, so it is only identifiable given enough structure to compute those statistics from:at least 2 locations, so there is at least one pair (with at least one nonzero lag between some pair) to compute an empirical extremal coefficient from at all;
at least 2 replicates, so the per-location rank transform is not degenerate – with exactly 1 replicate every location’s rank trivially collapses to a single point, making the empirical extremal coefficient identically 1 (full dependence) for every pair regardless of distance, which is not a real dependence signal.
Returns a
SmithMaxStableFit(the fitted model plus fit-quality diagnostics); raisesValueErrorif the inputs are malformed or too small to attempt a fit at all.
- class LedoitWolfEstimator(dim=None, name=None, keys=None)[source]
Bases:
ParameterEstimatorEstimate a multivariate Gaussian with a Ledoit-Wolf-shrunk covariance.
Returns a
MultivariateGaussianDistributionwhose mean is the sample mean and whose covariance is shrunk toward a scaled identity by the data-driven Ledoit-Wolf intensity. The chosen intensity is exposed on the returned distribution asdist.shrinkagefor inspection. When the accumulated sufficient statistics carry too little effective weight or no identifiable dispersion, raisesLedoitWolfInsufficientData.- accumulator_factory()[source]
Return an accumulator factory for streaming Ledoit-Wolf sufficient statistics.
- Return type:
LedoitWolfAccumulatorFactory
- estimate(nobs, suff_stat)[source]
Estimate a Gaussian distribution from accumulated Ledoit-Wolf sufficient statistics.
- Raises:
LedoitWolfInsufficientData – If the accumulated weight is below
_MIN_EFFECTIVE_COUNTor the observations contain no numerically identifiable dispersion.ValueError – If the shrinkage computation itself produces a non-finite mean or covariance despite adequate, already-validated sufficient statistics (a numerical-overflow defect, not an insufficient-data condition). A substantively non-positive-semidefinite result is rejected by
MultivariateGaussianDistribution’s own construction, which this relies on as the final PSD gate.
- Return type:
MultivariateGaussianDistribution
- exception LedoitWolfInsufficientData(reason, effective_count, dim)[source]
Bases:
ValueErrorSpecific estimation error raised when the data cannot identify covariance.
effective_countanddimlet a caller decide whether to accumulate more data and retry. Raising, rather than returning a foreign non-distribution object, preserves the commonParameterEstimatorresult protocol.
- cost_curve(depth, grade, throughput, *, params)[source]
Parametric mining + processing cost in $/t, as a function of depth, grade, and throughput.
depth,grade, andthroughputare broadcastable array-likes (one entry per block or per scheduling period; scalars broadcast against the others).paramsrecognizes (all optional, defaulting to zero/no-effect):base_cost: $/t floor cost.haul_cost_per_m: $/t per metre of depth – haulage and pumping/dewatering cost, modeled as linear in depth, so the curve is strictly increasing indepthwhenever this is positive.grade_complexity_coef: $/t scale of a1 / grademetallurgical-complexity penalty – lower-grade ore needs proportionally more material handled and processed per unit of recovered metal, so this term falls as grade rises.throughput_scale_coef/design_capacity: the plant has one throughput,design_capacity(Q*), at which fixed costs are spread most efficiently; cost rises quadratically away from it in either direction –throughput_scale_coef * ((Q - Q*) / Q*) ** 2– capturing both under-utilized fixed-cost drag belowQ*and overtime/expediting/accelerated-wear cost above it (the classic “decreasing then rising past design capacity” U-shaped average-cost curve).
depthmust be finite and non-negative (metres below surface/datum; there is no such thing as negative haul depth);gradeandthroughputmust be finite and strictly positive (both are also divisors elsewhere in the mine-planning stack, and a zero or negative ore grade or throughput is not a physical plan). Every numericparamsentry must be finite, and the cost-coefficient entries (base_cost,haul_cost_per_m,grade_complexity_coef,throughput_scale_coef) must be non-negative – a negative coefficient would let one additive term of the curve go negative and silently discount an otherwise non-negative total cost into a subsidy (MXR-080-0119). RaisesValueErrorif any of these are violated.Returns the elementwise $/t cost, broadcast to the common shape of the three inputs.
- capex_opex(plan, *, params)[source]
Roll a mine plan’s tonnage/depth/grade/throughput profile up into (total capex, total opex).
planis a mapping or attribute-bearing object exposing, per scheduling period:tonnage: array-like, tonnes mined/processed each period (required).depth,grade,throughput: array-likes (or scalars, broadcast againsttonnage) fed tocost_curve()to get each period’s $/t.capex_schedule(optional): array-like of lumpy capital spend per period (e.g. pre-strip, plant construction, fleet purchases); summed into total capex on top of the params below.
paramsis passed through tocost_curve()for the opex side, plus two capex-only keys:capex_fixed(one-off, tonnage-independent capital) andcapex_per_tonne(sustaining capital that scales with total tonnage mined over the plan).Total opex is
sum(tonnage * cost_curve(depth, grade, throughput, params=params)); total capex iscapex_fixed + capex_per_tonne * sum(tonnage) + sum(capex_schedule). Returns(capex, opex), both plain floats – the totalsmonte_carlo_npv()(J2) discounts into a DCF, and the same $/t curve this function calls is what feeds block_cost for H’s optimizers.tonnagemust be finite and non-negative and have at least one period;depth/grade/throughputmust each be a scalar or sharetonnage’s exact shape (see_require_scalar_or_period_shape());capex_schedule, if given, must be finite and non-negative. A negative tonnage or a negative cost/capital term would otherwise turn modeled spending into modeled revenue with no error (MXR-080-0119); raisesValueErrorif violated.
- class NPVDistribution(samples, mean, p10, p50, p90, sensitivity)[source]
Bases:
_NPVDistributionFieldsA Monte-Carlo DCF outcome: the full NPV sample, not just a point estimate.
samplesis the length-narray of per-draw NPVs (real dollars, one entry per Monte-Carlo trial) thatvalue_at_risk()/conditional_value_at_risk()(J5) consume directly.mean/p10/p50/p90are the usual project-finance summary of that distribution.sensitivitydecomposes the NPV variance into the grade- and price-uncertainty contributions, as Sobol first-order variance-based sensitivity indices (seemonte_carlo_npv()and_sobol_first_order_share()): keyed"grade"/"price"(each factor’s fraction of total variance explained on its own, mathematically in[0, 1]and not required to sum to1– NPV is multiplicative in grade and price, so some variance is a grade/price interaction effect attributed to neither) plus the raw"grade_variance"/"price_variance"/"total_variance".Construction validates
samples: non-empty and finite (no NaN/Inf) – defense-in-depth so invalid state can never flow downstream to a caller even if some upstream computation (here,monte_carlo_npv()) fails to validate its own inputs, the same guard applied tocarcinogenic_risk.RiskQuantityand the_SampleDerivedQuantitycarriers elsewhere inmixle.analysis.
- monte_carlo_npv(posterior, price_paths, cost_model, schedule, *, discount_rate, n=10000, rng)[source]
Monte-Carlo discounted-cash-flow NPV distribution (DR-ALG J2).
Draws
ngrade realizations off the IC-1posteriorand pairs them, draw for draw, withn(resampled/aligned as needed) price scenarios fromprice_paths; for each draw and each of theschedule’s periods,cashflow_t = tonnage_t * grade_t * price_t - opex_t - capex_t, discounted atdiscount_rate(period0undiscounted, periodtdivided by(1 + discount_rate) ** t) and summed into oneNPVper draw:posterior: an IC-1 Posterior (frozen mixle.reason.posterior_protocol.Posterior); its.samples(n, rng)are the grade draws. A single project-life grade (d == 1) broadcasts across every period; a per-period posterior (d == len(schedule)) is used period by period.price_paths: a scenario-major(m, n_periods)array-like (one row per scenario), OR a J1PriceForecast.pathspassed directly –(n_periods, m), time-major – detected and transposed automatically (see_align_price_paths()). Resampled with replacement tonrows whenm != n.cost_model: declares exactly one supported positional signature, eithercost_model(t, tonnage_t)orcost_model(t), to get that period’s deterministicopex_t. Signature binding selects the form before execution; an interface accepting both is rejected as ambiguous rather than guessed.schedule: per-periodtonnage(required) andcapex(optional, default zero); see_unpack_schedule().len(schedule)’s tonnage vector fixesn_periods.discount_rate: the DCF discount rate per period. Must be finite and strictly greater than-1–discount_rate <= -1makes the per-period discount factor1 / (1 + discount_rate) ** tdivide by zero (at exactly-1) or alternate sign every other period (below-1), neither of which is a coherent discount rate.n/rng: Monte-Carlo draw count and the shared numpy.random.Generator.nmust be a positiveint(not merely a positive-valued float);posterior.samples(n, rng)is required to return exactlyndraws, checked explicitly rather than trusted – a posterior that returns fewer (e.g. a single row) used to silently numpy-broadcast across every price scenario, fabricatingn“independent” draws that all shared one real grade sample (MXR-080-0118).
Every numeric input – the grade draws,
price_paths,schedule’stonnage/capex, andcost_model’s returnedopex– must be finite and non-negative; raisesValueErrorif any of these,n, ordiscount_rateare violated, before any DCF arithmetic runs.Returns an NPVDistribution with the raw
samples,mean/p10/p50/p90, and asensitivitydict decomposing NPV variance into grade vs. price contributions as Sobol first-order variance-based sensitivity indices (Saltelli/Jansen two-independent-sample estimator; see_sobol_first_order_share()) – each factor’s own share of the total NPV variance, estimated from a SECOND joint sample independent of the onesamples/mean/p10/p50/p90are built from, so the indices stay meaningful (population-bounded in[0, 1]) even when a caller’sprice_pathshappen to be paired against the posterior’s draws in a way that makes the actual joint sample’s own variance small (MXR-080-0117). Not a full ANOVA decomposition (grade and price’s interaction/second-order effect, if any, is not itself reported), but each reported share is individually a real, correctly-estimated first-order Sobol index.
- class EmissionFactors(scope1, scope2, scope3, sigma=None)[source]
Bases:
objectPer-activity-key CO2e emission factors, one dict per GHG-Protocol scope.
scope1/scope2/scope3map an activity key (e.g."diesel_L","grid_kWh","explosives_kg","transport_t_km") to a CO2e factor expressed per unit of that activity (e.g. kg CO2e per litre of diesel). A key absent from a scope’s dict simply does not contribute to that scope.sigmaoptionally gives the standard deviation of each factor (not the activity quantity itself), keyed the same way across all three scopes, for Monte-Carlo uncertainty propagation inemissions_footprint(); a key with no entry insigmais treated as exactly known (std 0).
- class Footprint(scope1, scope2, scope3, total, ci=None, provenance=<factory>)[source]
Bases:
objectA Scope 1/2/3 CO2e footprint with an optional 90% credible interval and full provenance.
scope1/scope2/scope3/totalare in the same physical CO2e units as the emission factors (typically kg CO2e / tCO2e).ciis the(lo, hi)90% Monte-Carlo interval ontotalwhen factor uncertainties were propagated, elseNone(the default – a caller that only needsclimate_terms()’ point costs can construct one withoutci/provenance).provenanceidentifies both inputs to theactivity x factorsproduct, plus how the interval was produced:factor_source; the 64-hexactivity_content_hashandfactor_content_hash(sha256 of the canonical activity / factor-model encodings, the same hashing convention IC-2 uses for field artifacts);factor_keys(the union of activity keys the factor model prices, for a readable diff when the two hashes disagree); thescopesactually included intotal; anddraws/uncertainty_propagateddescribing the Monte-Carlo configuration behindci. Hashing the activity alone was not enough to distinguish two runs: the same schedule costed against a supplier-specific factor set and against a national average is a different footprint with different provenance.The record is frozen: a reported footprint is evidence, and rebinding
totalorscope2after construction would leave the numbers, the interval and the provenance describing three different computations.ciis normalized to a(lo, hi)tuple andprovenanceto a dict this record owns, so a later edit to the caller’s own dict cannot retroactively rewrite what a footprint claims it was computed from.That copy is DEEP (MXR-080-1900).
dict(provenance)detaches only the top level, so a nested container –provenance["scopes"]as a list, a nested factor-model dict – stayed aliased to the caller’s own object and could be rewritten after the footprint was reported. Deliberately NOT checked here: thattotalequalsscope1 + scope2 + scope3. The producer always makes it so, but the record is documented as constructible directly for callers that only needclimate_terms()’ point costs, and the test-suite already builds partially-specified footprints; a sum invariant would refuse states the library legitimately accepts.
- emissions_footprint(activity, factors, *, scopes=(1, 2, 3), n=0, rng=None)[source]
Scope 1/2/3 CO2e footprint of a production
activityschedule.Each
activitykey (e.g.diesel_L,grid_kWh,explosives_kg,transport_t_km) is priced by the corresponding factor in whichever offactors.scope1/scope2/scope3includes that key: Scope 1 is direct combustion/blasting, Scope 2 is purchased electricity, Scope 3 is upstream reagents plus downstream transport.scopesselects which of the three scopes are actually included in the returned footprint (a scope not requested reports0.0and does not contribute tototal– e.g.scopes=(1, 2)for a Scope-1/2-only disclosure).If
n > 0andfactors.sigmais supplied, each priced factor is treated asNormal(mean=factor, std=sigma.get(key, 0.0))and resampledntimes (factors with nosigmaentry stay fixed); the resulting distribution oftotalyields a 90% credible interval inci. Without both a positivenand a non-emptysigma,ciisNone– the point total is still returned, just without an uncertainty band.provenancealways fingerprints both factors of theactivity x factorsproduct –activity_content_hashANDfactor_content_hash(64-hex sha256 over the canonical encodings, IC-2’s hashing convention) – alongsidefactor_keys,scopes,drawsanduncertainty_propagated. A downstream carbon-cost or transition-risk term (L3/L6) can therefore be traced back to the exact activity schedule and the exact factor table it was priced with; recording only the activity meant a footprint restated against a revised or supplier-specific factor set was indistinguishable from the original by its provenance alone.scopesmust not contain duplicate entries (each GHG-Protocol scope may appear at most once) – a duplicate raises rather than being silently summed once on the point-total path and once per occurrence on the Monte-Carlo path, which previously left the point estimate and the credible interval describing two different footprints.activityvalues must be finite and nonnegative;factors(scope1/scope2/scope3) values must be finite;factors.sigmavalues, when supplied, must be finite and nonnegative – a negative sigma is rejected rather than silently treated as an exactly-known (zero-uncertainty) factor.nmust be an exact non-Boolean nonnegative integer; zero explicitly requests the point estimate without an interval. All of this is validated before either the point-total or the Monte-Carlo path runs.
- class TransitionRiskResult(samples, prior_dominated, scenario_mean, ranking, carbon_cost, provenance)[source]
Bases:
objectThe carbon-adjusted NPV distribution across carbon-price/policy scenarios (L3).
Satisfies the frozen
mixle.reason.posterior_protocol.DerivedQuantitystructural protocol –samples,prior_dominated,credible_interval– so a carbon-adjusted value can flow anywhere a DerivedQuantity is expected (J5 tail risk, J2 re-valuation).samplesis shaped(n, k): thenbaselinenpv_samplesdraws, each re-priced under every one of thekcarbon-price scenarios (one column per scenario) – the re-ranking below stays uncertainty-aware rather than collapsing straight to a point estimate.prior_dominatedis alwaysFalse: there is no prior/regulariser here, the distribution’s width is set entirely bynpv_samples.Beyond the protocol,
scenario_mean(per-scenario mean carbon-adjusted NPV),ranking(scenario indices sorted best -> worst byscenario_mean), andcarbon_cost(the priced-and-discounted carbon cost subtracted from each scenario) carry the scenario-level comparisontransition_risk()exists to produce.Construction validates
samples: non-empty and finite (no NaN/Inf) – defense-in-depth so invalid state can never flow downstream to a caller even if some upstream pushforward (here,transition_risk()) fails to validate its own inputs, the same guard applied tocarcinogenic_risk.RiskQuantityand the_SampleDerivedQuantitycarriers elsewhere inmixle.analysis.The record is frozen and owns its arrays (validated copies, write-locked), so
rankingandscenario_meancannot drift out of agreement with thesamplesthey were derived from: an unfrozen record let a caller reorder the ranking or overwrite a scenario mean while every summary method kept reporting off the original draws.Freezing only stopped the drift AFTER construction, though. Construction itself accepted any finite
scenario_meanand any permutationranking, so a record could be born already contradicting its own draws –scenario_meanreporting one ordering of the scenarios whilecredible_interval(), computed offsamples, reported another (MXR-080-1900). Two agreement invariants now close that, both holding exactly for whattransition_risk()produces:scenario_meanmust equal the per-scenario (column) mean ofsamples, to a tolerance scaled by the magnitude of the draws – summation order, not disagreement, is all that is allowed to separate them.scenario_mean[ranking]must be non-increasing, i.e. the ranking really does run best -> worst. Ties may be broken in any order, sincenp.argsortgives no stable tie order and demanding a particular one would refuse rankings the producer itself emits.
Additionally, when
provenancecarries the module’s own"carbon_cost"key (the doubled representationtransition_risk()writes), it must agree with thecarbon_costfield – the two used to be free to disagree about the very cost the summaries were priced from.Deliberately NOT checked: that
samples[:, j] + carbon_cost[j]recovers one common baseline NPV across scenarios. It is true oftransition_risk()’s output, but the record is also constructed directly with independentsamples/carbon_costin this package’s own tests, so requiring it would reject states the library legitimately produces.- Parameters:
- transition_risk(footprint, carbon_price_paths, *, npv_samples, discount=None)[source]
Carbon-adjusted NPV distribution + scenario ranking under a set of carbon-price paths (L3).
For each of the
kscenarios incarbon_price_paths(a(k,)flat price or a(k, t)per-period path – see_coerce_price_paths()), the priced carbon cost isfootprint.total * sum_t(price[t] * discount[t])(discountdefaults to all-ones, i.e. no discounting, when omitted – pass period discount factors, e.g.1 / (1 + r) ** t, to match a J2monte_carlo_npvDCF). That per-scenario carbon cost is subtracted from every draw of the baselinenpv_samples(a J2 NPVDistribution.samples-shaped(n,)array), yielding an(n, k)carbon-adjusted value distribution: one re-priced NPV distribution per scenario, still carrying the original valuation uncertainty.Scenarios are ranked by mean carbon-adjusted NPV (
scenario_mean, descending: best scenario first) so a high-carbon-price/policy scenario reliably re-ranks below a low-price one, with the gap between any two scenarios scaling linearly infootprint.total– a bigger footprint pays proportionally more carbon cost under the same price paths. The returnedTransitionRiskResultsatisfies IC-1’s DerivedQuantity protocol so the re-ranking is always inspectable with a credible interval, not just a point estimate; it feeds J5 tail risk and J2 re-valuation directly.Validated before any ranking happens:
carbon_price_pathsmust have at least one scenario and at least one period, with every price finite and nonnegative;discount, when supplied, must be finite and nonnegative;footprint.totalmust be finite; andnpv_samplesmust be a non-empty, finite, one-dimensional(n,)array (a multi-dimensional array is rejected, not silently flattened). Letting any of these through used to allow NaN scenario means and a meaningless ranking to construct successfully, failing only later when a caller requested a credible interval.
- class WaterBudget(*args, **kwargs)[source]
Bases:
ProtocolStructural stand-in for L2’s WaterBudget (mixle-pde) – see the module’s repo-boundary note.
Never constructed here; a real
WaterBudget(or anything shaped like one) is passed in and duck-typed viagetattr(water, ..., default)throughout this module, so a caller-supplied object satisfying onlyshortfall_m3(the one required field) still works – the optional fields default to absent, matching the “or None if absent” fallbacks already documented on_water_demand_m3()and_shortfall_time_fraction().
- climate_terms(footprint, water, *, carbon_price, water_limit_m3=None)[source]
Fold an emissions footprint and a water budget into J6’s carbon cost + H4’s water constraint.
Returns
{"carbon_cost": float, "water_feasible": bool | None, "water_binding": bool | None, "shortfall_time_fraction": float | None}:carbon_cost = footprint.total * carbon_price– the priced carbon term J6’s objective (analysis/valuation.py) subtracts alongside capex_opex’s cost roll-up. Always a definite float; it does not depend on water evidence.water_bindingis set when the L2 WaterBudget.shortfall_m3 > 0 (the routed water balance already ran dry at some point over the plan) OR when a caller-suppliedwater_limit_m3is given and the budget’s demand exceeds it;water_feasibleis its negation – a hard constraint H4 (stochastic_opt.py) folds into a per-option cost (derating or dropping the option) rather than into the mean-grade objective. Either can come backNone(unknown) instead of a definitebool– see below.shortfall_time_fractionis the fraction of the water budget’s own per-step storage trajectory sitting at a binding zero (_shortfall_time_fraction()) – a deterministic duration/occupancy statistic over ONE trajectory, reported independently ofwater_limit_m3even for options that are feasible on average but fragile. This is NOT a probability: it carries no uncertainty information. A genuine cross-realization probability requires an explicit ensemble of trajectories (_shortfall_probability_over_ensemble()), which is not available from a singlewaterobject.
Missing or non-finite water evidence is an explicit UNKNOWN state, never a permissive pass:
When
waterisNone(no water budget available for this option – e.g. an inland option with no catchment exposure),water_feasible,water_binding, andshortfall_time_fractionare allNone: climate risk for that option is carbon-only, but the water terms are reported as “not evaluated”, not silently “known feasible”.When
shortfall_m3, the water-limit demand, orwater_limit_m3itself is non-finite (NaN/inf) or negative, that check contributesNone(unknown) rather than silently comparing and passing as non-binding. All three are physical volumes in m3 and cannot be negative under the documented contract, so a negative one is malformed evidence rather than evidence of comfortable headroom (or, for a negative demand/limit, of a violation). The two binding checks combine via three-valued (Kleene) OR: a confirmed binding signal from either check always wins, an unknown never gets rounded down to “feasible”.When a
waterobject is supplied but carries no readable shortfall – the attribute is absent,None, a Boolean, or non-numeric – the shortfall check likewise contributesNone. This is the case the promise above used to skip (MXR-080-1900): thewater is Nonebranch abstained correctly, but a present-but-empty water object tookgetattr(..., shortfall_m3, 0.0)’s numeric default and reportedwater_feasible=True, a definite all-clear on a hard constraint from an object holding no water evidence at all.shortfall_time_fractionreportsNonein the same case rather than a confident0.0(“never short over the whole trajectory”) – unless a realstoragetrajectory is present, which is genuine evidence and is still used.
- resistance_raster(habitat, *, floor=1e-3)[source]
Per-cell movement cost from a fitted suitability field:
cost_c = 1 / max(suitability_c, floor).Reads only
habitat.mean(N1’s fitted intensity fieldlambda_c);floorkeeps near-zero suitability cells at a large-but-finite cost (near-impermeable) rather than blowing up toinf, so the resulting raster is always safe to feed straight intoleast_cost_corridor()/habitat_connectivity()without any further cleanup.- Raises:
ValueError – if
flooris not finite and strictly positive, or ifhabitat.meancontains a NaN/Inf/negative entry. An earlier revision silently clamped negative suitability into the same near-impermeable cost as zero suitability (MXR-080-0073), hiding invalid upstream model output; a negative suitability now always raises instead of being clamped. Zero suitability remains a legitimate input (clamped tofloor, as documented above).- Parameters:
habitat (HabitatModel)
floor (float)
- Return type:
np.ndarray
- least_cost_corridor(resistance, patch_a, patch_b)[source]
Cheapest movement path between two cells of a resistance raster (the least-cost corridor).
resistanceis ann-dimensional cost raster (e.g. fromresistance_raster());patch_a/patch_bare flat (row-major/np.ravel) cell indices into it. Delegates tomixle.relations.ShortestPathover the rook-adjacency grid graph with each edge weighted by_edge_cost()(mean resistance of its two endpoint cells); a cell resistance ofinf(a mined footprint,fragmentation_impact()) makes every edge touching it non-finite and so unusable, effectively removing that cell from the graph. Lower resistance = better connected.ShortestPath’s underlying engine (mixle.relations.best_first_paths) is a pure best-first search with no closed/visited set – intentional there, since it is also used to enumerate k best (not just the single best) paths, where revisiting a state via a costlier path is legitimate output. On a cyclic grid graph asked for only its single best path (k=1), that same lack of a closed set is combinatorially explosive (every back-and-forth detour is a distinct, never-pruned path). Sincesuccessors(state)is called exactly once per pop and pops come out in non-decreasing cost order, the first call for a given state is provably its cheapest arrival (Dijkstra’s invariant, valid here because every edge cost is non-negative) – so a small closed set local to this call, never touchingrelations.py, turns the search back into ordinary Dijkstra: each state’s successors are computed once and[](already settled) on every later call.
- effective_conductance(resistance, sources, sinks)[source]
Circuit-theory effective conductance between
sourcesandsinksover a resistance raster.Solves the standard Dirichlet/Laplacian boundary-value problem for landscape connectivity (McRae et al. 2008; the “Circuitscape” formulation): every
sourcescell is held at potential1and everysinkscell at potential0– equivalent to shorting each terminal’s cells together with an ideal conductor, with no arbitrary coupling constant needed (unlike a max-flow super-source/-sink). The remaining free cells’ potentials solve the reduced linear systemL_free @ v_free = -L_free,fixed @ v_fixedfor the graph LaplacianL(edge weight = conductance =1 / edge_cost,_edge_cost()); the total current flowing out of the source terminal at this unit potential difference is the effective conductance (I = G @ V,V = 1), read off directly from the Laplacian: current out of nodeiis(L @ v)[i], which is exactly0at every free node (that is what the linear solve enforces) and the true injected/withdrawn current at every fixed node.This is a genuinely different quantity from maximum flow (
max_flow_connectivity()): conductances in series add reciprocally (two unit conductances in series give an effective conductance of0.5, not1), so a max-flow solver – which only sees bottleneck capacity and ignores series resistance entirely – systematically overstates connectivity whenever current has to cross more than one edge to reach the sink. On a unit-conductance square between opposite corners this function returns the textbook value1; a max-flow solver over the same graph returns2(the sum of the two parallel corner-to-corner paths’ raw capacities, MXR-080-0071).Cells with no path at all to either terminal (an isolated island cut off by
+inf-resistance neighbours on every side) carry no current and are excluded from the linear solve rather than left to make it singular; a raster whosesourceshave no path to itssinksat all correctly yields0.0(the linear system still solves – each side simply settles to its own terminal’s potential everywhere reachable, so no potential gradient, hence no current, exists anywhere).
- habitat_connectivity(resistance, sources, sinks)[source]
Domain-friendly alias for
effective_conductance(), the circuit-theory connectivity metric.Kept under this name because it is the pre-existing public entry point N4/N6/J callers and tests already use; see
effective_conductance()for the full Dirichlet/Laplacian derivation and its McRae et al. (2008) citation. Prior to MXR-080-0071 this function actually computed maximum flow (nowmax_flow_connectivity()) despite the circuit-theory claim in its docstring – a different, systematically-inflated quantity for any network with more than one edge between source and sink. It now computes the real thing.
- max_flow_connectivity(resistance, sources, sinks)[source]
Maximum-flow connectivity between
sourcesandsinksover a resistance raster’s conductance graph.Builds a conductance graph (arc capacity
1 / edge_costbetween rook-adjacent cells) with a super-source oversourcesand a super-sink oversinks, then returnsmixle.relations.max_flow()’s value between them – the maximum flow (bottleneck-limited throughput) the habitat network can carry from the source patch(es) to the sink patch(es).This is NOT the circuit-theory effective-conductance quantity (
effective_conductance()/habitat_connectivity()): max flow only sees the tightest cut and entirely ignores series resistance, so it systematically overstates connectivity whenever current must cross more than one edge to reach the sink (MXR-080-0071). This function exists as a distinctly-named, independently useful bottleneck-throughput metric in its own right – never use it as a stand-in for the other.
- fragmentation_impact(resistance, footprint_mask, sources, sinks)[source]
Habitat-connectivity impact of a development footprint: baseline vs. disturbed corridor/connectivity.
Sets every
footprint_maskcell’s resistance toinf(impassable / zero conductance – an H3 mine plan, in this module’s worked instantiation) and recomputes bothleast_cost_corridor()(between the firstsources/sinkscell, taken as the representative patch-to-patch corridor endpoints – a judgment call: the public API only takes cell sets here, not a single designated pair) andhabitat_connectivity()(over the fullsources/sinkssets) before and after. This is the population-viability proxy N6/J feed into the biodiversity-offset objective: a footprint that severs the only corridor raisescorridor_resistanceand dropsconnectivitysharply; one that misses every real corridor leaves both essentially unchanged.- Returns:
A dict with
corridor_resistance_baseline/corridor_resistance_mined,connectivity_baseline/connectivity_mined,delta(connectivity_baseline - connectivity_mined, i.e. connectivity lost), andmincut_edges– the baseline network’s minimum-cut arcs (mixle.relations.min_cut()), i.e. the single weakest link a footprint would need to sever to maximally damage connectivity.- Parameters:
- Return type:
- habitat_offset_liability(plan_footprint, habitat, *, offset_ratio, unit_offset_cost)[source]
Priced biodiversity-offset liability of disturbing
plan_footprint(a J6 priced-objective term).lost_equivalents = sum_{c in footprint} suitability_c * area_c(suitability = N1’s fittedHabitatModel.mean); the liability isoffset_ratio * lost_equivalents * unit_offset_cost– an additive dollar term the same shape J6’spriced_liabilitiesalready sums for carbon/health/ remediation (workstream-J.md J6).offset_ratio=0orunit_offset_cost=0reduces this to zero, i.e. no biodiversity-offset requirement – both remain valid, meaningful inputs and are not rejected.Because
lost_equivalentsis linear in the boolean footprint, the per-cell rateoffset_ratio * unit_offset_cost * suitability_c * area_cis itself a valid per-block deduction a MILP-based optimizer (H4/J6’srisk_adjusted_plan) can net directly out of expected per-block profit – this function is the scalar evaluator for a given (candidate or solved) footprint.- Raises:
ValueError – if
offset_ratio/unit_offset_costis negative or non-finite, or if_lost_equivalentsrejectsplan_footprint/habitat. A negative ratio or cost used to silently turn habitat damage into a “profit” (MXR-080-0073); both are now rejected. Also if the priced product itself is not representable in float64 – finite inputs near the float64 ceiling used to publish an infinite liability (MXR-080-1572).- Parameters:
- Return type:
- no_net_loss_constraint(plan_footprint, habitat, *, offset_ratio)[source]
Hard no-net-loss constraint payload: created/purchased offsets >=
offset_ratio * lost_equivalents.Returns a dict carrying both the raw quantities (
lost_equivalents,per_cell_lost_equivalents,required_offset) and a solver-agnostic linear-constraint row in this repo’s standardcoeffs @ x <= boundconvention (mixle.relations/mixle.stochastic_opt’sa_ubrows), expressed over a singleoffsets_createddecision variable:coeffs=[-1.0],bound=-required_offsetencodes-offsets_created <= -required_offset, i.e.offsets_created >= required_offset. Placing that row (and theoffsets_createdcolumn it references) into the wider extraction/offset-purchase decision space is H4/J6’s job – this module never edits their MILP variable indexing, only hands them the row.Because that row is emitted ALREADY normalized into the
<=convention, its"sense"label is"<="– the label describes the row that is actually here, not the inequality the row expresses aboutoffsets_created. It used to read">="(MXR-080-1570), which made the payload internally contradictory:mixle.analysis.objective.hard_constraints()andmixle.stochastic_opt.risk_adjusted_plan()both trust the label and negate a">="row into<=form, so a second negation turned the no-net-loss FLOOR into the CEILINGoffsets_created <= required_offset– permitting zero offsets and forbidding any offset purchase above the bare minimum, i.e. exactly the opposite of a no-net-loss guarantee.- Raises:
ValueError – if
offset_ratiois negative or non-finite, or if_lost_equivalentsrejectsplan_footprint/habitat(NaN/negative suitability or area). A NaN or negativelost_equivalentsused to be able to propagate intorequired_offset, silently weakening or inverting a constraint that is supposed to be a hard floor (MXR-080-0073); both inputs are now validated before this payload is built. Also ifrequired_offsetitself is not representable in float64 – finite inputs near the float64 ceiling used to publish an infinite bound, which is not a hard constraint but an unsatisfiable row (MXR-080-1572).- Parameters:
plan_footprint (np.ndarray)
habitat (HabitatModel)
offset_ratio (float)
- Return type:
- class SpeciesObservation(species_id, detection, location, crs=None, covariates=<factory>, modality='occurrence', provenance=<factory>)[source]
Bases:
objectOne presence/absence record for a species (an IC-4
Observationspecialisation).locationis the (already discretised) study-area coordinate: for a presence record consumed byfit_sdm(), the first component oflocationis the fractional cell index ([0, K)) on the same grid as thecovariates/cell_areapassed tofit_sdm– resolving a real-worldcrs-referenced location onto that grid is covariate/CRS ingest (B-series), out of scope here.
- class HabitatModel(beta, beta_cov, design, cell_area, *, species_id, var_scale=1.0, prior_dominated=False, provenance=None)[source]
Bases:
objectFitted IPP habitat-suitability field; satisfies IC-1
Posteriorover the suitability field.beta/beta_covare the Laplace-approximate posterior over the log-linear intensity coefficients;designis the(K, p)covariate design matrix (intercept + environmental covariates);cell_areais the per-cell area used as the Poisson offset during fitting. The suitability field is the fitted intensitylambda_c = exp(design_c @ beta)(mean);samples()/cov/credible_interval()push the beta-posterior forward through the same log-link (a delta-method / lognormal approximation), scaled by a held-out-calibrated variance multiplier, so every downstream consumer (N2’s no-mine mask, N4’s resistance raster) sees one calibrated field posterior.species_idis required (MXR-080-1590): a suitability field is a statement about ONE species, and a model with no identity on it cannot be checked against the species whose legal exclusion it is being used to impose (MXR-080-1591) or traced back to the dataset it was fitted from.provenancecarries immutable input receipts (fit_sdm()fills in the occurrence, background, covariate and cell-area digests) so a published field can be tied to its source data.- Parameters:
- samples(n, rng)[source]
Draw
nintensity-field realisations by pushingbetadraws throughexp(X @ beta).- Returns:
(n, K)array of intensity-field draws.- Raises:
ValueError –
nis not a positive exact integer (MXR-080-0114).- Parameters:
- Return type:
- property mean: ndarray
Fitted intensity field
lambda_c = exp(design_c @ beta)– the suitability surface.Uses the same overflow-safe
expas fitting (MXR-080-0114): a linear predictor that never overflowed under the training-time clip can still overflow plainexp()at a different design row (e.g. an extrapolated covariate) or for a directly-constructed model.
- property cov: ndarray | LinearOperator
Delta-method covariance of the intensity field, recalibrated.
Domains with at most 2,048 cells return a dense
(K, K)array. Larger domains return a symmetricLinearOperatorbacked by the(K, p)intensity Jacobian and(p, p)coefficient covariance, avoiding quadratic field storage.- Raises:
ValueError – the Jacobian or requested covariance operation is not finite. In particular, finite model inputs can still imply a covariance outside float64’s representable range; such a matrix is not published as posterior evidence.
- credible_interval(level)[source]
Per-cell central credible interval of the suitability field (lognormal delta-method).
- Raises:
ValueError –
levelis not finite and strictly in(0, 1)(MXR-080-0114).- Parameters:
level (float)
- Return type:
- derived_quantity(fn, n, rng)[source]
Pushforward
fnovernintensity-field draws into aDerivedQuantity(IC-1).
- fit_sdm(occurrences, covariates, cell_area, *, species_id, background=None, ridge=1e-3)[source]
Fit a presence-only habitat-suitability model as an inhomogeneous-Poisson point process.
Discretizes the study area into
K = covariates.shape[0]cells, binsoccurrences(onlydetection=Truerecords) into per-cell presence counts, and fits a log-linear intensitylog lambda_c = [1, covariates_c] @ betaby maximizing the frozen IPP log-likelihood with aridge * ||beta||^2penalty and alog(area_c)offset (Poisson GLM equivalent to MaxEnt). Ifbackgroundquadrature points are given, they are binned the same way and folded into the offset as extra effective area (an effort/thinning correction for presence-only sampling bias). The returnedHabitatModel’s field variance is recalibrated on an internal held-out cell fold viamixle.analysis.kriging.calibrate_variance()so its credible intervals hit their nominal (90%) coverage.species_idnames the ONE species being modelled and is required (MXR-080-1590). Everyoccurrencesrecord must carry that same id: the fit used to bin every truthy detection regardless of which species it belonged to, so a single fitted intensity could silently pool records from unrelated species into one field that claimed to describe all of them. The id is carried onto the returned model, together with immutable input digests inprovenance, so a published scientific result can be traced back to the exact dataset it came from and so a downstream legal exclusion can check the field it is applying actually belongs to the species it is applying it for (MXR-080-1591).- Parameters:
occurrences (list[SpeciesObservation]) – presence records for
species_id; eachlocation’s first component is the fractional cell index. A record for any other species is rejected, not silently pooled.covariates (ndarray) –
(K, p - 1)environmental covariates per cell (an intercept column is prepended).cell_area (ndarray) –
(K,)per-cell area (the Poisson offset).species_id (str) – the species this field is being fitted for.
background (ndarray | None) – optional quadrature/background point locations (same cell-index convention as
occurrences), used to correct for uneven survey effort.ridge (float) – L2 penalty strength on
beta(also regularizes the Laplace covariance).
- Returns:
A fitted
HabitatModelcarryingspecies_idand input receipts inprovenance.- Raises:
ValueError –
ridgeis not finite and non-negative;covariatesis not finite;cell_areadoes not have exactly one finite, strictly positive entry per covariate row; any presence or background location is non-finite or outside the cell-index domain[0, K)(MXR-080-0111);species_idis not a non-empty string or any occurrence record names a different species (MXR-080-1590); or the internal beta fit does not converge to finite coefficients (MXR-080-0113).- Return type:
HabitatModel
- critical_habitat_exclusion(habitat, listed, *, suitability_cut, buffer_cells=0)[source]
Boolean no-mine mask over blocks (work-plan algorithm steps 1-2).
For every
listedrecord whosecritical_habitatflag is set, THAT SPECIES’ OWN fitted suitability field contributes itslambda_c >= suitability_cutcells (via IC-12’scritical_habitat_mask) to the exclusion; a record withcritical_habitat=False(a species that is tracked but has no critical-habitat designation) contributes nothing. If nolistedrecord qualifies, nothing is excluded. The per-species contributions are unioned (OR).habitatis either a singleHabitatModelor an explicit{species_id: HabitatModel}mapping, and every qualifying listed species must have a model whosespecies_idmatches it exactly (MXR-080-1591). Previously any qualifying record applied whatever single field happened to be passed in, and no comparison was made at all: a model fitted for species A could impose species B’s legal exclusion, and several listed species all collapsed onto species A’s same field. A qualifying species with no matching model raises rather than borrowing another species’ range.When a matched fit is prior-dominated (
habitat’s own honesty flag: not enough presence data has yet informed the field), there isn’t enough evidence to clear any block, so the mask is excluded conservatively in full (every block is treated as potential critical habitat) rather than optimistically reporting whatever the under-determined field happens to say.buffer_cellsconservatively dilates the resulting mask outward (_dilate_conservatively()) to approximate a regulatory buffer around a designation boundary.A real
CriticalHabitatDesignationpolygon (a regulator’s own mapped boundary, independent of the fitted suitability field) is not rasterized here: that requires resolving itsSpatialBounds(realcrscoordinates) onto this function’s abstract block-index grid, which is exactly the covariate/CRS-ingest machinery N1 scoped out (B-series, not yet landed) – see the PR notes for this documented gap. Once a grid-registration utility lands, folding a designation in is an additionalORintomaskbefore the buffer dilation, using the same conservative-inclusion rule.- Parameters:
habitat (HabitatModel | dict[str, HabitatModel]) – N1’s fitted habitat-suitability field(s) (IC-12
HabitatModel, satisfies IC-1Posterior) – one model, or a{species_id: HabitatModel}mapping.listed (Sequence[ListedSpecies]) –
ListedSpeciesrecords (mixle_knowledge.contracts);critical_habitat,species_id(to match the record to its own fitted field) and, when informativeness must be checked, the matched model’s own honesty flag are consulted here – every other field (citation, jurisdiction, listing status) is provenance the caller and downstream audit trail carry, not logic this function branches on.suitability_cut (float) – the fitted-intensity threshold passed to
critical_habitat_mask.buffer_cells (int) – conservative dilation radius, in blocks, applied after the union.
- Returns:
A
(K,)boolean array,Truewhere the block is excluded (no-mine).- Return type:
np.ndarray
- apply_habitat_constraints(network, exclusion_mask)[source]
Fold a critical-habitat exclusion mask into an IC-9-shaped network payload (work-plan algorithm step 3); H1/H4 read this, this module never calls the solver.
networkis a plain mapping over the reference block network, in exactlymixle.relations.min_cost_flow()’s frozen(cap, cost, supply)shape:"cap"/"cost"are(n, n)arc matrices,"supply"is the optional length-nnode supply vector, and"block_nodes"(optional, defaults toarange(len(exclusion_mask))) maps each block index to its node index in that network – the same convention G9’sapply_env_constraintsuses.Excluded blocks become forbidden nodes: every arc touching one has its capacity zeroed (no flow can originate from, or land back on, a no-mine block), and, if that node carried supply, the supply is zeroed too (there is nothing left to extract there). Any
nodes/arcs/fixed_costs/demandsthe caller supplied (the shapemixle.relations.network_design()itself takes) are passed through unchanged alongsideforbidden_nodes, so a fixed-charge caller can exclude the same nodes from its own arc set.Every input is validated before any exclusion is applied, so a rejected call never leaves a partially-mutated result (MXR-080-0092):
cap/costmust be equal-shape, square, and finite, andcapmust additionally be nonnegative (a negative arc capacity has no meaning);supply, when supplied, must be a finite length-nvector; andblock_nodesmust be exact, unique node ids in[0, n)(see_validate_block_nodes()for the fractional/negative/out-of-range failure modes this replaces).- Parameters:
- Returns:
cap/cost(habitat-adjusted),forbidden_nodes(sorted node-id list), andsupplywhen the caller provided one, plus any pass-through fields.- Return type:
A new dict
- Raises:
ValueError – if
cap/costare not equal-shape finite square arrays, ifcapcontains a negative entry, ifsupplyis not a finite length-nvector, or ifblock_nodesis not a length-matched array of exact, unique, in-range node ids.
- priced_liabilities(plan, *, carbon_price, health_cost, remediation_cost)[source]
Net a plan’s per-block environmental/health/carbon liabilities into one additive dollar array.
planis a mapping or attribute-bearing object exposing three per-block, length-n_blocksarray-likes:grade: the block grade (or other geology/complexity proxy) fed toremediation_cost– a G9-style environmental remediation-cost model keyed on what is actually mined.exposure: the block’s public-health exposure proxy fed tohealth_cost– a K6-style health-cost model.emissions: the block’s carbon-equivalent emissions, priced directly atcarbon_price(an L6-style market/regulatory carbon price is a multiplier, not a fitted cost model, so no callable is needed for it).
Returns a dict with per-block arrays
"remediation","health","carbon", their elementwise sum"total"(the single per-block liabilitymixle.stochastic_opt.risk_adjusted_plan()nets out of revenue), and the scalar roll-ups"remediation_total"/"health_total"/"carbon_total"/"grand_total"for reporting.Every
planfield is validated BEFORE it is handed to a cost callable (MXR-080-1573). The callables are opaque user code: a model that ignores its argument, saturates, or returns a constant turns a NaN grade or an infinite exposure into a perfectly ordinary finite cost, so the invalid source evidence disappears and the resulting liability record carries no trace that the plan it was priced from was malformed.grade/exposureare checked for finiteness only, not sign – both are declared as arbitrary geology/complexity and exposure proxies, so a negative value is a legitimate reading; it is only the prices that carry the non-negativity convention.Every assembled output is likewise validated after the fact: three individually finite per-block costs near the float64 ceiling can overflow
total, and finite per-block totals can overflowgrand_total’s reduction, publishing an infinite liability from a record that claims to be validated (MXR-080-1573).- Raises:
ValueError – if
carbon_price,plan’semissions, or either cost callable’s return value is non-finite or negative – a negative liability would net out of revenue as a profit increase rather than a cost, and a NaN would propagate unguarded into the objective – ifplan’sgrade/exposureis non-finite, if any ofplan’sgrade/exposure/emissionsor a cost callable’s return value has the wrong shape, or if the netted per-blocktotalor any scalar roll-up is not representable in float64.- Parameters:
- Return type:
- hard_constraints(*, no_mine_mask=None, caps=None)[source]
Assemble the
constraintsdictmixle.stochastic_opt.risk_adjusted_plan()consumes.no_mine_mask: an optional boolean array-like, lengthn_blocks.Trueentries are a G9-style no-mine/buffer-zone polygon’s enclosed blocks – hard-fixed tox_b = 0(stronger than an inequality row: those blocks can never be selected, regardless of the rest of the objective).caps: an optional list of linear activity caps – a K6 exposure budget, an L6 water budget, or any othercoeffs @ x <sense> boundrow over the block decision vector. Each entry is a dict with"coeffs"(length-n_blocksarray-like) and"bound"(float), plus an optional"sense"("<="by default;">="is negated into the solver’s standard<=form sorisk_adjusted_plannever has to special-case it). This is the samecoeffs/bound/sensefield namingmixle.analysis.biodiversity.no_net_loss_constraint()already uses for its own linear-constraint payload.
Returns a dict with whichever of
"no_mine_mask"/"caps"were supplied (both are omitted, i.e. an empty dict, when neither argument is given – meaning no hard constraint at all).- Raises:
ValueError – if
no_mine_maskis not a genuine 1-D boolean array (data that is merely truthy under an implicit bool cast, e.g. a float array or a NaN entry, is rejected rather than silently coerced), if any cap’s"coeffs"is not a finite 1-D array, if any cap’s"bound"is not finite, or if a cap’s"sense"is not"<="/">=".- Parameters:
- Return type:
- class OptionValue(value, exercise_boundary, premium_over_npv)[source]
Bases:
NamedTupleThe priced option, alongside the exercise policy that generated it.
valueis the total value of holding the option (project value + optionality).exercise_boundaryis the per-time-step critical underlying value at which immediate exercise first becomes optimal (nanat any step where it is never optimal to exercise).premium_over_npvisvalueminus the naivenpv_dist.mean– the dollar amount the flexibility to defer/expand/abandon is worth over just committing to the point-estimate NPV today.- value: float
Alias for field number 0
- exercise_boundary: ndarray
Alias for field number 1
- premium_over_npv: float
Alias for field number 2
- real_option_value(npv_dist, *, volatility, horizon, kind='defer', rate, n_steps=None, expand_fraction=0.3, expansion_cost=0.0, salvage_value=0.0)[source]
Price the defer/expand/abandon option on a project via an additive binomial lattice on NPV.
The NPV process is modeled as a driftless arithmetic random walk started at
npv_dist.mean: each ofn_steps(default:max(horizon, 1)) steps up tohorizonmoves the value by+-hwithh = volatility * scale * sqrt(dt),scale = abs(npv_dist.mean)(or1.0if the mean is exactly zero, sovolatilityis never degenerate) – i.e.volatilityis a fractional per-sqrt-period dispersion relative to the project’s own scale, matching how the parameter is usually quoted (e.g.0.3~ “30% dispersion”), while still letting the walk go negative the way a signed NPV must be able to. The risk-neutral measure is taken driftless (an NPV is already the discounted expectation of a self-financing project, so there is no further risk-neutral drift to add);rateonly discounts the continuation value, which is what makes immediate exercise optimal once volatility vanishes. At every node the holder may exercise (payoff via_intrinsic(), depending onkind) or continue holding the option; the American value is the larger of the two, by backward induction from the horizon.- Parameters:
npv_dist (NPVDistribution) – anything exposing
.mean(a float) – typically J2’sNPVDistribution. Only the mean is used;real_option_valueprices the option on top of the point estimate, not a re-derivation of the distribution itself. Must be finite.volatility (float) – fractional (per-sqrt-period) dispersion of NPV around its mean. Must be finite and
>= 0;0means no dispersion and the option collapses to immediate exercise ofkind’s own payoff onnpv_dist.mean(_intrinsic()) –max(npv_dist.mean, 0)for"defer",max(npv_dist.mean, salvage_value)for"abandon",npv_dist.mean + max(expand_fraction * max(npv_dist.mean, 0) - expansion_cost, 0)for"expand".horizon (int) – number of periods over which the option may be exercised.
0means “decide now”. Must be a non-Boolean integral scalar>= 0.kind (str) – one of
"defer","expand","abandon".rate (float) – per-period discount rate applied to the continuation value. Must be finite.
n_steps (int | None) – lattice steps (defaults to
max(horizon, 1), i.e. one step per period). Must be a non-Boolean positive integral scalar when given explicitly.expand_fraction (float) – for
kind="expand", the fractional capacity bonus applied to a positive underlying value when the expansion is exercised. Must be finite and nonnegative.expansion_cost (float) – for
kind="expand", the investment cost of actually exercising the expansion, netted againstexpand_fraction’s capacity bonus – without it, a positive-NPV project always “expands” for free and there is no real exercise decision being priced (see_intrinsic()). Ignored for other kinds. Must be finite and nonnegative; default0.0(no cost, matching this function’s behavior beforeexpansion_costexisted).salvage_value (float) – for
kind="abandon", the value recovered by abandoning the project instead of riding out its NPV, replacing the previous hardcoded assumption of a total write-off. Ignored for other kinds. Must be finite; default0.0(total write-off, matching this function’s behavior beforesalvage_valueexisted).
- Returns:
An
OptionValue.- Return type:
OptionValue
- class GaussianObservationModel(obs_matrix, obs_cov)[source]
Bases:
NamedTupleA declared linear-Gaussian observation model:
y = obs_matrix @ theta + noise,noise ~ N(0, obs_cov), for a latentthetadistributed as the current posterior’s(mean, cov).MXR-080-0109: the opt-in heuristic invents future-posterior centers/spreads by rescaling draws from the CURRENT posterior; it never samples an observation from a likelihood/forward model and conditions on it, so it is not a real Bayesian preposterior for non-Gaussian, multimodal, or constrained beliefs. This is the simplest well-defined experiment likelihood for which the preposterior – the distribution of what TOMORROW’s posterior could look like, before the observation is actually taken – has a closed form: conjugate-Gaussian updating. Passing this to
voi_dollars()/voi_estimate()(asobservation_model=) is what makes them compute a genuine expected value of sample information (EVSI): simulate a hypotheticalyfrom theta’s actual prior-predictive marginal, analytically condition the CURRENT posterior on thatyto get a real future posterior, decide against it, and average over many simulatedy– rather than the opt-in heuristic’s rescaling of today’s draws (seevoi_estimate()’s docstring for why that heuristic is not a real preposterior in general).obs_matrix(H, shape(k, d)) andobs_cov(R, shape(k, k), symmetric positive definite) together say: a real experiment would return ak-dimensional noisy linear readout of thed-dimensional latent quantity the posterior is over (d=posterior.mean’s length). This path requiresposterior.mean/posterior.covto fully characterize the belief – i.e. a genuinely Gaussian posterior – since that is what makes the conjugate update exact; a denseposterior.covis required (a matrix-freeLinearOperatorcovariance is not supported here).obs_matrix = 0(orobs_covlarge) declares a literally uninformative experiment – VOI comes back exactly zero for that case, not just approximately (seevoi_estimate()’s docstring).- obs_matrix: ndarray
Alias for field number 0
- obs_cov: ndarray
Alias for field number 1
- class VoiEstimate(value, standard_error, ci_low, ci_high, method)[source]
Bases:
NamedTupleA Monte Carlo value-of-information estimate, reported honestly alongside its own sampling noise (MXR-080-0108: independent no-info/with-info draws made a should-be-zero difference noisy, and flooring that noise at zero upward-biased it; see
voi_estimate()’s docstring for the fix).valueis the point estimate,E[value | info] - E[value | no info]– NOT floored at zero (seevoi_estimate()’s docstring for why flooring a noisy estimate at zero systematically biases it upward, rather than merely rounding it).standard_erroris the paired-difference Monte Carlo standard error ofvalueacross then_outerhypothetical-information replicates (paired because each replicate’s with-info and no-info evaluations share their underlying random draw – seevoi_estimate()).ci_low/ci_highare an approximate two-sided 95% confidence interval (value +- 1.96 * standard_error; a normal approximation, not exact for smalln_outer). Whenci_low <= 0 <= ci_high,valueis statistically indistinguishable from “no additional value” – a more honest read than a bare point estimate that happens to be (noisily) positive or negative.methodnames which computation produced the estimate:"variance_rescaling_heuristic"(an explicit opt-in Gaussian-dispersion approximation, exact only when the posterior is close to Gaussian, unimodal, and unconstrained) or"gaussian_conjugate_evsi"(a genuine sample-then-condition preposterior computation under a declared linear-Gaussianobservation_model).- value: float
Alias for field number 0
- standard_error: float
Alias for field number 1
- ci_low: float
Alias for field number 2
- ci_high: float
Alias for field number 3
- method: str
Alias for field number 4
- voi_estimate(posterior, decision_fn, drill_info, *, rng, n_outer=64, n_inner=256, observation_model=None)[source]
Value of information, honestly: the point estimate together with its own Monte Carlo uncertainty.
decision_fnmaps a set of posterior draws (an(n, d)array, physical units) to the dollar value of the single best decision made using that belief state – e.g. a risk-neutral go/no-go choice ismax(samples.mean(), 0), not an average of a per-draw payoff: the latter implicitly assumes the realization is already known, which is exactly the perfect-information case this function is pricing the gap to, not the belief itself.Two methods are available:
observation_model=Nonetogether with explicitdrill_info["method"] == "variance_rescaling_heuristic": a pre-posterior Monte Carlo built on the law of total variance, NOT real Bayesian conditioning on a simulated observation. Today’s posterior variance splits into “where the post-drill posterior will be centered” (unknown until the drillhole is actually put in) and “how much spread remains once it lands there.” If the drillhole is expected to remove a fractionrof variance (_variance_reduction()), then forn_outerhypothetical drill outcomes a center is drawn with standard deviation scaled bysqrt(r)around today’s posterior mean (how much the belief could plausibly shift), and a shared base draw (see “Common random numbers” below) is re-centered there and rescaled bysqrt(1 - r)for the remaining spread. This INVENTS future-belief “centers” by rescaling today’s draws rather than drawing them from an actual forward/likelihood model, so it is accurate only when the posterior is (close to) Gaussian, unimodal, and unconstrained – outside that regime it is flagged with aUserWarning(see_warn_if_not_gaussian_like()), not silently trusted.observation_model=aGaussianObservationModel(method="gaussian_conjugate_evsi"): a genuine sample-then-condition preposterior. A hypothetical observationyis simulated from the latent’s real prior-predictive marginal (y = H @ theta + noisefortheta ~ N(posterior.mean, posterior.cov)), the CURRENT posterior is analytically conditioned on thatyvia closed-form conjugate-Gaussian updating to get a genuine future posterior, and the decision is re-made against it – averaged over many simulatedyforE[value | info]. This is the textbook EVSI construction for the declared-likelihood case, but it still requires the PRIOR (posterior.mean/posterior.cov) to be genuinely Gaussian for the conjugate update to be exact – also screened by the same warning.
Common random numbers (MXR-080-0108): each of the
n_outerreplicates draws ONE base/innovation sample and uses it for BOTH the no-info decision and the with-info (refined) decision for that replicate, differing only by a deterministic transform (the heuristic’s re-centering/rescaling, or the conjugate update’s affine map) – not independent draws for the two sides. This matters when there is truly no information to gain (r = 0for the heuristic;obs_matrix = 0orobs_covarbitrarily large for the declared-likelihood path): with independent draws, the no-info and with-info values would be two separately noisy estimates of the same quantity, so their difference would be noise centered at zero; flooring that noise at zero (the previous behavior) kept only its positive half, upward-biasing the reported VOI (a standard-normal fake posterior with a go/no-go decision spuriously returned positive VOI at zero information for most tested seeds). With a shared draw, the with-info value is bit-for-bit identical to the no-info value whenever there is truly no information (checked directly for the heuristic via an analytic short-circuit below, not just left to fall out of the Monte Carlo – and provably exact by construction for the declared-likelihood path too), and the two stay correlated – hence lower-variance in their difference – away from that boundary as well.- Parameters:
posterior (Posterior) – the current (pre-drill) belief, satisfying IC-1’s
Posteriorprotocol.decision_fn (Callable[[ndarray], float]) – belief state (
(n, d)draws) -> expected dollar value of the best decision.drill_info (dict[str, Any]) – describes the hypothetical drillhole. Recognized keys:
method(must explicitly be"variance_rescaling_heuristic"when noobservation_modelis supplied),variance_reduction(direct fractional variance reduction, default0.5), orcandidate_geometry+forward_op(+ optionalregion/cell_volumes) to route through the C8 VOI hook when available;n_outer_samples/n_inner_samplesoverride the Monte Carlo sample counts. Ignored (aside from the sample-count overrides) whenobservation_modelis given.rng (Generator) – seeded random generator for reproducibility.
n_outer (int) – number of hypothetical-information replicates (overridden by
drill_info).n_inner (int) – posterior draws per replicate (overridden by
drill_info).observation_model (GaussianObservationModel | None) – when given, a declared
GaussianObservationModelroutes the computation through the genuine"gaussian_conjugate_evsi"path instead of the opt-in heuristic; see above. Requires a dense (non-LinearOperator)posterior.cov.
- Returns:
the (unfloored) point estimate plus its Monte Carlo uncertainty.
- Return type:
A
VoiEstimate
- voi_dollars(posterior, decision_fn, drill_info, *, rng, n_outer=64, n_inner=256, observation_model=None)[source]
The value-of-information point estimate, in dollars:
E[value | info] - E[value | no info].A thin convenience wrapper around
voi_estimate()for callers that only want the point estimate; see its docstring for the full method (the opt-in heuristic vs. the genuineobservation_modelEVSI path, common random numbers, the zero-information analytic equality, and whatmethodthe estimate used) and for how to also get the Monte Carlo standard error / CI.- Parameters:
posterior (Posterior) – forwarded to
voi_estimate()unchanged.decision_fn (Callable[[ndarray], float]) – forwarded to
voi_estimate()unchanged.drill_info (dict[str, Any]) – forwarded to
voi_estimate()unchanged.rng (Generator) – forwarded to
voi_estimate()unchanged.n_outer (int) – forwarded to
voi_estimate()unchanged.n_inner (int) – forwarded to
voi_estimate()unchanged.observation_model (GaussianObservationModel | None) – forwarded to
voi_estimate()unchanged.
- Returns:
The value-of-information point estimate, in dollars. NOT floored at zero – see
voi_estimate()’s docstring for why a floor is dishonest, not merely conservative; this can be a small negative number when the true VOI is at or near zero, which is the honest reflection of Monte Carlo noise, not a defect. Usevoi_estimate()if you need to tell that case apart from a real, larger difference.- Return type:
- class VoiStoppingDecision(voi_dollars, sample_cost, net_value, keep_sampling, standard_error)[source]
Bases:
NamedTupleA real decision-theoretic answer to “should we sample again?”: compare the value of one more sample against its cost, rather than an arbitrary uncertainty threshold picked by hand.
standard_erroris the underlyingVoiEstimate’s Monte Carlo standard error, carried through so a caller can tell a close-to-the-costnet_value(statistical noise) apart from a real, larger gap – the same honestyvoi_estimate()reports at the VOI level (MXR-080-0108/0110).- Parameters:
- voi_dollars: float
Alias for field number 0
- sample_cost: float
Alias for field number 1
- net_value: float
Alias for field number 2
- keep_sampling: bool
Alias for field number 3
- standard_error: float
Alias for field number 4
- voi_stopping_decision(posterior, decision_fn, drill_info, *, sample_cost, rng, n_outer=64, n_inner=256, observation_model=None)[source]
Should the next sample (drillhole, monitoring well, survey station, …) actually be taken?
A real, principled alternative to picking an uncertainty threshold by hand and telling an LLM loop-controller to stop when it’s cleared (the pattern used, and flagged as a real gap, in
experiments/adaptive-groundwater-monitoringandexperiments/adaptive-gravity-survey-design): sample again iffvoi_dollars()– the actual expected dollar value the next sample would add to the decision – exceeds what that sample costs. As uncertainty tightens,voi_dollarsshrinks toward zero (there’s less left to learn that would change the decision), so this converges to a stopping rule on its own without any separately-chosen threshold; the only free parameter issample_cost, which is an actual real-world number (drilling/survey cost) rather than an arbitrary uncertainty width.- Parameters:
posterior (Posterior) – forwarded to
voi_estimate()unchanged – see its docstring for what each means.decision_fn (Callable[[ndarray], float]) – forwarded to
voi_estimate()unchanged – see its docstring for what each means.drill_info (dict[str, Any]) – forwarded to
voi_estimate()unchanged – see its docstring for what each means.rng (Generator) – forwarded to
voi_estimate()unchanged – see its docstring for what each means.n_outer (int) – forwarded to
voi_estimate()unchanged – see its docstring for what each means.n_inner (int) – forwarded to
voi_estimate()unchanged – see its docstring for what each means.observation_model (GaussianObservationModel | None) – forwarded to
voi_estimate()unchanged – see its docstring for what each means.sample_cost (float) – the real dollar cost of taking the next sample. Must be finite (MXR-080-0110: an unvalidated NaN cost previously compared as
Falseagainst everything, silently forcingkeep_sampling=Falseregardless of the actual VOI).
- Returns:
the computed VOI (with its standard error), the cost it was compared against, their difference, and whether sampling should continue (
voi_dollars > sample_cost).- Return type:
A
VoiStoppingDecision
- safety_risk_surface(deformation, *, gradient_limit, slope=None)[source]
Map a deformation field into a per-cell P(tilt > gradient_limit) safety-risk surface.
- Parameters:
deformation (Posterior | np.ndarray) – an IC-1 Posterior over a flattened (d,) subsidence/deformation field (the G4 poroelastic InSAR-inversion case), or a plain np.ndarray spatial grid (any shape) of already-point-estimated deformation values.
gradient_limit (float) – the tilt/gradient magnitude above which a cell is considered geotechnically unsafe (e.g. an angular-distortion or differential-settlement limit).
slope (np.ndarray | None) – an optional static terrain-slope field, same spatial shape as the deformation grid. When given, it is added to the deformation-induced gradient magnitude before the exceedance test: a cell that is already steep needs less additional differential movement to cross gradient_limit.
- Returns:
A DerivedQuantity whose samples are the per-cell exceedance indicator (0.0/1.0) drawn over the posterior’s Monte-Carlo replicates (or a single deterministic replicate for an ndarray input), flattened in the grid’s row-major (C) order, together with a credible interval and the prior_dominated flag. The per-cell risk probability is samples.mean(axis=0).
- Return type:
DerivedQuantity
gradient_limitmust be finite and non-negative (a tilt magnitude threshold cannot itself be negative), andslope/anndarraydeformationmust be finite (MXR-080-0098): a NaN either way would otherwise compare False against> gradient_limitand silently mark an unevaluable cell as “not exceeding” instead of raising. Posterior evidence additionally requires a finite flattened mean and exactly(_MC_SAMPLES, n_cells)finite draws; the returned binary risk samples are validated independently before publication.
- incident_probability(hazard, exposure_map, *, model='logit')[source]
Combine a per-cell hazard probability surface with a people-exposure_map into incident risk.
A hazard exceedance is only a safety incident if someone is exposed to it: an empty, unstable cell and a busy, unstable cell are not the same risk. hazard is expected in [0, 1] (e.g. the per-cell mean of safety_risk_surface(…).samples); exposure_map is a non-negative people- density/occupancy weight of the same shape.
exposure_mapis on the expected people present scale: a cell’s value is the mean occupancy of that cell, so0means nobody,1means about one person on average, and larger values are denser. Both models take that scale literally and return0for an unoccupied cell.- Parameters:
hazard (ndarray) – per-cell hazard probability, shape (*grid_shape,), values in [0, 1].
exposure_map (ndarray) – per-cell non-negative expected-occupancy weight, same shape as hazard.
model (str) – “logit” (default) is a logistic link that multiplies the hazard’s own odds by the probability that anybody is actually present, 1 - exp(-exposure_map) (the Poisson occupancy probability for that mean): an unoccupied cell yields 0, denser occupancy monotonically raises the result, and it saturates at the hazard probability itself. “linear” is the simple product hazard * exposure_map clipped to [0, 1].
- Returns:
Per-cell incident probability, same shape as hazard, values in [0, 1].
- Return type:
hazardoutside[0, 1]is REJECTED, not clipped into a falsely-confident boundary probability (MXR-080-0098): the pre-fix"logit"path clipped any hazard value – 5.0, -3.0, whatever – straight into[eps, 1 - eps]before ever checking it was a valid probability to begin with. The narroweps-clip that remains below is purely for the logit transform’s own numerical stability at the exact 0/1 boundary of an ALREADY-valid probability, not a substitute for validating the input range.The
"logit"occupancy factor used to belog1p(exposure_map)added to the log-odds (MXR-080-1589). That has no zero element:log1p(0)is0, so an empty cell added no log-odds at all and hazard0.8at zero occupancy returned incident probability0.8– flatly contradicting this function’s own stated contract that an exceedance is only an incident when someone is exposed. It was also unbounded above, so a dense-enough cell reported MORE incidents than hazard exceedances. Multiplying the odds by an occupancy PROBABILITY fixes both: zero occupancy is the zero element, and the incident probability can never exceed the hazard probability it is conditioned on.
- class ExceedanceReport(alerts, prob_exceed, false_alarm_target, calibrated=True, warmed_up=<factory>)[source]
Bases:
objectPer-timestep exceedance call: which points alert, their raw probability, and the target rate.
alertsis the boolean array the caller (or the mlops drift/retrain wiring) acts on; whencalibratedis True it is already conformal-calibrated – do not re-thresholdprob_exceedagain downstream.calibrated(MXR-080-0097) is False whenever the returned alerts do NOT carry the distribution-free false-alarm-rate guarantee:calibwas omitted (the monitored series – including any anomalies it contains – scored itself, which is neither independent of nor exchangeable with what it is calibrating), or the effective calibration set was too small to givefalse_alarm_targetreal resolution (see_min_adequate_calib_size). The alerts are still computed on a best-effort basis in that case, but the caller must treat them as a heuristic, not a proven bound.warmed_up(MXR-080-0096) marks which timesteps had enough strictly-prior history to be scored at all; an unwarmed timestep always reportsprob_exceed == 0.0and never alerts – an explicit “not yet evaluated”, never a confident “safe”.The record is frozen and owns read-only copies of its three arrays.
calibratedandwarmed_upare the honesty flags that qualifyalerts; while the record was mutable, flippingcalibratedto True or overwritingalertsin place upgraded a best-effort heuristic into an apparently-proven false-alarm bound with nothing else changing.Construction additionally enforces the record’s own domain (MXR-080-1900), which it previously did not check at all –
prob_exceedof5.0,-3.0orNaN, afalse_alarm_targetof7.5, and aprob_exceed/warmed_uparray of a different length thanalertsall constructed silently, so the record could report an alert whose “probability” was not one, at a “target rate” that was not a rate, for timesteps that did not line up with the alerts:prob_exceedfinite and in[0, 1]. It is a survival-function value by construction, so this refuses nothing the producer emits.false_alarm_targetfinite and strictly in(0, 1)– the same rangeexposure_exceedance_monitor()already requires of itsalpha, now also true of a directly constructed report.prob_exceedthe same length asalerts, one probability per alert.warmed_upeither the same length asalertsor EMPTY. Empty is the field’s own default and means “warm-up was not recorded”; refusing it would reject reports the package’s own tests construct, and it is not the same claim as a per-timestep flag that disagrees with the alerts.
- exposure_exceedance_monitor(series, limit, *, alpha=0.05, calib=None)[source]
Flag exceedance excursions in
seriesagainstlimitat a calibrated false-alarm rate.- Parameters:
series (ndarray) –
(n,)monitoring readings (e.g. silica PM4 concentration over time). Must be finite.limit (float) – the occupational/community exposure limit being monitored against. Must be finite.
alpha (float) – target false-alarm rate (
ExceedanceReport.false_alarm_target); the empirical alert rate on exchangeable, non-exceeding data is bounded by this via conformal calibration – but only when the returnedcalibratedis True (seecalibbelow). Must be in(0, 1).calib (ndarray | None) –
(m,)held-out reference readings known to be exposure-compliant (sub-limit), drawn from a stable, anomaly-free reference period and scored by the same past-only rule asseries– the temporal-exchangeability scheme this monitor’s false-alarm-rate guarantee depends on. When omitted,seriesscores itself (a graceful degradation for callers with no separate holdout) – but the monitored series, including any anomalies it contains, is neither independent of nor exchangeable with what it is calibrating, so the returned report is explicitly markedcalibrated=Falseand thealphabound is NOT guaranteed (MXR-080-0097). An explicitly-suppliedcalibthat is too small to givealphareal resolution (fewer than roughly_min_adequate_calib_size(alpha)usable, warmed-up points) is likewise reported uncalibrated rather than silently accepted.
- Returns:
An
ExceedanceReport. Whencalibratedis False the alerts are still a best-effort heuristic, not a proven distribution-free bound.- Return type:
ExceedanceReport
- Algorithm:
At every timestep
tofseries(and, separately, ofcalib), fit a causal (strictly past-only) local Gaussian SCALEstd[t]fromseries[max(0, t-window):t]– neverseries[t]itself, never anything at or aftert– via_causal_local_scale(). Timesteps with too little prior history are not “warmed up” (ExceedanceReport.warmed_up) and are reported as an explicit not-yet-evaluated state (prob_exceed=0.0, never alerting) instead of reaching past the window into a global – and, for an early index, necessarily partly-future – statistic (MXR-080-0096).Score
prob_exceed[t]as the probability that the TRUE exposure level exceedslimit, treating the OBSERVEDseries[t]itself as a noisy measurement of that level with scalestd[t]:P(X > limit)forX ~ Normal(series[t], std[t])(scipy.stats.norm.sf()). Centering on the observation itself – rather than on the local mean of prior readings alone, which is blind to what was actually just measured – is what lets a single sudden extreme reading enter its own score and trigger at the exact timestep it happens, instead of only once later readings drag a lagging rolling mean toward it (MXR-080-0096).Calibrate the alert threshold: treat each WARMED-UP calibration timestep’s
prob_exceed(scored by the identical observed-value rule) as a one-sided conformal nonconformity score (mixle.inference.conformal.split_conformal(),side="upper", against a constant zero “prediction”) on the known-safecalibset. The returned upper bound is the smallest cutoff such that, under exchangeability withcalib, at most analphafraction of non-exceeding timesteps would clear it – a distribution-free false-alarm-rate guarantee, not a normal-theory approximation, PROVIDEDcalibgenuinely satisfies that exchangeability and is large enough (seecalibabove andcalibrated).Alert wherever a warmed-up
seriestimestep’sprob_exceedclears that threshold.
- class SlopeFactor(oral_csf=None, inhalation_iur=None, sigma_log=0.0, source='EPA-IRIS')[source]
Bases:
objectA chemical’s cancer potency, EPA-IRIS style.
- oral_csf
oral cancer slope factor, (mg/kg-day)^-1.
Noneif the chemical has no established oral potency.- Type:
float | None
- inhalation_iur
inhalation unit risk, (ug/m3)^-1.
Noneif no established inhalation potency.- Type:
float | None
- sigma_log
log-scale standard deviation of a multiplicative log-normal uncertainty band around the point-estimate slope factor (0.0 = treat the slope factor as fixed). Must be finite and non-negative – validated at construction (a negative or NaN value has no valid meaning as a standard deviation, so it is rejected here rather than silently treated as zero/fixed uncertainty downstream).
- Type:
- source
provenance tag for the potency values (default the EPA IRIS database, the regulatory-toxicology standard; caller-supplied values should override this).
- Type:
- class RiskQuantity(samples, prior_dominated=False)[source]
Bases:
objectA pushforward risk distribution: draws + credible interval + the
prior_dominatedflag.Structurally satisfies the IC-1
DerivedQuantityprotocol (samples,prior_dominated,credible_interval) and additionally exposesmean– the point estimate callers actually read off first.Construction validates
samples: non-empty, finite, and a valid probability-like value in[0, 1](the range theexcess_lifetime_cancer_risk/radon_wlm_riskLNT pushforward always produces from validated non-negative inputs – see_lnt_risk()). This is defense-in-depth: invalid state cannot silently flow downstream to a caller even if some upstream pushforward fails to validate its own inputs.- property mean: float
the sample-mean excess risk.
- Type:
Point estimate
- credible_interval(level=0.9)[source]
Central
levelcredible interval of the risk samples (e.g.level=0.9-> 5%/95%).levelgoes through the sharedmixle.analysisinterval contract (validated_level()), so every IC-1 carrier in the package rejects the same out-of-range levels the same way (MXR-080-1580).
- excess_lifetime_cancer_risk(exposure, sf, *, route='oral', n=2000, rng=None)[source]
Excess lifetime cancer risk under the linear no-threshold model (DR-ALG K7).
route="oral"treatsexposureas an LADD (lifetime average daily dose, mg/kg-day) and multiplies throughSlopeFactor.oral_csf;route="inhalation"treatsexposureas an air concentration (ug/m3) multiplied throughSlopeFactor.inhalation_iur.- Parameters:
exposure (Posterior | ndarray | float) – the lifetime-average dose/concentration. An IC-1
Posterior(itsderived_quantitypushforward is used, soprior_dominatedpropagates from the exposure posterior), a plain array of exposure samples (already representing exposure uncertainty), or a single deterministic scalar. Every exposure draw must reduce to a single value per sample – one route/chemical/time-point/receptor already selected or aggregated by the caller – and must be finite and non-negative; this is enforced uniformly for all three forms, including aPosterior’s own draws (there is no honest way to guess, on the caller’s behalf, which route/chemical/time point/receptor a genuinely multi-dimensional draw was meant to represent, so that case is rejected with a clear error rather than silently collapsed to its first column).sf (SlopeFactor) – the chemical’s
SlopeFactor. Its potency coefficient forroute, and itssigma_log, must be finite and non-negative (sigma_logis also validated atSlopeFactorconstruction).route (str) –
"oral"or"inhalation".n (int) – number of posterior draws to take when
exposureis aPosterior, or the number of slope-factor draws to take whenexposureis a bare scalar andsf.sigma_log > 0. Must be a positive exact integer. On thePosteriorpath this is now a hard delivery receipt (MXR-080-1900): a posterior that returns fewer thanndraws is an error, not a quietly smaller risk distribution.rng (Generator | None) – numpy random Generator (a fresh default one is created if omitted).
- Returns:
A
DerivedQuantityof excess lifetime cancer risk (samples + CI +prior_dominated).- Return type:
DerivedQuantity
A Boolean
exposure(scalar ordtype=boolarray) is rejected rather than coerced:Trueis a flag, not a dose, andfloat(True) == 1.0made one indistinguishable from a measured 1 mg/kg-day LADD once it was inside the LNT product (MXR-080-1900).
- radon_wlm_risk(wlm, *, risk_per_wlm=5.38e-4, n=2000, rng=None)[source]
Radon lung-cancer risk from cumulative working-level-months (BEIR-VI linear coefficient).
risk = wlm * risk_per_wlmfor small products (the BEIR-VI committee’s linear excess-relative-risk coefficient per WLM of cumulative radon-progeny exposure, default5.38e-4per WLM), saturating via the same1 - exp(-x)low-dose-linear-no-threshold form_lnt_risk()uses once that product would exceed ~0.01 – without it, cumulative exposures large enough to be realistic over a working lifetime push the bare linear form above 1, which is not a valid probability. Bothwlmandrisk_per_wlmmust be finite and non-negative (exposure and potency are never negative).- Parameters:
wlm (Posterior | ndarray | float) – cumulative working-level-months. An IC-1
Posteriorover exposure (its ownderived_quantitypushforward is used, soprior_dominatedpropagates from the exposure posterior), a plain array of exposure samples (already representing exposure uncertainty), or a single deterministic scalar. Each draw must reduce to one value per sample, exactly asexcess_lifetime_cancer_risk()requires – a(n, d>1)matrix mixes route/receptor/time axes this function has no honest way to collapse.risk_per_wlm (float) – BEIR-VI risk coefficient per WLM.
n (int) – number of posterior draws to take when
wlmis aPosterior. Must be a positive exact integer. Ignored for the array/scalar paths, which carry their own sample count.rng (Generator | None) – numpy random Generator used to draw from a
Posterior(a fresh default one is created if omitted). Unused for the array/scalar paths, which are deterministic.
- Returns:
A
DerivedQuantityof radon-attributable lung-cancer risk.- Return type:
DerivedQuantity
n/rngwere previously accepted and never used at all, on any path – a negative, fractional or Boolean draw count was silently taken and discarded (MXR-080-1574). They now drive thePosteriorpath they were reserved for, andnis validated on every path so an impossible request is rejected rather than ignored. On that pathnis additionally a delivery receipt: a posterior returning fewer thanndraws raises instead of yielding a thinner risk distribution that reads like the full one (MXR-080-1900). A Booleanwlmis rejected for the same reason a Boolean dose is –float(True) == 1.0is not one working-level-month.
Submodules¶
- mixle.analysis.biodiversity module
- mixle.analysis.carcinogenic_risk module
- mixle.analysis.covariance_shrinkage module
- mixle.analysis.coverage module
- mixle.analysis.developmental_risk module
- mixle.analysis.emissions module
- mixle.analysis.epidemiology module
- mixle.analysis.extreme module
- mixle.analysis.habitat_constraints module
- mixle.analysis.health_risk module
- mixle.analysis.kde module
- mixle.analysis.kriging module
- mixle.analysis.max_stable module
- mixle.analysis.objective module
- mixle.analysis.rank_aggregation module
- mixle.analysis.real_options module
- mixle.analysis.sdm module
- mixle.analysis.spatial_mixture module
- mixle.analysis.valuation module