Skip to main content

rstorch_python/
autograd.rs

1//! Autograd module bindings with enhanced gradient computation support
2
3use crate::{error::PyResult, py_result, tensor::PyTensor};
4use pyo3::prelude::*;
5use pyo3::types::PyAny;
6use pyo3::wrap_pyfunction;
7use pyo3::PyRefMut;
8use std::cell::RefCell;
9
10/// Global autograd state manager
11pub struct AutogradState {
12    enabled: bool,
13    anomaly_detection: bool,
14}
15
16impl AutogradState {
17    fn new() -> Self {
18        Self {
19            enabled: true,
20            anomaly_detection: false,
21        }
22    }
23
24    fn set_enabled(&mut self, enabled: bool) {
25        self.enabled = enabled;
26    }
27
28    fn is_enabled(&self) -> bool {
29        self.enabled
30    }
31
32    fn set_anomaly_detection(&mut self, enabled: bool) {
33        self.anomaly_detection = enabled;
34    }
35
36    fn is_anomaly_detection_enabled(&self) -> bool {
37        self.anomaly_detection
38    }
39}
40
41// Global state instance (in a real implementation, this would be more sophisticated)
42thread_local! {
43    static AUTOGRAD_STATE: RefCell<AutogradState> = RefCell::new(AutogradState::new());
44}
45
46/// Gradient computation context manager - disables gradient computation
47#[pyclass(name = "no_grad")]
48pub struct PyNoGrad {
49    prev_state: bool,
50}
51
52#[pymethods]
53impl PyNoGrad {
54    #[new]
55    fn new() -> Self {
56        let prev_state = AUTOGRAD_STATE.with(|state| state.borrow().is_enabled());
57        Self { prev_state }
58    }
59
60    fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
61        // Disable gradients
62        AUTOGRAD_STATE.with(|state| {
63            slf.prev_state = state.borrow().is_enabled();
64            state.borrow_mut().set_enabled(false);
65        });
66        slf
67    }
68
69    fn __exit__(
70        slf: PyRefMut<'_, Self>,
71        _exc_type: Option<Py<PyAny>>,
72        _exc_val: Option<Py<PyAny>>,
73        _exc_tb: Option<Py<PyAny>>,
74    ) -> PyResult<bool> {
75        // Restore previous gradient state
76        AUTOGRAD_STATE.with(|state| {
77            state.borrow_mut().set_enabled(slf.prev_state);
78        });
79        Ok(false)
80    }
81
82    /// Check if we're currently in no_grad context
83    #[staticmethod]
84    fn is_enabled() -> bool {
85        !AUTOGRAD_STATE.with(|state| state.borrow().is_enabled())
86    }
87}
88
89/// Enable gradient computation context manager - forces gradient computation on
90#[pyclass(name = "enable_grad")]
91pub struct PyEnableGrad {
92    prev_state: bool,
93}
94
95#[pymethods]
96impl PyEnableGrad {
97    #[new]
98    fn new() -> Self {
99        let prev_state = AUTOGRAD_STATE.with(|state| state.borrow().is_enabled());
100        Self { prev_state }
101    }
102
103    fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
104        // Enable gradients
105        AUTOGRAD_STATE.with(|state| {
106            slf.prev_state = state.borrow().is_enabled();
107            state.borrow_mut().set_enabled(true);
108        });
109        slf
110    }
111
112    fn __exit__(
113        slf: PyRefMut<'_, Self>,
114        _exc_type: Option<Py<PyAny>>,
115        _exc_val: Option<Py<PyAny>>,
116        _exc_tb: Option<Py<PyAny>>,
117    ) -> PyResult<bool> {
118        // Restore previous gradient state
119        AUTOGRAD_STATE.with(|state| {
120            state.borrow_mut().set_enabled(slf.prev_state);
121        });
122        Ok(false)
123    }
124
125    /// Check if we're currently in enable_grad context
126    #[staticmethod]
127    fn is_enabled() -> bool {
128        AUTOGRAD_STATE.with(|state| state.borrow().is_enabled())
129    }
130}
131
132/// Set gradient computation mode context manager - sets specific gradient mode
133#[pyclass(name = "set_grad_enabled")]
134pub struct PySetGradEnabled {
135    mode: bool,
136    prev_state: bool,
137}
138
139#[pymethods]
140impl PySetGradEnabled {
141    #[new]
142    fn new(mode: bool) -> Self {
143        let prev_state = AUTOGRAD_STATE.with(|state| state.borrow().is_enabled());
144        Self { mode, prev_state }
145    }
146
147    fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
148        // Set gradient mode
149        AUTOGRAD_STATE.with(|state| {
150            slf.prev_state = state.borrow().is_enabled();
151            state.borrow_mut().set_enabled(slf.mode);
152        });
153        slf
154    }
155
156    fn __exit__(
157        slf: PyRefMut<'_, Self>,
158        _exc_type: Option<Py<PyAny>>,
159        _exc_val: Option<Py<PyAny>>,
160        _exc_tb: Option<Py<PyAny>>,
161    ) -> PyResult<bool> {
162        // Restore previous gradient state
163        AUTOGRAD_STATE.with(|state| {
164            state.borrow_mut().set_enabled(slf.prev_state);
165        });
166        Ok(false)
167    }
168}
169
170/// Anomaly detection context manager
171#[pyclass(name = "detect_anomaly")]
172pub struct PyDetectAnomaly {
173    mode: bool,
174    prev_state: bool,
175}
176
177#[pymethods]
178impl PyDetectAnomaly {
179    #[new]
180    fn new(mode: bool) -> Self {
181        let prev_state = AUTOGRAD_STATE.with(|state| state.borrow().is_anomaly_detection_enabled());
182        Self { mode, prev_state }
183    }
184
185    fn __enter__(mut slf: PyRefMut<'_, Self>) -> PyRefMut<'_, Self> {
186        // Set anomaly detection mode
187        AUTOGRAD_STATE.with(|state| {
188            slf.prev_state = state.borrow().is_anomaly_detection_enabled();
189            state.borrow_mut().set_anomaly_detection(slf.mode);
190        });
191        slf
192    }
193
194    fn __exit__(
195        slf: PyRefMut<'_, Self>,
196        _exc_type: Option<Py<PyAny>>,
197        _exc_val: Option<Py<PyAny>>,
198        _exc_tb: Option<Py<PyAny>>,
199    ) -> PyResult<bool> {
200        // Restore previous anomaly detection state
201        AUTOGRAD_STATE.with(|state| {
202            state.borrow_mut().set_anomaly_detection(slf.prev_state);
203        });
204        Ok(false)
205    }
206
207    /// Check if we're currently in anomaly detection mode
208    #[staticmethod]
209    fn is_enabled() -> bool {
210        AUTOGRAD_STATE.with(|state| state.borrow().is_anomaly_detection_enabled())
211    }
212}
213
214/// Function class for custom autograd functions
215#[pyclass(name = "Function")]
216pub struct PyFunction;
217
218#[pymethods]
219impl PyFunction {
220    #[staticmethod]
221    fn apply(inputs: Vec<PyTensor>) -> PyResult<PyTensor> {
222        // Basic passthrough for now
223        if inputs.is_empty() {
224            return Err(PyErr::new::<pyo3::exceptions::PyValueError, _>(
225                "Function.apply requires at least one input",
226            ));
227        }
228        Ok(inputs[0].clone())
229    }
230
231    // Note: forward and backward need more complex implementation for PyO3 0.25
232    // fn forward(ctx: Py<PyAny>, inputs: Vec<PyTensor>) -> PyResult<PyTensor> {
233    //     Err(PyErr::new::<pyo3::exceptions::PyNotImplementedError, _>(
234    //         "Subclasses must implement forward method"
235    //     ))
236    // }
237    //
238    // fn backward(ctx: Py<PyAny>, grad_output: &PyTensor) -> PyResult<Vec<Option<PyTensor>>> {
239    //     Err(PyErr::new::<pyo3::exceptions::PyNotImplementedError, _>(
240    //         "Subclasses must implement backward method"
241    //     ))
242    // }
243}
244
245/// Gradient computation utilities
246pub struct AutogradUtils;
247
248impl AutogradUtils {
249    /// Compute gradients with respect to inputs
250    pub fn grad(
251        outputs: Vec<PyTensor>,
252        inputs: Vec<PyTensor>,
253        _grad_outputs: Option<Vec<Option<PyTensor>>>,
254        _retain_graph: Option<bool>,
255        _create_graph: Option<bool>,
256        _only_inputs: Option<bool>,
257        _allow_unused: Option<bool>,
258    ) -> PyResult<Vec<Option<PyTensor>>> {
259        // For now, implement basic backward pass
260        if outputs.len() != 1 {
261            return Err(PyErr::new::<pyo3::exceptions::PyNotImplementedError, _>(
262                "Multiple outputs not yet supported",
263            ));
264        }
265
266        let output = &outputs[0];
267        py_result!(output.tensor.backward())?;
268
269        // Return gradients for inputs
270        let mut grads = Vec::new();
271        for input in inputs {
272            grads.push(input.tensor.grad().map(|g| PyTensor { tensor: g }));
273        }
274
275        Ok(grads)
276    }
277}
278
279use pyo3::types::{PyModule, PyModuleMethods};
280
281/// Register autograd module
282pub fn register_autograd_module(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
283    // Add context managers
284    m.add_class::<PyNoGrad>()?;
285    m.add_class::<PyEnableGrad>()?;
286    m.add_class::<PySetGradEnabled>()?;
287    m.add_class::<PyDetectAnomaly>()?;
288
289    // Add Function class
290    m.add_class::<PyFunction>()?;
291
292    // Add utility functions
293    #[pyfunction]
294    fn grad(
295        outputs: Vec<PyTensor>,
296        inputs: Vec<PyTensor>,
297        grad_outputs: Option<Vec<Option<PyTensor>>>,
298        retain_graph: Option<bool>,
299        create_graph: Option<bool>,
300        only_inputs: Option<bool>,
301        allow_unused: Option<bool>,
302    ) -> PyResult<Vec<Option<PyTensor>>> {
303        AutogradUtils::grad(
304            outputs,
305            inputs,
306            grad_outputs,
307            retain_graph,
308            create_graph,
309            only_inputs,
310            allow_unused,
311        )
312    }
313
314    m.add_function(wrap_pyfunction!(grad, m)?)?;
315
316    #[pyfunction]
317    fn backward(
318        tensors: Vec<PyTensor>,
319        _grad_tensors: Option<Vec<Option<PyTensor>>>,
320        _retain_graph: Option<bool>,
321        _create_graph: Option<bool>,
322        _inputs: Option<Vec<PyTensor>>,
323    ) -> PyResult<()> {
324        for tensor in tensors {
325            py_result!(tensor.tensor.backward())?;
326        }
327        Ok(())
328    }
329
330    m.add_function(wrap_pyfunction!(backward, m)?)?;
331
332    #[pyfunction]
333    fn is_grad_enabled() -> bool {
334        AUTOGRAD_STATE.with(|state| state.borrow().is_enabled())
335    }
336
337    #[pyfunction]
338    fn set_grad_enabled(mode: bool) {
339        AUTOGRAD_STATE.with(|state| {
340            state.borrow_mut().set_enabled(mode);
341        });
342    }
343
344    #[pyfunction]
345    fn detect_anomaly(mode: Option<bool>) -> PyResult<PyDetectAnomaly> {
346        let mode = mode.unwrap_or(true);
347        Ok(PyDetectAnomaly::new(mode))
348    }
349
350    #[pyfunction]
351    fn is_anomaly_detection_enabled() -> bool {
352        AUTOGRAD_STATE.with(|state| state.borrow().is_anomaly_detection_enabled())
353    }
354
355    #[pyfunction]
356    fn set_anomaly_detection(mode: bool) {
357        AUTOGRAD_STATE.with(|state| {
358            state.borrow_mut().set_anomaly_detection(mode);
359        });
360    }
361
362    m.add_function(wrap_pyfunction!(is_grad_enabled, m)?)?;
363    m.add_function(wrap_pyfunction!(set_grad_enabled, m)?)?;
364    m.add_function(wrap_pyfunction!(detect_anomaly, m)?)?;
365    m.add_function(wrap_pyfunction!(is_anomaly_detection_enabled, m)?)?;
366    m.add_function(wrap_pyfunction!(set_anomaly_detection, m)?)?;
367
368    Ok(())
369}