1use 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};
14use 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#[pyfunction]
24pub fn get_version() -> String {
25 env!("CARGO_PKG_VERSION").to_string()
26}
27
28#[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 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 let features = [
70 #[cfg(feature = "pandas-integration")]
71 "pandas-integration",
72 ];
73
74 info.insert("features".to_string(), features.join(", "));
75
76 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#[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#[pyfunction]
100pub fn get_hardware_info() -> HashMap<String, bool> {
101 let mut info = HashMap::new();
102
103 #[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 #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
121 {
122 info.insert("simd_support".to_string(), false);
123 }
124
125 info.insert("cuda_available".to_string(), false);
127 info.insert("opencl_available".to_string(), false);
128
129 info.insert("parallel_support".to_string(), true);
131 info.insert("num_cpus".to_string(), num_cpus::get() > 1);
132
133 info
134}
135
136#[pyfunction]
138pub fn get_memory_info() -> HashMap<String, u64> {
139 let mut info = HashMap::new();
140
141 info.insert("num_cpus".to_string(), num_cpus::get() as u64);
143
144 info.insert("available_memory_mb".to_string(), 0);
147 info.insert("used_memory_mb".to_string(), 0);
148
149 info
150}
151
152#[pyfunction]
154pub fn set_config(option: &str, _value: &str) -> PyResult<()> {
155 match option {
156 "n_jobs" => {
157 Ok(())
160 }
161 "assume_finite" => {
162 Ok(())
164 }
165 "working_memory" => {
166 Ok(())
168 }
169 _ => Err(pyo3::exceptions::PyValueError::new_err(format!(
170 "Unknown configuration option: {}",
171 option
172 ))),
173 }
174}
175
176#[pyfunction]
178pub fn get_config() -> HashMap<String, String> {
179 let mut config = HashMap::new();
180
181 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#[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#[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 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; results.insert(
238 "matrix_multiplication_100x100_ms".to_string(),
239 matrix_mul_time,
240 );
241
242 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 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
262pub 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
277pub 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
292pub 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
297pub fn ndarray1_to_numpy<'py>(py: Python<'py>, array: Array1<f64>) -> Py<PyArray1<f64>> {
299 core_array1_to_py(py, &array)
300}