File size: 4,145 Bytes
5e84645
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//! 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<pyo3::types::PyType> {
    py.get_type::<pyo3::panic::PanicException>().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<std::collections::HashMap<String, String>> {
    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(())
}