scirs2_python/
autograd.rs1use pyo3::prelude::*;
9use scirs2_autograd::variable::NamespaceTrait;
10use scirs2_autograd::VariableEnvironment;
11use scirs2_numpy::{IntoPyArray, PyArray1, PyArray2, PyArrayMethods};
12
13#[pyclass(name = "VariableEnvironment", unsendable)]
27pub struct PyVariableEnvironment {
28 inner: VariableEnvironment<f64>,
29}
30
31#[pymethods]
32impl PyVariableEnvironment {
33 #[new]
35 fn new() -> Self {
36 Self {
37 inner: VariableEnvironment::new(),
38 }
39 }
40
41 fn set_variable(&mut self, name: &str, array: &Bound<'_, PyAny>) -> PyResult<()> {
47 if let Ok(arr1d) = array.cast::<PyArray1<f64>>() {
49 let binding = arr1d.readonly();
50 let data = binding.as_array().to_owned();
51 self.inner.name(name).set(data);
52 return Ok(());
53 }
54
55 if let Ok(arr2d) = array.cast::<PyArray2<f64>>() {
57 let binding = arr2d.readonly();
58 let data = binding.as_array().to_owned();
59 self.inner.name(name).set(data);
60 return Ok(());
61 }
62
63 Err(PyErr::new::<pyo3::exceptions::PyTypeError, _>(
64 "Array must be 1D or 2D float64 numpy array",
65 ))
66 }
67
68 fn get_variable(&self, py: Python, name: &str) -> PyResult<Py<PyAny>> {
76 let namespace = self.inner.default_namespace();
77 let array_ref = namespace.get_array_by_name(name).ok_or_else(|| {
78 PyErr::new::<pyo3::exceptions::PyKeyError, _>(format!("Variable '{}' not found", name))
79 })?;
80
81 let array = array_ref.borrow();
82
83 match array.ndim() {
85 1 => {
86 let arr1d = array
87 .view()
88 .into_dimensionality::<scirs2_core::ndarray::Ix1>()
89 .map_err(|e| {
90 PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
91 "Dimension error: {}",
92 e
93 ))
94 })?;
95 Ok(arr1d.to_owned().into_pyarray(py).unbind().into())
96 }
97 2 => {
98 let arr2d = array
99 .view()
100 .into_dimensionality::<scirs2_core::ndarray::Ix2>()
101 .map_err(|e| {
102 PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(format!(
103 "Dimension error: {}",
104 e
105 ))
106 })?;
107 Ok(arr2d.to_owned().into_pyarray(py).unbind().into())
108 }
109 _ => Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
110 "Only 1D and 2D arrays are currently supported",
111 )),
112 }
113 }
114
115 fn list_variables(&self) -> Vec<String> {
120 self.inner
121 .default_namespace()
122 .current_var_names()
123 .into_iter()
124 .map(|s: &str| s.to_string())
125 .collect()
126 }
127
128 fn save(&self, path: &str) -> PyResult<()> {
133 self.inner
134 .save(path)
135 .map_err(|e| PyErr::new::<pyo3::exceptions::PyIOError, _>(format!("Save error: {}", e)))
136 }
137
138 #[staticmethod]
146 fn load(path: &str) -> PyResult<Self> {
147 let inner = VariableEnvironment::<f64>::load(path).map_err(|e| {
148 PyErr::new::<pyo3::exceptions::PyIOError, _>(format!("Load error: {}", e))
149 })?;
150
151 Ok(Self { inner })
152 }
153
154 fn __len__(&self) -> usize {
156 self.inner.default_namespace().current_var_names().len()
157 }
158
159 fn __repr__(&self) -> String {
161 format!(
162 "VariableEnvironment({} variables)",
163 self.inner.default_namespace().current_var_names().len()
164 )
165 }
166}
167
168pub fn register_module(m: &Bound<'_, PyModule>) -> PyResult<()> {
173 m.add_class::<PyVariableEnvironment>()?;
175
176 m.add("__doc__", "Automatic differentiation and variable management\n\n\
178 This module provides model parameter storage and persistence from scirs2-autograd.\n\
179 Due to Rust lifetime complexities in the computational graph API, full autodiff\n\
180 functionality is not exposed. For neural network training, we recommend:\n\n\
181 - PyTorch: Industry-standard deep learning framework\n\
182 - TensorFlow: Comprehensive ML platform\n\n\
183 scirs2 arrays are NumPy-compatible, enabling seamless integration with these frameworks.\n\n\
184 Use VariableEnvironment for:\n\
185 - Storing and managing model parameters\n\
186 - Saving/loading trained model weights\n\
187 - Transferring weights between scirs2 and PyTorch/TensorFlow")?;
188
189 Ok(())
190}