//! pyo3 binding — the `vectorhd_core` Python extension module. //! //! Built by maturin with `--features python`. **Importing it is always optional**: the Python side //! probes for it once (`vectorhd.analytic.backend`) and falls back to the pure-Python kernel, so //! neither the package nor its test suite ever acquires a hard Rust dependency. //! //! Zero-copy where it matters: array arguments arrive as `PyReadonlyArray*` views over the caller's //! numpy buffers, so a window call marshals pointers rather than copying the target patch. That //! matters here — P0 exists to measure per-call FFI overhead against a kernel whose per-window work //! is only tens of microseconds, and a copy of the patch would dominate the thing being measured. use pyo3::prelude::*; /// True when this build implements C-5's quadratic per-region colour model (P1a). #[pyfunction] fn supports_quadratic() -> bool { true } /// True when this build implements the four bezigon priors (P1b R1). /// /// Probed, not assumed, for the same reason `supports_quadratic` is: a P1a extension exposes /// `windowed_data` and nothing else, and a missing probe must read as "no" so the caller keeps its /// Python path rather than calling into a symbol that is not there. #[pyfunction] fn supports_priors() -> bool { true } /// True when this build exposes the per-window objective handle (P1b R2). /// /// This is the third capability level: `auto` resolves objective-handle → kernel-only → python, /// and each rung must be independently probeable so an older extension degrades to the rung it /// actually implements instead of failing. #[pyfunction] fn supports_objective_handle() -> bool { true } /// The exception type a Rust panic surfaces as, so Python can guard against it explicitly. /// /// pyo3 raises `pyo3_runtime.PanicException`, whose module name is **synthetic** — there is no /// importable `pyo3_runtime`, so Python cannot obtain the class by importing it and would /// otherwise have to trigger a panic to learn the type. Since it derives from `BaseException` /// rather than `Exception`, a bare `except Exception` does not catch it, and the orchestrator's /// never-fail refine guard needs the class to widen its catch correctly. Exposing it is the /// extension declaring its own failure mode instead of leaving the caller to guess. #[pyfunction] fn panic_exception_type(py: Python<'_>) -> Py { py.get_type::().unbind() } /// Crate version, so Python can log which kernel it actually loaded. #[pyfunction] fn version() -> &'static str { env!("CARGO_PKG_VERSION") } /// Build-time facts worth asserting from the Python side. #[pyfunction] fn kernel_info() -> PyResult> { let mut m = std::collections::HashMap::new(); m.insert("version".into(), env!("CARGO_PKG_VERSION").into()); m.insert("name".into(), env!("CARGO_PKG_NAME").into()); // The optimizer's forward model is f64 throughout; a f32 build would silently break the // 1e-9 equivalence gates, so the Python side asserts this. m.insert("float".into(), "f64".into()); // Capability flags. The Python dispatch routes quadratic-colour windows here only when this // says yes, so an older extension keeps the per-window Python fallback instead of silently // producing flat-colour answers for a quadratic model. m.insert("quadratic_color".into(), "true".into()); m.insert("priors".into(), "true".into()); m.insert("objective_handle".into(), "true".into()); Ok(m) } #[pymodule] fn vectorhd_core(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_function(wrap_pyfunction!(version, m)?)?; m.add_function(wrap_pyfunction!(kernel_info, m)?)?; m.add_function(wrap_pyfunction!(supports_quadratic, m)?)?; m.add_function(wrap_pyfunction!(supports_priors, m)?)?; m.add_function(wrap_pyfunction!(supports_objective_handle, m)?)?; m.add_function(wrap_pyfunction!(panic_exception_type, m)?)?; crate::window::register(m)?; crate::priors::register(m)?; crate::handle::register(m)?; Ok(()) }