mixle.analysis.valuation module¶
Parametric extraction-cost curves and Monte-Carlo project valuation (work-plan Sec.7-J, J4).
A general project-economics toolkit: a parametric per-unit cost as a function of depth, grade (concentration of the recovered constituent), and throughput; a capex/opex roll-up over a multi-period plan; and a Monte-Carlo discounted-cash-flow valuation over posterior grade draws and price-path scenarios. This module’s worked instantiation is mine economics – J’s objective function needs a $/t cost that is a function of where the ore is (depth), what it is (grade), and how fast it is mined (throughput) before J2 can turn price paths + posterior grade draws into an NPV distribution, and before H’s block-level optimizers (mixle.stochastic_opt, mixle.relations) have a block_cost to subtract from revenue – but the cost curve and DCF machinery apply to any extraction/production project with a depth-, grade-, and throughput-sensitive cost structure:
cost_curve()– parametric mining + processing cost in $/t, monotone increasing in haul/ pumping depth, complexity-adjusted by grade, and shaped like the classic economies-of-scale curve in throughput: cheapest at the plant’s design capacity, more expensive both under- and over-utilized.
capex_opex()– rolls a period-by-period mine plan (tonnage, depth, grade, throughput, plus any lumpy capital spend) up into total capital and total operating cost, viacost_curve().
This module is created by J4 (Wave 1) and extended here by J2 (Wave 2) with monte_carlo_npv() /
NPVDistribution, the risk-neutral expected-DCF distribution H4’s objective is priced against:
monte_carlo_npv()– Monte-Carlo DCF overposterior.samples(n, rng) x price scenarios(DR-ALG J2): grade draws come straight off an IC-1 Posterior (frozen mixle.reason .posterior_protocol.Posterior), price scenarios come from J1’sPriceForecast.paths(or any array-like of per-period price paths), and per-period tonnage/capex come offschedule. Returns the full NPVDistribution (mean, P10/P50/P90, and a grade-vs-price variance decomposition) – a distribution, never a single point estimate.
Repo-boundary note (see the PR body for the full explanation): as of this PR, J1
(mixle.inference.price_forecast.forecast_price, whose PriceForecast.paths this task’s own Algorithm
text names as the price-scenario source) and A5 (mixle_pde.decision_quantities, IC-8’s calibrated
net-pay/tonnage quantities) had not landed. monte_carlo_npv therefore consumes price_paths and
schedule structurally (an (n_paths, n_periods) array-like and a tonnage/capex-per-period
mapping or array-like, respectively) rather than importing either concrete module by name – exactly
what its frozen Public API signature already commits to (price_paths: Any, schedule: Any). A
real PriceForecast.paths / A5 decision-quantity slots in without any change to this function once
those tasks land, since both already produce array-likes of that shape.
- 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.
- 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.
- 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.
- 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.