fix(gaps): G1 — re-enable Python bindings (PyO3 0.25, Python 3.14)

- Upgrade workspace pyo3 0.24 → 0.25 and numpy 0.24 → 0.25 for Python 3.14 support
- rtx-sklearn-py: replace pinned pyo3 0.20 / pyo3-asyncio 0.20 / numpy 0.20 with workspace versions;
  remove broken pyo3-asyncio async feature; update pyo3-build-config to 0.24
- rtx-bindings: uncomment pyo3/numpy/ndarray optional deps; enable python feature in Cargo.toml
- Migrate rtx-bindings python/ to PyO3 0.25 Bound API:
  &PyAny → Bound<'py, PyAny>, downcast/extract on Bound types, remove rtx_runtime import,
  remove InferenceError arm (variant not in enum), fix py_shape_to_shape signature
- Migrate rtx-sklearn-py src/ to PyO3 0.25 Bound API:
  #[pymodule] fn now takes &Bound<'_, PyModule>, &PyDict → &Bound<'py, PyDict>,
  from_array returns Bound (unbind instead of to_owned), PyTuple::new now fallible,
  use numpy::ndarray (0.16) over workspace ndarray (0.15) to resolve trait mismatches

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-26 13:55:09 +00:00
co-authored by Claude Sonnet 4.6
parent 228137555f
commit 448c0a0be5
14 changed files with 310 additions and 355 deletions
@@ -2,8 +2,8 @@
Simplified sklearn-compatible regressor wrappers
*/
use ndarray::Array1;
use numpy::{PyArray1, PyReadonlyArrayDyn};
use numpy::ndarray::Array1;
use numpy::{PyArray1, PyReadonlyArrayDyn, PyArrayMethods};
use pyo3::prelude::*;
use std::collections::HashMap;
@@ -44,7 +44,7 @@ impl LinearRegression {
ArrayConverter::validate_fit_input(&x, Some(&y)).map_err(|e| PyErr::from(e))?;
let x_view = ArrayConverter::to_array_view2(&x).map_err(|e| PyErr::from(e))?;
let y_view = ArrayConverter::to_array_view1(&y).map_err(|e| PyErr::from(e))?;
let _y_view = ArrayConverter::to_array_view1(&y).map_err(|e| PyErr::from(e))?;
self.n_features_in = Some(x_view.shape()[1]);
@@ -81,15 +81,18 @@ impl LinearRegression {
predictions[i] = sum + intercept;
}
Python::with_gil(|py| Ok(ArrayConverter::from_array1(py, predictions)?.to_owned()))
Python::with_gil(|py| Ok(ArrayConverter::from_array1(py, predictions)?.unbind()))
}
fn score(&self, x: PyReadonlyArrayDyn<f64>, y: PyReadonlyArrayDyn<f64>) -> PyResult<f64> {
let predictions = self.predict(x)?;
Python::with_gil(|py| {
let pred_array = predictions.as_ref(py).readonly();
let pred_view = ArrayConverter::to_dyn_view(&pred_array);
use numpy::PyArrayMethods;
let pred_bound = predictions.bind(py);
let pred_array = pred_bound.readonly();
// Convert Ix1 readonly array view to ndarray view directly
let pred_view = pred_array.as_array();
let y_view = ArrayConverter::to_array_view1(&y).map_err(|e| PyErr::from(e))?;
let y_mean = y_view.iter().sum::<f64>() / y_view.len() as f64;
@@ -97,7 +100,7 @@ impl LinearRegression {
let ss_res: f64 = pred_view
.iter()
.zip(y_view.iter())
.map(|(pred, actual)| (actual - pred).powi(2))
.map(|(pred, actual)| (actual - pred) as f64 * (actual - pred) as f64)
.sum();
let ss_tot: f64 = y_view.iter().map(|actual| (actual - y_mean).powi(2)).sum();
@@ -107,13 +110,14 @@ impl LinearRegression {
}
fn get_params(&self) -> PyResult<HashMap<String, PyObject>> {
use pyo3::IntoPyObjectExt;
Python::with_gil(|py| {
let mut params = HashMap::new();
params.insert(
"fit_intercept".to_string(),
self.fit_intercept.to_object(py),
self.fit_intercept.into_py_any(py)?,
);
params.insert("device".to_string(), self.device.to_string().to_object(py));
params.insert("device".to_string(), self.device.to_string().into_py_any(py)?);
Ok(params)
})
}
@@ -123,7 +127,7 @@ impl LinearRegression {
match &self.coef_ {
Some(coef) => Python::with_gil(|py| {
Ok(Some(
ArrayConverter::from_array1(py, coef.clone())?.to_owned(),
ArrayConverter::from_array1(py, coef.clone())?.unbind(),
))
}),
None => Ok(None),