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:
co-authored by
Claude Sonnet 4.6
parent
228137555f
commit
448c0a0be5
+2
-2
@@ -543,8 +543,8 @@ comfy-table = "7.1"
|
|||||||
colorful = "0.2"
|
colorful = "0.2"
|
||||||
walkdir = "2.5"
|
walkdir = "2.5"
|
||||||
toml = "0.8"
|
toml = "0.8"
|
||||||
pyo3 = { version = "0.24", features = ["extension-module"] }
|
pyo3 = { version = "0.25", features = ["extension-module"] }
|
||||||
numpy = "0.24"
|
numpy = "0.25"
|
||||||
petgraph = "0.6"
|
petgraph = "0.6"
|
||||||
|
|
||||||
# Federated learning specific
|
# Federated learning specific
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
rtx-tensor = { workspace = true }
|
rtx-tensor = { workspace = true }
|
||||||
rtx-autograd = { workspace = true }
|
rtx-autograd = { workspace = true }
|
||||||
|
|
||||||
# Python bindings with PyO3 (disabled for now due to version conflicts)
|
# Python bindings with PyO3
|
||||||
# pyo3 = { workspace = true, optional = true }
|
pyo3 = { workspace = true, optional = true }
|
||||||
# ndarray = { workspace = true, optional = true }
|
numpy = { workspace = true, optional = true }
|
||||||
|
ndarray = { workspace = true, optional = true }
|
||||||
|
|
||||||
# C API support
|
# C API support
|
||||||
libc = { version = "0.2", optional = true }
|
libc = { version = "0.2", optional = true }
|
||||||
@@ -59,7 +60,7 @@ path = "tests/dlpack_interop.rs"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["c-api"]
|
default = ["c-api"]
|
||||||
# python = ["dep:pyo3", "dep:ndarray"]
|
python = ["dep:pyo3", "dep:numpy", "dep:ndarray"]
|
||||||
c-api = ["dep:libc"]
|
c-api = ["dep:libc"]
|
||||||
onnx = ["dep:serde_json"]
|
onnx = ["dep:serde_json"]
|
||||||
dlpack = []
|
dlpack = []
|
||||||
|
|||||||
@@ -1,35 +1,24 @@
|
|||||||
//! Async tensor operations for Python
|
//! Async tensor operations for Python
|
||||||
|
//!
|
||||||
|
//! pyo3_asyncio is incompatible with pyo3 0.25. Async functions are replaced
|
||||||
|
//! with sync stubs that perform the operations directly.
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3_asyncio::tokio::future_into_py;
|
use crate::BindingError;
|
||||||
use tokio::time::{sleep, Duration};
|
|
||||||
use crate::{BindingError, Result};
|
|
||||||
use super::PyTensor;
|
use super::PyTensor;
|
||||||
|
|
||||||
/// Async matrix multiplication
|
/// Matrix multiplication (sync; formerly async via pyo3_asyncio)
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
pub fn matmul_async(py: Python, a: PyTensor, b: PyTensor) -> PyResult<&PyAny> {
|
pub fn matmul_async(a: PyTensor, b: PyTensor) -> PyResult<PyTensor> {
|
||||||
future_into_py(py, async move {
|
|
||||||
// Simulate async computation delay (in real implementation, this would be actual GPU async ops)
|
|
||||||
sleep(Duration::from_millis(1)).await;
|
|
||||||
|
|
||||||
let result = a.inner().matmul(b.inner())
|
let result = a.inner().matmul(b.inner())
|
||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
|
|
||||||
Ok(PyTensor::new(result))
|
Ok(PyTensor::new(result))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Async sum operation
|
/// Sum operation (sync; formerly async via pyo3_asyncio)
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
pub fn sum_async(py: Python, tensor: PyTensor) -> PyResult<&PyAny> {
|
pub fn sum_async(tensor: PyTensor) -> PyResult<PyTensor> {
|
||||||
future_into_py(py, async move {
|
|
||||||
// Simulate async computation delay
|
|
||||||
sleep(Duration::from_millis(1)).await;
|
|
||||||
|
|
||||||
let result = tensor.inner().sum(None)
|
let result = tensor.inner().sum(None)
|
||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
|
|
||||||
Ok(PyTensor::new(result))
|
Ok(PyTensor::new(result))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
//! Factory functions for creating tensors from Python
|
//! Factory functions for creating tensors from Python
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use numpy::{PyArrayDyn, PyReadonlyArrayDyn};
|
use numpy::{PyArrayDyn, PyArrayMethods};
|
||||||
use rtx_tensor::{Tensor, Device, DType, Shape};
|
use rtx_tensor::{Tensor, Shape};
|
||||||
use crate::{BindingError, Result};
|
use crate::BindingError;
|
||||||
use super::{PyTensor, py_shape_to_shape, py_device_to_device, get_runtime};
|
use super::{PyTensor, py_shape_to_shape, py_device_to_device};
|
||||||
|
|
||||||
/// Create tensor filled with zeros
|
/// Create tensor filled with zeros
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
pub fn zeros(py: Python, shape: &PyAny, device: Option<&str>) -> PyResult<PyTensor> {
|
pub fn zeros<'py>(shape: &Bound<'py, PyAny>, device: Option<&str>) -> PyResult<PyTensor> {
|
||||||
let tensor_shape = py_shape_to_shape(py, shape)?;
|
let tensor_shape = py_shape_to_shape(shape)?;
|
||||||
let target_device = py_device_to_device(device)?;
|
let target_device = py_device_to_device(device)?;
|
||||||
|
|
||||||
let tensor = Tensor::zeros(tensor_shape, &target_device)
|
let tensor = Tensor::zeros(tensor_shape, &target_device)
|
||||||
@@ -20,8 +20,8 @@ pub fn zeros(py: Python, shape: &PyAny, device: Option<&str>) -> PyResult<PyTens
|
|||||||
|
|
||||||
/// Create tensor filled with ones
|
/// Create tensor filled with ones
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
pub fn ones(py: Python, shape: &PyAny, device: Option<&str>) -> PyResult<PyTensor> {
|
pub fn ones<'py>(shape: &Bound<'py, PyAny>, device: Option<&str>) -> PyResult<PyTensor> {
|
||||||
let tensor_shape = py_shape_to_shape(py, shape)?;
|
let tensor_shape = py_shape_to_shape(shape)?;
|
||||||
let target_device = py_device_to_device(device)?;
|
let target_device = py_device_to_device(device)?;
|
||||||
|
|
||||||
let tensor = Tensor::ones(tensor_shape, &target_device)
|
let tensor = Tensor::ones(tensor_shape, &target_device)
|
||||||
@@ -71,7 +71,7 @@ device: Option<&str>
|
|||||||
|
|
||||||
/// Create tensor from NumPy array
|
/// Create tensor from NumPy array
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
pub fn from_numpy(py: Python, array: &PyAny, device: Option<&str>) -> PyResult<PyTensor> {
|
pub fn from_numpy<'py>(py: Python<'py>, array: &Bound<'py, PyAny>, device: Option<&str>) -> PyResult<PyTensor> {
|
||||||
let target_device = py_device_to_device(device)?;
|
let target_device = py_device_to_device(device)?;
|
||||||
|
|
||||||
// Try to extract as different array types
|
// Try to extract as different array types
|
||||||
@@ -82,13 +82,7 @@ pub fn from_numpy(py: Python, array: &PyAny, device: Option<&str>) -> PyResult<P
|
|||||||
// Get shape and data
|
// Get shape and data
|
||||||
let shape = Shape::new(ndarray.shape().to_vec())
|
let shape = Shape::new(ndarray.shape().to_vec())
|
||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
let data: Vec<f32> = if ndarray.is_standard_layout() {
|
let data: Vec<f32> = ndarray.iter().cloned().collect();
|
||||||
// Fast path for standard layout
|
|
||||||
ndarray.iter().cloned().collect()
|
|
||||||
} else {
|
|
||||||
// Handle non-standard layouts
|
|
||||||
ndarray.iter().cloned().collect()
|
|
||||||
};
|
|
||||||
|
|
||||||
let tensor = Tensor::from_data(data, shape, &target_device)
|
let tensor = Tensor::from_data(data, shape, &target_device)
|
||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
@@ -111,25 +105,23 @@ pub fn from_numpy(py: Python, array: &PyAny, device: Option<&str>) -> PyResult<P
|
|||||||
// Try to convert via Python buffer protocol
|
// Try to convert via Python buffer protocol
|
||||||
let numpy = py.import("numpy")?;
|
let numpy = py.import("numpy")?;
|
||||||
let converted = numpy.call_method1("asarray", (array,))?;
|
let converted = numpy.call_method1("asarray", (array,))?;
|
||||||
let float_array = numpy.call_method1("astype", (converted, "float32"))?;
|
let float_array = numpy.call_method1("astype", (&converted, "float32"))?;
|
||||||
|
|
||||||
// Recursively call with converted array
|
// Recursively call with converted array
|
||||||
from_numpy(py, float_array, device)
|
from_numpy(py, &float_array, device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create tensor filled with ones and then multiply by scalar (helper for tests)
|
/// Create tensor filled with a scalar value
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
pub fn full(py: Python, shape: &PyAny, value: f32, device: Option<&str>) -> PyResult<PyTensor> {
|
pub fn full<'py>(shape: &Bound<'py, PyAny>, value: f32, device: Option<&str>) -> PyResult<PyTensor> {
|
||||||
let tensor_shape = py_shape_to_shape(py, shape)?;
|
let tensor_shape = py_shape_to_shape(shape)?;
|
||||||
let target_device = py_device_to_device(device)?;
|
let target_device = py_device_to_device(device)?;
|
||||||
|
|
||||||
// Create ones tensor and scale it
|
// Create ones tensor and scale it
|
||||||
let ones_tensor = Tensor::ones(tensor_shape, &target_device)
|
let ones_tensor = Tensor::ones(tensor_shape, &target_device)
|
||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
|
|
||||||
// For now, we'll create the scaled data manually
|
|
||||||
// In a full implementation, this would use a scalar multiplication operation
|
|
||||||
let mut data = ones_tensor.to_cpu()
|
let mut data = ones_tensor.to_cpu()
|
||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,8 @@
|
|||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::exceptions::{PyRuntimeError, PyValueError};
|
use pyo3::exceptions::{PyRuntimeError, PyValueError};
|
||||||
use pyo3::types::{PyList, PyTuple};
|
use pyo3::types::{PyList, PyTuple};
|
||||||
use numpy::{PyArray1, PyArray2, PyArrayDyn, PyReadonlyArrayDyn};
|
|
||||||
use crate::{BindingError, Result};
|
use crate::{BindingError, Result};
|
||||||
use rtx_tensor::{Tensor, Device, DType, Shape};
|
use rtx_tensor::{Device, Shape};
|
||||||
use rtx_runtime::Runtime;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub mod exceptions;
|
pub mod exceptions;
|
||||||
pub mod tensor;
|
pub mod tensor;
|
||||||
@@ -54,9 +51,6 @@ impl From<BindingError> for PyErr {
|
|||||||
BindingError::AutogradError { message } => {
|
BindingError::AutogradError { message } => {
|
||||||
PyValueError::new_err(format!("Autograd error: {}", message))
|
PyValueError::new_err(format!("Autograd error: {}", message))
|
||||||
}
|
}
|
||||||
BindingError::InferenceError(e) => {
|
|
||||||
PyValueError::new_err(format!("Inference error: {}", e))
|
|
||||||
}
|
|
||||||
BindingError::IoError(e) => {
|
BindingError::IoError(e) => {
|
||||||
PyValueError::new_err(format!("IO error: {}", e))
|
PyValueError::new_err(format!("IO error: {}", e))
|
||||||
}
|
}
|
||||||
@@ -69,7 +63,7 @@ impl From<BindingError> for PyErr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert Python shape-like objects to Shape
|
/// Convert Python shape-like objects to Shape
|
||||||
fn py_shape_to_shape(py: Python, obj: &PyAny) -> PyResult<Shape> {
|
pub(crate) fn py_shape_to_shape<'py>(obj: &Bound<'py, PyAny>) -> PyResult<Shape> {
|
||||||
if let Ok(list) = obj.downcast::<PyList>() {
|
if let Ok(list) = obj.downcast::<PyList>() {
|
||||||
let dims: Vec<usize> = list
|
let dims: Vec<usize> = list
|
||||||
.iter()
|
.iter()
|
||||||
@@ -90,7 +84,7 @@ fn py_shape_to_shape(py: Python, obj: &PyAny) -> PyResult<Shape> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convert device string to Device
|
/// Convert device string to Device
|
||||||
fn py_device_to_device(device_str: Option<&str>) -> Result<Device> {
|
pub(crate) fn py_device_to_device(device_str: Option<&str>) -> Result<Device> {
|
||||||
match device_str {
|
match device_str {
|
||||||
None | Some("cpu") => Ok(Device::cuda(0).unwrap_or(Device::default())),
|
None | Some("cpu") => Ok(Device::cuda(0).unwrap_or(Device::default())),
|
||||||
Some(s) if s.starts_with("cuda") => {
|
Some(s) if s.starts_with("cuda") => {
|
||||||
@@ -143,10 +137,3 @@ fn py_shape_to_shape(py: Python, obj: &PyAny) -> PyResult<Shape> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Initialize runtime for tensor operations
|
|
||||||
fn get_runtime() -> Arc<Runtime> {
|
|
||||||
// In a real implementation, this would be a singleton
|
|
||||||
// For now, create a new runtime each time
|
|
||||||
Arc::new(Runtime::new().expect("Failed to initialize runtime"))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
//! Python tensor wrapper providing PyTorch-like API
|
//! Python tensor wrapper providing PyTorch-like API
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyList, PyTuple};
|
use pyo3::types::PyTuple;
|
||||||
use numpy::{PyArrayDyn, PyReadonlyArrayDyn, ToPyArray};
|
use numpy::{PyArray1, PyArray2, PyArrayDyn};
|
||||||
use rtx_tensor::{Tensor, Device, DType, Shape};
|
use rtx_tensor::{Tensor, Device, DType, Shape};
|
||||||
use crate::{BindingError, Result};
|
use crate::{BindingError, Result};
|
||||||
use super::{py_shape_to_shape, py_device_to_device, get_runtime};
|
use super::{py_shape_to_shape, py_device_to_device};
|
||||||
|
|
||||||
/// Python wrapper for RustyTorch++ Tensor
|
/// Python wrapper for RustyTorch++ Tensor
|
||||||
///
|
///
|
||||||
@@ -54,10 +54,10 @@ impl PyTensor {
|
|||||||
#[getter]
|
#[getter]
|
||||||
fn device(&self) -> String {
|
fn device(&self) -> String {
|
||||||
match self.inner.device() {
|
match self.inner.device() {
|
||||||
Device::cuda(0).unwrap_or(Device::default()) => "cpu".to_string(),
|
|
||||||
Device::Cuda(id) => format!("cuda:{}", id),
|
Device::Cuda(id) => format!("cuda:{}", id),
|
||||||
Device::Rocm(id) => format!("rocm:{}", id),
|
Device::Rocm(id) => format!("rocm:{}", id),
|
||||||
Device::Metal(id) => format!("metal:{}", id),
|
Device::Metal(id) => format!("metal:{}", id),
|
||||||
|
_ => "cpu".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,6 +77,7 @@ impl PyTensor {
|
|||||||
DType::I2 => "int2".to_string(),
|
DType::I2 => "int2".to_string(),
|
||||||
DType::I1 => "int1".to_string(),
|
DType::I1 => "int1".to_string(),
|
||||||
DType::Bool => "bool".to_string(),
|
DType::Bool => "bool".to_string(),
|
||||||
|
_ => "unknown".to_string(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,7 +100,7 @@ impl PyTensor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Get single item from tensor (for scalars or single element access)
|
/// Get single item from tensor (for scalars or single element access)
|
||||||
fn item(&self, py: Python, indices: Option<&PyTuple>) -> PyResult<f32> {
|
fn item(&self, indices: Option<&Bound<'_, PyTuple>>) -> PyResult<f32> {
|
||||||
match indices {
|
match indices {
|
||||||
None => {
|
None => {
|
||||||
// Scalar access
|
// Scalar access
|
||||||
@@ -147,16 +148,16 @@ impl PyTensor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reshape tensor to new shape
|
/// Reshape tensor to new shape
|
||||||
fn reshape(&self, py: Python, shape: &PyAny) -> PyResult<PyTensor> {
|
fn reshape<'py>(&self, shape: &Bound<'py, PyAny>) -> PyResult<PyTensor> {
|
||||||
let new_shape = py_shape_to_shape(py, shape)?;
|
let new_shape = py_shape_to_shape(shape)?;
|
||||||
let reshaped = self.inner.reshape(new_shape)
|
let reshaped = self.inner.reshape(new_shape)
|
||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
Ok(PyTensor::new(reshaped))
|
Ok(PyTensor::new(reshaped))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create view with new shape (no data copy)
|
/// Create view with new shape (no data copy)
|
||||||
fn view(&self, py: Python, shape: &PyAny) -> PyResult<PyTensor> {
|
fn view<'py>(&self, shape: &Bound<'py, PyAny>) -> PyResult<PyTensor> {
|
||||||
let new_shape = py_shape_to_shape(py, shape)?;
|
let new_shape = py_shape_to_shape(shape)?;
|
||||||
let view = self.inner.view(new_shape)
|
let view = self.inner.view(new_shape)
|
||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
Ok(PyTensor::new(view))
|
Ok(PyTensor::new(view))
|
||||||
@@ -211,24 +212,27 @@ impl PyTensor {
|
|||||||
.map_err(|e| BindingError::TensorError(e))?;
|
.map_err(|e| BindingError::TensorError(e))?;
|
||||||
|
|
||||||
let shape = self.inner.shape().dims();
|
let shape = self.inner.shape().dims();
|
||||||
|
// Use numpy's bundled ndarray to avoid version mismatch between
|
||||||
|
// workspace ndarray 0.15 and numpy 0.25's ndarray 0.16.
|
||||||
let ndarray = match shape.len() {
|
let ndarray = match shape.len() {
|
||||||
1 => {
|
1 => {
|
||||||
let arr = ndarray::Array1::from_vec(data);
|
let arr = numpy::ndarray::Array1::from_vec(data);
|
||||||
arr.to_pyarray(py).to_object(py)
|
PyArray1::from_owned_array(py, arr).into_any().unbind()
|
||||||
}
|
}
|
||||||
2 => {
|
2 => {
|
||||||
let arr = ndarray::Array2::from_shape_vec((shape[0], shape[1]), data)
|
let arr = numpy::ndarray::Array2::from_shape_vec(
|
||||||
.map_err(|e| BindingError::RuntimeError {
|
(shape[0], shape[1]), data,
|
||||||
|
).map_err(|e| BindingError::RuntimeError {
|
||||||
message: format!("Failed to create 2D array: {}", e)
|
message: format!("Failed to create 2D array: {}", e)
|
||||||
})?;
|
})?;
|
||||||
arr.to_pyarray(py).to_object(py)
|
PyArray2::from_owned_array(py, arr).into_any().unbind()
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let arr = ndarray::ArrayD::from_shape_vec(shape, data)
|
let arr = numpy::ndarray::ArrayD::from_shape_vec(shape.to_vec(), data)
|
||||||
.map_err(|e| BindingError::RuntimeError {
|
.map_err(|e| BindingError::RuntimeError {
|
||||||
message: format!("Failed to create ND array: {}", e)
|
message: format!("Failed to create ND array: {}", e)
|
||||||
})?;
|
})?;
|
||||||
arr.to_pyarray(py).to_object(py)
|
PyArrayDyn::from_owned_array(py, arr).into_any().unbind()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -20,14 +20,11 @@ rtx-validation = { path = "../../core/rtx-validation" }
|
|||||||
rtx-tensor = { path = "../../core/rtx-tensor" }
|
rtx-tensor = { path = "../../core/rtx-tensor" }
|
||||||
|
|
||||||
# PyO3 for Python bindings
|
# PyO3 for Python bindings
|
||||||
# Note: extension-module is enabled by default but disabled for tests
|
pyo3 = { workspace = true, features = ["multiple-pymethods", "abi3-py38"] }
|
||||||
# When running tests, use: cargo test --no-default-features --features gpu,async
|
|
||||||
pyo3 = { version = "0.20", features = ["multiple-pymethods", "abi3-py38", "auto-initialize"] }
|
|
||||||
pyo3-asyncio = { version = "0.20", features = ["tokio-runtime"], optional = true }
|
|
||||||
|
|
||||||
# Numerical computing
|
# Numerical computing
|
||||||
numpy = "0.20"
|
numpy = { workspace = true }
|
||||||
ndarray = "0.15"
|
ndarray = { workspace = true }
|
||||||
|
|
||||||
# Async support
|
# Async support
|
||||||
tokio = { workspace = true }
|
tokio = { workspace = true }
|
||||||
@@ -48,16 +45,15 @@ rand = "0.8"
|
|||||||
num_cpus = "1.16"
|
num_cpus = "1.16"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
pyo3-build-config = "0.20"
|
pyo3-build-config = "0.24"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
proptest = { workspace = true }
|
proptest = { workspace = true }
|
||||||
tokio-test = { workspace = true }
|
tokio-test = { workspace = true }
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["gpu", "async"]
|
default = ["gpu"]
|
||||||
gpu = []
|
gpu = []
|
||||||
async = ["pyo3-asyncio"]
|
|
||||||
extension-module = ["pyo3/extension-module"]
|
extension-module = ["pyo3/extension-module"]
|
||||||
|
|
||||||
# The extension-module feature should only be enabled when building the Python module,
|
# The extension-module feature should only be enabled when building the Python module,
|
||||||
|
|||||||
@@ -33,12 +33,11 @@ clf = RandomForestClassifier(n_estimators=100).fit(X, y)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Allow unsafe operations in unsafe functions generated by PyO3 macros
|
// Allow unsafe operations in unsafe functions generated by PyO3 macros
|
||||||
// This is a known issue with PyO3 0.20 and Rust 2024 edition
|
|
||||||
// The macro-generated code contains unsafe operations that need to be allowed
|
|
||||||
#![allow(unsafe_op_in_unsafe_fn)]
|
#![allow(unsafe_op_in_unsafe_fn)]
|
||||||
|
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyDict, PyTuple};
|
use pyo3::types::{PyDict, PyTuple};
|
||||||
|
use pyo3::types::PyModuleMethods;
|
||||||
use pyo3::wrap_pyfunction;
|
use pyo3::wrap_pyfunction;
|
||||||
|
|
||||||
pub mod error;
|
pub mod error;
|
||||||
@@ -53,7 +52,9 @@ use wrappers::{
|
|||||||
|
|
||||||
/// Initialize the rustytorch_ml Python module
|
/// Initialize the rustytorch_ml Python module
|
||||||
#[pymodule]
|
#[pymodule]
|
||||||
fn rustytorch_ml(_py: Python, m: &PyModule) -> PyResult<()> {
|
fn rustytorch_ml(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
|
let py = m.py();
|
||||||
|
|
||||||
// Add module metadata
|
// Add module metadata
|
||||||
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
||||||
m.add("__author__", "RustyTorch++ Team")?;
|
m.add("__author__", "RustyTorch++ Team")?;
|
||||||
@@ -65,7 +66,7 @@ fn rustytorch_ml(_py: Python, m: &PyModule) -> PyResult<()> {
|
|||||||
// Register exception classes
|
// Register exception classes
|
||||||
m.add(
|
m.add(
|
||||||
"SklearnError",
|
"SklearnError",
|
||||||
_py.get_type::<pyo3::exceptions::PyRuntimeError>(),
|
py.get_type::<pyo3::exceptions::PyRuntimeError>(),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Classifiers
|
// Classifiers
|
||||||
@@ -103,15 +104,15 @@ fn rustytorch_ml(_py: Python, m: &PyModule) -> PyResult<()> {
|
|||||||
m.add_function(wrap_pyfunction!(utils::benchmark_against_sklearn, m)?)?;
|
m.add_function(wrap_pyfunction!(utils::benchmark_against_sklearn, m)?)?;
|
||||||
|
|
||||||
// Module-level configuration
|
// Module-level configuration
|
||||||
setup_module_config(_py, m)?;
|
setup_module_config(py, m)?;
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Setup module-level configuration and logging
|
/// Setup module-level configuration and logging
|
||||||
fn setup_module_config(_py: Python, m: &PyModule) -> PyResult<()> {
|
fn setup_module_config(py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||||
// Set default configuration
|
// Set default configuration
|
||||||
let config = PyDict::new(_py);
|
let config = PyDict::new(py);
|
||||||
config.set_item("default_device", "cpu")?;
|
config.set_item("default_device", "cpu")?;
|
||||||
config.set_item("enable_gpu", false)?;
|
config.set_item("enable_gpu", false)?;
|
||||||
config.set_item("default_batch_size", 1000)?;
|
config.set_item("default_batch_size", 1000)?;
|
||||||
@@ -121,7 +122,7 @@ fn setup_module_config(_py: Python, m: &PyModule) -> PyResult<()> {
|
|||||||
m.add("config", config)?;
|
m.add("config", config)?;
|
||||||
|
|
||||||
// Add version info
|
// Add version info
|
||||||
let version_info = PyTuple::new(_py, [0, 1, 0]);
|
let version_info = PyTuple::new(py, [0_u32, 1, 0])?;
|
||||||
m.add("version_info", version_info)?;
|
m.add("version_info", version_info)?;
|
||||||
|
|
||||||
// Add sklearn compatibility info
|
// Add sklearn compatibility info
|
||||||
@@ -144,7 +145,7 @@ mod tests {
|
|||||||
fn test_module_creation() {
|
fn test_module_creation() {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let module = PyModule::new(py, "rustytorch_ml").unwrap();
|
let module = PyModule::new(py, "rustytorch_ml").unwrap();
|
||||||
assert!(rustytorch_ml(py, module).is_ok());
|
assert!(rustytorch_ml(&module).is_ok());
|
||||||
|
|
||||||
// Check that classes are registered
|
// Check that classes are registered
|
||||||
assert!(module.hasattr("DecisionTreeClassifier").unwrap());
|
assert!(module.hasattr("DecisionTreeClassifier").unwrap());
|
||||||
|
|||||||
@@ -3,8 +3,10 @@ Utility functions and types for sklearn-py bindings
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
use crate::error::SklearnResult;
|
use crate::error::SklearnResult;
|
||||||
use ndarray::{Array1, Array2, ArrayViewD};
|
// Use numpy's bundled ndarray (0.16) to avoid version mismatch with workspace ndarray (0.15).
|
||||||
use numpy::{PyArray1, PyArray2, PyReadonlyArrayDyn};
|
// PyReadonlyArray and PyArray types require numpy's ndarray version.
|
||||||
|
use numpy::ndarray::{Array1, Array2, ArrayViewD};
|
||||||
|
use numpy::{PyArray1, PyArray2, PyReadonlyArrayDyn, PyArrayMethods};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyDict;
|
use pyo3::types::PyDict;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -65,7 +67,7 @@ impl DeviceConfig {
|
|||||||
pub struct ArrayConverter;
|
pub struct ArrayConverter;
|
||||||
|
|
||||||
impl ArrayConverter {
|
impl ArrayConverter {
|
||||||
/// Convert Python array to ndarray ArrayView2 (dynamic view)
|
/// Convert Python array to ndarray ArrayViewD (expects 2D)
|
||||||
pub fn to_array_view2<'py, T>(
|
pub fn to_array_view2<'py, T>(
|
||||||
py_array: &'py PyReadonlyArrayDyn<T>,
|
py_array: &'py PyReadonlyArrayDyn<T>,
|
||||||
) -> SklearnResult<ArrayViewD<'py, T>>
|
) -> SklearnResult<ArrayViewD<'py, T>>
|
||||||
@@ -84,7 +86,7 @@ impl ArrayConverter {
|
|||||||
Ok(array)
|
Ok(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert Python array to ndarray ArrayView1
|
/// Convert Python array to ndarray ArrayViewD (expects 1D)
|
||||||
pub fn to_array_view1<'py, T>(
|
pub fn to_array_view1<'py, T>(
|
||||||
py_array: &'py PyReadonlyArrayDyn<T>,
|
py_array: &'py PyReadonlyArrayDyn<T>,
|
||||||
) -> SklearnResult<ArrayViewD<'py, T>>
|
) -> SklearnResult<ArrayViewD<'py, T>>
|
||||||
@@ -103,51 +105,32 @@ impl ArrayConverter {
|
|||||||
Ok(array)
|
Ok(array)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert ndarray Array2 to Python array
|
/// Convert ndarray Array2 to Python array.
|
||||||
pub fn from_array2<T>(py: Python<'_>, array: Array2<T>) -> PyResult<&PyArray2<T>>
|
/// Returns a `Bound<'py, PyArray2<T>>` — call `.into_py(py)` or use directly.
|
||||||
|
pub fn from_array2<T>(py: Python<'_>, array: Array2<T>) -> PyResult<Bound<'_, PyArray2<T>>>
|
||||||
where
|
where
|
||||||
T: numpy::Element,
|
T: numpy::Element,
|
||||||
{
|
{
|
||||||
Ok(PyArray2::from_owned_array(py, array))
|
Ok(PyArray2::from_owned_array(py, array))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert ndarray Array1 to Python array
|
/// Convert ndarray Array1 to Python array.
|
||||||
pub fn from_array1<T>(py: Python<'_>, array: Array1<T>) -> PyResult<&PyArray1<T>>
|
/// Returns a `Bound<'py, PyArray1<T>>` — call `.into_py(py)` or use directly.
|
||||||
|
pub fn from_array1<T>(py: Python<'_>, array: Array1<T>) -> PyResult<Bound<'_, PyArray1<T>>>
|
||||||
where
|
where
|
||||||
T: numpy::Element,
|
T: numpy::Element,
|
||||||
{
|
{
|
||||||
Ok(PyArray1::from_owned_array(py, array))
|
Ok(PyArray1::from_owned_array(py, array))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert PyReadonlyArray1 to ArrayViewD (for type compatibility)
|
/// Convert any readonly dyn array to dynamic view (generic helper)
|
||||||
pub fn to_array_view1_from_readonly1<'py, T>(
|
pub fn to_dyn_view<'py, T>(
|
||||||
py_array: &'py numpy::PyReadonlyArray<'py, T, ndarray::Ix1>,
|
py_array: &'py PyReadonlyArrayDyn<T>,
|
||||||
) -> SklearnResult<ArrayViewD<'py, T>>
|
|
||||||
where
|
|
||||||
T: numpy::Element,
|
|
||||||
{
|
|
||||||
Ok(py_array.as_array().into_dyn())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert PyReadonlyArray2 to ArrayViewD (for type compatibility)
|
|
||||||
pub fn to_array_view2_from_readonly2<'py, T>(
|
|
||||||
py_array: &'py numpy::PyReadonlyArray<'py, T, ndarray::Ix2>,
|
|
||||||
) -> SklearnResult<ArrayViewD<'py, T>>
|
|
||||||
where
|
|
||||||
T: numpy::Element,
|
|
||||||
{
|
|
||||||
Ok(py_array.as_array().into_dyn())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Convert any readonly array to dynamic view (generic helper)
|
|
||||||
pub fn to_dyn_view<'py, T, D>(
|
|
||||||
py_array: &'py numpy::PyReadonlyArray<'py, T, D>,
|
|
||||||
) -> ArrayViewD<'py, T>
|
) -> ArrayViewD<'py, T>
|
||||||
where
|
where
|
||||||
T: numpy::Element,
|
T: numpy::Element,
|
||||||
D: ndarray::Dimension,
|
|
||||||
{
|
{
|
||||||
py_array.as_array().into_dyn()
|
py_array.as_array()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Validate input shapes for sklearn methods
|
/// Validate input shapes for sklearn methods
|
||||||
@@ -272,10 +255,8 @@ impl ArrayConverter {
|
|||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
pub fn set_random_state(seed: Option<u64>) -> PyResult<()> {
|
pub fn set_random_state(seed: Option<u64>) -> PyResult<()> {
|
||||||
// Set global random state for reproducibility
|
// Set global random state for reproducibility
|
||||||
// This would integrate with the underlying ML libraries
|
|
||||||
match seed {
|
match seed {
|
||||||
Some(s) => {
|
Some(s) => {
|
||||||
// Set seeds for various RNGs used in the crate
|
|
||||||
println!("Setting random state to: {}", s);
|
println!("Setting random state to: {}", s);
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
@@ -297,7 +278,7 @@ pub fn get_device_info(py: Python) -> PyResult<PyObject> {
|
|||||||
#[cfg(feature = "gpu")]
|
#[cfg(feature = "gpu")]
|
||||||
{
|
{
|
||||||
info.set_item("gpu_available", true)?;
|
info.set_item("gpu_available", true)?;
|
||||||
info.set_item("gpu_count", 1)?; // Would query actual GPU count
|
info.set_item("gpu_count", 1)?;
|
||||||
info.set_item("gpu_memory", "Unknown")?;
|
info.set_item("gpu_memory", "Unknown")?;
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "gpu"))]
|
#[cfg(not(feature = "gpu"))]
|
||||||
@@ -330,7 +311,6 @@ pub fn enable_gpu_acceleration(enable: bool) -> PyResult<bool> {
|
|||||||
|
|
||||||
#[pyfunction]
|
#[pyfunction]
|
||||||
pub fn get_memory_usage() -> PyResult<f64> {
|
pub fn get_memory_usage() -> PyResult<f64> {
|
||||||
// Return current memory usage in MB
|
|
||||||
#[cfg(target_os = "linux")]
|
#[cfg(target_os = "linux")]
|
||||||
{
|
{
|
||||||
use std::fs;
|
use std::fs;
|
||||||
@@ -339,7 +319,7 @@ pub fn get_memory_usage() -> PyResult<f64> {
|
|||||||
if line.starts_with("VmRSS:") {
|
if line.starts_with("VmRSS:") {
|
||||||
if let Some(kb_str) = line.split_whitespace().nth(1) {
|
if let Some(kb_str) = line.split_whitespace().nth(1) {
|
||||||
if let Ok(kb) = kb_str.parse::<f64>() {
|
if let Ok(kb) = kb_str.parse::<f64>() {
|
||||||
return Ok(kb / 1024.0); // Convert to MB
|
return Ok(kb / 1024.0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -347,7 +327,6 @@ pub fn get_memory_usage() -> PyResult<f64> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback - return 0 if we can't determine memory usage
|
|
||||||
Ok(0.0)
|
Ok(0.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,12 +341,13 @@ pub fn benchmark_against_sklearn(
|
|||||||
let runs = n_runs.unwrap_or(5);
|
let runs = n_runs.unwrap_or(5);
|
||||||
let result = PyDict::new(py);
|
let result = PyDict::new(py);
|
||||||
|
|
||||||
|
let x_array = x.as_array();
|
||||||
result.set_item("model", model_name)?;
|
result.set_item("model", model_name)?;
|
||||||
result.set_item("n_samples", x.shape()[0])?;
|
result.set_item("n_samples", x_array.shape()[0])?;
|
||||||
result.set_item("n_features", x.shape()[1])?;
|
result.set_item("n_features", x_array.shape()[1])?;
|
||||||
result.set_item("n_runs", runs)?;
|
result.set_item("n_runs", runs)?;
|
||||||
|
|
||||||
// Mock benchmark results (would perform actual benchmarking)
|
// Mock benchmark results
|
||||||
result.set_item("rustytorch_fit_time", 0.1)?;
|
result.set_item("rustytorch_fit_time", 0.1)?;
|
||||||
result.set_item("sklearn_fit_time", 0.25)?;
|
result.set_item("sklearn_fit_time", 0.25)?;
|
||||||
result.set_item("speedup_factor", 2.5)?;
|
result.set_item("speedup_factor", 2.5)?;
|
||||||
@@ -386,13 +366,14 @@ pub struct ParamValidator;
|
|||||||
|
|
||||||
impl ParamValidator {
|
impl ParamValidator {
|
||||||
/// Validate parameter dictionary from Python
|
/// Validate parameter dictionary from Python
|
||||||
pub fn validate_params(
|
pub fn validate_params<'py>(
|
||||||
params: &PyDict,
|
params: &Bound<'py, PyDict>,
|
||||||
valid_params: &[&str],
|
valid_params: &[&str],
|
||||||
) -> SklearnResult<HashMap<String, PyObject>> {
|
) -> SklearnResult<HashMap<String, PyObject>> {
|
||||||
|
use pyo3::types::PyDictMethods;
|
||||||
let mut validated = HashMap::new();
|
let mut validated = HashMap::new();
|
||||||
|
|
||||||
for (key, value) in params {
|
for (key, value) in params.iter() {
|
||||||
let key_str: String = key.extract().map_err(|_| {
|
let key_str: String = key.extract().map_err(|_| {
|
||||||
crate::sklearn_error!(InvalidParameter, "Parameter key must be string")
|
crate::sklearn_error!(InvalidParameter, "Parameter key must be string")
|
||||||
})?;
|
})?;
|
||||||
@@ -406,7 +387,7 @@ impl ParamValidator {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
validated.insert(key_str, value.into());
|
validated.insert(key_str, value.unbind());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(validated)
|
Ok(validated)
|
||||||
@@ -450,8 +431,8 @@ impl AsyncHelper {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use ndarray::arr2;
|
use numpy::ndarray::arr2;
|
||||||
use numpy::PyArray2;
|
use numpy::PyArrayDyn;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_device_config() {
|
fn test_device_config() {
|
||||||
@@ -470,9 +451,8 @@ mod tests {
|
|||||||
fn test_array_converter() {
|
fn test_array_converter() {
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let arr = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
|
let arr = arr2(&[[1.0, 2.0], [3.0, 4.0]]);
|
||||||
// Convert to dynamic dimensionality array
|
|
||||||
let arr_dyn = arr.into_dyn();
|
let arr_dyn = arr.into_dyn();
|
||||||
let py_arr = numpy::PyArrayDyn::from_owned_array(py, arr_dyn.clone());
|
let py_arr = PyArrayDyn::from_owned_array(py, arr_dyn);
|
||||||
let readonly = py_arr.readonly();
|
let readonly = py_arr.readonly();
|
||||||
|
|
||||||
let view = ArrayConverter::to_array_view2(&readonly).unwrap();
|
let view = ArrayConverter::to_array_view2(&readonly).unwrap();
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Simplified sklearn-compatible classifier wrappers that compile with current API
|
Simplified sklearn-compatible classifier wrappers that compile with current API
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use ndarray::{Array1, Array2};
|
use numpy::ndarray::{Array1, Array2};
|
||||||
use numpy::{PyArray1, PyArray2, PyReadonlyArrayDyn};
|
use numpy::{PyArray1, PyArray2, PyReadonlyArrayDyn};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::PyDict;
|
use pyo3::types::PyDict;
|
||||||
@@ -196,7 +196,7 @@ impl DecisionTreeClassifier {
|
|||||||
let predictions: Array1<i64> =
|
let predictions: Array1<i64> =
|
||||||
Array1::from_iter(predictions_data.iter().map(|&x| x.round() as i64));
|
Array1::from_iter(predictions_data.iter().map(|&x| x.round() as i64));
|
||||||
|
|
||||||
Python::with_gil(|py| Ok(ArrayConverter::from_array1(py, predictions)?.to_owned()))
|
Python::with_gil(|py| Ok(ArrayConverter::from_array1(py, predictions)?.unbind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Predict class probabilities
|
/// Predict class probabilities
|
||||||
@@ -251,7 +251,7 @@ impl DecisionTreeClassifier {
|
|||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
Python::with_gil(|py| Ok(ArrayConverter::from_array2(py, probabilities)?.to_owned()))
|
Python::with_gil(|py| Ok(ArrayConverter::from_array2(py, probabilities)?.unbind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Score the model on test data
|
/// Score the model on test data
|
||||||
@@ -259,8 +259,11 @@ impl DecisionTreeClassifier {
|
|||||||
let predictions = self.predict(x)?;
|
let predictions = self.predict(x)?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let pred_array = predictions.as_ref(py).readonly();
|
use numpy::PyArrayMethods;
|
||||||
let pred_view = ArrayConverter::to_dyn_view(&pred_array);
|
let pred_bound = predictions.bind(py);
|
||||||
|
let pred_array = pred_bound.readonly();
|
||||||
|
// Convert Ix1 readonly array view to dynamic 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_view = ArrayConverter::to_array_view1(&y).map_err(|e| PyErr::from(e))?;
|
||||||
|
|
||||||
if pred_view.len() != y_view.len() {
|
if pred_view.len() != y_view.len() {
|
||||||
@@ -281,26 +284,21 @@ impl DecisionTreeClassifier {
|
|||||||
|
|
||||||
/// Get model parameters
|
/// Get model parameters
|
||||||
fn get_params(&self) -> PyResult<HashMap<String, PyObject>> {
|
fn get_params(&self) -> PyResult<HashMap<String, PyObject>> {
|
||||||
|
use pyo3::IntoPyObjectExt;
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert("criterion".to_string(), self.criterion.to_object(py));
|
params.insert("criterion".to_string(), self.criterion.clone().into_py_any(py)?);
|
||||||
params.insert("max_depth".to_string(), self.max_depth.to_object(py));
|
params.insert("max_depth".to_string(), self.max_depth.into_py_any(py)?);
|
||||||
params.insert(
|
params.insert("min_samples_split".to_string(), self.min_samples_split.into_py_any(py)?);
|
||||||
"min_samples_split".to_string(),
|
params.insert("min_samples_leaf".to_string(), self.min_samples_leaf.into_py_any(py)?);
|
||||||
self.min_samples_split.to_object(py),
|
params.insert("random_state".to_string(), self.random_state.into_py_any(py)?);
|
||||||
);
|
params.insert("device".to_string(), self.device.to_string().into_py_any(py)?);
|
||||||
params.insert(
|
|
||||||
"min_samples_leaf".to_string(),
|
|
||||||
self.min_samples_leaf.to_object(py),
|
|
||||||
);
|
|
||||||
params.insert("random_state".to_string(), self.random_state.to_object(py));
|
|
||||||
params.insert("device".to_string(), self.device.to_string().to_object(py));
|
|
||||||
Ok(params)
|
Ok(params)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Set model parameters
|
/// Set model parameters
|
||||||
fn set_params(&mut self, params: &PyDict) -> PyResult<()> {
|
fn set_params<'py>(&mut self, params: &Bound<'py, PyDict>) -> PyResult<()> {
|
||||||
let valid_params = vec![
|
let valid_params = vec![
|
||||||
"criterion",
|
"criterion",
|
||||||
"max_depth",
|
"max_depth",
|
||||||
@@ -343,7 +341,7 @@ impl DecisionTreeClassifier {
|
|||||||
match &self.classes {
|
match &self.classes {
|
||||||
Some(classes) => Python::with_gil(|py| {
|
Some(classes) => Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array1(py, classes.clone())?.to_owned(),
|
ArrayConverter::from_array1(py, classes.clone())?.unbind(),
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -375,7 +373,7 @@ impl DecisionTreeClassifier {
|
|||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array1(py, importances)?.to_owned(),
|
ArrayConverter::from_array1(py, importances)?.unbind(),
|
||||||
))
|
))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Simplified sklearn-compatible clustering wrappers
|
Simplified sklearn-compatible clustering wrappers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use ndarray::{Array1, Array2, Axis};
|
use numpy::ndarray::{Array1, Array2, Axis};
|
||||||
use numpy::{PyArray1, PyArray2, PyReadonlyArrayDyn};
|
use numpy::{PyArray1, PyArray2, PyReadonlyArrayDyn};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
@@ -190,7 +190,7 @@ impl KMeans {
|
|||||||
labels[sample_idx] = best_cluster as i64;
|
labels[sample_idx] = best_cluster as i64;
|
||||||
}
|
}
|
||||||
|
|
||||||
Python::with_gil(|py| Ok(ArrayConverter::from_array1(py, labels)?.to_owned()))
|
Python::with_gil(|py| Ok(ArrayConverter::from_array1(py, labels)?.unbind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fit_predict(&mut self, x: PyReadonlyArrayDyn<f64>) -> PyResult<Py<PyArray1<i64>>> {
|
fn fit_predict(&mut self, x: PyReadonlyArrayDyn<f64>) -> PyResult<Py<PyArray1<i64>>> {
|
||||||
@@ -198,7 +198,7 @@ impl KMeans {
|
|||||||
|
|
||||||
match &self.labels_ {
|
match &self.labels_ {
|
||||||
Some(labels) => Python::with_gil(|py| {
|
Some(labels) => Python::with_gil(|py| {
|
||||||
Ok(ArrayConverter::from_array1(py, labels.clone())?.to_owned())
|
Ok(ArrayConverter::from_array1(py, labels.clone())?.unbind())
|
||||||
}),
|
}),
|
||||||
None => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
|
None => Err(PyErr::new::<pyo3::exceptions::PyRuntimeError, _>(
|
||||||
"fit_predict failed: no labels available",
|
"fit_predict failed: no labels available",
|
||||||
@@ -227,16 +227,17 @@ impl KMeans {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Python::with_gil(|py| Ok(ArrayConverter::from_array2(py, distances)?.to_owned()))
|
Python::with_gil(|py| Ok(ArrayConverter::from_array2(py, distances)?.unbind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_params(&self) -> PyResult<HashMap<String, PyObject>> {
|
fn get_params(&self) -> PyResult<HashMap<String, PyObject>> {
|
||||||
|
use pyo3::IntoPyObjectExt;
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert("n_clusters".to_string(), self.n_clusters.to_object(py));
|
params.insert("n_clusters".to_string(), self.n_clusters.into_py_any(py)?);
|
||||||
params.insert("random_state".to_string(), self.random_state.to_object(py));
|
params.insert("random_state".to_string(), self.random_state.into_py_any(py)?);
|
||||||
params.insert("max_iter".to_string(), self.max_iter.to_object(py));
|
params.insert("max_iter".to_string(), self.max_iter.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)
|
Ok(params)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -246,7 +247,7 @@ impl KMeans {
|
|||||||
match &self.cluster_centers_ {
|
match &self.cluster_centers_ {
|
||||||
Some(centers) => Python::with_gil(|py| {
|
Some(centers) => Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array2(py, centers.clone())?.to_owned(),
|
ArrayConverter::from_array2(py, centers.clone())?.unbind(),
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -258,7 +259,7 @@ impl KMeans {
|
|||||||
match &self.labels_ {
|
match &self.labels_ {
|
||||||
Some(labels) => Python::with_gil(|py| {
|
Some(labels) => Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array1(py, labels.clone())?.to_owned(),
|
ArrayConverter::from_array1(py, labels.clone())?.unbind(),
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -299,12 +300,12 @@ impl DBSCAN {
|
|||||||
|
|
||||||
fn fit_predict(&mut self, x: PyReadonlyArrayDyn<f64>) -> PyResult<Py<PyArray1<i64>>> {
|
fn fit_predict(&mut self, x: PyReadonlyArrayDyn<f64>) -> PyResult<Py<PyArray1<i64>>> {
|
||||||
let x_view = ArrayConverter::to_array_view2(&x).map_err(|e| PyErr::from(e))?;
|
let x_view = ArrayConverter::to_array_view2(&x).map_err(|e| PyErr::from(e))?;
|
||||||
// Simplified: assign random labels for demonstration
|
// Simplified: assign zero labels for demonstration
|
||||||
let labels = Array1::zeros(x_view.shape()[0]);
|
let labels = Array1::zeros(x_view.shape()[0]);
|
||||||
self.labels_ = Some(labels.clone());
|
self.labels_ = Some(labels.clone());
|
||||||
self.is_fitted = true;
|
self.is_fitted = true;
|
||||||
|
|
||||||
Python::with_gil(|py| Ok(ArrayConverter::from_array1(py, labels)?.to_owned()))
|
Python::with_gil(|py| Ok(ArrayConverter::from_array1(py, labels)?.unbind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
@@ -312,7 +313,7 @@ impl DBSCAN {
|
|||||||
match &self.labels_ {
|
match &self.labels_ {
|
||||||
Some(labels) => Python::with_gil(|py| {
|
Some(labels) => Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array1(py, labels.clone())?.to_owned(),
|
ArrayConverter::from_array1(py, labels.clone())?.unbind(),
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Simplified sklearn-compatible model selection wrappers
|
Simplified sklearn-compatible model selection wrappers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use ndarray::{Array1, Array2, Axis};
|
use numpy::ndarray::{Array1, Array2, Axis};
|
||||||
use numpy::{PyArray1, PyReadonlyArrayDyn};
|
use numpy::{PyArray1, PyReadonlyArrayDyn};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use pyo3::types::{PyDict, PyTuple};
|
use pyo3::types::{PyDict, PyTuple};
|
||||||
@@ -27,7 +27,7 @@ pub struct GridSearchCV {
|
|||||||
impl GridSearchCV {
|
impl GridSearchCV {
|
||||||
#[new]
|
#[new]
|
||||||
#[pyo3(signature = (estimator, param_grid, cv=5))]
|
#[pyo3(signature = (estimator, param_grid, cv=5))]
|
||||||
fn new(estimator: PyObject, param_grid: &PyDict, cv: Option<usize>) -> PyResult<Self> {
|
fn new<'py>(estimator: PyObject, param_grid: &Bound<'py, PyDict>, cv: Option<usize>) -> PyResult<Self> {
|
||||||
// Convert param_grid from PyDict to HashMap (simplified)
|
// Convert param_grid from PyDict to HashMap (simplified)
|
||||||
let param_grid_map = HashMap::new(); // Placeholder
|
let param_grid_map = HashMap::new(); // Placeholder
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ impl GridSearchCV {
|
|||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let x_view = ArrayConverter::to_array_view2(&x).map_err(|e| PyErr::from(e))?;
|
let x_view = ArrayConverter::to_array_view2(&x).map_err(|e| PyErr::from(e))?;
|
||||||
let dummy_preds = Array1::<f64>::zeros(x_view.shape()[0]);
|
let dummy_preds = Array1::<f64>::zeros(x_view.shape()[0]);
|
||||||
Ok(ArrayConverter::from_array1(py, dummy_preds)?.to_object(py))
|
Ok(ArrayConverter::from_array1(py, dummy_preds)?.into_any().unbind())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,8 +66,15 @@ impl GridSearchCV {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
fn best_params_(&self) -> Option<HashMap<String, PyObject>> {
|
fn best_params_(&self) -> PyResult<Option<PyObject>> {
|
||||||
self.best_params_.clone()
|
match &self.best_params_ {
|
||||||
|
Some(_params) => Python::with_gil(|py| {
|
||||||
|
let dict = PyDict::new(py);
|
||||||
|
// Return empty dict for the placeholder
|
||||||
|
Ok(Some(dict.into_any().unbind()))
|
||||||
|
}),
|
||||||
|
None => Ok(None),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +95,7 @@ pub fn cross_val_score(
|
|||||||
// Simplified - return dummy cross-validation scores
|
// Simplified - return dummy cross-validation scores
|
||||||
let scores = Array1::from_vec(vec![0.9, 0.92, 0.88, 0.91, 0.89][..cv_folds].to_vec());
|
let scores = Array1::from_vec(vec![0.9, 0.92, 0.88, 0.91, 0.89][..cv_folds].to_vec());
|
||||||
|
|
||||||
Ok(ArrayConverter::from_array1(py, scores)?.to_owned())
|
Ok(ArrayConverter::from_array1(py, scores)?.unbind())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Simplified sklearn-compatible train_test_split function
|
/// Simplified sklearn-compatible train_test_split function
|
||||||
@@ -154,21 +161,16 @@ pub fn train_test_split(
|
|||||||
let y_train_1d = Array1::from_vec(y_train.iter().copied().collect());
|
let y_train_1d = Array1::from_vec(y_train.iter().copied().collect());
|
||||||
let y_test_1d = Array1::from_vec(y_test.iter().copied().collect());
|
let y_test_1d = Array1::from_vec(y_test.iter().copied().collect());
|
||||||
|
|
||||||
let x_train_py = ArrayConverter::from_array2(py, x_train_2d)?;
|
let x_train_py = ArrayConverter::from_array2(py, x_train_2d)?.into_any().unbind();
|
||||||
let x_test_py = ArrayConverter::from_array2(py, x_test_2d)?;
|
let x_test_py = ArrayConverter::from_array2(py, x_test_2d)?.into_any().unbind();
|
||||||
let y_train_py = ArrayConverter::from_array1(py, y_train_1d)?;
|
let y_train_py = ArrayConverter::from_array1(py, y_train_1d)?.into_any().unbind();
|
||||||
let y_test_py = ArrayConverter::from_array1(py, y_test_1d)?;
|
let y_test_py = ArrayConverter::from_array1(py, y_test_1d)?.into_any().unbind();
|
||||||
|
|
||||||
// Return tuple
|
// Return tuple
|
||||||
let result = PyTuple::new(
|
let result = PyTuple::new(
|
||||||
py,
|
py,
|
||||||
&[
|
&[x_train_py, x_test_py, y_train_py, y_test_py],
|
||||||
x_train_py.to_object(py),
|
)?;
|
||||||
x_test_py.to_object(py),
|
|
||||||
y_train_py.to_object(py),
|
|
||||||
y_test_py.to_object(py),
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(result.to_object(py))
|
Ok(result.into_any().unbind())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
Simplified sklearn-compatible preprocessing wrappers
|
Simplified sklearn-compatible preprocessing wrappers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use ndarray::{Array1, Array2, Axis};
|
use numpy::ndarray::{Array1, Array2, Axis};
|
||||||
use numpy::{PyArray1, PyArray2, PyReadonlyArrayDyn};
|
use numpy::{PyArray1, PyArray2, PyReadonlyArrayDyn};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@ impl StandardScaler {
|
|||||||
))
|
))
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
Ok(ArrayConverter::from_array2(py, transformed_2d)?.to_owned())
|
Ok(ArrayConverter::from_array2(py, transformed_2d)?.unbind())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,7 +144,7 @@ impl StandardScaler {
|
|||||||
))
|
))
|
||||||
},
|
},
|
||||||
)?;
|
)?;
|
||||||
Ok(ArrayConverter::from_array2(py, transformed_2d)?.to_owned())
|
Ok(ArrayConverter::from_array2(py, transformed_2d)?.unbind())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -176,7 +176,7 @@ impl StandardScaler {
|
|||||||
e
|
e
|
||||||
))
|
))
|
||||||
})?;
|
})?;
|
||||||
Ok(ArrayConverter::from_array2(py, inv_transformed_2d)?.to_owned())
|
Ok(ArrayConverter::from_array2(py, inv_transformed_2d)?.unbind())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,7 +185,7 @@ impl StandardScaler {
|
|||||||
match &self.mean_ {
|
match &self.mean_ {
|
||||||
Some(mean) => Python::with_gil(|py| {
|
Some(mean) => Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array1(py, mean.clone())?.to_owned(),
|
ArrayConverter::from_array1(py, mean.clone())?.unbind(),
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -197,7 +197,7 @@ impl StandardScaler {
|
|||||||
match &self.scale_ {
|
match &self.scale_ {
|
||||||
Some(scale) => Python::with_gil(|py| {
|
Some(scale) => Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array1(py, scale.clone())?.to_owned(),
|
ArrayConverter::from_array1(py, scale.clone())?.unbind(),
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -282,7 +282,7 @@ impl MinMaxScaler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Python::with_gil(|py| Ok(ArrayConverter::from_array2(py, transformed)?.to_owned()))
|
Python::with_gil(|py| Ok(ArrayConverter::from_array2(py, transformed)?.unbind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fit_transform(&mut self, x: PyReadonlyArrayDyn<f64>) -> PyResult<Py<PyArray2<f64>>> {
|
fn fit_transform(&mut self, x: PyReadonlyArrayDyn<f64>) -> PyResult<Py<PyArray2<f64>>> {
|
||||||
@@ -320,7 +320,7 @@ impl MinMaxScaler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Python::with_gil(|py| Ok(ArrayConverter::from_array2(py, transformed)?.to_owned()))
|
Python::with_gil(|py| Ok(ArrayConverter::from_array2(py, transformed)?.unbind()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[getter]
|
#[getter]
|
||||||
@@ -328,7 +328,7 @@ impl MinMaxScaler {
|
|||||||
match &self.data_min_ {
|
match &self.data_min_ {
|
||||||
Some(data_min) => Python::with_gil(|py| {
|
Some(data_min) => Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array1(py, data_min.clone())?.to_owned(),
|
ArrayConverter::from_array1(py, data_min.clone())?.unbind(),
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
@@ -355,20 +355,20 @@ impl OneHotEncoder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fit(&mut self, x: &PyAny) -> PyResult<()> {
|
fn fit<'py>(&mut self, _x: &Bound<'py, PyAny>) -> PyResult<()> {
|
||||||
self.is_fitted = true;
|
self.is_fitted = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform(&self, x: &PyAny) -> PyResult<Py<PyArray2<f64>>> {
|
fn transform<'py>(&self, _x: &Bound<'py, PyAny>) -> PyResult<Py<PyArray2<f64>>> {
|
||||||
// Placeholder - return identity transform
|
// Placeholder - return identity transform
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let dummy = Array2::eye(2);
|
let dummy = Array2::eye(2);
|
||||||
Ok(ArrayConverter::from_array2(py, dummy)?.to_owned())
|
Ok(ArrayConverter::from_array2(py, dummy)?.unbind())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fit_transform(&mut self, x: &PyAny) -> PyResult<Py<PyArray2<f64>>> {
|
fn fit_transform<'py>(&mut self, x: &Bound<'py, PyAny>) -> PyResult<Py<PyArray2<f64>>> {
|
||||||
self.fit(x)?;
|
self.fit(x)?;
|
||||||
self.transform(x)
|
self.transform(x)
|
||||||
}
|
}
|
||||||
@@ -392,20 +392,20 @@ impl LabelEncoder {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fit(&mut self, y: &PyAny) -> PyResult<()> {
|
fn fit<'py>(&mut self, _y: &Bound<'py, PyAny>) -> PyResult<()> {
|
||||||
self.is_fitted = true;
|
self.is_fitted = true;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn transform(&self, y: &PyAny) -> PyResult<Py<PyArray1<i64>>> {
|
fn transform<'py>(&self, _y: &Bound<'py, PyAny>) -> PyResult<Py<PyArray1<i64>>> {
|
||||||
// Placeholder - return dummy labels
|
// Placeholder - return dummy labels
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let dummy = Array1::zeros(10);
|
let dummy = Array1::zeros(10);
|
||||||
Ok(ArrayConverter::from_array1(py, dummy)?.to_owned())
|
Ok(ArrayConverter::from_array1(py, dummy)?.unbind())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn fit_transform(&mut self, y: &PyAny) -> PyResult<Py<PyArray1<i64>>> {
|
fn fit_transform<'py>(&mut self, y: &Bound<'py, PyAny>) -> PyResult<Py<PyArray1<i64>>> {
|
||||||
self.fit(y)?;
|
self.fit(y)?;
|
||||||
self.transform(y)
|
self.transform(y)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
Simplified sklearn-compatible regressor wrappers
|
Simplified sklearn-compatible regressor wrappers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use ndarray::Array1;
|
use numpy::ndarray::Array1;
|
||||||
use numpy::{PyArray1, PyReadonlyArrayDyn};
|
use numpy::{PyArray1, PyReadonlyArrayDyn, PyArrayMethods};
|
||||||
use pyo3::prelude::*;
|
use pyo3::prelude::*;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -44,7 +44,7 @@ impl LinearRegression {
|
|||||||
ArrayConverter::validate_fit_input(&x, Some(&y)).map_err(|e| PyErr::from(e))?;
|
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 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]);
|
self.n_features_in = Some(x_view.shape()[1]);
|
||||||
|
|
||||||
@@ -81,15 +81,18 @@ impl LinearRegression {
|
|||||||
predictions[i] = sum + intercept;
|
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> {
|
fn score(&self, x: PyReadonlyArrayDyn<f64>, y: PyReadonlyArrayDyn<f64>) -> PyResult<f64> {
|
||||||
let predictions = self.predict(x)?;
|
let predictions = self.predict(x)?;
|
||||||
|
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let pred_array = predictions.as_ref(py).readonly();
|
use numpy::PyArrayMethods;
|
||||||
let pred_view = ArrayConverter::to_dyn_view(&pred_array);
|
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_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;
|
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
|
let ss_res: f64 = pred_view
|
||||||
.iter()
|
.iter()
|
||||||
.zip(y_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();
|
.sum();
|
||||||
|
|
||||||
let ss_tot: f64 = y_view.iter().map(|actual| (actual - y_mean).powi(2)).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>> {
|
fn get_params(&self) -> PyResult<HashMap<String, PyObject>> {
|
||||||
|
use pyo3::IntoPyObjectExt;
|
||||||
Python::with_gil(|py| {
|
Python::with_gil(|py| {
|
||||||
let mut params = HashMap::new();
|
let mut params = HashMap::new();
|
||||||
params.insert(
|
params.insert(
|
||||||
"fit_intercept".to_string(),
|
"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)
|
Ok(params)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -123,7 +127,7 @@ impl LinearRegression {
|
|||||||
match &self.coef_ {
|
match &self.coef_ {
|
||||||
Some(coef) => Python::with_gil(|py| {
|
Some(coef) => Python::with_gil(|py| {
|
||||||
Ok(Some(
|
Ok(Some(
|
||||||
ArrayConverter::from_array1(py, coef.clone())?.to_owned(),
|
ArrayConverter::from_array1(py, coef.clone())?.unbind(),
|
||||||
))
|
))
|
||||||
}),
|
}),
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
|
|||||||
Reference in New Issue
Block a user