Rust + PyO3 function skill (eo-processor)
Use this skill when you need to add or change a Rust implementation that is exposed to Python. The goal is: correct math + stable API + high performance + predictable behavior.
Activation checklist (before you code)
- Identify the public Python name of the function (e.g.,
ndvi) and confirm whether this is:
- a new function, or
- a behavior change, or
- a bug fix (no API change).
- Find existing patterns in:
src/ for Rust/PyO3 style
python/eo_processor/ for exports/docstrings
python/eo_processor/__init__.pyi for typing
tests/ for numerical expectations.
- Define expected behavior for:
- shape mismatches
- division by zero / near-zero denominators
- NaN/Inf handling
- dtype support and output dtype.
If anything is ambiguous, decide based on existing functions in this repo (stay consistent).
Design rules (repo conventions)
1) Keep the core pure
- Rust compute kernels should be pure functions: no file I/O, no network access, no global state.
- Deterministic results for deterministic inputs.
2) Validate shapes early, fail clearly
- If the function expects aligned shapes, check and raise a Python-friendly error.
- Do not silently broadcast unless the repo already does that everywhere.
3) Numerical stability is not optional
- For normalized differences and ratios, guard denominators with a small epsilon.
- Use a consistent epsilon strategy across similar functions.
- Make NaN behavior explicit (propagate vs sanitize) and match existing functions.
4) Avoid unnecessary allocations
- Prefer single-pass loops where possible.
- Avoid creating multiple temporaries for large rasters.
- If using ndarray operations, be mindful of intermediate allocations.
5) Don’t introduce unsafe without a compelling reason
- If you think you need
unsafe, stop and provide:
- justification (benchmark evidence),
- a safety argument,
- tests that would catch UB-like symptoms.
Implementation workflow (step-by-step)
Step A: Specify the API contract
Write down:
- signature at Python level (args, defaults, return)
- expected input shapes and dtypes
- output dtype
- error behavior & messages
Example contract for a normalized difference:
- Inputs:
a, b float arrays (1D or 2D depending on existing patterns)
- Output: float array same shape
- Math:
(a - b) / (a + b + EPS)
- Errors: shape mismatch ->
ValueError (or the project’s standard)
Step B: Implement Rust function with PyO3 glue
Typical structure:
- Accept
Python token and PyReadonlyArray* inputs.
- Convert to
ndarray::ArrayView* using .as_array().
- Validate shape compatibility.
- Allocate output (or create new array) and fill it.
- Return
PyArray* via into_pyarray(py) (or existing conventions).
Prefer returning a new array rather than mutating inputs.
Step C: Register with the module
- Ensure the
#[pyfunction] is added to the module in the #[pymodule] initializer.
- Keep ordering, naming, and grouping consistent with neighboring functions.
Step D: Maintain Python surface coherence
Whenever you add/rename a Rust-exposed function, you almost always need to update:
python/eo_processor/__init__.py (exports and __all__)
python/eo_processor/__init__.pyi (typing stub)
- docs (
README.md at minimum; docs/ if used by the repo)
- tests (
tests/)
If the change is internal-only, do not export it publicly.
Error handling guidance (PyO3)
- Prefer returning
PyResult<T>.
- Use Pythonic errors; typical choices:
ValueError for invalid shapes/values
TypeError for wrong types (rare if signature enforces arrays)
- Error messages should be:
- actionable
- short
- consistent across functions.
Performance guidance
What to optimize first
- Avoid extra allocations / temporaries.
- Ensure tight loops with minimal branching in the inner loop.
- Use contiguous iteration patterns when possible.
When to benchmark
Benchmark if:
- you’re adding a new kernel, or
- you changed the inner loop, or
- you changed dtype handling.
Don’t claim speedups without before/after numbers and array sizes.
Testing expectations (what to add)
Add tests that cover:
- Correctness: known inputs with known outputs (small arrays).
- Stability: near-zero denominators; ensure outputs are finite if expected.
- Shape behavior: mismatch raises the correct error.
- NaN behavior: if inputs contain NaN, outcome matches contract.
- Dtype behavior: float32/float64 if supported by project conventions.
For numerical comparisons:
- use tolerances appropriate for float64/float32
- avoid exact equality for floats unless intentionally exact.
Documentation expectations
For a new EO index function, document:
- the formula
- what each band represents
- recommended input scaling (e.g., reflectance 0–1 vs scaled ints)
- typical output range and interpretation
Keep docs short in README.md and move deeper material to docs/ if present.
“Done” checklist (must be true before you stop)
Common pitfalls (avoid these)
- Returning wrong shape due to accidental broadcast or flattening
- Implicit dtype casts causing precision loss
- Division by zero producing noisy Inf/NaN without being documented
- Adding a Rust function but forgetting Python
__init__.py / __init__.pyi
- Performance regressions from extra temporaries
- Changing behavior without updating tests and docs
Local references (repo)
- Engineering rules & checklists:
AGENTS.md
- User docs and API overview:
README.md, QUICKSTART.md
- Existing workflows/examples:
WORKFLOWS.md, examples/
- Rust crate entrypoints and module registration:
src/
- Python exports/stubs:
python/eo_processor/
- Tests:
tests/
Converted and distributed by TomeVault — claim your Tome and manage your conversions.
1---2name: rust-pyo3-function3description: Implement or modify a Rust function exposed to Python via PyO3 in eo-processor. Use when adding new compute kernels, fixing numerical/shape bugs in the Rust core, or wiring Rust functions into the Python module safely and consistently. Use when this capability is needed.4---56# Rust + PyO3 function skill (eo-processor)78Use this skill when you need to add or change a Rust implementation that is exposed to Python. The goal is: **correct math + stable API + high performance + predictable behavior**.910## Activation checklist (before you code)11121. Identify the *public Python name* of the function (e.g., `ndvi`) and confirm whether this is:13 - a new function, or14 - a behavior change, or15 - a bug fix (no API change).162. Find existing patterns in:17 - `src/` for Rust/PyO3 style18 - `python/eo_processor/` for exports/docstrings19 - `python/eo_processor/__init__.pyi` for typing20 - `tests/` for numerical expectations.213. Define expected behavior for:22 - shape mismatches23 - division by zero / near-zero denominators24 - NaN/Inf handling25 - dtype support and output dtype.2627If anything is ambiguous, decide based on existing functions in this repo (stay consistent).2829---3031## Design rules (repo conventions)3233### 1) Keep the core pure34- Rust compute kernels should be **pure functions**: no file I/O, no network access, no global state.35- Deterministic results for deterministic inputs.3637### 2) Validate shapes early, fail clearly38- If the function expects aligned shapes, check and raise a Python-friendly error.39- Do not silently broadcast unless the repo already does that everywhere.4041### 3) Numerical stability is not optional42- For normalized differences and ratios, guard denominators with a small epsilon.43- Use a *consistent* epsilon strategy across similar functions.44- Make NaN behavior explicit (propagate vs sanitize) and match existing functions.4546### 4) Avoid unnecessary allocations47- Prefer single-pass loops where possible.48- Avoid creating multiple temporaries for large rasters.49- If using ndarray operations, be mindful of intermediate allocations.5051### 5) Don’t introduce `unsafe` without a compelling reason52- If you think you need `unsafe`, stop and provide:53 - justification (benchmark evidence),54 - a safety argument,55 - tests that would catch UB-like symptoms.5657---5859## Implementation workflow (step-by-step)6061### Step A: Specify the API contract62Write down:63- signature at Python level (args, defaults, return)64- expected input shapes and dtypes65- output dtype66- error behavior & messages6768Example contract for a normalized difference:69- Inputs: `a`, `b` float arrays (1D or 2D depending on existing patterns)70- Output: float array same shape71- Math: `(a - b) / (a + b + EPS)`72- Errors: shape mismatch -> `ValueError` (or the project’s standard)7374### Step B: Implement Rust function with PyO3 glue75Typical structure:761. Accept `Python` token and `PyReadonlyArray*` inputs.772. Convert to `ndarray::ArrayView*` using `.as_array()`.783. Validate shape compatibility.794. Allocate output (or create new array) and fill it.805. Return `PyArray*` via `into_pyarray(py)` (or existing conventions).8182Prefer returning a new array rather than mutating inputs.8384### Step C: Register with the module85- Ensure the `#[pyfunction]` is added to the module in the `#[pymodule]` initializer.86- Keep ordering, naming, and grouping consistent with neighboring functions.8788### Step D: Maintain Python surface coherence89Whenever you add/rename a Rust-exposed function, you almost always need to update:90- `python/eo_processor/__init__.py` (exports and `__all__`)91- `python/eo_processor/__init__.pyi` (typing stub)92- docs (`README.md` at minimum; `docs/` if used by the repo)93- tests (`tests/`)9495If the change is internal-only, do not export it publicly.9697---9899## Error handling guidance (PyO3)100101- Prefer returning `PyResult<T>`.102- Use Pythonic errors; typical choices:103 - `ValueError` for invalid shapes/values104 - `TypeError` for wrong types (rare if signature enforces arrays)105- Error messages should be:106 - actionable107 - short108 - consistent across functions.109110---111112## Performance guidance113114### What to optimize first1151. Avoid extra allocations / temporaries.1162. Ensure tight loops with minimal branching in the inner loop.1173. Use contiguous iteration patterns when possible.118119### When to benchmark120Benchmark if:121- you’re adding a new kernel, or122- you changed the inner loop, or123- you changed dtype handling.124125Don’t claim speedups without before/after numbers and array sizes.126127---128129## Testing expectations (what to add)130131Add tests that cover:1321. **Correctness**: known inputs with known outputs (small arrays).1332. **Stability**: near-zero denominators; ensure outputs are finite if expected.1343. **Shape behavior**: mismatch raises the correct error.1354. **NaN behavior**: if inputs contain NaN, outcome matches contract.1365. **Dtype behavior**: float32/float64 if supported by project conventions.137138For numerical comparisons:139- use tolerances appropriate for float64/float32140- avoid exact equality for floats unless intentionally exact.141142---143144## Documentation expectations145146For a new EO index function, document:147- the formula148- what each band represents149- recommended input scaling (e.g., reflectance 0–1 vs scaled ints)150- typical output range and interpretation151152Keep docs short in `README.md` and move deeper material to `docs/` if present.153154---155156## “Done” checklist (must be true before you stop)157158- [ ] Rust function implemented with correct math and shape checks159- [ ] Function is registered in the PyO3 module160- [ ] Python exports updated (if public)161- [ ] Typing stubs updated (if public)162- [ ] Tests added/updated and pass163- [ ] Docs updated (if public)164- [ ] Lint/format gates pass for Rust (+ Python if touched)165- [ ] No unnecessary new dependencies166167---168169## Common pitfalls (avoid these)170171- Returning wrong shape due to accidental broadcast or flattening172- Implicit dtype casts causing precision loss173- Division by zero producing noisy Inf/NaN without being documented174- Adding a Rust function but forgetting Python `__init__.py` / `__init__.pyi`175- Performance regressions from extra temporaries176- Changing behavior without updating tests and docs177178---179180## Local references (repo)181182- Engineering rules & checklists: `AGENTS.md`183- User docs and API overview: `README.md`, `QUICKSTART.md`184- Existing workflows/examples: `WORKFLOWS.md`, `examples/`185- Rust crate entrypoints and module registration: `src/`186- Python exports/stubs: `python/eo_processor/`187- Tests: `tests/`188189---190> Converted and distributed by [TomeVault](https://tomevault.io/claim/bnjam) — claim your Tome and manage your conversions.191<!-- tomevault:4.0:skill_md:2026-04-15 -->