sc_neurocore_engine/bindings/sc_inference.rs
1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Commercial license available
3// © Concepts 1996–2026 Miroslav Šotek. All rights reserved.
4// © Code 2020–2026 Miroslav Šotek. All rights reserved.
5// ORCID: 0009-0009-3560-0851
6// Contact: www.anulum.li | protoscience@anulum.li
7// SC-NeuroCore — stochastic-inference PyO3 binding
8
9//! Python binding for inference over caller-owned packed stochastic weights.
10
11use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
12use pyo3::exceptions::PyValueError;
13use pyo3::prelude::*;
14
15use crate::sc_inference;
16
17/// Register stochastic-inference bindings with the extension module.
18pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
19 module.add_function(wrap_pyfunction!(py_sc_forward_packed, module)?)?;
20 Ok(())
21}
22
23/// Stochastic forward pass over caller-owned packed weight bitstreams.
24///
25/// Parity contract with `sc_neurocore.accel.sc_forward`: this Rust path and the
26/// NumPy fallback return bit-identical results for a fixed seed because the input
27/// encoder is the deterministic 16-bit LFSR comparator.
28///
29/// `weights_packed` is row-major `n_out * n_in * n_words` (`n_words =
30/// ceil(length / 64)`); `input_probs` is `n_in` float64 in `[0, 1]`. Returns an
31/// `n_out` float64 array, the AND-then-popcount estimate of
32/// `weights @ input_probs` divided by `length`.
33#[pyfunction]
34#[pyo3(signature = (weights_packed, n_out, n_in, n_words, input_probs, length, seed))]
35#[allow(clippy::too_many_arguments)]
36fn py_sc_forward_packed<'py>(
37 py: Python<'py>,
38 weights_packed: PyReadonlyArray1<'py, u64>,
39 n_out: usize,
40 n_in: usize,
41 n_words: usize,
42 input_probs: PyReadonlyArray1<'py, f64>,
43 length: usize,
44 seed: u64,
45) -> PyResult<Bound<'py, PyArray1<f64>>> {
46 let outputs = sc_inference::sc_forward_packed(
47 weights_packed.as_slice()?,
48 n_out,
49 n_in,
50 n_words,
51 input_probs.as_slice()?,
52 length,
53 seed,
54 )
55 .map_err(|e| PyValueError::new_err(e.to_string()))?;
56 Ok(outputs.into_pyarray(py))
57}