mixle.blending module

Blending: minimum-cost mixture of several sources hitting a target attribute window.

A blend draws quantity w_s from each of several sources (stockpiles, batches, parcels), each with its own per-attribute grade and unit cost, to hit a target window on the combined material’s attributes – spec_lo[e] <= (sum_s w_s grades[s,e]) / sum_s w_s <= spec_hi[e] for every attribute e – at minimum blending cost. Fixing the total blended quantity to the requested demand linearizes the otherwise-fractional ratio constraint into a pair of linear inequalities, so the whole problem is a linear (or, with a minimum-parcel floor per source, mixed-integer) program solved by mixle.relations.branch_and_bound_milp(). When the spec window cannot be met by any blend of the given sources, mixle.relations.irreducible_infeasible_subset() names the conflicting constraint rows – which attribute window, high or low, is unmeetable – rather than just reporting “infeasible”.

This is the classic blending problem of operations research: fuel/petroleum blending (octane rating), feed formulation (protein/nutrient content), alloy composition, and ore blending & grade control (the worked example below) all reduce to the identical LP/MILP – sources, a per-attribute grade matrix, and a target window, nothing domain-specific in the construction itself.

>>> import numpy as np
>>> grades = np.array([[0.50], [0.55], [0.65], [0.70]])  # Fe fraction per stockpile
>>> costs = np.array([10.0, 12.0, 15.0, 18.0])
>>> avail = np.array([1000.0, 1000.0, 1000.0, 1000.0])
>>> cost, tonnage = blend_to_spec(grades, costs, [0.58], [0.62], avail, 1000.0)
blend_to_spec(grades, costs, spec_lo, spec_hi, avail, demand, *, min_parcel=None)[source]

Minimum-cost blend hitting a target attribute window, drawing quantity from several sources.

grades is (n_sources, n_elements), costs/avail are length n_sources, spec_lo/spec_hi are length n_elements. The blend must total demand units and, for every attribute e, spec_lo[e] <= blended_grade[e] <= spec_hi[e]. Solved as an LP by default (branch_and_bound_milp with no integer variables -> the HiGHS relaxation); pass min_parcel to additionally require that any source actually drawn from contributes at least that many units (a discrete parcel-draw floor), which turns it into a MILP.

Returns (min blend cost, per-source tonnage). Raises ValueError – naming the conflicting constraint rows from mixle.relations.irreducible_infeasible_subset() – when no blend of the given sources can meet the spec window at the requested tonnage.

Parameters:
Return type:

tuple[float, ndarray]

blend_feasibility(grades, spec_lo, spec_hi, avail, demand)[source]

Whether some blend of the sources can meet the spec window at demand units, cost aside.

Returns None when feasible. Otherwise returns the row indices of an irreducible infeasible subset (mixle.relations.irreducible_infeasible_subset()) of the linearized spec-window / total-quantity system – the minimal set of conflicting constraints, i.e. which attribute’s window (and whether its floor or ceiling) cannot be reached given the sources on hand.

Parameters:
Return type:

list[int] | None