Skip to main content

sc_neurocore_engine/bindings/
fault.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 — Byte-level fault-injection PyO3 bindings
8
9//! Python bindings for deterministic byte-level hardware fault injection.
10
11use numpy::{IntoPyArray, PyArray1, PyReadonlyArray1};
12use pyo3::prelude::*;
13
14/// Register byte-level fault-injection functions with the extension module.
15pub(crate) fn register(module: &Bound<'_, PyModule>) -> PyResult<()> {
16    module.add_function(wrap_pyfunction!(py_inject_bitflip_u8, module)?)?;
17    module.add_function(wrap_pyfunction!(py_inject_stuck_at_0_u8, module)?)?;
18    module.add_function(wrap_pyfunction!(py_inject_stuck_at_1_u8, module)?)?;
19    module.add_function(wrap_pyfunction!(py_inject_dropout_u8, module)?)?;
20    module.add_function(wrap_pyfunction!(py_inject_gaussian_u8, module)?)?;
21    Ok(())
22}
23
24type FaultResult = (Py<PyArray1<u8>>, u64);
25
26fn inject<'py>(
27    py: Python<'py>,
28    bitstream: PyReadonlyArray1<'_, u8>,
29    ber: f64,
30    seed: u64,
31    operation: fn(&mut [u8], f64, u64) -> u64,
32) -> PyResult<FaultResult> {
33    let mut bytes = bitstream.as_slice()?.to_vec();
34    let affected = operation(&mut bytes, ber, seed);
35    Ok((bytes.into_pyarray(py).into(), affected))
36}
37
38/// Flip each byte-encoded bit independently with probability `ber`.
39#[pyfunction]
40fn py_inject_bitflip_u8<'py>(
41    py: Python<'py>,
42    bitstream: PyReadonlyArray1<'_, u8>,
43    ber: f64,
44    seed: u64,
45) -> PyResult<FaultResult> {
46    inject(py, bitstream, ber, seed, crate::fault::inject_bitflip_u8)
47}
48
49/// Force selected byte-encoded bits to zero.
50#[pyfunction]
51fn py_inject_stuck_at_0_u8<'py>(
52    py: Python<'py>,
53    bitstream: PyReadonlyArray1<'_, u8>,
54    ber: f64,
55    seed: u64,
56) -> PyResult<FaultResult> {
57    inject(py, bitstream, ber, seed, crate::fault::inject_stuck_at_0_u8)
58}
59
60/// Force selected byte-encoded bits to one.
61#[pyfunction]
62fn py_inject_stuck_at_1_u8<'py>(
63    py: Python<'py>,
64    bitstream: PyReadonlyArray1<'_, u8>,
65    ber: f64,
66    seed: u64,
67) -> PyResult<FaultResult> {
68    inject(py, bitstream, ber, seed, crate::fault::inject_stuck_at_1_u8)
69}
70
71/// Drop selected byte-encoded spikes to zero.
72#[pyfunction]
73fn py_inject_dropout_u8<'py>(
74    py: Python<'py>,
75    bitstream: PyReadonlyArray1<'_, u8>,
76    ber: f64,
77    seed: u64,
78) -> PyResult<FaultResult> {
79    inject(py, bitstream, ber, seed, crate::fault::inject_dropout_u8)
80}
81
82/// Apply thresholded Gaussian noise to byte-encoded bits.
83#[pyfunction]
84fn py_inject_gaussian_u8<'py>(
85    py: Python<'py>,
86    bitstream: PyReadonlyArray1<'_, u8>,
87    ber: f64,
88    seed: u64,
89) -> PyResult<FaultResult> {
90    inject(py, bitstream, ber, seed, crate::fault::inject_gaussian_u8)
91}