sklears_python/
utils.rs

1//! Python bindings for utility functions
2//!
3//! This module provides Python bindings for sklears utilities,
4//! including version information and build details.
5
6// Use SciRS2-Core for array operations instead of direct ndarray
7use numpy::{PyArray1, PyArray2, PyArrayMethods};
8use pyo3::exceptions::PyValueError;
9use pyo3::ffi;
10use pyo3::prelude::*;
11use pyo3::types::PyAny;
12use pyo3::Bound;
13use scirs2_autograd::ndarray::{Array1, Array2};
14// Use SciRS2-Core for random number generation instead of direct rand
15use scirs2_core::random::{thread_rng, Rng};
16use std::collections::HashMap;
17
18use crate::linear::{
19    core_array1_to_py, core_array2_to_py, pyarray_to_core_array1, pyarray_to_core_array2,
20};
21
22/// Get the version of sklears
23#[pyfunction]
24pub fn get_version() -> String {
25    env!("CARGO_PKG_VERSION").to_string()
26}
27
28/// Get build information about sklears
29#[pyfunction]
30pub fn get_build_info() -> HashMap<String, String> {
31    let mut info = HashMap::new();
32
33    info.insert("version".to_string(), env!("CARGO_PKG_VERSION").to_string());
34    info.insert("authors".to_string(), env!("CARGO_PKG_AUTHORS").to_string());
35    info.insert(
36        "description".to_string(),
37        env!("CARGO_PKG_DESCRIPTION").to_string(),
38    );
39    info.insert(
40        "homepage".to_string(),
41        env!("CARGO_PKG_HOMEPAGE").to_string(),
42    );
43    info.insert(
44        "repository".to_string(),
45        env!("CARGO_PKG_REPOSITORY").to_string(),
46    );
47    info.insert("license".to_string(), env!("CARGO_PKG_LICENSE").to_string());
48    info.insert(
49        "rust_version".to_string(),
50        env!("CARGO_PKG_RUST_VERSION").to_string(),
51    );
52
53    // Build-time information
54    info.insert(
55        "target_triple".to_string(),
56        std::env::var("TARGET").unwrap_or_else(|_| "unknown".to_string()),
57    );
58    info.insert(
59        "build_profile".to_string(),
60        if cfg!(debug_assertions) {
61            "debug"
62        } else {
63            "release"
64        }
65        .to_string(),
66    );
67
68    // Feature information
69    let features = [
70        #[cfg(feature = "pandas-integration")]
71        "pandas-integration",
72    ];
73
74    info.insert("features".to_string(), features.join(", "));
75
76    // Dependency versions
77    info.insert("scirs2_core_version".to_string(), "workspace".to_string());
78    info.insert("pyo3_version".to_string(), "0.26".to_string());
79    info.insert("numpy_version".to_string(), "0.26".to_string());
80
81    info
82}
83
84/// Check if specific features are enabled
85#[pyfunction]
86pub fn has_feature(feature_name: &str) -> bool {
87    match feature_name {
88        "pandas-integration" => {
89            #[cfg(feature = "pandas-integration")]
90            return true;
91            #[cfg(not(feature = "pandas-integration"))]
92            return false;
93        }
94        _ => false,
95    }
96}
97
98/// Get hardware acceleration capabilities
99#[pyfunction]
100pub fn get_hardware_info() -> HashMap<String, bool> {
101    let mut info = HashMap::new();
102
103    // CPU features
104    #[cfg(target_arch = "x86_64")]
105    {
106        info.insert("x86_64".to_string(), true);
107        info.insert("avx2".to_string(), is_x86_feature_detected!("avx2"));
108        info.insert("fma".to_string(), is_x86_feature_detected!("fma"));
109        info.insert("sse4_1".to_string(), is_x86_feature_detected!("sse4.1"));
110        info.insert("sse4_2".to_string(), is_x86_feature_detected!("sse4.2"));
111    }
112
113    #[cfg(target_arch = "aarch64")]
114    {
115        info.insert("aarch64".to_string(), true);
116        info.insert("neon".to_string(), cfg!(target_feature = "neon"));
117    }
118
119    // Other architectures
120    #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
121    {
122        info.insert("simd_support".to_string(), false);
123    }
124
125    // GPU support (placeholder - would need actual detection)
126    info.insert("cuda_available".to_string(), false);
127    info.insert("opencl_available".to_string(), false);
128
129    // Thread support
130    info.insert("parallel_support".to_string(), true);
131    info.insert("num_cpus".to_string(), num_cpus::get() > 1);
132
133    info
134}
135
136/// Get memory usage information
137#[pyfunction]
138pub fn get_memory_info() -> HashMap<String, u64> {
139    let mut info = HashMap::new();
140
141    // Get number of CPUs
142    info.insert("num_cpus".to_string(), num_cpus::get() as u64);
143
144    // Physical memory would require additional dependencies
145    // This is a placeholder
146    info.insert("available_memory_mb".to_string(), 0);
147    info.insert("used_memory_mb".to_string(), 0);
148
149    info
150}
151
152/// Set global configuration options
153#[pyfunction]
154pub fn set_config(option: &str, _value: &str) -> PyResult<()> {
155    match option {
156        "n_jobs" => {
157            // Set global parallelism configuration
158            // This would require implementing global state management
159            Ok(())
160        }
161        "assume_finite" => {
162            // Set validation configuration
163            Ok(())
164        }
165        "working_memory" => {
166            // Set memory limit for operations
167            Ok(())
168        }
169        _ => Err(pyo3::exceptions::PyValueError::new_err(format!(
170            "Unknown configuration option: {}",
171            option
172        ))),
173    }
174}
175
176/// Get current configuration
177#[pyfunction]
178pub fn get_config() -> HashMap<String, String> {
179    let mut config = HashMap::new();
180
181    // Default configuration values
182    config.insert("n_jobs".to_string(), "1".to_string());
183    config.insert("assume_finite".to_string(), "false".to_string());
184    config.insert("working_memory".to_string(), "1024".to_string());
185    config.insert("print_changed_only".to_string(), "true".to_string());
186    config.insert("display".to_string(), "text".to_string());
187
188    config
189}
190
191/// Print system information
192#[pyfunction]
193pub fn show_versions() -> String {
194    let mut output = String::new();
195
196    output.push_str("sklears information:\n");
197    output.push_str("=====================\n");
198
199    let build_info = get_build_info();
200    for (key, value) in &build_info {
201        output.push_str(&format!("{}: {}\n", key, value));
202    }
203
204    output.push_str("\nHardware information:\n");
205    output.push_str("====================\n");
206
207    let hardware_info = get_hardware_info();
208    for (key, value) in &hardware_info {
209        output.push_str(&format!("{}: {}\n", key, value));
210    }
211
212    output.push_str("\nMemory information:\n");
213    output.push_str("==================\n");
214
215    let memory_info = get_memory_info();
216    for (key, value) in &memory_info {
217        output.push_str(&format!("{}: {}\n", key, value));
218    }
219
220    output
221}
222
223/// Performance testing utility
224#[pyfunction]
225pub fn benchmark_basic_operations() -> HashMap<String, f64> {
226    use std::time::Instant;
227
228    let mut results = HashMap::new();
229    let mut rng = thread_rng();
230
231    // Matrix multiplication benchmark
232    let start = Instant::now();
233    let a = Array2::from_shape_fn((100, 100), |_| rng.gen::<f64>());
234    let b = Array2::from_shape_fn((100, 100), |_| rng.gen::<f64>());
235    let _c = a.dot(&b);
236    let matrix_mul_time = start.elapsed().as_nanos() as f64 / 1_000_000.0; // Convert to milliseconds
237    results.insert(
238        "matrix_multiplication_100x100_ms".to_string(),
239        matrix_mul_time,
240    );
241
242    // Vector operations benchmark
243    let start = Instant::now();
244    let v1 = Array1::from_shape_fn(10000, |_| rng.gen::<f64>());
245    let v2 = Array1::from_shape_fn(10000, |_| rng.gen::<f64>());
246    let _dot_product = v1.dot(&v2);
247    let vector_ops_time = start.elapsed().as_nanos() as f64 / 1_000_000.0;
248    results.insert("vector_dot_product_10k_ms".to_string(), vector_ops_time);
249
250    // Memory allocation benchmark
251    let start = Instant::now();
252    let _large_array = Array2::<f64>::zeros((1000, 1000));
253    let allocation_time = start.elapsed().as_nanos() as f64 / 1_000_000.0;
254    results.insert(
255        "memory_allocation_1M_elements_ms".to_string(),
256        allocation_time,
257    );
258
259    results
260}
261
262/// Convert NumPy array to ndarray Array2`<f64>`
263pub fn numpy_to_ndarray2(py_array: &PyArray2<f64>) -> PyResult<Array2<f64>> {
264    Python::with_gil(|py| {
265        let ptr = py_array as *const PyArray2<f64> as *mut ffi::PyObject;
266        let bound_any = unsafe { Bound::<PyAny>::from_borrowed_ptr(py, ptr) };
267        let bound_array = bound_any.downcast::<PyArray2<f64>>()?;
268        let readonly = bound_array.try_readonly().map_err(|err| {
269            PyValueError::new_err(format!(
270                "Failed to borrow NumPy array as read-only view: {err}"
271            ))
272        })?;
273        pyarray_to_core_array2(readonly)
274    })
275}
276
277/// Convert NumPy array to ndarray Array1`<f64>`
278pub fn numpy_to_ndarray1(py_array: &PyArray1<f64>) -> PyResult<Array1<f64>> {
279    Python::with_gil(|py| {
280        let ptr = py_array as *const PyArray1<f64> as *mut ffi::PyObject;
281        let bound_any = unsafe { Bound::<PyAny>::from_borrowed_ptr(py, ptr) };
282        let bound_array = bound_any.downcast::<PyArray1<f64>>()?;
283        let readonly = bound_array.try_readonly().map_err(|err| {
284            PyValueError::new_err(format!(
285                "Failed to borrow NumPy array as read-only view: {err}"
286            ))
287        })?;
288        pyarray_to_core_array1(readonly)
289    })
290}
291
292/// Convert ndarray Array2`<f64>` to NumPy array
293pub fn ndarray_to_numpy<'py>(py: Python<'py>, array: Array2<f64>) -> Py<PyArray2<f64>> {
294    core_array2_to_py(py, &array).expect("Failed to convert ndarray to NumPy array")
295}
296
297/// Convert ndarray Array1`<f64>` to NumPy array
298pub fn ndarray1_to_numpy<'py>(py: Python<'py>, array: Array1<f64>) -> Py<PyArray1<f64>> {
299    core_array1_to_py(py, &array)
300}