rextio-numpy
Rextio plugin that lowers eligible NumPy code to native Rust.
The first-party Rextio plugin for NumPy.
It implements Rextio plugin protocol v2 (rextio.plugins.api): the plugin
self-describes, as machine-readable rule records, which NumPy usage lowers to
Rust (via the ndarray crate) and which stays on the Python fallback —
following Rextio's core contract (CPython-equivalent semantics or fall back).
Status: 0.1.2 public Alpha
rextio-numpy 0.1.2 was released on 2026-07-26. The prior published
cut was rextio-numpy 0.1.1 (2026-07-14).
Implements plugin API 1.5 end to end: the annotation vocabulary, the
deterministic claim pass (including keyword/literal axis metadata and
structured ClaimExpr trees from core API 1.2), lower() emission to Rust via
the ndarray crate, multi-op elementwise chain fusion via
operand_mode="leaves", non-chained comparison claims with resident boolean
results, three-argument numpy.where, receiver metadata for certified ndarray
methods, and pinned crate injection (rust-numpy numpy =0.29.0; ndarray via
its re-export).
Dependency: requires rextio>=0.1.6,<0.2. NumPy is deliberately not
a runtime dependency of this package — only the user-facing
rextio_numpy.types vocabulary imports NumPy in the user project.
Core compatibility
This release requires core rextio>=0.1.6,<0.2 and plugin API 1.5 for
comparison claim sites and resident-result propagation. It does not implement
or advertise the optional standalone-artifact capability.
Annotation vocabulary (rextio_numpy.types)
| Annotation | Rank | dtype |
|---|---|---|
F64Arr1 / F64Arr2 |
1 / 2 | float64 |
F32Arr1 / F32Arr2 |
1 / 2 | float32 |
I64Arr1 / I64Arr2 |
1 / 2 | int64 |
Plain runtime aliases of numpy.ndarray — no runtime validation. The
analyzer resolves them to plugin type keys when the plugin is enabled.
Comparison results use two additional plugin-owned resident types internally
(rextio-numpy/bool-1d and rextio-numpy/bool-2d). They deliberately have no
annotation spellings (PluginType.annotations == ()), no public
BoolArr1 / BoolArr2 aliases, and no Python boundary conversion. Source code
cannot name or forge them: a mask must be produced by a claimed comparison and
consumed inside generated native code.
Exact base-ndarray boundary
The annotations are nominal, so static analysis cannot tell an exact
numpy.ndarray from numpy.matrix, numpy.memmap, or a custom ndarray
subclass. Every plugin-typed native parameter therefore applies NumPy's exact
C-level ndarray check before copying data. If the native route is executed
with a subclass, it deterministically raises:
TypeError: rextio-numpy native boundary requires exact numpy.ndarray; ndarray subclasses are unsupported
This is a runtime native-boundary rejection, not an automatic static fallback.
Exact base-ndarray views and strided arrays remain supported. Convert with
numpy.asarray before the typed hot path when subclass behavior is irrelevant;
when matrix, __array_ufunc__, __array_priority__, or other subclass/subok
semantics matter, keep the enclosing function on Python fallback.
Native surface (verified)
- Element-wise
+ - * /on same-dtype float64 / float32 / int64 arrays of rank 1 or 2 — array–array under full supported NumPy broadcasting for ranks 1–2 (including 1-D↔2-D and zero-size axes), array–scalar, and scalar–array; operand order preserved for-and/. int64 true division (/) yields a float64 array at the broadcast result rank (RXTP-NUMPY-001). - Exact binary ufunc calls
numpy.add,numpy.subtract,numpy.multiply, andnumpy.dividewith exactly two positional operands reuse that same dtype/rank/broadcast matrix (RXTP-NUMPY-007). Optional ufunc arguments such asout,where,dtype, andcasting, extra operands, and mixed/unsupported dtypes remain on Python fallback. - Non-chained comparisons
== != < <= > >=over same-dtype float64 / float32 / int64 rank-1/rank-2 arrays, including every rank-1↔rank-2 broadcast pairing and matching array↔Python-scalar forms. The result is a resident rank-1/rank-2 boolean array that cannot cross a Python parameter or return boundary; a direct exported return is rejected by Core withRXT092(RXTP-NUMPY-009). Chained, identity, and membership comparisons stay fallback. - Resident-mask logical composition: exact
numpy.logical_not(mask),numpy.logical_and(left, right), andnumpy.logical_or(left, right)consume only resident rank-1/rank-2 boolean masks produced by supported comparisons or logical calls. Binary forms use NumPy-compatible rank-1/rank-2 broadcasting, including zero axes; masks remain unnameable and cannot cross a Python boundary (RXTP-NUMPY-015/016). - Exact three-positional-argument
numpy.where(condition, x, y)whereconditionis one resident comparison/logical result and the branches are same-dtype numeric arrays, or one array plus its matching Python scalar. At least one branch must be an array. Condition-only, keyword, two-scalar, and mixed-dtype forms stay fallback. Core-canonicalized import aliases such asnp.whereandfrom numpy import where as chooseare supported; runtime assignment/rebinding aliases stay fallback. Comparison and branch shapes are validated independently under NumPy broadcasting, including zero axes (RXTP-NUMPY-014). numpy.dot(a, b)/a.dot(b)on same-dtype 1-D float64 and int64 only. float32 1-D dots are deliberately fallback (sequential f32 accumulation diverges materially from NumPy pairwise summation; the plugin API has no enforceable runtime length gate). 2-D operands and@/ matmul stay unclaimed (RXTP-NUMPY-002). Rank-2 matmul research retained product decision NO-GO / fallback-retained for this cut.- Whole-array reductions (module-call form, no keywords):
numpy.sumon float64 and int64, ranks 1–2numpy.meanon float64, ranks 1–2numpy.max/numpy.minon int64, ranks 1–2, including equivalenta.max()/a.min()method forms- float32 sum/mean and int64 mean are fallback (material accumulation-order divergence; no runtime length gate).
- Floating whole-array max/min stay fallback because NaN payload/sign and signed-zero tie behavior is platform/SIMD-dependent.
- Equivalent
a.sum()/a.mean()method forms are native under the current receiver-metadata contract (RXTP-NUMPY-003). - Empty int64 max/min raises NumPy-compatible
ValueError(RXTP-NUMPY-008).
- Literal-axis reductions (
numpy.sum|mean|max|min(a, axis=<int literal>),numpy.sum|mean|max|min(a, <int literal>), or the equivalent ndarray method — exactly one named or positional axis):sum: f64/i64 ranks 1–2;mean: f64 ranks 1–2max/min: int64 ranks 1–2 only. Float extrema remain fallback: NumPy's NaN payload/sign and signed-zero tie behavior varies by supported platform/SIMD profile and has no single stable native equivalent.- Negative axes are normalized at claim time; out-of-range,
None, tuple, dynamic,axis=+N(when core does not extractUAdd), additional positional arguments, and extra-kw forms stay fallback - Rank-1 → core builtin
float/int(not NumPy scalar subclasses); rank-2 single-axis → matching rank-1 plugin array - f64 axis sum/mean: NumPy-compatible pairwise (fast-stride) / sequential (slow-stride) dispatch from runtime strides — C and F layouts
- Empty max/min reduced dimension →
ValueErrorwith NumPy-compatible text - Empty mean value semantics match; native leg omits NumPy's
RuntimeWarning(documented divergence) (RXTP-NUMPY-004)
- Elementwise chain fusion (binary-op trees of 2–8 pure array-name
binops, same dtype, ranks 1–2; f64/f32
+ - * /, i64+ - *only): claimed withoperand_mode="leaves"underrextio-numpy/elementwise-chain-fusionso core subsumes descendant per-op claims. One fused helper: LTR postorder broadcast validation, leaf views only, one output allocation/data pass, AST evaluation order preserved (i64 wrapping at every intermediate). Out-of-scope trees keep ordinary per-op elementwise (RXTP-NUMPY-005). - Exact unary module calls
numpy.negative(a),numpy.absolute(a),numpy.abs(a), andnumpy.square(a)on f64/f32/i64 rank-1/rank-2 arrays (RXTP-NUMPY-006). Noout,where, dtype override, or unary method form is claimed. Floating signed-zero/NaN/infinity values follow NumPy; int64 negative/absolute/square use wraparound arithmetic, includingINT64_MIN.
Shape/length mismatches raise ValueError with NumPy's exact messages.
int64 +, -, *, sum, and dot use wraparound arithmetic matching
NumPy release builds. Floating reductions/dots are certified
within-tolerance (not universal bit-equivalence). Whole-array float64
sum/mean may still differ from NumPy pairwise order within 1e-12; literal-
axis f64 sum/mean intentionally match NumPy's pairwise/sequential layout
rules.
Python scalar annotations inherit Core's native scalar domains. In particular,
int lowers to signed i64. Values in [-2**63, 2**63-1] are certified for
the comparison/where scalar lanes; an arbitrary Python integer outside that
range is a boundary type-contract violation and raises OverflowError before
the plugin helper runs (the ordinary NumPy fallback may accept it). float32
lanes apply NumPy 2.4 weak-scalar narrowing from Python float, including
overflow to infinity, NaN, infinities, and signed zero; comparison and both
scalar branch positions are bit-certified.
Optimization-safe lower validation
Every native lowerer — elementwise binops and exact ufunc-call aliases, dot,
reductions, unary calls, and fusion — independently revalidates its
claim/context contract before emitting Rust. The explicit ValueError guards
remain active under python -O /
PYTHONOPTIMIZE=1: they verify the route rule and reconstructed result type,
operand mode and placement, operand arity/types, and the route's permitted
literals, keywords, callables, expression, and receiver metadata. Forged or
inconsistent metadata therefore fails closed instead of emitting a helper.
For non-literal operands, both Core's omitted operand_literals form and its
arity-matched ClaimLiteral(is_literal=False) placeholders are accepted;
populated slots require the route's exact count and lane-specific,
type-compatible literal metadata.
Required CI installs the public Core rextio==0.1.6 release, requires host
plugin API 1.5 or later, runs the complete real-Cargo suite without test
selection, and rejects skipped certification cases.
Accepted release divergence: missing NumPy RuntimeWarning
Certified acceptance surface: values, dtypes, and exceptions.
Accepted for this release (do not overclaim warning equivalence): native
empty-mean / empty-axis-lane, divide-by-zero, invalid-value /
invalid-reduction, elementwise, fused-elementwise, and related covered paths
may omit NumPy RuntimeWarning emissions. Values still match the certified
contract; warning parity is not part of the acceptance surface.
Full rule surface
| Rule | Outcome | Code |
|---|---|---|
Non-chained == != < <= > >= on same-dtype f64/f32/i64 ranks 1–2 (broadcasting, array↔scalar); resident bool result |
native (verified) | RXTP-NUMPY-009 |
numpy.logical_not over one resident bool mask |
native (verified) | RXTP-NUMPY-015 |
numpy.logical_and / numpy.logical_or over two resident bool masks (rank-1/rank-2 broadcast) |
native (verified) | RXTP-NUMPY-016 |
Exact three-positional numpy.where(condition, x, y) with resident comparison/logical condition and bounded same-dtype branches |
native (verified) | RXTP-NUMPY-014 |
Element-wise + - * / on same-dtype f64/f32/i64 ranks 1–2 (broadcasting, array↔scalar) |
native (verified) | RXTP-NUMPY-001 |
Exact two-positional/no-keyword numpy.add/subtract/multiply/divide(a, b) over the same matrix |
native (verified) | RXTP-NUMPY-007 |
numpy.dot(a, b) / a.dot(b) on same-dtype 1-D f64/i64 (not f32, not 2-D, not @) |
native (verified) | RXTP-NUMPY-002 |
Whole-array numpy.sum / a.sum on f64/i64 ranks 1–2; numpy.mean / a.mean on f64 ranks 1–2 (no kwargs) |
native (verified) | RXTP-NUMPY-003 |
Whole-array numpy.max/min(a) / a.max/min() on i64 ranks 1–2 (no arguments/options) |
native (verified) | RXTP-NUMPY-008 |
Literal-axis module or ndarray-method sum/mean/max/min with one named or positional integer axis (see native surface) |
native (verified) | RXTP-NUMPY-004 |
| Multi-op elementwise chain fusion (2–8 pure array-name binops; leaves mode) | native (verified) | RXTP-NUMPY-005 |
Exact numpy.negative/absolute/abs/square(a) on f64/f32/i64 ranks 1–2 |
native (verified) | RXTP-NUMPY-006 |
| Operand types outside the claimed set (incl. float extrema, f32 sum/mean/dots, i64 mean, mixed dtypes) | fallback | RXTP-NUMPY-010 |
| Rank > 2 or unknown rank | fallback | RXTP-NUMPY-011 |
| Mutating aliased views | fallback | RXTP-NUMPY-012 |
Runtime ndarray subclasses / matrix / __array_ufunc__ overrides at a native boundary |
reject (runtime TypeError; not static fallback) | RXTP-NUMPY-013 |
Any other NumPy API (unsupported method forms, non-literal/tuple axis, 2-D matmul/@, …) |
fallback | RXTP-NUMPY-019 |
All rules are experimental. Codes RXTP-NUMPY-011/012/013/019 are
declarative-only: they document exclusion boundaries in the rule records but
are never attached to claim diagnostics. RXTP-NUMPY-013 documents a runtime
native-boundary rejection rather than fallback; other uncovered sites surface
as core's RXT030. Only RXTP-NUMPY-010 is actively emitted by claim().
NumPy itself is deliberately not a dependency of the plugin — only the
user-facing rextio_numpy.types vocabulary module imports it, in the user's
project. A @numba.*-decorated function is always respected as the user's
opt-in to Numba's semantics and is never lowered by this plugin.
Benchmarks
The public honest benchmark suite is repository / source-checkout tooling (not a PyPI entry point). It measures a fixed F64Arr1 scenario subset (independent of the full released surface) — fallback vs native wall latency, reporting wins and losses honestly. A result below 1× is valid and rendered as such.
Usage
# rextio.toml
[rust]
build_tool = "cargo"
[plugins]
enabled = ["rextio-numpy"]
import numpy as np
from rextio_numpy.types import F64Arr1 # plain runtime alias of numpy.ndarray
def dot(a: F64Arr1, b: F64Arr1) -> float:
return np.dot(a, b)
pip install rextio-numpy # 0.1.2 requires rextio >= 0.1.6
rextio capabilities --format json # numpy rules appear under "rules"
rextio build . # lowered kernels compile via cargo
Note: The 0.1.2 surface requires a core that provides plugin API 1.5 (
rextio>=0.1.6). To work against the surface from a source checkout, see Development below.
Development
Core for this release requires rextio>=0.1.6,<0.2. For source
development against a sibling Core checkout that exposes plugin API 1.5,
install both trees editable:
uv venv --python 3.11 .venv
uv pip install --python .venv/bin/python -e path/to/rextio
uv pip install --python .venv/bin/python --no-deps -e .
uv pip install --python .venv/bin/python pytest ruff mypy
.venv/bin/python -m pytest
Benchmark suite (from a source checkout; see benchmarks/README.md):
python -m benchmarks --list
python -m benchmarks --output-dir /tmp/rextio-numpy-bench
Verified suite totals (this release)
On this tree:
.venv/bin/python -m pytest --collect-only -qreports 969 collected tests total.- The focused collection command
.venv/bin/python -m pytest tests/test_certification_real_cargo.py --collect-only -qreports 151 real-Cargo certification cases.
Those 151 cases are cargo-gated and may also skip via dependency
importorskip conditions (e.g. NumPy, Hypothesis). Re-collect after material
test changes; do not treat these numbers as a product API.
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rextio_numpy-0.1.2.tar.gz.
File metadata
- Download URL: rextio_numpy-0.1.2.tar.gz
- Upload date:
- Size: 140.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8ae05eba569f3a9852b9ef44507c399c0f1c6d61367d8784fb1986672aa3abf3
|
|
| MD5 |
ed18d444069a315e9b2842dd623360db
|
|
| BLAKE2b-256 |
d7e37b2305d10c41a3a592cfe3558ed1aed5631ee9d6a1bfe60e4636c38fa831
|
File details
Details for the file rextio_numpy-0.1.2-py3-none-any.whl.
File metadata
- Download URL: rextio_numpy-0.1.2-py3-none-any.whl
- Upload date:
- Size: 74.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/6.2.0 CPython/3.11.9
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b823ca3264b85c8003117ad9f8e43b981e21d67b764b8c3c1c0f8cb237404fae
|
|
| MD5 |
dc69497d0cac7d9d7ece75046d36e68a
|
|
| BLAKE2b-256 |
3cee28b41c2012424cbc1fd22914c82f6e790d178a45142f8b3cff15b8bbc83d
|