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 listed record whose critical_habitat flag is set, THAT SPECIES’ OWN fitted suitability field contributes its lambda_c >= suitability_cut cells (via IC-12’s critical_habitat_mask) to the exclusion; a record with critical_habitat=False (a species that is tracked but has no critical-habitat designation) contributes nothing. If no listed record qualifies, nothing is excluded. The per-species contributions are unioned (OR).

habitat is either a single HabitatModel or an explicit {species_id: HabitatModel} mapping, and every qualifying listed species must have a model whose species_id matches 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_cells conservatively dilates the resulting mask outward (_dilate_conservatively()) to approximate a regulatory buffer around a designation boundary.

A real CriticalHabitatDesignation polygon (a regulator’s own mapped boundary, independent of the fitted suitability field) is not rasterized here: that requires resolving its SpatialBounds (real crs coordinates) 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 additional OR into mask before 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-1 Posterior) – one model, or a {species_id: HabitatModel} mapping.

  • listed (Sequence[ListedSpecies]) – ListedSpecies records (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, True where 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.

network is a plain mapping over the reference block network, in exactly mixle.relations.min_cost_flow()’s frozen (cap, cost, supply) shape: "cap"/"cost" are (n, n) arc matrices, "supply" is the optional length-n node supply vector, and "block_nodes" (optional, defaults to arange(len(exclusion_mask))) maps each block index to its node index in that network – the same convention G9’s apply_env_constraints uses.

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/ demands the caller supplied (the shape mixle.relations.network_design() itself takes) are passed through unchanged alongside forbidden_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/cost must be equal-shape, square, and finite, and cap must additionally be nonnegative (a negative arc capacity has no meaning); supply, when supplied, must be a finite length-n vector; and block_nodes must be exact, unique node ids in [0, n) (see _validate_block_nodes() for the fractional/negative/out-of-range failure modes this replaces).

Parameters:
  • network (dict[str, Any]) – the reference network payload (cap/cost/supply/block_nodes, plus any network_design-shaped pass-through fields).

  • exclusion_mask (ndarray) – the (K,) boolean no-mine mask from critical_habitat_exclusion().

Returns:

cap/cost (habitat-adjusted), forbidden_nodes (sorted node-id list), and supply when the caller provided one, plus any pass-through fields.

Return type:

A new dict

Raises:

ValueError – if cap/cost are not equal-shape finite square arrays, if cap contains a negative entry, if supply is not a finite length-n vector, or if block_nodes is not a length-matched array of exact, unique, in-range node ids.