Skip to main content

ras_commander_fast_map/
lib.rs

1//! Native 2D flood inundation mapping: horizontal, sloping, and ras2fim-2D Step05.
2//!
3//! Extends the immutable baseline prototype `prototype/kernel` (RasMapper_Stored_Maps_Rust,
4//! 2026-10-22, CLB Engineering): the barycentric evaluation, the `100 * DBL_EPSILON` inclusion
5//! test and the lowest-simplex-id tie rule are copied from that kernel. Upstream algorithm:
6//! Andy Carter, PE / University of Texas at Austin, ras2fim-2d `3146f647`, Step05 (BSD-3-Clause).
7//!
8//! The application uses [`step05`], which preserves the upstream float32 sequence
9//! and native approximate reprojection over supplied SciPy transforms and axes.
10//! The older [`session`] diagnostic retains its distinct f64 arithmetic.
11//! Step05 and triangulated-session scope boundaries:
12//! - Interpolation topology is always caller-supplied global SciPy/Qhull simplices. There is no
13//!   native triangulation and never a per-AOI or per-tile retriangulation.
14//! - Wet masks and sample preparation are caller-supplied (upstream/Python adapters).
15//! - Native terrain is same-CRS, north-up, single-band float32 GeoTIFF read through terrio with
16//!   an explicit sampling formula. Other terrain types require separate qualification.
17//! - The diagnostic session's f64 arithmetic does not replace the Step05 float32 sequence.
18//!
19//! Independent [`horizontal`] and [`sloping_session`] methods prepare pixel ownership
20//! directly from cell polygons. Plain sloping reconstructs hydraulic connectivity
21//! and corner interpolation rather than using the Step05 SciPy topology.
22#![forbid(unsafe_code)]
23
24/// Version of the standalone crate and Python wheel.
25pub const ENGINE_VERSION: &str = env!("CARGO_PKG_VERSION");
26/// Source revision of an official build, or `unknown` for an unversioned archive.
27pub const ENGINE_COMMIT: &str = env!("FAST_MAP_ENGINE_COMMIT");
28
29pub mod aoi;
30pub mod boundary_contour;
31pub mod boundary_session;
32pub mod cache;
33mod finalize;
34mod geotiff;
35pub mod horizontal;
36pub mod interp;
37pub mod model;
38pub mod ownership;
39mod retained;
40pub mod scipy_interp;
41pub mod session;
42pub mod sink;
43pub mod sloping;
44mod sloping_binary;
45pub mod sloping_session;
46pub mod step05;
47pub mod terrain;
48pub mod terrain_scope;
49pub mod velocity;
50pub mod whole_simulation;
51
52use std::fmt;
53
54/// Identifies the method semantics. Changing any recorded convention must change this string.
55pub const METHOD_VERSION: &str = "ras2fim-batch-prototype/0.1.0 method-r1";
56
57/// Identifies the output/failure/budget contract (not the numerical method, which is unchanged).
58pub const CONTRACT_VERSION: &str = "ras2fim-batch-prototype contract-r2-hardening";
59
60/// Errors are typed so callers can distinguish invalid input, budgets and unsupported cases.
61#[derive(Debug, Clone, PartialEq)]
62pub enum MapError {
63    Invalid(String),
64    Budget(String),
65    Unsupported(String),
66    Io(String),
67    Terrain(String),
68    /// A caught panic or broken internal invariant (never a silent partial result).
69    Internal(String),
70}
71
72impl fmt::Display for MapError {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        match self {
75            MapError::Invalid(s) => write!(f, "invalid input: {s}"),
76            MapError::Budget(s) => write!(f, "budget exceeded: {s}"),
77            MapError::Unsupported(s) => write!(f, "unsupported: {s}"),
78            MapError::Io(s) => write!(f, "io error: {s}"),
79            MapError::Terrain(s) => write!(f, "terrain error: {s}"),
80            MapError::Internal(s) => write!(f, "internal error: {s}"),
81        }
82    }
83}
84
85impl std::error::Error for MapError {}
86
87impl From<std::io::Error> for MapError {
88    fn from(e: std::io::Error) -> Self {
89        MapError::Io(e.to_string())
90    }
91}
92
93impl MapError {
94    pub fn kind(&self) -> &'static str {
95        match self {
96            MapError::Invalid(_) => "invalid",
97            MapError::Budget(_) => "budget",
98            MapError::Unsupported(_) => "unsupported",
99            MapError::Io(_) => "io",
100            MapError::Terrain(_) => "terrain",
101            MapError::Internal(_) => "internal",
102        }
103    }
104}
105
106/// Static description of the implemented semantics, emitted in every report.
107pub fn method_record() -> serde_json::Value {
108    serde_json::json!({
109        "method_version": METHOD_VERSION,
110        "interpolation": {
111            "topology": "caller-supplied global SciPy/Qhull simplices only; no native or per-AOI triangulation",
112            "evaluation": "f64 barycentric linear interpolation (baseline prototype kernel)",
113            "inclusion_tolerance": "all barycentric coordinates >= -100*DBL_EPSILON",
114            "tie_rule": "lowest supplied simplex index among containing simplices (order independent)",
115            "degenerate_simplices": "skipped when determinant == 0 or exact 1-norm reciprocal condition < 1000*DBL_EPSILON; modeled on SciPy's singular-transform skip, not verified bit-identical to SciPy's LAPACK estimate",
116            "scipy_eps_broad_fallback": "not implemented; no claim of SciPy find_simplex walk parity",
117            "query_point": "x = x0 + col*dx, y = y0 + row*dy in f64 (explicit grid); terrain-aligned grid: x = gt0 + (abs_col + 0.5)*gt1, y = gt3 + (abs_row + 0.5)*gt5"
118        },
119        "mask": "caller-supplied rasterized wet union on the profile grid (upstream/Python adapter); unmasked pixels are nodata",
120        "depth_wse": "depth = raw_wse - ground (f64); wet iff ground present and depth >= 0; wse = raw_wse where wet. Baseline prototype semantics; upstream float32 writes, '<0 -> NaN' sequencing and the Step05 nudge are NOT replicated",
121        "terrain_modes": {
122            "caller_buffer": "per-profile pre-aligned f64 terrain buffer, null/NaN = nodata (old oracle parity mode)",
123            "native_geotiff": {
124                "format": "same-CRS, north-up (gt2 == gt4 == 0, gt1 > 0, gt5 < 0), single-band float32 GeoTIFF/BigTIFF via terrio 0.4.2 lazy tile reads",
125                "sample_formula": "col = floor((xs - gt0) / gt1), row = floor((ys - gt3) / gt5) in f64",
126                "query_convention": "explicit per session: pixel_center (xs = x0 + (col + 0.5)*dx, ys = y0 + (row + 0.5)*dy) or query_point (xs, ys = query point)",
127                "nodata_policy": "explicit per session: dry (declared-nodata sentinel -> dry) or raw_value (sentinel subtracted as a number, upstream-like raw subtraction; not qualified)",
128                "outside_extent": "dry",
129                "nan_value": "dry",
130                "reprojection": "never; terrain EPSG must equal the declared session EPSG",
131                "not_qualified": "GDAL Warp nearest-neighbour, Step05 inverted-bounds lattice, nudge, upstream raw-sentinel behaviour; rasterio rowcol uses a linear solve and may differ at exact pixel-boundary ties (counted as terrain_near_boundary_samples)"
132            }
133        },
134        "nonfinite_terrain": "NaN terrain is dry; declared NaN or +/-inf nodata is accepted (a pixel is the sentinel iff v == nodata as f32, GDAL-like; report states whether that cast is exact); any other infinite terrain value that is sampled is rejected (native: typed terrain error failing the profile; caller_buffer: invalid request)",
135        "aoi": "pixel windows on each profile's own grid; AOI output equals the crop of the full-grid output by construction (same tiles, same per-pixel formula); bounds select pixels whose center lies in [min, max)",
136        "outputs": "per (profile, AOI) unit: little-endian f64 rasters with NaN nodata, simplex_id as i64 with -1 nodata, row-major in grid row order",
137        "output_contract": {
138            "contract_version": CONTRACT_VERSION,
139            "commit": "unit.json status in_progress is written before any data; data is written as <product>.bin.partial, fsynced (errors propagate), renamed to .bin, directory synced where supported, then unit.json is atomically replaced with status complete. A .bin is valid only when unit.json says complete",
140            "failure": "a failed unit, or every open unit after a session error or caught panic, has its data removed and unit.json status failed; report.json is still written with status aborted when possible (otherwise stderr says so and in_progress/.partial names remain)",
141            "json_writes": "<name>.partial, fsync, rename",
142            "ids": "[A-Za-z0-9_.-]{1,64}, unique case-insensitively, no trailing '.', no Windows device names (CON, PRN, AUX, NUL, COM0-9, LPT0-9, with any extension)"
143        },
144        "bounds": {
145            "enforced": "decoded resident terrain tiles <= max_cache_bytes; evaluated tile buffers <= effective_tiles_in_flight x output_tile^2 x 40 bytes; plan tiles per profile <= 4194304",
146            "admission_estimates": "per-profile load+prepare peak from request content / file lengths before any read (binary/inline: buffer sizes plus an assumed one-copy R-tree bulk-load; legacy JSON: 64 x file length heuristic) checked against max_profile_bytes; optional max_session_bytes packs batches over accounted static terms + batch load peaks + plan metadata",
147            "not_bounded": "decoder transients (estimated only), allocator fragmentation, thread stacks, codec state, JSON transients; no hard total memory bound is claimed"
148        }
149    })
150}