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