//! C ABI for the xy native core (design dossier §32: the native Rust core //! runs inside the Python process, operating zero-copy over NumPy/Arrow buffers). //! //! The C ABI is independent of the CPython version — one cdylib per platform //! covers all Pythons (§33's wheel-matrix goal, minus the ABI cross-product). //! Compute kernels remain self-contained; static export uses the focused Rust //! `png` crate so rasterization and compression stay fused. //! //! Phase 0 exposes stateless kernels over caller-owned buffers — the canonical //! column store stays on the Python side as NumPy arrays (CPU is the truth, GPU //! and every derived buffer is a cache, §27). A Rust-owned column store arrives //! with Tier 2/3. //! //! Safety contract (enforced by the single ctypes wrapper in //! `python/xy/_native.py`, the only in-tree caller): non-empty inputs //! use non-null, properly aligned pointers sized as documented per function. //! Empty inputs are accepted without dereferencing their pointers; invalid //! pointer/argument combinations return the documented error sentinel instead of //! panicking across the C boundary. pub mod css; #[allow(dead_code)] // generated by scripts/gen_font.py; some metrics unused by the rasterizer mod font; pub mod kernels; pub mod raster; mod simd; pub mod svg; pub mod tiles; mod transition; use kernels::ZoneMap; fn finite_gt(lo: f64, hi: f64) -> bool { lo.is_finite() && hi.is_finite() && hi > lo } fn finite_ordered(lo: f64, hi: f64) -> bool { lo.is_finite() && hi.is_finite() && hi >= lo } /// Panic backstop for the C ABI: a Rust panic must never unwind across /// `extern "C"` into the host interpreter — that is undefined behavior and in /// practice aborts the embedding CPython process. Any panic (an internal /// assert, a worker-join failure, an OOM unwind) maps to the calling entry /// point's error sentinel instead; output buffers may then be partially /// written, exactly like the existing invalid-argument paths, and callers /// already treat the sentinel as "output undefined". `AssertUnwindSafe` is /// sound because nothing observes the closure's captures after a panic. This /// backstop only operates when the target supports panic unwinding; on /// panic-abort targets such as wasm32, an internal panic aborts the instance. fn ffi_guard(sentinel: R, body: impl FnOnce() -> R) -> R { std::panic::catch_unwind(std::panic::AssertUnwindSafe(body)).unwrap_or(sentinel) } /// Convert parallel pointer/length arrays into call-scoped immutable slices. /// Empty spans may carry null pointers; no slice outlives the FFI call. unsafe fn borrowed_byte_spans<'a>( pointers: *const *const u8, lengths: *const usize, count: usize, ) -> Option> { if count == 0 { return Some(Vec::new()); } if pointers.is_null() || lengths.is_null() { return None; } let pointers = std::slice::from_raw_parts(pointers, count); let lengths = std::slice::from_raw_parts(lengths, count); let mut spans = Vec::with_capacity(count); for (&pointer, &length) in pointers.iter().zip(lengths) { if length == 0 { spans.push(&[][..]); } else if pointer.is_null() { return None; } else { spans.push(std::slice::from_raw_parts(pointer, length)); } } Some(spans) } /// ABI version — bumped on any signature change. The Python wrapper checks this /// at load time and refuses a mismatched library loudly (§33 comm-versioning /// rule, applied to the in-process boundary). pub const ABI_VERSION: u32 = 47; const FACTORIZE_CAPACITY_EXCEEDED: usize = usize::MAX - 1; #[no_mangle] pub extern "C" fn xy_abi_version() -> u32 { ABI_VERSION } /// Encode homogeneous fixed-width NumPy records as stable animation identity /// keys. `kind` is 0 for UTF-32 Unicode, 1 for fixed bytes, 2 for bool, 3 for /// signed integers, 4 for unsigned integers, and 5 for f64. Integer widths are /// 1/2/4/8 bytes; Unicode width is a positive multiple of four. `swap_endian` /// must be zero or one. /// /// Returns 0 on success, 1 for scalar data this kernel declines to tokenize /// (the caller falls back to its reference encoder), 2 for a duplicate token, /// 3 for a digest collision, and 4 for invalid arguments. Statuses 1, 2, and 3 /// write `out_error_first`/`out_error_index`: the offending row for 1, and the /// prior/current pair for 2 and 3. Status 4 writes neither, and is a caller /// bug rather than a data property — keeping it distinct from 1 stops a /// layout-contract drift from degrading silently into the slow path. /// /// # Safety /// For non-empty input, `data` addresses `len * width` readable bytes and each /// key output addresses `len` writable u32s. Error outputs address one writable /// usize each. Input and output spans must not overlap. #[no_mangle] pub unsafe extern "C" fn xy_transition_keys_fixed( data: *const u8, len: usize, width: usize, kind: u32, swap_endian: i32, out_lo: *mut u32, out_hi: *mut u32, out_error_first: *mut usize, out_error_index: *mut usize, ) -> i32 { if !matches!(swap_endian, 0 | 1) { return 4; } if len == 0 { return 0; } if out_error_first.is_null() || out_error_index.is_null() { return 4; } let byte_len = match len.checked_mul(width) { Some(value) if width > 0 => value, _ => return 4, }; if data.is_null() || out_lo.is_null() || out_hi.is_null() { return 4; } let data = std::slice::from_raw_parts(data, byte_len); let low = std::slice::from_raw_parts_mut(out_lo, len); let high = std::slice::from_raw_parts_mut(out_hi, len); ffi_guard(4, || { match transition::encode_fixed_into(data, width, kind, swap_endian != 0, low, high) { Ok(()) => 0, Err(transition::TransitionKeyError::Invalid { index }) => match index { Some(index) => { *out_error_first = index; *out_error_index = index; 1 } None => 4, }, Err(transition::TransitionKeyError::Duplicate { first, index }) => { *out_error_first = first; *out_error_index = index; 2 } Err(transition::TransitionKeyError::Collision { first, index }) => { *out_error_first = first; *out_error_index = index; 3 } } }) } /// Serialize parallel f64 screen coordinates into SVG polyline path data. /// Returns the required byte count, or `usize::MAX` for invalid inputs. When /// `out_cap` is too small no bytes are written, allowing callers to retry. /// /// # Safety /// `x` and `y` must point to `len` readable f64s. When `out_cap` is sufficient, /// `out` must point to `out_cap` writable bytes. #[no_mangle] pub unsafe extern "C" fn xy_svg_poly_path( x: *const f64, y: *const f64, len: usize, out: *mut u8, out_cap: usize, ) -> usize { if len == 0 || x.is_null() || y.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let Some(path) = ffi_guard(None, || svg::poly_path(x, y)) else { return usize::MAX; }; let required = path.len(); if out_cap < required { return required; } if out.is_null() { return usize::MAX; } std::slice::from_raw_parts_mut(out, out_cap)[..required].copy_from_slice(path.as_bytes()); required } /// Factor `len` fixed-width records into first-seen u32 codes. Returns the /// number of unique records or `usize::MAX` on invalid pointers/dimensions. /// /// # Safety /// `data` must address `len * width` readable bytes. `out_codes` and /// `out_unique_indices` must each address `len` writable u32 values. #[no_mangle] pub unsafe extern "C" fn xy_factorize_fixed( data: *const u8, len: usize, width: usize, out_codes: *mut u32, out_unique_indices: *mut u32, ) -> usize { if len == 0 { return 0; } let byte_len = match len.checked_mul(width) { Some(value) if width > 0 => value, _ => return usize::MAX, }; if data.is_null() || out_codes.is_null() || out_unique_indices.is_null() { return usize::MAX; } let data = std::slice::from_raw_parts(data, byte_len); let codes = std::slice::from_raw_parts_mut(out_codes, len); let unique_indices = std::slice::from_raw_parts_mut(out_unique_indices, len); ffi_guard(usize::MAX, || { kernels::factorize_fixed_into(data, width, codes, unique_indices).unwrap_or(usize::MAX) }) } /// Palette-sized fixed-record factorization with one u8 code per row. Returns /// `usize::MAX - 1` when `unique_capacity` is exceeded so the caller can retry /// the general u32 path, and `usize::MAX` for invalid arguments/panics. /// /// # Safety /// `data`/`out_codes` address `len * width` readable bytes / `len` writable /// bytes. `out_unique_indices` addresses `unique_capacity` writable u32s. #[no_mangle] pub unsafe extern "C" fn xy_factorize_fixed_u8( data: *const u8, len: usize, width: usize, out_codes: *mut u8, out_unique_indices: *mut u32, unique_capacity: usize, ) -> usize { if len == 0 { return 0; } let byte_len = match len.checked_mul(width) { Some(value) if width > 0 => value, _ => return usize::MAX, }; if unique_capacity == 0 || unique_capacity > 256 || data.is_null() || out_codes.is_null() || out_unique_indices.is_null() { return usize::MAX; } let data = std::slice::from_raw_parts(data, byte_len); let codes = std::slice::from_raw_parts_mut(out_codes, len); let unique_indices = std::slice::from_raw_parts_mut(out_unique_indices, unique_capacity); ffi_guard(usize::MAX, || { kernels::factorize_fixed_u8_into(data, width, codes, unique_indices) .unwrap_or(FACTORIZE_CAPACITY_EXCEEDED) }) } /// Palette-sized fixed-record factorization with exact per-code u64 counts. /// Counts use the same first-seen order as `out_unique_indices`. Return and /// pointer semantics match [`xy_factorize_fixed_u8`]. /// /// # Safety /// `out_counts` addresses `unique_capacity` writable u64 values in addition /// to the spans required by [`xy_factorize_fixed_u8`]. #[no_mangle] pub unsafe extern "C" fn xy_factorize_fixed_u8_counts( data: *const u8, len: usize, width: usize, out_codes: *mut u8, out_unique_indices: *mut u32, out_counts: *mut u64, unique_capacity: usize, ) -> usize { if len == 0 { return 0; } let byte_len = match len.checked_mul(width) { Some(value) if width > 0 => value, _ => return usize::MAX, }; if unique_capacity == 0 || unique_capacity > 256 || data.is_null() || out_codes.is_null() || out_unique_indices.is_null() || out_counts.is_null() { return usize::MAX; } let data = std::slice::from_raw_parts(data, byte_len); let codes = std::slice::from_raw_parts_mut(out_codes, len); let unique_indices = std::slice::from_raw_parts_mut(out_unique_indices, unique_capacity); let counts = std::slice::from_raw_parts_mut(out_counts, unique_capacity); ffi_guard(usize::MAX, || { kernels::factorize_fixed_u8_counts_into(data, width, codes, unique_indices, counts) .unwrap_or(FACTORIZE_CAPACITY_EXCEEDED) }) } /// Compact factorization for one-codepoint NumPy Unicode records. The bounded /// Unicode scalar domain uses direct lookup instead of record hashing. Return /// semantics match [`xy_factorize_fixed_u8_counts`]. /// /// # Safety /// `data` addresses `len` readable u32 records. Output spans follow /// [`xy_factorize_fixed_u8_counts`]. #[no_mangle] pub unsafe extern "C" fn xy_factorize_unicode1_u8_counts( data: *const u32, len: usize, swap_endian: i32, out_codes: *mut u8, out_unique_indices: *mut u32, out_counts: *mut u64, unique_capacity: usize, ) -> usize { if len == 0 { return 0; } if unique_capacity == 0 || unique_capacity > 256 || !matches!(swap_endian, 0 | 1) || data.is_null() || out_codes.is_null() || out_unique_indices.is_null() || out_counts.is_null() { return usize::MAX; } let data = std::slice::from_raw_parts(data, len); let codes = std::slice::from_raw_parts_mut(out_codes, len); let unique_indices = std::slice::from_raw_parts_mut(out_unique_indices, unique_capacity); let counts = std::slice::from_raw_parts_mut(out_counts, unique_capacity); ffi_guard(usize::MAX, || { kernels::factorize_unicode1_u8_counts_into( data, swap_endian != 0, codes, unique_indices, counts, ) .unwrap_or(FACTORIZE_CAPACITY_EXCEEDED) }) } /// Remap byte codes in place. Returns 1 on success and 0 for invalid input or /// a code outside the mapping. /// /// # Safety /// `values` addresses `len` writable bytes and `mapping` addresses /// `mapping_len` readable bytes. #[no_mangle] pub unsafe extern "C" fn xy_remap_u8( values: *mut u8, len: usize, mapping: *const u8, mapping_len: usize, ) -> i32 { if len == 0 { return 1; } if values.is_null() || mapping.is_null() || mapping_len == 0 { return 0; } let values = std::slice::from_raw_parts_mut(values, len); let mapping = std::slice::from_raw_parts(mapping, mapping_len); ffi_guard(0, || kernels::remap_u8_inplace(values, mapping) as i32) } /// CSS value validation (styling contract; `src/css.rs`). `kind` selects the /// grammar: `0` = property declaration (`prop` + `value`), `1` = color /// (`value` only), `2` = length token list, `3` = number. /// /// Returns `1` when the value parsed statically (for `kind == 1` the RGBA /// channels are written to `out_rgba` when it is non-null and the color is /// statically resolvable — `currentColor` parses without static channels), /// `2` when the value is valid but browser-resolved (`var()`, `oklch()`, /// `calc()`, unknown-property passthrough), `0` on invalid pointers or /// non-UTF-8 input, and a negative error code otherwise: -1 empty, -2 unsafe /// character (`;`/`{`/`}`/` i32 { // Null-with-length is invalid; empty inputs never dereference (the same // contract as the buffer kernels — a null pointer is only legal at len 0). if (value.is_null() && value_len > 0) || (prop.is_null() && prop_len > 0) { return 0; } let bytes = |p: *const u8, n: usize| -> &[u8] { if n == 0 { &[] } else { std::slice::from_raw_parts(p, n) } }; let (Ok(value), Ok(prop)) = ( std::str::from_utf8(bytes(value, value_len)), std::str::from_utf8(bytes(prop, prop_len)), ) else { return 0; }; ffi_guard(0, || { let checked = match kind { 0 => css::check_declaration(prop, value), 1 => css::parse_color(value), 2 => { let toks: Vec<&str> = value.split_whitespace().collect(); if toks.is_empty() { Err(css::CssErr::Empty) } else { toks.iter() .try_fold(css::Checked::Parsed(None), |acc, tok| { Ok(match (acc, css::check_length_token(tok)?) { (css::Checked::Passthrough, _) | (_, css::Checked::Passthrough) => { css::Checked::Passthrough } _ => css::Checked::Parsed(None), }) }) } } 3 => value .trim() .parse::() .ok() .filter(|v| v.is_finite()) .map(|_| css::Checked::Parsed(None)) .ok_or(css::CssErr::BadNumber), _ => return 0, }; match checked { Ok(css::Checked::Parsed(rgba)) => { if let (Some(c), false) = (rgba, out_rgba.is_null()) { std::slice::from_raw_parts_mut(out_rgba, 4).copy_from_slice(&c); } 1 } Ok(css::Checked::Passthrough) => 2, Err(e) => -(e as i32), } }) } /// Zone maps (§22) over `data[0..len]` in chunks of `chunk_size`. /// /// Output arrays must each hold `ceil(len / chunk_size)` elements. /// The final two arrays contain the positive-only min/max values used by log /// autorange; empty positive chunks retain `+∞`/`-∞` sentinels. /// Returns the number of chunks written. /// /// # Safety /// `data` must point to `len` readable f64s; each out pointer to /// `ceil(len/chunk_size)` writable elements; `chunk_size > 0`. #[no_mangle] pub unsafe extern "C" fn xy_zone_maps( data: *const f64, len: usize, chunk_size: usize, out_min: *mut f64, out_max: *mut f64, out_count: *mut u64, out_null_count: *mut u64, out_sum: *mut f64, out_sum_sq: *mut f64, out_positive_min: *mut f64, out_positive_max: *mut f64, ) -> usize { if chunk_size == 0 { return usize::MAX; } if len == 0 { return 0; } let n_chunks = len.div_ceil(chunk_size); if data.is_null() || out_min.is_null() || out_max.is_null() || out_count.is_null() || out_null_count.is_null() || out_sum.is_null() || out_sum_sq.is_null() || out_positive_min.is_null() || out_positive_max.is_null() { return usize::MAX; } let data = std::slice::from_raw_parts(data, len); let zms = match ffi_guard(None, || Some(kernels::zone_maps(data, chunk_size))) { Some(z) => z, None => return usize::MAX, }; debug_assert_eq!(zms.len(), n_chunks); for (i, zm) in zms.iter().enumerate() { let ZoneMap { min, max, positive_min, positive_max, count, null_count, sum, sum_sq, } = *zm; *out_min.add(i) = min; *out_max.add(i) = max; *out_count.add(i) = count; *out_null_count.add(i) = null_count; *out_sum.add(i) = sum; *out_sum_sq.add(i) = sum_sq; *out_positive_min.add(i) = positive_min; *out_positive_max.add(i) = positive_max; } zms.len() } /// Paired zone maps for equal-length x/y columns. Each output buffer contains /// `ceil(len / chunk_size)` stable `repr(C)` [`ZoneMap`] records. /// /// # Safety /// `x` and `y` address `len` readable f64s; `out_x` and `out_y` address the /// required number of writable [`ZoneMap`] records. #[no_mangle] pub unsafe extern "C" fn xy_zone_maps_pair( x: *const f64, y: *const f64, len: usize, chunk_size: usize, out_x: *mut ZoneMap, out_y: *mut ZoneMap, ) -> usize { if chunk_size == 0 { return usize::MAX; } if len == 0 { return 0; } if x.is_null() || y.is_null() || out_x.is_null() || out_y.is_null() { return usize::MAX; } let n_chunks = len.div_ceil(chunk_size); let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let Some((x_maps, y_maps)) = ffi_guard(None, || kernels::zone_maps_pair(x, y, chunk_size)) else { return usize::MAX; }; debug_assert_eq!(x_maps.len(), n_chunks); debug_assert_eq!(y_maps.len(), n_chunks); std::slice::from_raw_parts_mut(out_x, n_chunks).copy_from_slice(&x_maps); std::slice::from_raw_parts_mut(out_y, n_chunks).copy_from_slice(&y_maps); n_chunks } /// Offset-encode (§4/§16): `out[i] = (data[i] - offset) * scale` as f32. /// Returns 1 on success (including the empty no-op), 0 on null arguments — /// callers must treat 0 as "output undefined". /// /// # Safety /// `data` must point to `len` readable f64s, `out` to `len` writable f32s. #[no_mangle] pub unsafe extern "C" fn xy_encode_f32( data: *const f64, len: usize, offset: f64, scale: f64, out: *mut f32, ) -> i32 { if len == 0 { return 1; } if data.is_null() || out.is_null() { return 0; } let data = std::slice::from_raw_parts(data, len); let out = std::slice::from_raw_parts_mut(out, len); ffi_guard(0, || { kernels::encode_f32_into(data, offset, scale, out); 1 }) } /// M4 decimation (§5 Tier 1): source indices of {first, min, max, last} per /// bucket over the visible window `[x0, x1)`. `x` must be ascending. /// /// `out` must hold `4 * n_buckets` u32s. Returns the count written, or /// `usize::MAX` on invalid arguments (non-finite bounds, x1 <= x0, /// n_buckets == 0, `len > u32::MAX`, or a `4 * n_buckets` that overflows). /// /// # Safety /// `x`/`y` must point to `len` readable f64s; `out` to `4 * n_buckets` /// writable u32s. #[no_mangle] pub unsafe extern "C" fn xy_m4_indices( x: *const f64, y: *const f64, len: usize, x0: f64, x1: f64, n_buckets: usize, out: *mut u32, ) -> usize { if n_buckets == 0 || !finite_gt(x0, x1) { return usize::MAX; } // Emitted indices are u32 row ids: a longer column would wrap them into // valid-looking wrong rows. The in-tree caller never ships one, but the // ABI must not depend on that. if len > u32::MAX as usize { return usize::MAX; } // Same defensive posture as xy_rasterize: a caller-supplied size product // must not wrap in release builds into a too-short slice. let out_len = match n_buckets.checked_mul(4) { Some(n) => n, None => return usize::MAX, }; if len == 0 { return 0; } if x.is_null() || y.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let idx = match ffi_guard(None, || Some(kernels::m4_indices(x, y, x0, x1, n_buckets))) { Some(idx) => idx, None => return usize::MAX, }; if idx.is_empty() { return 0; } if out.is_null() { return usize::MAX; } let out = std::slice::from_raw_parts_mut(out, out_len); out[..idx.len()].copy_from_slice(&idx); idx.len() } /// Fused M4 decimation for a parallel x/y pair. Unlike `xy_m4_indices`, this /// writes the selected values directly and avoids returning an index array for /// Python to gather through NumPy. SVG and PNG payload construction both use /// this entry point, so their common reduction path stays native. /// /// Returns the number of values written, or `usize::MAX` on invalid input. /// `out_x` and `out_y` must each hold `4 * n_buckets` f64 values. /// /// # Safety /// `x` and `y` must point to `len` readable f64s; both output pointers must /// address the documented writable capacity. #[no_mangle] pub unsafe extern "C" fn xy_m4_points( x: *const f64, y: *const f64, len: usize, x0: f64, x1: f64, n_buckets: usize, out_x: *mut f64, out_y: *mut f64, ) -> usize { if n_buckets == 0 || !finite_gt(x0, x1) || len > u32::MAX as usize { return usize::MAX; } let out_len = match n_buckets.checked_mul(4) { Some(n) => n, None => return usize::MAX, }; if len == 0 { return 0; } if x.is_null() || y.is_null() || out_x.is_null() || out_y.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let Some(idx) = ffi_guard(None, || Some(kernels::m4_indices(x, y, x0, x1, n_buckets))) else { return usize::MAX; }; let out_x = std::slice::from_raw_parts_mut(out_x, out_len); let out_y = std::slice::from_raw_parts_mut(out_y, out_len); for (dst, &source) in idx.iter().enumerate() { let source = source as usize; out_x[dst] = x[source]; out_y[dst] = y[source]; } idx.len() } /// Native stacked-series layout. `values`, `out_lower`, and `out_upper` are /// row-major `rows * cols` f64 buffers. `baseline` uses the generic engine /// layout ids documented by `kernels::stacked_bounds_into`. /// /// # Safety /// Every pointer must address `rows * cols` readable/writable f64 values. #[no_mangle] pub unsafe extern "C" fn xy_stacked_bounds( values: *const f64, rows: usize, cols: usize, baseline: u32, out_lower: *mut f64, out_upper: *mut f64, ) -> i32 { let Some(len) = rows.checked_mul(cols) else { return 0; }; if len == 0 || values.is_null() || out_lower.is_null() || out_upper.is_null() { return 0; } let values = std::slice::from_raw_parts(values, len); let lower = std::slice::from_raw_parts_mut(out_lower, len); let upper = std::slice::from_raw_parts_mut(out_upper, len); ffi_guard(0, || { i32::from(kernels::stacked_bounds_into( values, rows, cols, baseline, lower, upper, )) }) } /// Weighted 2-D histogram with arbitrary edges. A null `weights` pointer means /// unit weights; all other buffers are caller-owned f64 arrays. /// /// # Safety /// Input pointers address the lengths described by their adjacent arguments; /// `out` addresses `(x_edge_len - 1) * (y_edge_len - 1)` writable f64s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_histogram2d( x: *const f64, y: *const f64, weights: *const f64, len: usize, x_edges: *const f64, x_edge_len: usize, y_edges: *const f64, y_edge_len: usize, out: *mut f64, ) -> i32 { if x_edge_len < 2 || y_edge_len < 2 || (len > 0 && (x.is_null() || y.is_null())) || x_edges.is_null() || y_edges.is_null() || out.is_null() { return 0; } let Some(out_len) = (x_edge_len - 1).checked_mul(y_edge_len - 1) else { return 0; }; let x = if len == 0 { &[][..] } else { std::slice::from_raw_parts(x, len) }; let y = if len == 0 { &[][..] } else { std::slice::from_raw_parts(y, len) }; let weights = if weights.is_null() { None } else { Some(std::slice::from_raw_parts(weights, len)) }; let x_edges = std::slice::from_raw_parts(x_edges, x_edge_len); let y_edges = std::slice::from_raw_parts(y_edges, y_edge_len); let out = std::slice::from_raw_parts_mut(out, out_len); ffi_guard(0, || { i32::from(kernels::histogram2d_into( x, y, weights, x_edges, y_edges, out, )) }) } /// Expand a rectilinear or curvilinear quadrilateral grid into two triangles /// per finite cell. Returns the written triangle count or `usize::MAX` on /// invalid arguments. /// /// # Safety /// `values` addresses `cell_rows * cell_cols` f64s. Coordinate lengths are /// explicit; each output addresses `2 * cell_rows * cell_cols` writable f64s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_quad_mesh_triangles( x: *const f64, x_len: usize, y: *const f64, y_len: usize, values: *const f64, cell_rows: usize, cell_cols: usize, layout: u32, out_x0: *mut f64, out_y0: *mut f64, out_x1: *mut f64, out_y1: *mut f64, out_x2: *mut f64, out_y2: *mut f64, out_values: *mut f64, ) -> usize { let Some(cell_count) = cell_rows.checked_mul(cell_cols) else { return usize::MAX; }; let Some(capacity) = cell_count.checked_mul(2) else { return usize::MAX; }; if cell_count == 0 || x.is_null() || y.is_null() || values.is_null() || out_x0.is_null() || out_y0.is_null() || out_x1.is_null() || out_y1.is_null() || out_x2.is_null() || out_y2.is_null() || out_values.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, x_len); let y = std::slice::from_raw_parts(y, y_len); let values = std::slice::from_raw_parts(values, cell_count); let x0 = std::slice::from_raw_parts_mut(out_x0, capacity); let y0 = std::slice::from_raw_parts_mut(out_y0, capacity); let x1 = std::slice::from_raw_parts_mut(out_x1, capacity); let y1 = std::slice::from_raw_parts_mut(out_y1, capacity); let x2 = std::slice::from_raw_parts_mut(out_x2, capacity); let y2 = std::slice::from_raw_parts_mut(out_y2, capacity); let scalar = std::slice::from_raw_parts_mut(out_values, capacity); ffi_guard(usize::MAX, || { kernels::quad_mesh_triangles_into( x, y, values, cell_rows, cell_cols, layout, x0, y0, x1, y1, x2, y2, scalar, ) .unwrap_or(usize::MAX) }) } /// Circular/annular sector tessellation. With `capacity == 0`, output pointers /// may be null and the required triangle count is returned. /// /// # Safety /// Inputs address `len` values; non-null outputs address `capacity` f64s each. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_sector_triangles( values: *const f64, len: usize, explode: *const f64, center_x: f64, center_y: f64, radius: f64, inner_radius: f64, start_degrees: f64, counterclockwise: i32, normalize: i32, out_x0: *mut f64, out_y0: *mut f64, out_x1: *mut f64, out_y1: *mut f64, out_x2: *mut f64, out_y2: *mut f64, out_sector: *mut f64, capacity: usize, ) -> usize { if len == 0 || values.is_null() || !matches!(counterclockwise, 0 | 1) || !matches!(normalize, 0 | 1) || (capacity > 0 && (out_x0.is_null() || out_y0.is_null() || out_x1.is_null() || out_y1.is_null() || out_x2.is_null() || out_y2.is_null() || out_sector.is_null())) { return usize::MAX; } let values = std::slice::from_raw_parts(values, len); let explode = if explode.is_null() { &[][..] } else { std::slice::from_raw_parts(explode, len) }; ffi_guard(usize::MAX, || { if capacity == 0 { return kernels::sector_triangles_into( values, explode, center_x, center_y, radius, inner_radius, start_degrees, counterclockwise == 1, normalize == 1, &mut [], &mut [], &mut [], &mut [], &mut [], &mut [], &mut [], ) .unwrap_or(usize::MAX); } let x0 = std::slice::from_raw_parts_mut(out_x0, capacity); let y0 = std::slice::from_raw_parts_mut(out_y0, capacity); let x1 = std::slice::from_raw_parts_mut(out_x1, capacity); let y1 = std::slice::from_raw_parts_mut(out_y1, capacity); let x2 = std::slice::from_raw_parts_mut(out_x2, capacity); let y2 = std::slice::from_raw_parts_mut(out_y2, capacity); let sector = std::slice::from_raw_parts_mut(out_sector, capacity); kernels::sector_triangles_into( values, explode, center_x, center_y, radius, inner_radius, start_degrees, counterclockwise == 1, normalize == 1, x0, y0, x1, y1, x2, y2, sector, ) .unwrap_or(usize::MAX) }) } /// Windowed real FFT, caller-owned nonnegative-frequency outputs. /// /// # Safety /// `data` addresses `len` f64s and each output addresses `nfft / 2 + 1` f64s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_rfft( data: *const f64, len: usize, nfft: usize, sample_rate: f64, out_frequency: *mut f64, out_real: *mut f64, out_imag: *mut f64, ) -> i32 { if len == 0 || data.is_null() || out_frequency.is_null() || out_real.is_null() || out_imag.is_null() { return 0; } let bins = nfft / 2 + 1; let data = std::slice::from_raw_parts(data, len); let frequency = std::slice::from_raw_parts_mut(out_frequency, bins); let real = std::slice::from_raw_parts_mut(out_real, bins); let imag = std::slice::from_raw_parts_mut(out_imag, bins); ffi_guard(0, || { i32::from(kernels::rfft_into( data, nfft, sample_rate, frequency, real, imag, )) }) } /// Native Welch auto/cross spectra. Null `y` computes only the x autospectrum. /// /// # Safety /// Non-null inputs address `len` f64s and all outputs address `nfft / 2 + 1` f64s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_welch_spectra( x: *const f64, y: *const f64, len: usize, nfft: usize, noverlap: usize, sample_rate: f64, out_frequency: *mut f64, out_pxx: *mut f64, out_pyy: *mut f64, out_pxy_real: *mut f64, out_pxy_imag: *mut f64, ) -> i32 { if len == 0 || x.is_null() || out_frequency.is_null() || out_pxx.is_null() || out_pyy.is_null() || out_pxy_real.is_null() || out_pxy_imag.is_null() { return 0; } let bins = nfft / 2 + 1; let x = std::slice::from_raw_parts(x, len); let y = if y.is_null() { None } else { Some(std::slice::from_raw_parts(y, len)) }; let frequency = std::slice::from_raw_parts_mut(out_frequency, bins); let pxx = std::slice::from_raw_parts_mut(out_pxx, bins); let pyy = std::slice::from_raw_parts_mut(out_pyy, bins); let pxy_real = std::slice::from_raw_parts_mut(out_pxy_real, bins); let pxy_imag = std::slice::from_raw_parts_mut(out_pxy_imag, bins); ffi_guard(0, || { i32::from(kernels::welch_spectra_into( x, y, nfft, noverlap, sample_rate, frequency, pxx, pyy, pxy_real, pxy_imag, )) }) } /// Time-major spectrogram power grid. Output sizes are derived from the /// caller's `nfft`, `noverlap`, and input length. /// /// # Safety /// `data` addresses `len` f64s and outputs address the derived sizes. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_spectrogram( data: *const f64, len: usize, nfft: usize, noverlap: usize, sample_rate: f64, out_frequency: *mut f64, out_time: *mut f64, out_power: *mut f64, ) -> i32 { if len == 0 || data.is_null() || nfft == 0 || noverlap >= nfft || out_frequency.is_null() || out_time.is_null() || out_power.is_null() { return 0; } let bins = nfft / 2 + 1; let segments = if len <= nfft { 1 } else { 1 + (len - nfft) / (nfft - noverlap) }; let Some(power_len) = bins.checked_mul(segments) else { return 0; }; let data = std::slice::from_raw_parts(data, len); let frequency = std::slice::from_raw_parts_mut(out_frequency, bins); let time = std::slice::from_raw_parts_mut(out_time, segments); let power = std::slice::from_raw_parts_mut(out_power, power_len); ffi_guard(0, || { i32::from(kernels::spectrogram_into( data, nfft, noverlap, sample_rate, frequency, time, power, )) }) } /// Lag correlation used by acorr/xcorr. /// /// # Safety /// Inputs address `len` f64s; outputs address `2 * max_lag + 1` f64s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_correlation( x: *const f64, y: *const f64, len: usize, max_lag: usize, normalize: i32, out_lag: *mut f64, out_correlation: *mut f64, ) -> i32 { if len == 0 || x.is_null() || y.is_null() || !matches!(normalize, 0 | 1) || out_lag.is_null() || out_correlation.is_null() { return 0; } let Some(output_len) = max_lag .checked_mul(2) .and_then(|value| value.checked_add(1)) else { return 0; }; let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let lag = std::slice::from_raw_parts_mut(out_lag, output_len); let correlation = std::slice::from_raw_parts_mut(out_correlation, output_len); ffi_guard(0, || { i32::from(kernels::correlation_into( x, y, max_lag, normalize == 1, lag, correlation, )) }) } /// Weighted empirical CDF. Returns the number of coalesced values or /// `usize::MAX` when the inputs are invalid. /// /// # Safety /// Inputs address `len` readable f64s and outputs address `len` writable f64s. #[no_mangle] pub unsafe extern "C" fn xy_weighted_ecdf( values: *const f64, weights: *const f64, len: usize, out_values: *mut f64, out_cumulative: *mut f64, ) -> usize { if len == 0 || values.is_null() || weights.is_null() || out_values.is_null() || out_cumulative.is_null() { return usize::MAX; } let values = std::slice::from_raw_parts(values, len); let weights = std::slice::from_raw_parts(weights, len); let output_values = std::slice::from_raw_parts_mut(out_values, len); let cumulative = std::slice::from_raw_parts_mut(out_cumulative, len); ffi_guard(usize::MAX, || { kernels::weighted_ecdf_into(values, weights, output_values, cumulative) .unwrap_or(usize::MAX) }) } /// Expand indexed topology into independent filled triangles. /// /// # Safety /// All pointers address the element counts derived from adjacent length arguments. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_indexed_triangles( x: *const f64, y: *const f64, vertex_count: usize, triangles: *const i64, face_count: usize, values: *const f64, value_len: usize, value_mode: u32, out_x0: *mut f64, out_y0: *mut f64, out_x1: *mut f64, out_y1: *mut f64, out_x2: *mut f64, out_y2: *mut f64, out_values: *mut f64, ) -> usize { let Some(index_count) = face_count.checked_mul(3) else { return usize::MAX; }; if x.is_null() || y.is_null() || triangles.is_null() || (value_len > 0 && values.is_null()) || out_x0.is_null() || out_y0.is_null() || out_x1.is_null() || out_y1.is_null() || out_x2.is_null() || out_y2.is_null() || out_values.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, vertex_count); let y = std::slice::from_raw_parts(y, vertex_count); let triangles = std::slice::from_raw_parts(triangles, index_count); let values = if value_len == 0 { &[][..] } else { std::slice::from_raw_parts(values, value_len) }; let x0 = std::slice::from_raw_parts_mut(out_x0, face_count); let y0 = std::slice::from_raw_parts_mut(out_y0, face_count); let x1 = std::slice::from_raw_parts_mut(out_x1, face_count); let y1 = std::slice::from_raw_parts_mut(out_y1, face_count); let x2 = std::slice::from_raw_parts_mut(out_x2, face_count); let y2 = std::slice::from_raw_parts_mut(out_y2, face_count); let scalar = std::slice::from_raw_parts_mut(out_values, face_count); ffi_guard(usize::MAX, || { kernels::indexed_triangles_into( x, y, triangles, values, value_mode, x0, y0, x1, y1, x2, y2, scalar, ) .unwrap_or(usize::MAX) }) } /// Emit unique line segments for indexed triangle edges. /// /// # Safety /// Vertex inputs address `vertex_count`, topology addresses `face_count * 3`, /// and each output addresses that same topology length. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_triangle_edges( x: *const f64, y: *const f64, vertex_count: usize, triangles: *const i64, face_count: usize, out_x0: *mut f64, out_x1: *mut f64, out_y0: *mut f64, out_y1: *mut f64, ) -> usize { let Some(capacity) = face_count.checked_mul(3) else { return usize::MAX; }; if x.is_null() || y.is_null() || triangles.is_null() || out_x0.is_null() || out_x1.is_null() || out_y0.is_null() || out_y1.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, vertex_count); let y = std::slice::from_raw_parts(y, vertex_count); let triangles = std::slice::from_raw_parts(triangles, capacity); let x0 = std::slice::from_raw_parts_mut(out_x0, capacity); let x1 = std::slice::from_raw_parts_mut(out_x1, capacity); let y0 = std::slice::from_raw_parts_mut(out_y0, capacity); let y1 = std::slice::from_raw_parts_mut(out_y1, capacity); ffi_guard(usize::MAX, || { kernels::triangle_edges_into(x, y, triangles, x0, x1, y0, y1).unwrap_or(usize::MAX) }) } /// Delaunay topology for finite unique 2-D points. `out` addresses /// `capacity * 3` writable i64 indices; returns the face count. /// /// # Safety /// `x` and `y` address `len` f64s; `out` addresses `capacity * 3` i64s. #[no_mangle] pub unsafe extern "C" fn xy_delaunay_triangles( x: *const f64, y: *const f64, len: usize, out: *mut i64, capacity: usize, ) -> usize { if len < 3 || x.is_null() || y.is_null() || out.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); ffi_guard(usize::MAX, || { let Some(triangles) = kernels::delaunay_triangles(x, y) else { return usize::MAX; }; if triangles.len() > capacity { return usize::MAX; } let Some(output_len) = capacity.checked_mul(3) else { return usize::MAX; }; let output = std::slice::from_raw_parts_mut(out, output_len); for (index, triangle) in triangles.iter().enumerate() { output[index * 3..index * 3 + 3].copy_from_slice(triangle); } triangles.len() }) } /// Ear-clipping topology for a finite simple polygon. `capacity` is the /// number of output faces, and `out` addresses `capacity * 3` i64 indices. /// /// # Safety /// `x` and `y` address `len` f64s; `out` addresses `capacity * 3` i64s. #[no_mangle] pub unsafe extern "C" fn xy_polygon_triangles( x: *const f64, y: *const f64, len: usize, out: *mut i64, capacity: usize, ) -> usize { if len < 3 || x.is_null() || y.is_null() || out.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); ffi_guard(usize::MAX, || { let Some(triangles) = kernels::polygon_triangles(x, y) else { return usize::MAX; }; if triangles.len() > capacity { return usize::MAX; } let Some(output_len) = capacity.checked_mul(3) else { return usize::MAX; }; let output = std::slice::from_raw_parts_mut(out, output_len); for (index, triangle) in triangles.iter().enumerate() { output[index * 3..index * 3 + 3].copy_from_slice(triangle); } triangles.len() }) } /// Indexed triangular isoline extraction. `capacity == 0` queries the segment /// count; otherwise all five outputs address `capacity` writable f64s. /// /// # Safety /// Inputs and outputs address the element counts described by their length arguments. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_marching_triangles( x: *const f64, y: *const f64, z: *const f64, vertex_count: usize, triangles: *const i64, face_count: usize, levels: *const f64, level_count: usize, out_x0: *mut f64, out_x1: *mut f64, out_y0: *mut f64, out_y1: *mut f64, out_levels: *mut f64, capacity: usize, ) -> usize { let Some(index_count) = face_count.checked_mul(3) else { return usize::MAX; }; if x.is_null() || y.is_null() || z.is_null() || triangles.is_null() || (level_count > 0 && levels.is_null()) || (capacity > 0 && (out_x0.is_null() || out_x1.is_null() || out_y0.is_null() || out_y1.is_null() || out_levels.is_null())) { return usize::MAX; } let x = std::slice::from_raw_parts(x, vertex_count); let y = std::slice::from_raw_parts(y, vertex_count); let z = std::slice::from_raw_parts(z, vertex_count); let triangles = std::slice::from_raw_parts(triangles, index_count); let levels = if level_count == 0 { &[][..] } else { std::slice::from_raw_parts(levels, level_count) }; ffi_guard(usize::MAX, || { if capacity == 0 { return kernels::marching_triangles_into( x, y, z, triangles, levels, &mut [], &mut [], &mut [], &mut [], &mut [], ) .unwrap_or(usize::MAX); } let x0 = std::slice::from_raw_parts_mut(out_x0, capacity); let x1 = std::slice::from_raw_parts_mut(out_x1, capacity); let y0 = std::slice::from_raw_parts_mut(out_y0, capacity); let y1 = std::slice::from_raw_parts_mut(out_y1, capacity); let out_levels = std::slice::from_raw_parts_mut(out_levels, capacity); kernels::marching_triangles_into(x, y, z, triangles, levels, x0, x1, y0, y1, out_levels) .unwrap_or(usize::MAX) }) } /// Vector origins/components to instanced shaft + arrowhead segments. /// Returns the written segment count or `usize::MAX` on invalid arguments. /// /// # Safety /// Inputs address `len` f64 values; outputs address `3 * len` writable f64s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_vector_segments( x: *const f64, y: *const f64, u: *const f64, v: *const f64, len: usize, scale: f64, pivot: u32, head_ratio: f64, out_x0: *mut f64, out_x1: *mut f64, out_y0: *mut f64, out_y1: *mut f64, ) -> usize { let Some(capacity) = len.checked_mul(3) else { return usize::MAX; }; if len == 0 { return 0; } if x.is_null() || y.is_null() || u.is_null() || v.is_null() || out_x0.is_null() || out_x1.is_null() || out_y0.is_null() || out_y1.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let u = std::slice::from_raw_parts(u, len); let v = std::slice::from_raw_parts(v, len); let x0 = std::slice::from_raw_parts_mut(out_x0, capacity); let x1 = std::slice::from_raw_parts_mut(out_x1, capacity); let y0 = std::slice::from_raw_parts_mut(out_y0, capacity); let y1 = std::slice::from_raw_parts_mut(out_y1, capacity); ffi_guard(usize::MAX, || { kernels::vector_segments_into(x, y, u, v, scale, pivot, head_ratio, x0, x1, y0, y1) .unwrap_or(usize::MAX) }) } /// Regular-grid streamline integration. With `capacity == 0`, returns the /// required segment count without writing outputs; otherwise writes four /// parallel segment columns and returns the same count. /// /// # Safety /// Coordinate/vector buffers and optional outputs have the dimensions given /// by `rows`, `cols`, and `capacity`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_streamlines( x_coords: *const f64, cols: usize, y_coords: *const f64, rows: usize, u: *const f64, v: *const f64, density: f64, max_steps: usize, out_x0: *mut f64, out_x1: *mut f64, out_y0: *mut f64, out_y1: *mut f64, capacity: usize, ) -> usize { let Some(len) = rows.checked_mul(cols) else { return usize::MAX; }; if rows < 2 || cols < 2 || x_coords.is_null() || y_coords.is_null() || u.is_null() || v.is_null() || (capacity > 0 && (out_x0.is_null() || out_x1.is_null() || out_y0.is_null() || out_y1.is_null())) { return usize::MAX; } let x_coords = std::slice::from_raw_parts(x_coords, cols); let y_coords = std::slice::from_raw_parts(y_coords, rows); let u = std::slice::from_raw_parts(u, len); let v = std::slice::from_raw_parts(v, len); ffi_guard(usize::MAX, || { let Some(segments) = kernels::streamlines(x_coords, y_coords, u, v, density, max_steps) else { return usize::MAX; }; if capacity == 0 { return segments.len(); } if capacity < segments.len() { return usize::MAX; } let x0 = std::slice::from_raw_parts_mut(out_x0, capacity); let x1 = std::slice::from_raw_parts_mut(out_x1, capacity); let y0 = std::slice::from_raw_parts_mut(out_y0, capacity); let y1 = std::slice::from_raw_parts_mut(out_y1, capacity); for (index, &(sx0, sx1, sy0, sy1)) in segments.iter().enumerate() { x0[index] = sx0; x1[index] = sx1; y0[index] = sy0; y1[index] = sy1; } segments.len() }) } /// Marching-squares isoline extraction over a regular grid. The first call /// may pass `capacity == 0` and null output pointers to query the required /// segment count; a later call writes the five parallel f64 output arrays. /// Returns the required/written segment count, or `usize::MAX` on invalid /// arguments or a panic inside the kernel. /// /// # Safety /// `z` points to `rows * cols` readable f64s; `x_coords` has `cols` values; /// `y_coords` has `rows` values; `levels` has `n_levels` values. When capacity /// is nonzero, every output pointer points to `capacity` writable f64s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_marching_squares( z: *const f64, rows: usize, cols: usize, x_coords: *const f64, y_coords: *const f64, levels: *const f64, n_levels: usize, corner_mask: u8, out_x0: *mut f64, out_x1: *mut f64, out_y0: *mut f64, out_y1: *mut f64, out_levels: *mut f64, capacity: usize, ) -> usize { if rows < 2 || cols < 2 { return usize::MAX; } let z_len = match rows.checked_mul(cols) { Some(n) => n, None => return usize::MAX, }; if z.is_null() || x_coords.is_null() || y_coords.is_null() { return usize::MAX; } if n_levels > 0 && levels.is_null() { return usize::MAX; } if capacity > 0 && (out_x0.is_null() || out_x1.is_null() || out_y0.is_null() || out_y1.is_null() || out_levels.is_null()) { return usize::MAX; } let z = std::slice::from_raw_parts(z, z_len); let x_coords = std::slice::from_raw_parts(x_coords, cols); let y_coords = std::slice::from_raw_parts(y_coords, rows); let levels = if n_levels == 0 { &[][..] } else { std::slice::from_raw_parts(levels, n_levels) }; if !x_coords.windows(2).all(|pair| pair[1] > pair[0]) || !y_coords.windows(2).all(|pair| pair[1] > pair[0]) || !x_coords.iter().all(|value| value.is_finite()) || !y_coords.iter().all(|value| value.is_finite()) || !levels.iter().all(|value| value.is_finite()) { return usize::MAX; } ffi_guard(usize::MAX, || { if capacity == 0 { let (empty_x0, empty_x1, empty_y0, empty_y1, empty_levels) = ( &mut [][..], &mut [][..], &mut [][..], &mut [][..], &mut [][..], ); kernels::marching_squares_into( z, rows, cols, x_coords, y_coords, levels, corner_mask != 0, empty_x0, empty_x1, empty_y0, empty_y1, empty_levels, ) } else { let x0_out = std::slice::from_raw_parts_mut(out_x0, capacity); let x1_out = std::slice::from_raw_parts_mut(out_x1, capacity); let y0_out = std::slice::from_raw_parts_mut(out_y0, capacity); let y1_out = std::slice::from_raw_parts_mut(out_y1, capacity); let level_out = std::slice::from_raw_parts_mut(out_levels, capacity); kernels::marching_squares_into( z, rows, cols, x_coords, y_coords, levels, corner_mask != 0, x0_out, x1_out, y0_out, y1_out, level_out, ) } }) } /// 2D density aggregation (§5 Tier 2): additively bin points into a `w × h` /// grid over the viewport. `out` must be `w * h` f32s (fully overwritten). /// /// # Safety /// `x`/`y` must point to `len` readable f64s; `out` to `w * h` writable f32s; /// `w > 0 && h > 0` and finite increasing bounds. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_bin_2d( x: *const f64, y: *const f64, len: usize, x0: f64, x1: f64, y0: f64, y1: f64, w: usize, h: usize, out: *mut f32, ) -> i32 { let bad = w == 0 || h == 0 || !finite_gt(x0, x1) || !finite_gt(y0, y1); if bad { return 0; } if out.is_null() { return 0; } let grid_len = match w.checked_mul(h) { Some(n) => n, None => return 0, }; let (x, y) = if len == 0 { (&[][..], &[][..]) } else { if x.is_null() || y.is_null() { return 0; } ( std::slice::from_raw_parts(x, len), std::slice::from_raw_parts(y, len), ) }; let out = std::slice::from_raw_parts_mut(out, grid_len); ffi_guard(0, || { kernels::bin_2d(x, y, x0, x1, y0, y1, w, h, out); 1 }) } /// f32-input density bin for the out-of-core spatial index (`_spatial.py`): /// bins memmap'd f32 (lon, lat) directly into a `w*h` f32 grid, skipping the /// f64 widening that dominates a windowed gather. Bit-identical to `xy_bin_2d` /// over the same points cast to f64. Returns 1 on success, 0 on bad args. /// /// # Safety /// `x`/`y` must each point to `len` readable f32 (or be null iff `len == 0`); /// `out` must point to `w*h` writable f32. #[no_mangle] pub unsafe extern "C" fn xy_bin_2d_f32( x: *const f32, y: *const f32, len: usize, x0: f64, x1: f64, y0: f64, y1: f64, w: usize, h: usize, out: *mut f32, ) -> i32 { let bad = w == 0 || h == 0 || !finite_gt(x0, x1) || !finite_gt(y0, y1); if bad || out.is_null() { return 0; } let grid_len = match w.checked_mul(h) { Some(n) => n, None => return 0, }; let (x, y) = if len == 0 { (&[][..], &[][..]) } else { if x.is_null() || y.is_null() { return 0; } ( std::slice::from_raw_parts(x, len), std::slice::from_raw_parts(y, len), ) }; let out = std::slice::from_raw_parts_mut(out, grid_len); ffi_guard(0, || { kernels::bin_2d_f32(x, y, x0, x1, y0, y1, w, h, out); 1 }) } /// Marshal the C-side color source for mean-color binning: exactly one of /// `idx` (one LUT index per point, with `lut`/`lut_len` as 1..=256 RGBA8 /// entries) or `rgba` (straight-alpha RGBA8, 4 bytes per point) must be /// non-null. Returns `None` for any other shape. /// /// # Safety /// Non-null pointers must address the documented lengths for `len` points. unsafe fn color_source_from_raw<'a>( len: usize, idx: *const u8, rgba: *const u8, lut: *const u8, lut_len: usize, ) -> Option> { match (idx.is_null(), rgba.is_null()) { (false, true) => { if lut.is_null() || lut_len == 0 || lut_len > 256 { return None; } Some(kernels::BinColorSource::Indexed { idx: std::slice::from_raw_parts(idx, len), lut: std::slice::from_raw_parts(lut as *const [u8; 4], lut_len), }) } (true, false) => Some(kernels::BinColorSource::Rgba(std::slice::from_raw_parts( rgba, len * 4, ))), _ => None, } } /// Mean-color companion grid to `xy_bin_2d` (§5 Tier 2, LOD doc §2): fill /// `out` (`w*h*4` straight-alpha RGBA8, row 0 = bottom) with each cell's /// alpha-weighted mean point color (linear-light average, sRGB bytes out) /// and mean point alpha. Cell membership is bit-identical to `xy_bin_2d`. /// Returns 1 on success, 0 on invalid arguments. /// /// # Safety /// `x`/`y` must point to `len` readable f64s; the color source pointers must /// satisfy `color_source_from_raw`; `out` must address `w*h*4` writable bytes. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_bin_2d_mean_color( x: *const f64, y: *const f64, len: usize, idx: *const u8, rgba: *const u8, lut: *const u8, lut_len: usize, x0: f64, x1: f64, y0: f64, y1: f64, w: usize, h: usize, out: *mut u8, ) -> i32 { if w == 0 || h == 0 || !finite_gt(x0, x1) || !finite_gt(y0, y1) || out.is_null() { return 0; } let Some(grid_len) = w.checked_mul(h).and_then(|n| n.checked_mul(4)) else { return 0; }; let out = std::slice::from_raw_parts_mut(out, grid_len); if len == 0 { out.fill(0); return 1; } if x.is_null() || y.is_null() { return 0; } let Some(colors) = color_source_from_raw(len, idx, rgba, lut, lut_len) else { return 0; }; let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); ffi_guard(0, || { kernels::bin_2d_mean_color(x, y, &colors, x0, x1, y0, y1, w, h, out); 1 }) } /// Native PNG rasterizer (dossier Phase 3). Paints a Python-built display list /// (`cmd[0..cmd_len]`, see `raster.rs`/`_raster.py`) into a caller-owned /// straight-alpha RGBA8 framebuffer `out` of `w*h*4` bytes. Returns 1 on /// success, 0 on a malformed command buffer or size mismatch (output undefined). /// /// # Safety /// `cmd` must point to `cmd_len` readable bytes (or be null iff `cmd_len == 0`); /// `out` must point to `w*h*4` writable bytes. #[no_mangle] pub unsafe extern "C" fn xy_rasterize( cmd: *const u8, cmd_len: usize, out: *mut u8, w: usize, h: usize, ) -> i32 { if out.is_null() || w == 0 || h == 0 { return 0; } let out_len = match w.checked_mul(h).and_then(|n| n.checked_mul(4)) { Some(n) => n, None => return 0, }; let cmds = if cmd_len == 0 { &[][..] } else if cmd.is_null() { return 0; } else { std::slice::from_raw_parts(cmd, cmd_len) }; let out = std::slice::from_raw_parts_mut(out, out_len); ffi_guard(0, || raster::rasterize_into(cmds, w, h, out) as i32) } /// Rasterize a display list that may reference an immutable external byte /// arena. The arena is borrowed only for this synchronous call; no pointer is /// retained by Rust. This keeps large already-built grids out of the command /// buffer without changing their ownership. /// /// # Safety /// Pointer contracts match `xy_rasterize`; `data` must point to `data_len` /// readable bytes, or be null iff `data_len == 0`. #[no_mangle] pub unsafe extern "C" fn xy_rasterize_data( cmd: *const u8, cmd_len: usize, data: *const u8, data_len: usize, out: *mut u8, w: usize, h: usize, ) -> i32 { if out.is_null() || w == 0 || h == 0 { return 0; } let out_len = match w.checked_mul(h).and_then(|n| n.checked_mul(4)) { Some(n) => n, None => return 0, }; let cmds = if cmd_len == 0 { &[][..] } else if cmd.is_null() { return 0; } else { std::slice::from_raw_parts(cmd, cmd_len) }; let arena = if data_len == 0 { &[][..] } else if data.is_null() { return 0; } else { std::slice::from_raw_parts(data, data_len) }; let out = std::slice::from_raw_parts_mut(out, out_len); ffi_guard(0, || { raster::rasterize_data_into(cmds, arena, w, h, out) as i32 }) } /// Rasterize with multiple immutable arenas, used by static export to borrow /// canonical arrays alongside the ordinary owned payload blob. /// /// # Safety /// `span_ptrs` and `span_lens` must each contain `span_count` readable entries; /// every non-empty span pointer must address its declared readable byte range. /// Other pointer contracts match `xy_rasterize`. #[no_mangle] pub unsafe extern "C" fn xy_rasterize_spans( cmd: *const u8, cmd_len: usize, span_ptrs: *const *const u8, span_lens: *const usize, span_count: usize, out: *mut u8, w: usize, h: usize, ) -> i32 { if out.is_null() || w == 0 || h == 0 { return 0; } let out_len = match w.checked_mul(h).and_then(|n| n.checked_mul(4)) { Some(n) => n, None => return 0, }; let cmds = if cmd_len == 0 { &[][..] } else if cmd.is_null() { return 0; } else { std::slice::from_raw_parts(cmd, cmd_len) }; let Some(spans) = borrowed_byte_spans(span_ptrs, span_lens, span_count) else { return 0; }; let out = std::slice::from_raw_parts_mut(out, out_len); ffi_guard(0, || { raster::rasterize_spans_into(cmds, &spans, w, h, out) as i32 }) } /// Fused native raster + fast PNG encoder. Returns the PNG byte count written /// to `out`, or `usize::MAX` when the command stream is malformed, dimensions /// overflow, or `out_capacity` is insufficient. /// /// # Safety /// Pointer contracts match `xy_rasterize`; `out` must point to /// `out_capacity` writable bytes. #[no_mangle] pub unsafe extern "C" fn xy_rasterize_png( cmd: *const u8, cmd_len: usize, out: *mut u8, out_capacity: usize, w: usize, h: usize, ) -> usize { if out.is_null() || out_capacity == 0 || w == 0 || h == 0 { return usize::MAX; } let cmds = if cmd_len == 0 { &[][..] } else if cmd.is_null() { return usize::MAX; } else { std::slice::from_raw_parts(cmd, cmd_len) }; let out = std::slice::from_raw_parts_mut(out, out_capacity); ffi_guard(usize::MAX, || { raster::rasterize_png_into(cmds, w, h, out).unwrap_or(usize::MAX) }) } /// Fused PNG rasterizer with the synchronous external arena accepted by /// `xy_rasterize_data`. /// /// # Safety /// Pointer contracts match `xy_rasterize_data`; `out` must point to /// `out_capacity` writable bytes. #[no_mangle] pub unsafe extern "C" fn xy_rasterize_png_data( cmd: *const u8, cmd_len: usize, data: *const u8, data_len: usize, out: *mut u8, out_capacity: usize, w: usize, h: usize, ) -> usize { if out.is_null() || out_capacity == 0 || w == 0 || h == 0 { return usize::MAX; } let cmds = if cmd_len == 0 { &[][..] } else if cmd.is_null() { return usize::MAX; } else { std::slice::from_raw_parts(cmd, cmd_len) }; let arena = if data_len == 0 { &[][..] } else if data.is_null() { return usize::MAX; } else { std::slice::from_raw_parts(data, data_len) }; let out = std::slice::from_raw_parts_mut(out, out_capacity); ffi_guard(usize::MAX, || { raster::rasterize_png_data_into(cmds, arena, w, h, out).unwrap_or(usize::MAX) }) } /// Fused PNG rasterizer backed by multiple synchronous immutable arenas. /// /// # Safety /// Span contracts match `xy_rasterize_spans`; output contracts match /// `xy_rasterize_png`. #[no_mangle] pub unsafe extern "C" fn xy_rasterize_png_spans( cmd: *const u8, cmd_len: usize, span_ptrs: *const *const u8, span_lens: *const usize, span_count: usize, out: *mut u8, out_capacity: usize, w: usize, h: usize, ) -> usize { if out.is_null() || out_capacity == 0 || w == 0 || h == 0 { return usize::MAX; } let cmds = if cmd_len == 0 { &[][..] } else if cmd.is_null() { return usize::MAX; } else { std::slice::from_raw_parts(cmd, cmd_len) }; let Some(spans) = borrowed_byte_spans(span_ptrs, span_lens, span_count) else { return usize::MAX; }; let out = std::slice::from_raw_parts_mut(out, out_capacity); ffi_guard(usize::MAX, || { raster::rasterize_png_spans_into(cmds, &spans, w, h, out).unwrap_or(usize::MAX) }) } /// Native heatmap scalar-to-RGBA mapper used by the static raster path. /// Returns 1 on success and 0 on invalid dimensions or pointers. /// /// # Safety /// `raw` contains `w*h` readable f64 values, `stops` contains /// `stop_count*3` readable bytes, and `out` contains `w*h*4` writable bytes. #[no_mangle] pub unsafe extern "C" fn xy_heatmap_rgba( raw: *const f64, w: usize, h: usize, stops: *const u8, stop_count: usize, alpha: u8, out: *mut u8, ) -> i32 { let Some(len) = w.checked_mul(h) else { return 0; }; if len == 0 || stop_count == 0 || raw.is_null() || stops.is_null() || out.is_null() { return 0; } let Some(out_len) = len.checked_mul(4) else { return 0; }; let Some(stop_len) = stop_count.checked_mul(3) else { return 0; }; let raw = std::slice::from_raw_parts(raw, len); let stop_bytes = std::slice::from_raw_parts(stops, stop_len); let stops = std::slice::from_raw_parts(stop_bytes.as_ptr().cast::<[u8; 3]>(), stop_count); let out = std::slice::from_raw_parts_mut(out, out_len); ffi_guard(0, || { kernels::heatmap_rgba_into(raw, w, h, stops, alpha, out) as i32 }) } /// Native log-u8 density colormap used by the static raster path. Returns 1 /// on success and 0 on invalid dimensions, values, or pointers. /// /// # Safety /// `encoded` contains `w*h` readable bytes, `stops` contains `stop_count*3` /// readable bytes, and `out` contains `w*h*4` writable bytes. #[no_mangle] pub unsafe extern "C" fn xy_density_rgba( encoded: *const u8, w: usize, h: usize, maximum: f64, stops: *const u8, stop_count: usize, opacity: f64, out: *mut u8, ) -> i32 { let Some(len) = w.checked_mul(h) else { return 0; }; let Some(out_len) = len.checked_mul(4) else { return 0; }; let Some(stop_len) = stop_count.checked_mul(3) else { return 0; }; if len == 0 || stop_count == 0 || encoded.is_null() || stops.is_null() || out.is_null() { return 0; } let encoded = std::slice::from_raw_parts(encoded, len); let stop_bytes = std::slice::from_raw_parts(stops, stop_len); let stops = std::slice::from_raw_parts(stop_bytes.as_ptr().cast::<[u8; 3]>(), stop_count); let out = std::slice::from_raw_parts_mut(out, out_len); ffi_guard(0, || { kernels::density_rgba_into(encoded, w, h, maximum, stops, opacity, out) as i32 }) } /// Log-encode a density grid into the one-byte wire/texture representation. /// Returns 1 on success and writes the original grid maximum. /// /// # Safety /// `grid` addresses `len` readable f32 values, `out` addresses `len` writable /// bytes, and `out_max` addresses one writable f64. #[no_mangle] pub unsafe extern "C" fn xy_density_log_u8( grid: *const f32, len: usize, out: *mut u8, out_max: *mut f64, ) -> i32 { if out_max.is_null() || (len > 0 && (grid.is_null() || out.is_null())) { return 0; } let grid = if len == 0 { &[][..] } else { std::slice::from_raw_parts(grid, len) }; let out = if len == 0 { &mut [][..] } else { std::slice::from_raw_parts_mut(out, len) }; ffi_guard(0, || { *out_max = kernels::density_log_u8_into(grid, out); 1 }) } /// Fused density scan (§5 Tier 2): one pass writing BOTH the count grid /// (bin_2d semantics: half-open finite window) and the ascending in-window /// row indices (range_indices semantics: inclusive window). Each output is /// bitwise identical to its standalone kernel. Returns the index count, or /// `usize::MAX` on invalid arguments. /// /// # Safety /// `x`/`y` must point to `len` readable f64s; `grid` to `w*h` writable f32s; /// `idx` to `len` writable u32s. #[no_mangle] pub unsafe extern "C" fn xy_bin_2d_indices( x: *const f64, y: *const f64, len: usize, x0: f64, x1: f64, y0: f64, y1: f64, w: usize, h: usize, grid: *mut f32, idx: *mut u32, ) -> usize { let bad = w == 0 || h == 0 || !finite_gt(x0, x1) || !finite_gt(y0, y1); if bad || grid.is_null() { return usize::MAX; } // u32 index ceiling + unwrappable grid size — see xy_m4_indices. if len > u32::MAX as usize { return usize::MAX; } let grid_len = match w.checked_mul(h) { Some(n) => n, None => return usize::MAX, }; let (x, y, idx) = if len == 0 { (&[][..], &[][..], &mut [][..]) } else { if x.is_null() || y.is_null() || idx.is_null() { return usize::MAX; } ( std::slice::from_raw_parts(x, len), std::slice::from_raw_parts(y, len), std::slice::from_raw_parts_mut(idx, len), ) }; let grid = std::slice::from_raw_parts_mut(grid, grid_len); if len == 0 { grid.fill(0.0); return 0; } ffi_guard(usize::MAX, || { kernels::bin_2d_indices(x, y, x0, x1, y0, y1, w, h, grid, idx) }) } /// Full-domain density first paint: one traversal writes the `bin_2d` grid /// and samples implicit row ids `0..len` with the same SplitMix predicate as /// `xy_sample_range_indices`. Returns the exact sample length; rows are copied /// only when `capacity` is sufficient. `usize::MAX` reports invalid arguments. /// /// # Safety /// `x`/`y` must point to `len` readable f64s; `grid` to `w*h` writable f32s. /// When `capacity > 0`, `out` must point to `capacity` writable u32s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_bin_2d_sample_range( x: *const f64, y: *const f64, len: usize, x0: f64, x1: f64, y0: f64, y1: f64, w: usize, h: usize, seed: u64, threshold: u64, grid: *mut f32, out: *mut u32, capacity: usize, ) -> usize { if w == 0 || h == 0 || !finite_gt(x0, x1) || !finite_gt(y0, y1) || grid.is_null() || len > u32::MAX as usize || capacity > len || (capacity > 0 && out.is_null()) { return usize::MAX; } let grid_len = match w.checked_mul(h) { Some(value) => value, None => return usize::MAX, }; let (x, y) = if len == 0 { (&[][..], &[][..]) } else { if x.is_null() || y.is_null() { return usize::MAX; } ( std::slice::from_raw_parts(x, len), std::slice::from_raw_parts(y, len), ) }; let grid = std::slice::from_raw_parts_mut(grid, grid_len); if len == 0 { grid.fill(0.0); return 0; } let out_rows = if capacity == 0 { &mut [][..] } else { std::slice::from_raw_parts_mut(out, capacity) }; ffi_guard(usize::MAX, || { kernels::bin_2d_sample_range(x, y, x0, x1, y0, y1, w, h, seed, threshold, grid, out_rows) }) } /// Categorical counterpart to [`xy_bin_2d_sample_range`]. Exact factorization /// counts define the per-code sampling thresholds and avoid a recount; the /// resulting grid and sampled rows match the standalone bin and counted /// stratified sampler. Returns `usize::MAX` on malformed arguments/codes. /// /// # Safety /// `x`/`y`/`groups` must point to `len` readable values and `counts` to /// `n_groups` readable u64 values. Remaining output contracts match /// [`xy_bin_2d_sample_range`]. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_bin_2d_stratified_sample_range_u8_counted( x: *const f64, y: *const f64, groups: *const u8, len: usize, counts: *const u64, n_groups: usize, x0: f64, x1: f64, y0: f64, y1: f64, w: usize, h: usize, seed: u64, fraction: f64, min_count: u64, grid: *mut f32, out: *mut u32, capacity: usize, ) -> usize { if w == 0 || h == 0 || !finite_gt(x0, x1) || !finite_gt(y0, y1) || !fraction.is_finite() || fraction <= 0.0 || grid.is_null() || counts.is_null() || n_groups == 0 || n_groups > 256 || len > u32::MAX as usize || capacity > len || (capacity > 0 && out.is_null()) { return usize::MAX; } let grid_len = match w.checked_mul(h) { Some(value) => value, None => return usize::MAX, }; let counts = std::slice::from_raw_parts(counts, n_groups); let (x, y, groups) = if len == 0 { (&[][..], &[][..], &[][..]) } else { if x.is_null() || y.is_null() || groups.is_null() { return usize::MAX; } ( std::slice::from_raw_parts(x, len), std::slice::from_raw_parts(y, len), std::slice::from_raw_parts(groups, len), ) }; let grid = std::slice::from_raw_parts_mut(grid, grid_len); ffi_guard(usize::MAX, || { let Some(selected) = kernels::bin_2d_stratified_sample_range_u8_counted( x, y, groups, counts, x0, x1, y0, y1, w, h, seed, fraction, min_count, grid, ) else { return usize::MAX; }; let written = selected.len(); if written > 0 && written <= capacity { std::ptr::copy_nonoverlapping(selected.as_ptr(), out, written); } written }) } /// Non-decreasing + NaN-poisoned check (`next >= prev` for every pair; any /// NaN fails its pairs) — the line/area sorted-ingest predicate (§28). /// Returns 1 when sorted, 0 when not. Null `data` with `len > 0` returns 0 /// (callers then sort, which is always safe). Empty and single-element /// inputs are sorted. /// /// # Safety /// `data` must point to `len` readable f64s (may be null only when `len == 0`). #[no_mangle] pub unsafe extern "C" fn xy_is_sorted(data: *const f64, len: usize) -> i32 { if len < 2 { return 1; } if data.is_null() { return 0; } let data = std::slice::from_raw_parts(data, len); ffi_guard(0, || i32::from(kernels::is_sorted_f64(data))) } /// NaN-skipping min/max (autorange primitive). Returns 1 and writes the result, /// or 0 if the input is empty / all-NaN. /// /// # Safety /// `data` must point to `len` readable f64s; out pointers to one writable f64. #[no_mangle] pub unsafe extern "C" fn xy_min_max( data: *const f64, len: usize, out_min: *mut f64, out_max: *mut f64, ) -> i32 { if len == 0 { return 0; } if data.is_null() || out_min.is_null() || out_max.is_null() { return 0; } let data = std::slice::from_raw_parts(data, len); match ffi_guard(None, || kernels::min_max(data)) { Some((mn, mx)) => { *out_min = mn; *out_max = mx; 1 } None => 0, } } /// Uniform fixed-bin histogram. Returns the count of finite in-range values, or /// `usize::MAX` on invalid arguments. `out_counts` must hold `n_bins` f64s. /// /// # Safety /// `data` must point to `len` readable f64s; `out_counts` to `n_bins` writable /// f64s; `n_bins > 0` and `lo`/`hi` finite increasing. #[no_mangle] pub unsafe extern "C" fn xy_histogram_uniform( data: *const f64, len: usize, lo: f64, hi: f64, n_bins: usize, density: i32, out_counts: *mut f64, ) -> usize { let bad = n_bins == 0 || !finite_gt(lo, hi); if bad { return usize::MAX; } if out_counts.is_null() { return usize::MAX; } let data = if len == 0 { &[][..] } else { if data.is_null() { return usize::MAX; } std::slice::from_raw_parts(data, len) }; let out = std::slice::from_raw_parts_mut(out_counts, n_bins); let total = match ffi_guard(None, || Some(kernels::histogram_uniform(data, lo, hi, out))) { Some(t) => t, None => return usize::MAX, }; if density != 0 && total > 0 { let bin_w = (hi - lo) / n_bins as f64; let denom = total as f64 * bin_w; for c in out.iter_mut() { *c /= denom; } } total as usize } /// Normalize f64 values into f32 `[0,1]`. `nan_mode=0` maps non-finite values to /// 0.0; `nan_mode=1` maps them to f32 NaN. Returns 1 on success (including the /// empty no-op), 0 on null arguments or a non-finite/inverted domain — the /// former silent-void failure left the output buffer uninitialized with no way /// to detect it. /// /// # Safety /// `data` must point to `len` readable f64s; `out` to `len` writable f32s. #[no_mangle] pub unsafe extern "C" fn xy_normalize_f32( data: *const f64, len: usize, lo: f64, hi: f64, nan_mode: i32, out: *mut f32, ) -> i32 { if len == 0 { return 1; } if data.is_null() || out.is_null() || !finite_gt(lo, hi) { return 0; } let data = std::slice::from_raw_parts(data, len); let out = std::slice::from_raw_parts_mut(out, len); let nan_value = if nan_mode == 1 { f32::NAN } else { 0.0 }; ffi_guard(0, || { kernels::normalize_f32_into(data, lo, hi, nan_value, out); 1 }) } /// Deterministic sampling mask (§5/§17): `out[i] = 1` iff /// `splitmix64(ids[i] + seed) <= threshold`. Bit-identical to /// `xy.lod.hash_row_ids` thresholding, fused into one pass. /// Returns 1 on success (including the empty no-op), 0 on null arguments. /// /// # Safety /// `ids` must point to `len` readable u64s; `out` to `len` writable u8s. #[no_mangle] pub unsafe extern "C" fn xy_sample_mask( ids: *const u64, len: usize, seed: u64, threshold: u64, out: *mut u8, ) -> i32 { if len == 0 { return 1; } if ids.is_null() || out.is_null() { return 0; } let ids = std::slice::from_raw_parts(ids, len); let out = std::slice::from_raw_parts_mut(out, len); ffi_guard(0, || { kernels::sample_mask(ids, seed, threshold, out); 1 }) } /// `xy_sample_mask` over u32 row indices: each id widens to u64 in-register, /// bit-identical to widening the full array first without that allocation. /// /// # Safety /// `ids` must point to `len` readable u32s; `out` to `len` writable u8s. #[no_mangle] pub unsafe extern "C" fn xy_sample_mask_u32( ids: *const u32, len: usize, seed: u64, threshold: u64, out: *mut u8, ) -> i32 { if len == 0 { return 1; } if ids.is_null() || out.is_null() { return 0; } let ids = std::slice::from_raw_parts(ids, len); let out = std::slice::from_raw_parts_mut(out, len); ffi_guard(0, || { kernels::sample_mask(ids, seed, threshold, out); 1 }) } /// Deterministically sample implicit row ids `0..size`. Returns the required /// output length; values are written only when `capacity` is sufficient. /// `usize::MAX` reports an invalid size/pointer combination. /// /// # Safety /// When `capacity > 0`, `out` must point to `capacity` writable u32 values. #[no_mangle] pub unsafe extern "C" fn xy_sample_range_indices( size: usize, seed: u64, threshold: u64, out: *mut u32, capacity: usize, ) -> usize { if size > u32::MAX as usize || (capacity > 0 && out.is_null()) { return usize::MAX; } let output = if capacity == 0 { &mut [][..] } else { std::slice::from_raw_parts_mut(out, capacity) }; ffi_guard(usize::MAX, || { kernels::sample_range_indices_into(size, seed, threshold, output) }) } /// Category-stratified sampling for implicit row ids `0..len` with compact /// u8 group codes. Returns the required output length; ascending row indices /// are written only when `capacity` is sufficient. `usize::MAX` reports /// invalid arguments or a group code outside `0..n_groups`. /// /// # Safety /// `groups` must point to `len` readable u8 values. When `capacity > 0`, /// `out` must point to `capacity` writable u32 values. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_stratified_sample_range_u8( groups: *const u8, len: usize, n_groups: usize, seed: u64, fraction: f64, min_count: u64, out: *mut u32, capacity: usize, ) -> usize { if len > u32::MAX as usize || n_groups == 0 || n_groups > 256 || !fraction.is_finite() || fraction <= 0.0 || (len > 0 && groups.is_null()) || (capacity > 0 && out.is_null()) { return usize::MAX; } let groups = if len == 0 { &[][..] } else { std::slice::from_raw_parts(groups, len) }; ffi_guard(usize::MAX, || { let Some(selected) = kernels::stratified_sample_range_u8(groups, n_groups, seed, fraction, min_count) else { return usize::MAX; }; if !selected.is_empty() && selected.len() <= capacity { let output = std::slice::from_raw_parts_mut(out, capacity); output[..selected.len()].copy_from_slice(&selected); } selected.len() }) } /// Count-reusing variant of [`xy_stratified_sample_range_u8`]. `counts` /// contains `n_groups` exact per-code counts from compact factorization, /// avoiding a source-sized recount before sampling. /// /// # Safety /// `counts` must point to `n_groups` readable u64 values. Other spans follow /// [`xy_stratified_sample_range_u8`]. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_stratified_sample_range_u8_counted( groups: *const u8, len: usize, counts: *const u64, n_groups: usize, seed: u64, fraction: f64, min_count: u64, out: *mut u32, capacity: usize, ) -> usize { if len > u32::MAX as usize || n_groups == 0 || n_groups > 256 || !fraction.is_finite() || fraction <= 0.0 || counts.is_null() || (len > 0 && groups.is_null()) || (capacity > 0 && out.is_null()) { return usize::MAX; } let groups = if len == 0 { &[][..] } else { std::slice::from_raw_parts(groups, len) }; let counts = std::slice::from_raw_parts(counts, n_groups); ffi_guard(usize::MAX, || { let Some(selected) = kernels::stratified_sample_range_u8_counted(groups, counts, seed, fraction, min_count) else { return usize::MAX; }; if !selected.is_empty() && selected.len() <= capacity { let output = std::slice::from_raw_parts_mut(out, capacity); output[..selected.len()].copy_from_slice(&selected); } selected.len() }) } /// Category-stratified sampling mask (§5/§17): per-category keep fractions /// scale as `min(1, fraction * sqrt(len / count))` and every category keeps at /// least `min(min_count, count)` of its lowest-hash rows. Bit-identical to the /// per-category NumPy reference in `xy.lod` (parity-tested), fused /// into one pass instead of O(len · n_groups) rescans. /// /// Returns 1 on success (including the empty no-op), 0 on null arguments, a /// non-finite or non-positive `fraction`, or a group code `>= n_groups` /// (output undefined). /// /// # Safety /// `ids` must point to `len` readable u64s, `groups` to `len` readable u32s, /// `out` to `len` writable u8s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_stratified_sample_mask( ids: *const u64, groups: *const u32, len: usize, n_groups: usize, seed: u64, fraction: f64, min_count: u64, out: *mut u8, ) -> i32 { if len == 0 { return 1; } if ids.is_null() || groups.is_null() || out.is_null() || n_groups == 0 || !fraction.is_finite() || fraction <= 0.0 { return 0; } let ids = std::slice::from_raw_parts(ids, len); let groups = std::slice::from_raw_parts(groups, len); let out = std::slice::from_raw_parts_mut(out, len); ffi_guard(0, || { kernels::stratified_sample_mask(ids, groups, n_groups, seed, fraction, min_count, out) as i32 }) } /// `xy_stratified_sample_mask` over u32 row indices, with the same in-register /// widening contract as [`xy_sample_mask_u32`]. /// /// # Safety /// `ids` and `groups` must point to `len` readable u32s; `out` to `len` /// writable u8s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_stratified_sample_mask_u32( ids: *const u32, groups: *const u32, len: usize, n_groups: usize, seed: u64, fraction: f64, min_count: u64, out: *mut u8, ) -> i32 { if len == 0 { return 1; } if ids.is_null() || groups.is_null() || out.is_null() || n_groups == 0 || !fraction.is_finite() || fraction <= 0.0 { return 0; } let ids = std::slice::from_raw_parts(ids, len); let groups = std::slice::from_raw_parts(groups, len); let out = std::slice::from_raw_parts_mut(out, len); ffi_guard(0, || { kernels::stratified_sample_mask(ids, groups, n_groups, seed, fraction, min_count, out) as i32 }) } /// Return the number of rows valid across `n_columns` f64 columns. A set bit /// in `positive_mask` requires that column to be positive as well as finite. /// With zero capacity this is an allocation-free parallel query; otherwise /// ascending u32 row IDs are written when they fit. `usize::MAX` is invalid. /// /// # Safety /// `columns` must point to `n_columns` readable pointers, each addressing /// `len` f64 values (a null data pointer is allowed only when `len == 0`). /// When `capacity > 0`, `out` must address that many writable u32 values. #[no_mangle] pub unsafe extern "C" fn xy_valid_indices_f64( columns: *const *const f64, n_columns: usize, len: usize, positive_mask: u64, out: *mut u32, capacity: usize, ) -> usize { if columns.is_null() || n_columns == 0 || n_columns > 64 || len > u32::MAX as usize || capacity > len || (capacity > 0 && out.is_null()) || (n_columns < 64 && positive_mask >> n_columns != 0) { return usize::MAX; } let pointers = std::slice::from_raw_parts(columns, n_columns); let mut slices = Vec::with_capacity(n_columns); for &pointer in pointers { if len > 0 && pointer.is_null() { return usize::MAX; } slices.push(if len == 0 { &[][..] } else { std::slice::from_raw_parts(pointer, len) }); } ffi_guard(usize::MAX, || { if capacity == 0 { kernels::valid_row_count_f64(&slices, positive_mask).unwrap_or(usize::MAX) } else { let output = std::slice::from_raw_parts_mut(out, capacity); if capacity == len { kernels::valid_row_indices_parallel_f64(&slices, positive_mask, output) .unwrap_or(usize::MAX) } else { kernels::valid_row_indices_f64(&slices, positive_mask, output).unwrap_or(usize::MAX) } } }) } /// Canonical row indices inside an inclusive rectangular window. Returns the /// count written. `out` must hold `len` u32s. /// /// # Safety /// `x`/`y` must point to `len` readable f64s; `out` to `len` writable u32s. #[no_mangle] pub unsafe extern "C" fn xy_range_indices( x: *const f64, y: *const f64, len: usize, lo_x: f64, hi_x: f64, lo_y: f64, hi_y: f64, out: *mut u32, ) -> usize { if !finite_ordered(lo_x, hi_x) || !finite_ordered(lo_y, hi_y) { return usize::MAX; } // u32 index ceiling — see xy_m4_indices. if len > u32::MAX as usize { return usize::MAX; } if len == 0 { return 0; } if x.is_null() || y.is_null() || out.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let out = std::slice::from_raw_parts_mut(out, len); ffi_guard(usize::MAX, || { kernels::range_indices(x, y, lo_x, hi_x, lo_y, hi_y, out) }) } /// Canonical row ids from `rows` that fall inside the rectangular window — /// the row-restricted twin of `xy_range_indices`, shaped like /// `xy_polygon_select`. Returns the count written; `out` must hold `n_rows` /// u32s. Row ids must be < `len`; an out-of-range id returns the error /// sentinel on every target, including panic-abort ones where the kernel's own /// indexing panic could not (see `kernels::range_scan_rows`). /// /// # Safety /// `x`/`y` must point to `len` readable f64s, `rows` to `n_rows` readable /// u32s, and `out` to `n_rows` writable u32s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_range_indices_rows( x: *const f64, y: *const f64, len: usize, rows: *const u32, n_rows: usize, lo_x: f64, hi_x: f64, lo_y: f64, hi_y: f64, out: *mut u32, ) -> usize { if !finite_ordered(lo_x, hi_x) || !finite_ordered(lo_y, hi_y) { return usize::MAX; } // u32 index ceiling — see xy_m4_indices. if len > u32::MAX as usize { return usize::MAX; } if n_rows == 0 { return 0; } if x.is_null() || y.is_null() || rows.is_null() || out.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let rows = std::slice::from_raw_parts(rows, n_rows); let out = std::slice::from_raw_parts_mut(out, n_rows); ffi_guard(usize::MAX, || { kernels::range_indices_rows(x, y, rows, lo_x, hi_x, lo_y, hi_y, out).unwrap_or(usize::MAX) }) } /// Canonical row ids from `rows` that fall inside the lasso polygon, by /// even-odd ray casting. Returns the count written; `out` must hold /// `n_rows` u32s. A polygon of fewer than 3 vertices selects nothing. Row ids /// must be < `len`; an out-of-range id returns the error sentinel on every /// target (see `kernels::range_scan_rows`). /// /// # Safety /// `x`/`y` must point to `len` readable f64s, `rows` to `n_rows` readable /// u32s, `poly_x`/`poly_y` to `n_poly` readable f64s, and `out` to `n_rows` /// writable u32s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_polygon_select( x: *const f64, y: *const f64, len: usize, rows: *const u32, n_rows: usize, poly_x: *const f64, poly_y: *const f64, n_poly: usize, out: *mut u32, ) -> usize { // u32 index ceiling — see xy_m4_indices. if len > u32::MAX as usize { return usize::MAX; } if n_rows == 0 { return 0; } // Fewer than three vertices encloses nothing. Answer before building any // slice: `from_raw_parts` requires a non-null, aligned pointer even at // length zero, so a caller passing null for an empty polygon must not // reach the constructions below. if n_poly < 3 { return 0; } if x.is_null() || y.is_null() || rows.is_null() || out.is_null() { return usize::MAX; } if poly_x.is_null() || poly_y.is_null() { return usize::MAX; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let rows = std::slice::from_raw_parts(rows, n_rows); let poly_x = std::slice::from_raw_parts(poly_x, n_poly); let poly_y = std::slice::from_raw_parts(poly_y, n_poly); let out = std::slice::from_raw_parts_mut(out, n_rows); ffi_guard(usize::MAX, || { kernels::polygon_select(x, y, rows, poly_x, poly_y, out).unwrap_or(usize::MAX) }) } /// Per-point local log density for a subset. Returns 1 on success, 0 on invalid /// grid/window arguments. /// /// # Safety /// `x`/`y` must point to `len` readable f64s; `out` to `len` writable f32s. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_local_log_density( x: *const f64, y: *const f64, len: usize, lo_x: f64, hi_x: f64, lo_y: f64, hi_y: f64, w: usize, h: usize, out: *mut f32, ) -> i32 { let bad = w == 0 || h == 0 || !finite_gt(lo_x, hi_x) || !finite_gt(lo_y, hi_y); if bad { return 0; } if len == 0 { return 1; } if x.is_null() || y.is_null() || out.is_null() { return 0; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); let out = std::slice::from_raw_parts_mut(out, len); ffi_guard(0, || { kernels::local_log_density(x, y, lo_x, hi_x, lo_y, hi_y, w, h, out); 1 }) } // -- tile pyramid (§5 Tier 3): opaque u64 handles, engine doc §3.3 ------------ /// Build a count pyramid over the given bounds. Returns a nonzero handle, or /// 0 on invalid arguments. The handle must be released with xy_pyramid_free. /// # Safety /// `x`/`y` must point to `len` readable f64s. #[no_mangle] pub unsafe extern "C" fn xy_pyramid_build( x: *const f64, y: *const f64, len: usize, x0: f64, x1: f64, y0: f64, y1: f64, base_dim: u32, ) -> u64 { if x.is_null() || y.is_null() || len == 0 { return 0; } let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); ffi_guard(0, || { match tiles::build(x, y, x0, x1, y0, y1, base_dim as usize) { Some(p) => tiles::reg_insert(p), None => 0, } }) } /// Build a pyramid with mean-color planes (LOD doc §2/§4.1) for a /// channel-bearing trace. Same handle registry and geometry as /// `xy_pyramid_build`; color source as in `xy_bin_2d_mean_color`. Returns the /// handle, or 0 on invalid arguments. /// /// # Safety /// `x`/`y` must point to `len` readable f64s; color source pointers must /// satisfy `color_source_from_raw`. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_pyramid_build_color( x: *const f64, y: *const f64, len: usize, idx: *const u8, rgba: *const u8, lut: *const u8, lut_len: usize, x0: f64, x1: f64, y0: f64, y1: f64, base_dim: u32, ) -> u64 { if x.is_null() || y.is_null() || len == 0 { return 0; } let Some(colors) = color_source_from_raw(len, idx, rgba, lut, lut_len) else { return 0; }; let x = std::slice::from_raw_parts(x, len); let y = std::slice::from_raw_parts(y, len); ffi_guard(0, || { match tiles::build_color(x, y, &colors, x0, x1, y0, y1, base_dim as usize) { Some(p) => tiles::reg_insert(p), None => 0, } }) } /// Increment a live pyramid from an appended point batch. Returns 1 when the /// update was applied, or 0 for a stale/busy handle, invalid pointers/lengths, /// or a finite point outside the pyramid's original domain. A rejected update /// never partially mutates the pyramid. /// /// # Safety /// `x`/`y` must point to `len` readable f64s (or may be null when `len == 0`). #[no_mangle] pub unsafe extern "C" fn xy_pyramid_append( handle: u64, x: *const f64, y: *const f64, len: usize, ) -> i32 { if len > 0 && (x.is_null() || y.is_null()) { return 0; } let x = if len == 0 { &[][..] } else { std::slice::from_raw_parts(x, len) }; let y = if len == 0 { &[][..] } else { std::slice::from_raw_parts(y, len) }; ffi_guard(0, || { tiles::reg_append(handle, x, y).unwrap_or(false) as i32 }) } /// Approximate in-window count from the finest level. 1 on success, 0 on a /// stale/invalid handle or bad arguments. /// # Safety /// `out_count` must point to a writable f64. #[no_mangle] pub unsafe extern "C" fn xy_pyramid_count( handle: u64, lo_x: f64, hi_x: f64, lo_y: f64, hi_y: f64, out_count: *mut f64, ) -> i32 { if out_count.is_null() || !finite_gt(lo_x, hi_x) || !finite_gt(lo_y, hi_y) { return 0; } ffi_guard(0, || { match tiles::reg_with(handle, |p| tiles::count(p, lo_x, hi_x, lo_y, hi_y)) { Some(c) => { *out_count = c; 1 } None => 0, } }) } /// Compose the window into a w×h grid. Returns the level used (>= 0), /// -1 on stale handle/bad args, -2 when the window outresolves the pyramid /// (caller must fall back to an exact re-bin and disclose it, §28). /// # Safety /// `out` must point to `w * h` writable f32s. #[no_mangle] pub unsafe extern "C" fn xy_pyramid_compose( handle: u64, lo_x: f64, hi_x: f64, lo_y: f64, hi_y: f64, w: usize, h: usize, max_upsample: usize, out: *mut f32, ) -> i32 { if out.is_null() || w == 0 || h == 0 || !finite_gt(lo_x, hi_x) || !finite_gt(lo_y, hi_y) { return -1; } let out_len = match w.checked_mul(h) { Some(n) => n, None => return -1, }; let out = std::slice::from_raw_parts_mut(out, out_len); let max_upsample = max_upsample.max(1); ffi_guard(-1, || { match tiles::reg_with(handle, |p| { tiles::compose(p, lo_x, hi_x, lo_y, hi_y, w, h, max_upsample, out) }) { Some(Some(level)) => level as i32, Some(None) => -2, None => -1, } }) } /// `xy_pyramid_compose` plus the mean-color plane: fills `out` with the same /// f32 counts (bit-identical) and `out_rgba` (`w*h*4`, straight-alpha RGBA8) /// with the composed mean colors. Returns the level used (>= 0), -1 for bad /// arguments/stale handle, or -2 when the window outresolves the pyramid OR /// the pyramid carries no color planes — the caller re-bins exactly either way. /// /// # Safety /// `out` must address `w*h` writable f32s and `out_rgba` `w*h*4` writable bytes. #[no_mangle] #[allow(clippy::too_many_arguments)] pub unsafe extern "C" fn xy_pyramid_compose_color( handle: u64, lo_x: f64, hi_x: f64, lo_y: f64, hi_y: f64, w: usize, h: usize, max_upsample: usize, out: *mut f32, out_rgba: *mut u8, ) -> i32 { if out.is_null() || out_rgba.is_null() || w == 0 || h == 0 || !finite_gt(lo_x, hi_x) || !finite_gt(lo_y, hi_y) { return -1; } let Some(out_len) = w.checked_mul(h) else { return -1; }; // Zero forgives a caller that forgot the knob; the count-only entry point // applies the same floor. let max_upsample = max_upsample.max(1); let out = std::slice::from_raw_parts_mut(out, out_len); let out_rgba = std::slice::from_raw_parts_mut(out_rgba, out_len * 4); ffi_guard(-1, || { match tiles::reg_with(handle, |p| { tiles::compose_color(p, lo_x, hi_x, lo_y, hi_y, w, h, max_upsample, out, out_rgba) }) { Some(Some(level)) => level as i32, Some(None) => -2, None => -1, } }) } /// Release a pyramid. 1 if it existed, 0 for stale/unknown handles. /// # Safety /// No pointer arguments; safe for any handle value. #[no_mangle] pub unsafe extern "C" fn xy_pyramid_free(handle: u64) -> i32 { ffi_guard(0, || if tiles::reg_remove(handle) { 1 } else { 0 }) } #[cfg(test)] mod tests { use super::*; #[test] #[cfg(panic = "unwind")] fn ffi_guard_maps_panic_to_sentinel() { // A panic anywhere behind the C ABI must become the entry point's // error sentinel, never an unwind across `extern "C"` (which would // abort the embedding interpreter). let hook = std::panic::take_hook(); std::panic::set_hook(Box::new(|_| {})); // silence the expected panic let got = ffi_guard(usize::MAX, || panic!("deliberate test panic")); std::panic::set_hook(hook); assert_eq!(got, usize::MAX); assert_eq!(ffi_guard(0i32, || 1i32), 1); } #[test] fn transition_key_ffi_reports_duplicate_rows_and_invalid_data() { let values = [7i16, -2, 7]; let mut low = [0u32; 3]; let mut high = [0u32; 3]; let mut first = usize::MAX; let mut index = usize::MAX; unsafe { assert_eq!( xy_transition_keys_fixed( values.as_ptr().cast(), values.len(), std::mem::size_of::(), transition::KIND_SIGNED, 0, low.as_mut_ptr(), high.as_mut_ptr(), &mut first, &mut index, ), 2 ); } assert_eq!((first, index), (0, 2)); let nonfinite = f64::INFINITY; first = usize::MAX; index = usize::MAX; unsafe { assert_eq!( xy_transition_keys_fixed( (&nonfinite as *const f64).cast(), 1, std::mem::size_of::(), transition::KIND_FLOAT64, 0, low.as_mut_ptr(), high.as_mut_ptr(), &mut first, &mut index, ), 1 ); } assert_eq!((first, index), (0, 0)); } #[test] fn transition_key_ffi_accepts_empty_null_spans_and_rejects_bad_pointers() { unsafe { assert_eq!( xy_transition_keys_fixed( std::ptr::null(), 0, 0, transition::KIND_BYTES, 0, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), ), 0 ); assert_eq!( xy_transition_keys_fixed( std::ptr::null(), 1, 1, transition::KIND_BYTES, 0, std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), std::ptr::null_mut(), ), 4 ); // A layout the caller should never send is status 4, not the // status-1 "declined this data" that means "use the oracle". let row = [0u8; 3]; let mut low = [0u32]; let mut high = [0u32]; let mut first = usize::MAX; let mut index = usize::MAX; assert_eq!( xy_transition_keys_fixed( row.as_ptr(), 1, 3, transition::KIND_UNICODE, 0, low.as_mut_ptr(), high.as_mut_ptr(), &mut first, &mut index, ), 4 ); assert_eq!((first, index), (usize::MAX, usize::MAX)); assert_eq!( xy_transition_keys_fixed( row.as_ptr(), 1, 1, transition::KIND_BYTES, 7, low.as_mut_ptr(), high.as_mut_ptr(), &mut first, &mut index, ), 4 ); } } #[test] #[cfg(target_pointer_width = "64")] fn index_emitting_entry_points_reject_u32_overflowing_len() { // Emitted row indices are u32: a column longer than u32::MAX would // wrap into valid-looking wrong rows. The guards fire before any // pointer is dereferenced, so tiny arrays with an absurd `len` are // safe to pass here. let x = [0.0f64, 1.0]; let y = [0.0f64, 1.0]; let too_long = u32::MAX as usize + 1; let groups = [0u8, 0]; let counts = [2u64]; let validity_columns = [x.as_ptr(), y.as_ptr()]; let mut idx = [0u32; 8]; let mut grid = [0f32; 4]; unsafe { assert_eq!( xy_m4_indices( x.as_ptr(), y.as_ptr(), too_long, 0.0, 1.0, 2, idx.as_mut_ptr() ), usize::MAX ); assert_eq!( xy_range_indices( x.as_ptr(), y.as_ptr(), too_long, 0.0, 1.0, 0.0, 1.0, idx.as_mut_ptr() ), usize::MAX ); assert_eq!( xy_valid_indices_f64( validity_columns.as_ptr(), validity_columns.len(), too_long, 0, idx.as_mut_ptr(), idx.len(), ), usize::MAX ); assert_eq!( xy_bin_2d_indices( x.as_ptr(), y.as_ptr(), too_long, 0.0, 1.0, 0.0, 1.0, 2, 2, grid.as_mut_ptr(), idx.as_mut_ptr() ), usize::MAX ); assert_eq!( xy_bin_2d_sample_range( x.as_ptr(), y.as_ptr(), too_long, 0.0, 1.0, 0.0, 1.0, 2, 2, 0, 0, grid.as_mut_ptr(), idx.as_mut_ptr(), idx.len(), ), usize::MAX ); assert_eq!( xy_bin_2d_stratified_sample_range_u8_counted( x.as_ptr(), y.as_ptr(), groups.as_ptr(), too_long, counts.as_ptr(), 1, 0.0, 1.0, 0.0, 1.0, 2, 2, 0, 0.5, 1, grid.as_mut_ptr(), idx.as_mut_ptr(), idx.len(), ), usize::MAX ); } } #[test] #[cfg(target_pointer_width = "64")] fn grid_size_products_that_overflow_are_rejected() { // In release builds an unchecked `w * h` wraps silently and the slice // built from it is shorter than the kernel-side expectation; every // grid entry point must refuse instead (xy_rasterize already did). let x = [0.5f64]; let y = [0.5f64]; let groups = [0u8]; let counts = [1u64]; let huge = usize::MAX / 2 + 1; // huge * 2 wraps to 0 let mut grid = [0f32; 4]; let mut idx = [0u32; 4]; unsafe { assert_eq!( xy_bin_2d( x.as_ptr(), y.as_ptr(), 1, 0.0, 1.0, 0.0, 1.0, huge, 2, grid.as_mut_ptr() ), 0 ); assert_eq!( xy_bin_2d_indices( x.as_ptr(), y.as_ptr(), 1, 0.0, 1.0, 0.0, 1.0, huge, 2, grid.as_mut_ptr(), idx.as_mut_ptr() ), usize::MAX ); assert_eq!( xy_bin_2d_sample_range( x.as_ptr(), y.as_ptr(), 1, 0.0, 1.0, 0.0, 1.0, huge, 2, 0, 0, grid.as_mut_ptr(), idx.as_mut_ptr(), 1, ), usize::MAX ); assert_eq!( xy_bin_2d_stratified_sample_range_u8_counted( x.as_ptr(), y.as_ptr(), groups.as_ptr(), 1, counts.as_ptr(), 1, 0.0, 1.0, 0.0, 1.0, huge, 2, 0, 0.5, 1, grid.as_mut_ptr(), idx.as_mut_ptr(), 1, ), usize::MAX ); assert_eq!( xy_m4_indices( x.as_ptr(), y.as_ptr(), 1, 0.0, 1.0, usize::MAX / 4 + 1, // n_buckets * 4 wraps idx.as_mut_ptr() ), usize::MAX ); assert_eq!( xy_pyramid_compose(0, 0.0, 1.0, 0.0, 1.0, huge, 2, 2, grid.as_mut_ptr()), -1 ); } } }