mixle.analysis.habitat_constraints module¶
N2 – critical-habitat & listed-species constraints into a network optimizer (workstream N; IC-9, IC-12, IC-1).
A general regulatory-exclusion constraint: turns N1’s fitted habitat-suitability field
(HabitatModel, IC-12/IC-1) and a set of typed, citation-backed
listed-species records (mixle_knowledge.contracts.ListedSpecies – this module never imports that
package, it only duck-types the two attributes it needs, so core mixle carries no hard dependency on
the paired mixle-knowledge contract) into a boolean exclusion mask over network nodes, folded into
an IC-9-shaped network payload – the one thing ANY siting/routing network optimizer needs to respect
a hard no-build constraint over critical habitat or listed-species range, not just a mine-planning
network optimizer (this module’s worked instantiation, hence “block” throughout for “node”). This
mirrors G9’s mixle_pde/reclamation.py:apply_env_constraints almost exactly – same “excluded
blocks become forbidden nodes / zero-capacity arcs” payload shape – but for critical-habitat/listed-
species law rather than seepage/subsidence risk.
Per the work-plan non-goal, this module never imports or calls the network-flow solver itself (no
dependency on mixle.relations, and no edit to it); it only produces the payload H1’s
min_cost_flow/network_design (or H4’s stochastic optimizer) reads.
- 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.