1use crate::{UtilsError, UtilsResult};
8use scirs2_core::ndarray::{Array1, Array2};
9use serde::{Deserialize, Serialize};
10use std::collections::HashMap;
11use std::ffi::{CStr, CString};
12use std::fmt;
13use std::os::raw::c_char;
14
15pub struct PythonInterop;
17
18impl PythonInterop {
19 pub fn array_to_python_buffer(array: &Array1<f64>) -> PyArrayBuffer {
21 PyArrayBuffer {
22 data: array.as_slice().expect("operation should succeed").to_vec(),
23 shape: vec![array.len()],
24 dtype: "float64".to_string(),
25 order: "C".to_string(),
26 }
27 }
28
29 pub fn array2_to_python_buffer(array: &Array2<f64>) -> PyArrayBuffer {
31 let (rows, cols) = array.dim();
32 PyArrayBuffer {
33 data: array.as_slice().expect("operation should succeed").to_vec(),
34 shape: vec![rows, cols],
35 dtype: "float64".to_string(),
36 order: "C".to_string(),
37 }
38 }
39
40 pub fn python_buffer_to_array(buffer: &PyArrayBuffer) -> UtilsResult<Array1<f64>> {
42 if buffer.shape.len() != 1 {
43 return Err(UtilsError::InvalidParameter(
44 "Expected 1D array".to_string(),
45 ));
46 }
47
48 if buffer.dtype != "float64" {
49 return Err(UtilsError::InvalidParameter(format!(
50 "Unsupported dtype: {}",
51 buffer.dtype
52 )));
53 }
54
55 Array1::from_vec(buffer.data.clone())
56 .into_shape_with_order(buffer.shape[0])
57 .map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
58 }
59
60 pub fn python_buffer_to_array2(buffer: &PyArrayBuffer) -> UtilsResult<Array2<f64>> {
62 if buffer.shape.len() != 2 {
63 return Err(UtilsError::InvalidParameter(
64 "Expected 2D array".to_string(),
65 ));
66 }
67
68 if buffer.dtype != "float64" {
69 return Err(UtilsError::InvalidParameter(format!(
70 "Unsupported dtype: {}",
71 buffer.dtype
72 )));
73 }
74
75 Array2::from_shape_vec((buffer.shape[0], buffer.shape[1]), buffer.data.clone())
76 .map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
77 }
78
79 pub fn generate_numpy_import_code(array_name: &str, buffer: &PyArrayBuffer) -> String {
81 format!(
82 r#"
83import numpy as np
84
85# Data generated by sklears-utils
86{} = np.array({:?}, dtype='{}').reshape({:?})
87"#,
88 array_name, buffer.data, buffer.dtype, buffer.shape
89 )
90 }
91
92 pub fn generate_function_call_template(
94 function_name: &str,
95 parameters: &[PythonParameter],
96 ) -> String {
97 let param_strings: Vec<String> = parameters
98 .iter()
99 .map(|p| match &p.value {
100 PythonValue::String(s) => format!("{}='{}'", p.name, s),
101 PythonValue::Number(n) => format!("{}={}", p.name, n),
102 PythonValue::Boolean(b) => {
103 format!("{}={}", p.name, if *b { "True" } else { "False" })
104 }
105 PythonValue::Array(name) => format!("{}={}", p.name, name),
106 })
107 .collect();
108
109 format!("{}({})", function_name, param_strings.join(", "))
110 }
111
112 pub fn create_ml_script(
114 model_type: &str,
115 training_data: &PyArrayBuffer,
116 labels: &PyArrayBuffer,
117 hyperparameters: &HashMap<String, f64>,
118 ) -> UtilsResult<String> {
119 let mut script = String::new();
120
121 script.push_str("import numpy as np\n");
123 script.push_str("from sklearn.model_selection import train_test_split\n");
124
125 match model_type {
126 "linear_regression" => {
127 script.push_str("from sklearn.linear_model import LinearRegression\n")
128 }
129 "random_forest" => {
130 script.push_str("from sklearn.ensemble import RandomForestRegressor\n")
131 }
132 "svm" => script.push_str("from sklearn.svm import SVC\n"),
133 _ => {
134 return Err(UtilsError::InvalidParameter(format!(
135 "Unsupported model type: {model_type}"
136 )))
137 }
138 }
139
140 script.push_str("\n# Data preparation\n");
141 script.push_str(&Self::generate_numpy_import_code("X", training_data));
142 script.push_str(&Self::generate_numpy_import_code("y", labels));
143
144 script.push_str("\n# Train-test split\n");
145 script.push_str("X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n");
146
147 script.push_str("\n# Model creation and training\n");
148 let model_creation = match model_type {
149 "linear_regression" => "model = LinearRegression()".to_string(),
150 "random_forest" => {
151 let n_estimators = hyperparameters.get("n_estimators").unwrap_or(&100.0);
152 format!(
153 "model = RandomForestRegressor(n_estimators={})",
154 *n_estimators as i32
155 )
156 }
157 "svm" => {
158 let c = hyperparameters.get("C").unwrap_or(&1.0);
159 format!("model = SVC(C={c})")
160 }
161 _ => {
162 return Err(UtilsError::InvalidParameter(format!(
163 "Unsupported model type: {model_type}"
164 )))
165 }
166 };
167
168 script.push_str(&model_creation);
169 script.push_str("\nmodel.fit(X_train, y_train)\n");
170
171 script.push_str("\n# Evaluation\n");
172 script.push_str("score = model.score(X_test, y_test)\n");
173 script.push_str("print(f'Model score: {score:.4f}')\n");
174
175 Ok(script)
176 }
177}
178
179pub struct WasmUtils;
181
182impl WasmUtils {
183 pub fn generate_wasm_signature(
185 function_name: &str,
186 parameters: &[WasmParameter],
187 return_type: WasmType,
188 ) -> String {
189 let param_strings: Vec<String> = parameters
190 .iter()
191 .map(|p| format!("{}: {}", p.name, p.param_type))
192 .collect();
193
194 format!(
195 "#[wasm_bindgen]\npub fn {}({}) -> {} {{",
196 function_name,
197 param_strings.join(", "),
198 return_type
199 )
200 }
201
202 pub fn generate_memory_helpers() -> String {
204 r#"
205use wasm_bindgen::prelude::*;
206
207// Memory management helpers for WASM
208#[wasm_bindgen]
209pub fn alloc(size: usize) -> *mut u8 {
210 let mut buf = Vec::with_capacity(size);
211 let ptr = buf.as_mut_ptr();
212 std::mem::forget(buf);
213 ptr
214}
215
216#[wasm_bindgen]
217pub fn dealloc(ptr: *mut u8, size: usize) {
218 unsafe {
219 let _ = Vec::from_raw_parts(ptr, size, size);
220 }
221}
222
223// Array helpers
224#[wasm_bindgen]
225pub struct Float64Array {
226 data: Vec<f64>,
227}
228
229#[wasm_bindgen]
230impl Float64Array {
231 #[wasm_bindgen(constructor)]
232 pub fn new(size: usize) -> Float64Array {
233 Float64Array {
234 data: vec![0.0; size],
235 }
236 }
237
238 #[wasm_bindgen(getter)]
239 pub fn length(&self) -> usize {
240 self.data.len()
241 }
242
243 #[wasm_bindgen]
244 pub fn get(&self, index: usize) -> f64 {
245 self.data.get(index).copied().unwrap_or(0.0)
246 }
247
248 #[wasm_bindgen]
249 pub fn set(&mut self, index: usize, value: f64) {
250 if index < self.data.len() {
251 self.data[index] = value;
252 }
253 }
254
255 #[wasm_bindgen]
256 pub fn as_ptr(&self) -> *const f64 {
257 self.data.as_ptr()
258 }
259}
260"#
261 .to_string()
262 }
263
264 pub fn generate_ml_bindings() -> String {
266 r#"
267use wasm_bindgen::prelude::*;
268
269// Linear algebra operations
270#[wasm_bindgen]
271pub fn dot_product(a: &[f64], b: &[f64]) -> f64 {
272 if a.len() != b.len() {
273 return 0.0;
274 }
275 a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
276}
277
278#[wasm_bindgen]
279pub fn matrix_multiply(
280 a: &[f64], a_rows: usize, a_cols: usize,
281 b: &[f64], b_rows: usize, b_cols: usize,
282 result: &mut [f64]
283) -> bool {
284 if a_cols != b_rows || result.len() != a_rows * b_cols {
285 return false;
286 }
287
288 for i in 0..a_rows {
289 for j in 0..b_cols {
290 let mut sum = 0.0;
291 for k in 0..a_cols {
292 sum += a[i * a_cols + k] * b[k * b_cols + j];
293 }
294 result[i * b_cols + j] = sum;
295 }
296 }
297 true
298}
299
300// Statistical functions
301#[wasm_bindgen]
302pub fn mean(data: &[f64]) -> f64 {
303 if data.is_empty() {
304 return 0.0;
305 }
306 data.iter().sum::<f64>() / data.len() as f64
307}
308
309#[wasm_bindgen]
310pub fn variance(data: &[f64]) -> f64 {
311 if data.len() < 2 {
312 return 0.0;
313 }
314 let mean_val = mean(data);
315 let variance = data.iter()
316 .map(|x| (x - mean_val).powi(2))
317 .sum::<f64>() / (data.len() - 1) as f64;
318 variance
319}
320
321#[wasm_bindgen]
322pub fn standard_deviation(data: &[f64]) -> f64 {
323 variance(data).sqrt()
324}
325"#
326 .to_string()
327 }
328
329 pub fn create_wasm_build_config() -> WasmBuildConfig {
331 WasmBuildConfig {
332 target: "wasm32-unknown-unknown".to_string(),
333 features: vec![
334 "wasm-bindgen".to_string(),
335 "console_error_panic_hook".to_string(),
336 ],
337 optimization: WasmOptimization::Size,
338 debug: false,
339 typescript_bindings: true,
340 }
341 }
342
343 pub fn generate_package_json(project_name: &str, version: &str) -> String {
345 format!(
346 r#"{{
347 "name": "{project_name}",
348 "version": "{version}",
349 "description": "WASM bindings for sklears ML utilities",
350 "main": "index.js",
351 "types": "index.d.ts",
352 "scripts": {{
353 "build": "wasm-pack build --target web --out-dir pkg",
354 "build:nodejs": "wasm-pack build --target nodejs --out-dir pkg-node",
355 "test": "wasm-pack test --headless --chrome"
356 }},
357 "devDependencies": {{
358 "wasm-pack": "^0.12.0"
359 }},
360 "files": [
361 "pkg/"
362 ],
363 "keywords": [
364 "wasm",
365 "machine-learning",
366 "sklears",
367 "linear-algebra"
368 ]
369}}"#
370 )
371 }
372}
373
374pub struct RInterop;
376
377impl RInterop {
378 pub fn array_to_r_vector(array: &Array1<f64>) -> RVector {
380 RVector {
381 data: array.as_slice().expect("operation should succeed").to_vec(),
382 length: array.len(),
383 r_type: RType::Numeric,
384 }
385 }
386
387 pub fn array2_to_r_matrix(array: &Array2<f64>) -> RMatrix {
389 let (rows, cols) = array.dim();
390
391 let mut col_major_data = vec![0.0; rows * cols];
393 for i in 0..rows {
394 for j in 0..cols {
395 col_major_data[j * rows + i] = array[[i, j]];
396 }
397 }
398
399 RMatrix {
400 data: col_major_data,
401 nrow: rows,
402 ncol: cols,
403 byrow: false, r_type: RType::Numeric,
405 }
406 }
407
408 pub fn r_vector_to_array(vector: &RVector) -> UtilsResult<Array1<f64>> {
410 if vector.r_type != RType::Numeric {
411 return Err(UtilsError::InvalidParameter(format!(
412 "Expected numeric vector, got {:?}",
413 vector.r_type
414 )));
415 }
416
417 Array1::from_vec(vector.data.clone())
418 .into_shape_with_order(vector.length)
419 .map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
420 }
421
422 pub fn r_matrix_to_array2(matrix: &RMatrix) -> UtilsResult<Array2<f64>> {
424 if matrix.r_type != RType::Numeric {
425 return Err(UtilsError::InvalidParameter(format!(
426 "Expected numeric matrix, got {:?}",
427 matrix.r_type
428 )));
429 }
430
431 if matrix.byrow {
432 Array2::from_shape_vec((matrix.nrow, matrix.ncol), matrix.data.clone())
434 .map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
435 } else {
436 let mut row_major_data = vec![0.0; matrix.data.len()];
438 for i in 0..matrix.nrow {
439 for j in 0..matrix.ncol {
440 row_major_data[i * matrix.ncol + j] = matrix.data[j * matrix.nrow + i];
441 }
442 }
443 Array2::from_shape_vec((matrix.nrow, matrix.ncol), row_major_data)
444 .map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
445 }
446 }
447
448 pub fn generate_r_vector_code(vector_name: &str, vector: &RVector) -> String {
450 format!(
451 "{} <- c({})",
452 vector_name,
453 vector
454 .data
455 .iter()
456 .map(|x| x.to_string())
457 .collect::<Vec<_>>()
458 .join(", ")
459 )
460 }
461
462 pub fn generate_r_matrix_code(matrix_name: &str, matrix: &RMatrix) -> String {
464 let data_str = matrix
465 .data
466 .iter()
467 .map(|x| x.to_string())
468 .collect::<Vec<_>>()
469 .join(", ");
470
471 format!(
472 "{} <- matrix(c({}), nrow = {}, ncol = {}, byrow = {})",
473 matrix_name,
474 data_str,
475 matrix.nrow,
476 matrix.ncol,
477 if matrix.byrow { "TRUE" } else { "FALSE" }
478 )
479 }
480
481 pub fn generate_r_dataframe_code(
483 df_name: &str,
484 columns: &HashMap<String, RVector>,
485 ) -> UtilsResult<String> {
486 let mut column_defs = Vec::new();
487
488 let first_length = columns.values().next().map(|v| v.length).unwrap_or(0);
490
491 for (name, vector) in columns {
492 if vector.length != first_length {
493 return Err(UtilsError::InvalidParameter(
494 "All columns must have the same length".to_string(),
495 ));
496 }
497
498 let data_str = vector
499 .data
500 .iter()
501 .map(|x| x.to_string())
502 .collect::<Vec<_>>()
503 .join(", ");
504
505 column_defs.push(format!("{name} = c({data_str})"));
506 }
507
508 Ok(format!(
509 "{} <- data.frame({})",
510 df_name,
511 column_defs.join(", ")
512 ))
513 }
514
515 pub fn generate_r_package_imports(packages: &[&str]) -> String {
517 let mut imports = String::new();
518
519 for package in packages {
520 imports.push_str(&format!("library({package})\n"));
521 }
522
523 imports
524 }
525
526 pub fn generate_r_function_call(function_name: &str, parameters: &[RParameter]) -> String {
528 let param_strings: Vec<String> = parameters
529 .iter()
530 .map(|p| match &p.value {
531 RValue::String(s) => format!("{} = \"{}\"", p.name, s),
532 RValue::Number(n) => format!("{} = {}", p.name, n),
533 RValue::Boolean(b) => format!("{} = {}", p.name, if *b { "TRUE" } else { "FALSE" }),
534 RValue::Vector(name) => format!("{} = {}", p.name, name),
535 RValue::Matrix(name) => format!("{} = {}", p.name, name),
536 RValue::DataFrame(name) => format!("{} = {}", p.name, name),
537 })
538 .collect();
539
540 format!("{}({})", function_name, param_strings.join(", "))
541 }
542
543 pub fn create_r_ml_script(
545 model_type: &str,
546 training_data: &RMatrix,
547 response_var: &RVector,
548 hyperparameters: &HashMap<String, f64>,
549 ) -> UtilsResult<String> {
550 let mut script = String::new();
551
552 match model_type {
554 "linear_regression" => {
555 script.push_str("# Linear regression using base R\n");
556 }
557 "random_forest" => {
558 script.push_str(&Self::generate_r_package_imports(&["randomForest"]));
559 }
560 "svm" => {
561 script.push_str(&Self::generate_r_package_imports(&["e1071"]));
562 }
563 "glm" => {
564 script.push_str("# Generalized linear model using base R\n");
565 }
566 "tree" => {
567 script.push_str(&Self::generate_r_package_imports(&["tree"]));
568 }
569 _ => {
570 return Err(UtilsError::InvalidParameter(format!(
571 "Unsupported R model type: {model_type}"
572 )))
573 }
574 }
575
576 script.push_str("\n# Data preparation\n");
577 script.push_str(&Self::generate_r_matrix_code("X", training_data));
578 script.push('\n');
579 script.push_str(&Self::generate_r_vector_code("y", response_var));
580 script.push('\n');
581
582 if matches!(model_type, "glm" | "tree") {
584 script.push_str("\n# Create data frame\n");
585 script.push_str("df <- data.frame(y = y, X)\n");
586 script.push_str("colnames(df) <- c('response', paste0('X', 1:ncol(X)))\n");
587 }
588
589 script.push_str("\n# Train-test split\n");
590 script.push_str("set.seed(42)\n");
591 script.push_str("train_indices <- sample(1:nrow(X), size = 0.8 * nrow(X))\n");
592 script.push_str("X_train <- X[train_indices, ]\n");
593 script.push_str("X_test <- X[-train_indices, ]\n");
594 script.push_str("y_train <- y[train_indices]\n");
595 script.push_str("y_test <- y[-train_indices]\n");
596
597 script.push_str("\n# Model creation and training\n");
598 let model_creation = match model_type {
599 "linear_regression" => "model <- lm(y_train ~ X_train)".to_string(),
600 "random_forest" => {
601 let ntree = hyperparameters.get("ntree").unwrap_or(&500.0);
602 let mtry = hyperparameters.get("mtry").unwrap_or(&3.0);
603 format!(
604 "model <- randomForest(x = X_train, y = y_train, ntree = {}, mtry = {})",
605 *ntree as i32, *mtry as i32
606 )
607 }
608 "svm" => {
609 let cost = hyperparameters.get("cost").unwrap_or(&1.0);
610 let gamma = hyperparameters.get("gamma").unwrap_or(&0.1);
611 format!("model <- svm(x = X_train, y = y_train, cost = {cost}, gamma = {gamma})")
612 }
613 "glm" => {
614 let family = "gaussian"; format!("df_train <- df[train_indices, ]\nmodel <- glm(response ~ ., data = df_train, family = {family})")
616 }
617 "tree" => {
618 "df_train <- df[train_indices, ]\nmodel <- tree(response ~ ., data = df_train)"
619 .to_string()
620 }
621 _ => {
622 return Err(UtilsError::InvalidParameter(format!(
623 "Unsupported R model type: {model_type}"
624 )))
625 }
626 };
627
628 script.push_str(&model_creation);
629 script.push('\n');
630
631 script.push_str("\n# Prediction and evaluation\n");
632 let prediction_code = match model_type {
633 "linear_regression" => {
634 "predictions <- predict(model, data.frame(X_test))\nrmse <- sqrt(mean((predictions - y_test)^2))\ncat('RMSE:', rmse, '\\n')"
635 },
636 "random_forest" => {
637 "predictions <- predict(model, X_test)\nrmse <- sqrt(mean((predictions - y_test)^2))\ncat('RMSE:', rmse, '\\n')"
638 },
639 "svm" => {
640 "predictions <- predict(model, X_test)\nrmse <- sqrt(mean((predictions - y_test)^2))\ncat('RMSE:', rmse, '\\n')"
641 },
642 "glm" | "tree" => {
643 "df_test <- df[-train_indices, ]\npredictions <- predict(model, df_test)\nrmse <- sqrt(mean((predictions - y_test)^2))\ncat('RMSE:', rmse, '\\n')"
644 },
645 _ => {
646 return Err(UtilsError::InvalidParameter(format!(
647 "Unsupported R model type for prediction: {model_type}"
648 )))
649 }
650 };
651
652 script.push_str(prediction_code);
653 script.push('\n');
654
655 script.push_str("\n# Model summary\n");
656 script.push_str("print(summary(model))\n");
657
658 Ok(script)
659 }
660
661 pub fn create_r_statistical_analysis(
663 data: &RMatrix,
664 analysis_type: &str,
665 ) -> UtilsResult<String> {
666 let mut script = String::new();
667
668 script.push_str("# Statistical analysis generated by sklears-utils\n");
669 script.push_str(&Self::generate_r_matrix_code("data", data));
670 script.push('\n');
671
672 match analysis_type {
673 "descriptive" => {
674 script.push_str("\n# Descriptive statistics\n");
675 script.push_str("summary(data)\n");
676 script.push_str("apply(data, 2, sd) # Standard deviations\n");
677 script.push_str("cor(data) # Correlation matrix\n");
678 }
679 "pca" => {
680 script.push_str("\n# Principal Component Analysis\n");
681 script.push_str("pca_result <- prcomp(data, center = TRUE, scale. = TRUE)\n");
682 script.push_str("summary(pca_result)\n");
683 script
684 .push_str("plot(pca_result$x[,1:2]) # Scatter plot of first two components\n");
685 }
686 "clustering" => {
687 script.push_str("\n# K-means clustering\n");
688 script.push_str("set.seed(42)\n");
689 script.push_str("kmeans_result <- kmeans(data, centers = 3)\n");
690 script.push_str("print(kmeans_result)\n");
691 script.push_str("plot(data, col = kmeans_result$cluster)\n");
692 }
693 "normality_test" => {
694 script.push_str("\n# Normality tests\n");
695 script.push_str("for(i in 1:ncol(data)) {\n");
696 script.push_str(" cat('Column', i, '\\n')\n");
697 script.push_str(" print(shapiro.test(data[,i]))\n");
698 script.push_str("}\n");
699 }
700 _ => {
701 return Err(UtilsError::InvalidParameter(format!(
702 "Unsupported analysis type: {analysis_type}"
703 )))
704 }
705 }
706
707 Ok(script)
708 }
709
710 pub fn convert_r_output(output: &str, expected_type: ROutputType) -> UtilsResult<ROutputValue> {
712 match expected_type {
713 ROutputType::Vector => {
714 let cleaned = output.replace("[1]", "").trim().to_string();
716 let values: Result<Vec<f64>, _> = cleaned
717 .split_whitespace()
718 .map(|s| s.parse::<f64>())
719 .collect();
720
721 match values {
722 Ok(v) => Ok(ROutputValue::Vector(v)),
723 Err(_) => Err(UtilsError::InvalidParameter(
724 "Failed to parse R vector output".to_string(),
725 )),
726 }
727 }
728 ROutputType::Scalar => {
729 let cleaned = output.replace("[1]", "");
731 let cleaned = cleaned.trim();
732 match cleaned.parse::<f64>() {
733 Ok(v) => Ok(ROutputValue::Scalar(v)),
734 Err(_) => Err(UtilsError::InvalidParameter(
735 "Failed to parse R scalar output".to_string(),
736 )),
737 }
738 }
739 ROutputType::String => Ok(ROutputValue::String(output.to_string())),
740 }
741 }
742}
743
744pub struct FFIUtils;
746
747impl FFIUtils {
748 pub fn create_c_signature(
750 function_name: &str,
751 parameters: &[CParameter],
752 return_type: CType,
753 ) -> String {
754 let param_strings: Vec<String> = parameters
755 .iter()
756 .map(|p| format!("{} {}", p.param_type, p.name))
757 .collect();
758
759 format!(
760 "extern \"C\" fn {}({}) -> {}",
761 function_name,
762 param_strings.join(", "),
763 return_type
764 )
765 }
766
767 pub fn generate_c_header(library_name: &str, functions: &[CFunctionSignature]) -> String {
769 let mut header = String::new();
770
771 let library_upper = library_name.to_uppercase();
772 header.push_str(&format!("#ifndef {library_upper}_H\n"));
773 header.push_str(&format!("#define {library_upper}_H\n\n"));
774 header.push_str("#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n");
775
776 for func in functions {
777 header.push_str(&format!("{};\n", func.signature));
778 }
779
780 header.push_str("\n#ifdef __cplusplus\n}\n#endif\n\n");
781 header.push_str(&format!("#endif // {library_upper}_H\n"));
782
783 header
784 }
785
786 pub fn rust_string_to_c(s: &str) -> UtilsResult<*mut c_char> {
788 let c_string = CString::new(s)
789 .map_err(|e| UtilsError::InvalidParameter(format!("Invalid C string: {e}")))?;
790 Ok(c_string.into_raw())
791 }
792
793 pub unsafe fn c_string_to_rust(ptr: *const c_char) -> UtilsResult<String> {
802 if ptr.is_null() {
803 return Err(UtilsError::InvalidParameter("Null pointer".to_string()));
804 }
805
806 let c_str = CStr::from_ptr(ptr);
807 c_str
808 .to_str()
809 .map(|s| s.to_string())
810 .map_err(|e| UtilsError::InvalidParameter(format!("Invalid UTF-8: {e}")))
811 }
812
813 pub fn create_array_transfer(data: &[f64]) -> ArrayTransfer {
815 ArrayTransfer {
816 data: data.as_ptr(),
817 length: data.len(),
818 capacity: data.len(),
819 }
820 }
821
822 pub fn generate_ffi_examples() -> String {
824 r#"
825// Example FFI functions for machine learning operations
826
827use std::os::raw::{c_double, c_int};
828use std::slice;
829
830#[repr(C)]
831pub struct ArrayTransfer {
832 pub data: *const f64,
833 pub length: usize,
834 pub capacity: usize,
835}
836
837// Linear regression example
838#[no_mangle]
839pub extern "C" fn linear_regression_fit(
840 x_data: *const c_double,
841 y_data: *const c_double,
842 n_samples: c_int,
843 coefficients: *mut c_double,
844 intercept: *mut c_double,
845) -> c_int {
846 if x_data.is_null() || y_data.is_null() || coefficients.is_null() || intercept.is_null() {
847 return -1; // Error: null pointer
848 }
849
850 unsafe {
851 let x_slice = slice::from_raw_parts(x_data, n_samples as usize);
852 let y_slice = slice::from_raw_parts(y_data, n_samples as usize);
853
854 // Simple linear regression calculation
855 let n = n_samples as f64;
856 let sum_x: f64 = x_slice.iter().sum();
857 let sum_y: f64 = y_slice.iter().sum();
858 let sum_xy: f64 = x_slice.iter().zip(y_slice.iter()).map(|(x, y)| x * y).sum();
859 let sum_xx: f64 = x_slice.iter().map(|x| x * x).sum();
860
861 let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
862 let intercept_val = (sum_y - slope * sum_x) / n;
863
864 *coefficients = slope;
865 *intercept = intercept_val;
866
867 0 // Success
868 }
869}
870
871// Array operations example
872#[no_mangle]
873pub extern "C" fn array_mean(
874 data: *const c_double,
875 length: c_int,
876 result: *mut c_double,
877) -> c_int {
878 if data.is_null() || result.is_null() || length <= 0 {
879 return -1;
880 }
881
882 unsafe {
883 let slice = slice::from_raw_parts(data, length as usize);
884 let mean = slice.iter().sum::<f64>() / length as f64;
885 *result = mean;
886 0
887 }
888}
889"#
890 .to_string()
891 }
892}
893
894#[derive(Debug, Clone, Serialize, Deserialize)]
897pub struct PyArrayBuffer {
898 pub data: Vec<f64>,
899 pub shape: Vec<usize>,
900 pub dtype: String,
901 pub order: String,
902}
903
904#[derive(Debug, Clone)]
905pub struct PythonParameter {
906 pub name: String,
907 pub value: PythonValue,
908}
909
910#[derive(Debug, Clone)]
911pub enum PythonValue {
912 String(String),
913 Number(f64),
914 Boolean(bool),
915 Array(String), }
917
918#[derive(Debug, Clone)]
919pub struct WasmParameter {
920 pub name: String,
921 pub param_type: WasmType,
922}
923
924#[derive(Debug, Clone)]
925pub enum WasmType {
926 F64,
927 F32,
928 I32,
929 U32,
930 Bool,
931 String,
932}
933
934impl fmt::Display for WasmType {
935 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
936 match self {
937 WasmType::F64 => write!(f, "f64"),
938 WasmType::F32 => write!(f, "f32"),
939 WasmType::I32 => write!(f, "i32"),
940 WasmType::U32 => write!(f, "u32"),
941 WasmType::Bool => write!(f, "bool"),
942 WasmType::String => write!(f, "String"),
943 }
944 }
945}
946
947#[derive(Debug, Clone)]
948pub struct WasmBuildConfig {
949 pub target: String,
950 pub features: Vec<String>,
951 pub optimization: WasmOptimization,
952 pub debug: bool,
953 pub typescript_bindings: bool,
954}
955
956#[derive(Debug, Clone)]
957pub enum WasmOptimization {
958 None,
959 Size,
960 Speed,
961}
962
963#[derive(Debug, Clone)]
964pub struct CParameter {
965 pub name: String,
966 pub param_type: CType,
967}
968
969#[derive(Debug, Clone)]
970pub enum CType {
971 Int,
972 Double,
973 Float,
974 CharPtr,
975 VoidPtr,
976 ConstCharPtr,
977 ConstDoublePtr,
978}
979
980impl fmt::Display for CType {
981 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
982 match self {
983 CType::Int => write!(f, "int"),
984 CType::Double => write!(f, "double"),
985 CType::Float => write!(f, "float"),
986 CType::CharPtr => write!(f, "char*"),
987 CType::VoidPtr => write!(f, "void*"),
988 CType::ConstCharPtr => write!(f, "const char*"),
989 CType::ConstDoublePtr => write!(f, "const double*"),
990 }
991 }
992}
993
994#[derive(Debug, Clone)]
995pub struct CFunctionSignature {
996 pub name: String,
997 pub signature: String,
998 pub description: String,
999}
1000
1001#[repr(C)]
1002#[derive(Debug, Clone)]
1003pub struct ArrayTransfer {
1004 pub data: *const f64,
1005 pub length: usize,
1006 pub capacity: usize,
1007}
1008
1009#[derive(Debug, Clone, Serialize, Deserialize)]
1012pub struct RVector {
1013 pub data: Vec<f64>,
1014 pub length: usize,
1015 pub r_type: RType,
1016}
1017
1018#[derive(Debug, Clone, Serialize, Deserialize)]
1019pub struct RMatrix {
1020 pub data: Vec<f64>,
1021 pub nrow: usize,
1022 pub ncol: usize,
1023 pub byrow: bool,
1024 pub r_type: RType,
1025}
1026
1027#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
1028pub enum RType {
1029 Numeric,
1030 Integer,
1031 Character,
1032 Logical,
1033 Factor,
1034}
1035
1036#[derive(Debug, Clone)]
1037pub struct RParameter {
1038 pub name: String,
1039 pub value: RValue,
1040}
1041
1042#[derive(Debug, Clone)]
1043pub enum RValue {
1044 String(String),
1045 Number(f64),
1046 Boolean(bool),
1047 Vector(String), Matrix(String), DataFrame(String), }
1051
1052#[derive(Debug, Clone)]
1053pub enum ROutputType {
1054 Vector,
1055 Scalar,
1056 String,
1057}
1058
1059#[derive(Debug, Clone)]
1060pub enum ROutputValue {
1061 Vector(Vec<f64>),
1062 Scalar(f64),
1063 String(String),
1064}
1065
1066#[allow(non_snake_case)]
1067#[cfg(test)]
1068mod tests {
1069 use super::*;
1070 use scirs2_core::ndarray::array;
1071
1072 #[test]
1073 fn test_python_array_conversion() {
1074 let arr = array![1.0, 2.0, 3.0, 4.0];
1075 let buffer = PythonInterop::array_to_python_buffer(&arr);
1076
1077 assert_eq!(buffer.data, vec![1.0, 2.0, 3.0, 4.0]);
1078 assert_eq!(buffer.shape, vec![4]);
1079 assert_eq!(buffer.dtype, "float64");
1080 assert_eq!(buffer.order, "C");
1081
1082 let converted =
1084 PythonInterop::python_buffer_to_array(&buffer).expect("operation should succeed");
1085 assert_eq!(converted, arr);
1086 }
1087
1088 #[test]
1089 fn test_python_array2_conversion() {
1090 let arr = array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
1091 let buffer = PythonInterop::array2_to_python_buffer(&arr);
1092
1093 assert_eq!(buffer.data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
1094 assert_eq!(buffer.shape, vec![3, 2]);
1095
1096 let converted =
1098 PythonInterop::python_buffer_to_array2(&buffer).expect("operation should succeed");
1099 assert_eq!(converted, arr);
1100 }
1101
1102 #[test]
1103 fn test_numpy_code_generation() {
1104 let buffer = PyArrayBuffer {
1105 data: vec![1.0, 2.0, 3.0],
1106 shape: vec![3],
1107 dtype: "float64".to_string(),
1108 order: "C".to_string(),
1109 };
1110
1111 let code = PythonInterop::generate_numpy_import_code("my_array", &buffer);
1112
1113 assert!(code.contains("import numpy as np"));
1114 assert!(code.contains("my_array = np.array"));
1115 assert!(code.contains("[1.0, 2.0, 3.0]"));
1116 assert!(code.contains("dtype='float64'"));
1117 assert!(code.contains("reshape([3])"));
1118 }
1119
1120 #[test]
1121 fn test_python_function_call_template() {
1122 let params = vec![
1123 PythonParameter {
1124 name: "n_estimators".to_string(),
1125 value: PythonValue::Number(100.0),
1126 },
1127 PythonParameter {
1128 name: "random_state".to_string(),
1129 value: PythonValue::Number(42.0),
1130 },
1131 PythonParameter {
1132 name: "verbose".to_string(),
1133 value: PythonValue::Boolean(true),
1134 },
1135 ];
1136
1137 let call = PythonInterop::generate_function_call_template("RandomForestRegressor", ¶ms);
1138
1139 assert!(call.contains("RandomForestRegressor("));
1140 assert!(call.contains("n_estimators=100"));
1141 assert!(call.contains("random_state=42"));
1142 assert!(call.contains("verbose=True"));
1143 }
1144
1145 #[test]
1146 fn test_ml_script_generation() {
1147 let training_data = PyArrayBuffer {
1148 data: vec![1.0, 2.0, 3.0, 4.0],
1149 shape: vec![2, 2],
1150 dtype: "float64".to_string(),
1151 order: "C".to_string(),
1152 };
1153
1154 let labels = PyArrayBuffer {
1155 data: vec![0.0, 1.0],
1156 shape: vec![2],
1157 dtype: "float64".to_string(),
1158 order: "C".to_string(),
1159 };
1160
1161 let mut hyperparams = HashMap::new();
1162 hyperparams.insert("n_estimators".to_string(), 50.0);
1163
1164 let script =
1165 PythonInterop::create_ml_script("random_forest", &training_data, &labels, &hyperparams)
1166 .expect("operation should succeed");
1167
1168 assert!(script.contains("import numpy as np"));
1169 assert!(script.contains("from sklearn.ensemble import RandomForestRegressor"));
1170 assert!(script.contains("train_test_split"));
1171 assert!(script.contains("RandomForestRegressor(n_estimators=50)"));
1172 assert!(script.contains("model.fit(X_train, y_train)"));
1173 assert!(script.contains("model.score(X_test, y_test)"));
1174 }
1175
1176 #[test]
1177 fn test_wasm_signature_generation() {
1178 let params = vec![
1179 WasmParameter {
1180 name: "a".to_string(),
1181 param_type: WasmType::F64,
1182 },
1183 WasmParameter {
1184 name: "b".to_string(),
1185 param_type: WasmType::F64,
1186 },
1187 ];
1188
1189 let signature = WasmUtils::generate_wasm_signature("add", ¶ms, WasmType::F64);
1190
1191 assert!(signature.contains("#[wasm_bindgen]"));
1192 assert!(signature.contains("pub fn add(a: f64, b: f64) -> f64"));
1193 }
1194
1195 #[test]
1196 fn test_wasm_memory_helpers() {
1197 let helpers = WasmUtils::generate_memory_helpers();
1198
1199 assert!(helpers.contains("pub fn alloc(size: usize)"));
1200 assert!(helpers.contains("pub fn dealloc(ptr: *mut u8, size: usize)"));
1201 assert!(helpers.contains("pub struct Float64Array"));
1202 assert!(helpers.contains("#[wasm_bindgen]"));
1203 }
1204
1205 #[test]
1206 fn test_wasm_ml_bindings() {
1207 let bindings = WasmUtils::generate_ml_bindings();
1208
1209 assert!(bindings.contains("pub fn dot_product"));
1210 assert!(bindings.contains("pub fn matrix_multiply"));
1211 assert!(bindings.contains("pub fn mean"));
1212 assert!(bindings.contains("pub fn variance"));
1213 assert!(bindings.contains("pub fn standard_deviation"));
1214 }
1215
1216 #[test]
1217 fn test_wasm_build_config() {
1218 let config = WasmUtils::create_wasm_build_config();
1219
1220 assert_eq!(config.target, "wasm32-unknown-unknown");
1221 assert!(config.features.contains(&"wasm-bindgen".to_string()));
1222 assert!(config.typescript_bindings);
1223 assert!(!config.debug);
1224 }
1225
1226 #[test]
1227 fn test_package_json_generation() {
1228 let json = WasmUtils::generate_package_json("my-ml-wasm", "0.1.0");
1229
1230 assert!(json.contains("\"name\": \"my-ml-wasm\""));
1231 assert!(json.contains("\"version\": \"0.1.0\""));
1232 assert!(json.contains("wasm-pack"));
1233 assert!(json.contains("\"machine-learning\""));
1234 }
1235
1236 #[test]
1237 fn test_c_signature_creation() {
1238 let params = vec![
1239 CParameter {
1240 name: "data".to_string(),
1241 param_type: CType::ConstDoublePtr,
1242 },
1243 CParameter {
1244 name: "length".to_string(),
1245 param_type: CType::Int,
1246 },
1247 ];
1248
1249 let signature = FFIUtils::create_c_signature("compute_mean", ¶ms, CType::Double);
1250
1251 assert!(signature.contains("extern \"C\" fn compute_mean"));
1252 assert!(signature.contains("const double* data"));
1253 assert!(signature.contains("int length"));
1254 assert!(signature.contains("-> double"));
1255 }
1256
1257 #[test]
1258 fn test_c_header_generation() {
1259 let functions = vec![
1260 CFunctionSignature {
1261 name: "add".to_string(),
1262 signature: "double add(double a, double b)".to_string(),
1263 description: "Add two numbers".to_string(),
1264 },
1265 CFunctionSignature {
1266 name: "multiply".to_string(),
1267 signature: "double multiply(double a, double b)".to_string(),
1268 description: "Multiply two numbers".to_string(),
1269 },
1270 ];
1271
1272 let header = FFIUtils::generate_c_header("mylib", &functions);
1273
1274 assert!(header.contains("#ifndef MYLIB_H"));
1275 assert!(header.contains("#define MYLIB_H"));
1276 assert!(header.contains("extern \"C\" {"));
1277 assert!(header.contains("double add(double a, double b);"));
1278 assert!(header.contains("double multiply(double a, double b);"));
1279 assert!(header.contains("#endif // MYLIB_H"));
1280 }
1281
1282 #[test]
1283 fn test_rust_to_c_string() {
1284 let rust_str = "Hello, World!";
1285 let c_ptr = FFIUtils::rust_string_to_c(rust_str).expect("operation should succeed");
1286
1287 let converted =
1289 unsafe { FFIUtils::c_string_to_rust(c_ptr).expect("operation should succeed") };
1290 assert_eq!(converted, rust_str);
1291
1292 unsafe {
1294 let _ = CString::from_raw(c_ptr);
1295 }
1296 }
1297
1298 #[test]
1299 fn test_array_transfer_creation() {
1300 let data = vec![1.0, 2.0, 3.0, 4.0];
1301 let transfer = FFIUtils::create_array_transfer(&data);
1302
1303 assert_eq!(transfer.length, 4);
1304 assert_eq!(transfer.capacity, 4);
1305 assert!(!transfer.data.is_null());
1306 }
1307
1308 #[test]
1309 fn test_ffi_examples_generation() {
1310 let examples = FFIUtils::generate_ffi_examples();
1311
1312 assert!(examples.contains("linear_regression_fit"));
1313 assert!(examples.contains("array_mean"));
1314 assert!(examples.contains("#[no_mangle]"));
1315 assert!(examples.contains("extern \"C\""));
1316 assert!(examples.contains("ArrayTransfer"));
1317 }
1318
1319 #[test]
1320 fn test_python_value_variants() {
1321 let string_val = PythonValue::String("test".to_string());
1322 let number_val = PythonValue::Number(42.0);
1323 let bool_val = PythonValue::Boolean(true);
1324 let array_val = PythonValue::Array("my_array".to_string());
1325
1326 match string_val {
1328 PythonValue::String(_) => {}
1329 _ => panic!(),
1330 }
1331 match number_val {
1332 PythonValue::Number(_) => {}
1333 _ => panic!(),
1334 }
1335 match bool_val {
1336 PythonValue::Boolean(_) => {}
1337 _ => panic!(),
1338 }
1339 match array_val {
1340 PythonValue::Array(_) => {}
1341 _ => panic!(),
1342 }
1343 }
1344
1345 #[test]
1346 fn test_wasm_type_display() {
1347 assert_eq!(WasmType::F64.to_string(), "f64");
1348 assert_eq!(WasmType::F32.to_string(), "f32");
1349 assert_eq!(WasmType::I32.to_string(), "i32");
1350 assert_eq!(WasmType::U32.to_string(), "u32");
1351 assert_eq!(WasmType::Bool.to_string(), "bool");
1352 assert_eq!(WasmType::String.to_string(), "String");
1353 }
1354
1355 #[test]
1356 fn test_c_type_display() {
1357 assert_eq!(CType::Int.to_string(), "int");
1358 assert_eq!(CType::Double.to_string(), "double");
1359 assert_eq!(CType::Float.to_string(), "float");
1360 assert_eq!(CType::CharPtr.to_string(), "char*");
1361 assert_eq!(CType::VoidPtr.to_string(), "void*");
1362 assert_eq!(CType::ConstCharPtr.to_string(), "const char*");
1363 assert_eq!(CType::ConstDoublePtr.to_string(), "const double*");
1364 }
1365
1366 #[test]
1369 fn test_r_array_conversion() {
1370 let arr = array![1.0, 2.0, 3.0, 4.0];
1371 let r_vector = RInterop::array_to_r_vector(&arr);
1372
1373 assert_eq!(r_vector.data, vec![1.0, 2.0, 3.0, 4.0]);
1374 assert_eq!(r_vector.length, 4);
1375 assert_eq!(r_vector.r_type, RType::Numeric);
1376
1377 let converted = RInterop::r_vector_to_array(&r_vector).expect("operation should succeed");
1379 assert_eq!(converted, arr);
1380 }
1381
1382 #[test]
1383 fn test_r_matrix_conversion() {
1384 let arr = array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
1385 let r_matrix = RInterop::array2_to_r_matrix(&arr);
1386
1387 assert_eq!(r_matrix.data, vec![1.0, 3.0, 5.0, 2.0, 4.0, 6.0]); assert_eq!(r_matrix.nrow, 3);
1389 assert_eq!(r_matrix.ncol, 2);
1390 assert!(!r_matrix.byrow);
1391 assert_eq!(r_matrix.r_type, RType::Numeric);
1392
1393 let converted = RInterop::r_matrix_to_array2(&r_matrix).expect("operation should succeed");
1395 assert_eq!(converted, arr);
1396 }
1397
1398 #[test]
1399 fn test_r_vector_code_generation() {
1400 let r_vector = RVector {
1401 data: vec![1.0, 2.0, 3.0],
1402 length: 3,
1403 r_type: RType::Numeric,
1404 };
1405
1406 let code = RInterop::generate_r_vector_code("my_vector", &r_vector);
1407
1408 assert_eq!(code, "my_vector <- c(1, 2, 3)");
1409 }
1410
1411 #[test]
1412 fn test_r_matrix_code_generation() {
1413 let r_matrix = RMatrix {
1414 data: vec![1.0, 2.0, 3.0, 4.0],
1415 nrow: 2,
1416 ncol: 2,
1417 byrow: false,
1418 r_type: RType::Numeric,
1419 };
1420
1421 let code = RInterop::generate_r_matrix_code("my_matrix", &r_matrix);
1422
1423 assert_eq!(
1424 code,
1425 "my_matrix <- matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2, byrow = FALSE)"
1426 );
1427 }
1428
1429 #[test]
1430 fn test_r_dataframe_code_generation() {
1431 let mut columns = HashMap::new();
1432
1433 columns.insert(
1434 "x".to_string(),
1435 RVector {
1436 data: vec![1.0, 2.0, 3.0],
1437 length: 3,
1438 r_type: RType::Numeric,
1439 },
1440 );
1441
1442 columns.insert(
1443 "y".to_string(),
1444 RVector {
1445 data: vec![4.0, 5.0, 6.0],
1446 length: 3,
1447 r_type: RType::Numeric,
1448 },
1449 );
1450
1451 let code = RInterop::generate_r_dataframe_code("my_df", &columns)
1452 .expect("operation should succeed");
1453
1454 let expected1 = "my_df <- data.frame(x = c(1, 2, 3), y = c(4, 5, 6))";
1456 let expected2 = "my_df <- data.frame(y = c(4, 5, 6), x = c(1, 2, 3))";
1457 assert!(code == expected1 || code == expected2);
1458 }
1459
1460 #[test]
1461 fn test_r_package_imports() {
1462 let packages = &["randomForest", "e1071", "ggplot2"];
1463 let imports = RInterop::generate_r_package_imports(packages);
1464
1465 assert!(imports.contains("library(randomForest)"));
1466 assert!(imports.contains("library(e1071)"));
1467 assert!(imports.contains("library(ggplot2)"));
1468 }
1469
1470 #[test]
1471 fn test_r_function_call_generation() {
1472 let params = vec![
1473 RParameter {
1474 name: "ntree".to_string(),
1475 value: RValue::Number(500.0),
1476 },
1477 RParameter {
1478 name: "mtry".to_string(),
1479 value: RValue::Number(3.0),
1480 },
1481 RParameter {
1482 name: "importance".to_string(),
1483 value: RValue::Boolean(true),
1484 },
1485 RParameter {
1486 name: "x".to_string(),
1487 value: RValue::Matrix("X_train".to_string()),
1488 },
1489 ];
1490
1491 let call = RInterop::generate_r_function_call("randomForest", ¶ms);
1492
1493 assert!(call.contains("randomForest("));
1494 assert!(call.contains("ntree = 500"));
1495 assert!(call.contains("mtry = 3"));
1496 assert!(call.contains("importance = TRUE"));
1497 assert!(call.contains("x = X_train"));
1498 }
1499
1500 #[test]
1501 fn test_r_ml_script_generation() {
1502 let training_data = RMatrix {
1503 data: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1504 nrow: 3,
1505 ncol: 2,
1506 byrow: false,
1507 r_type: RType::Numeric,
1508 };
1509
1510 let response_var = RVector {
1511 data: vec![1.0, 0.0, 1.0],
1512 length: 3,
1513 r_type: RType::Numeric,
1514 };
1515
1516 let mut hyperparams = HashMap::new();
1517 hyperparams.insert("ntree".to_string(), 100.0);
1518 hyperparams.insert("mtry".to_string(), 1.0);
1519
1520 let script = RInterop::create_r_ml_script(
1521 "random_forest",
1522 &training_data,
1523 &response_var,
1524 &hyperparams,
1525 )
1526 .expect("operation should succeed");
1527
1528 assert!(script.contains("library(randomForest)"));
1529 assert!(script.contains("X <- matrix"));
1530 assert!(script.contains("y <- c"));
1531 assert!(script.contains("randomForest(x = X_train, y = y_train, ntree = 100, mtry = 1)"));
1532 assert!(script.contains("train_indices"));
1533 assert!(script.contains("predictions <- predict"));
1534 assert!(script.contains("summary(model)"));
1535 }
1536
1537 #[test]
1538 fn test_r_linear_regression_script() {
1539 let training_data = RMatrix {
1540 data: vec![1.0, 2.0, 3.0, 4.0],
1541 nrow: 2,
1542 ncol: 2,
1543 byrow: false,
1544 r_type: RType::Numeric,
1545 };
1546
1547 let response_var = RVector {
1548 data: vec![1.0, 2.0],
1549 length: 2,
1550 r_type: RType::Numeric,
1551 };
1552
1553 let hyperparams = HashMap::new();
1554
1555 let script = RInterop::create_r_ml_script(
1556 "linear_regression",
1557 &training_data,
1558 &response_var,
1559 &hyperparams,
1560 )
1561 .expect("operation should succeed");
1562
1563 assert!(script.contains("# Linear regression using base R"));
1564 assert!(script.contains("model <- lm(y_train ~ X_train)"));
1565 assert!(!script.contains("library(")); }
1567
1568 #[test]
1569 fn test_r_statistical_analysis() {
1570 let data = RMatrix {
1571 data: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
1572 nrow: 3,
1573 ncol: 2,
1574 byrow: false,
1575 r_type: RType::Numeric,
1576 };
1577
1578 let script = RInterop::create_r_statistical_analysis(&data, "descriptive")
1580 .expect("operation should succeed");
1581 assert!(script.contains("summary(data)"));
1582 assert!(script.contains("apply(data, 2, sd)"));
1583 assert!(script.contains("cor(data)"));
1584
1585 let script = RInterop::create_r_statistical_analysis(&data, "pca")
1587 .expect("operation should succeed");
1588 assert!(script.contains("prcomp(data, center = TRUE, scale. = TRUE)"));
1589 assert!(script.contains("plot(pca_result$x[,1:2])"));
1590
1591 let script = RInterop::create_r_statistical_analysis(&data, "clustering")
1593 .expect("operation should succeed");
1594 assert!(script.contains("kmeans(data, centers = 3)"));
1595 assert!(script.contains("plot(data, col = kmeans_result$cluster)"));
1596 }
1597
1598 #[test]
1599 fn test_r_output_conversion() {
1600 let vector_output = "[1] 1.0 2.5 3.8";
1602 let result = RInterop::convert_r_output(vector_output, ROutputType::Vector)
1603 .expect("operation should succeed");
1604 match result {
1605 ROutputValue::Vector(v) => assert_eq!(v, vec![1.0, 2.5, 3.8]),
1606 _ => panic!("Expected vector output"),
1607 }
1608
1609 let scalar_output = "[1] 42.5";
1611 let result = RInterop::convert_r_output(scalar_output, ROutputType::Scalar)
1612 .expect("operation should succeed");
1613 match result {
1614 ROutputValue::Scalar(s) => assert_eq!(s, 42.5),
1615 _ => panic!("Expected scalar output"),
1616 }
1617
1618 let string_output = "This is a test string";
1620 let result = RInterop::convert_r_output(string_output, ROutputType::String)
1621 .expect("operation should succeed");
1622 match result {
1623 ROutputValue::String(s) => assert_eq!(s, "This is a test string"),
1624 _ => panic!("Expected string output"),
1625 }
1626 }
1627
1628 #[test]
1629 fn test_r_matrix_row_major_conversion() {
1630 let r_matrix = RMatrix {
1631 data: vec![1.0, 2.0, 3.0, 4.0],
1632 nrow: 2,
1633 ncol: 2,
1634 byrow: true, r_type: RType::Numeric,
1636 };
1637
1638 let converted = RInterop::r_matrix_to_array2(&r_matrix).expect("operation should succeed");
1639
1640 let expected = array![[1.0, 2.0], [3.0, 4.0]];
1643 assert_eq!(converted, expected);
1644 }
1645
1646 #[test]
1647 fn test_r_type_variants() {
1648 let numeric = RType::Numeric;
1649 let integer = RType::Integer;
1650 let character = RType::Character;
1651 let logical = RType::Logical;
1652 let factor = RType::Factor;
1653
1654 assert_eq!(numeric, RType::Numeric);
1656 assert_eq!(integer, RType::Integer);
1657 assert_eq!(character, RType::Character);
1658 assert_eq!(logical, RType::Logical);
1659 assert_eq!(factor, RType::Factor);
1660 }
1661
1662 #[test]
1663 fn test_r_value_variants() {
1664 let string_val = RValue::String("test".to_string());
1665 let number_val = RValue::Number(42.0);
1666 let bool_val = RValue::Boolean(true);
1667 let vector_val = RValue::Vector("my_vector".to_string());
1668 let matrix_val = RValue::Matrix("my_matrix".to_string());
1669 let df_val = RValue::DataFrame("my_df".to_string());
1670
1671 match string_val {
1673 RValue::String(_) => {}
1674 _ => panic!(),
1675 }
1676 match number_val {
1677 RValue::Number(_) => {}
1678 _ => panic!(),
1679 }
1680 match bool_val {
1681 RValue::Boolean(_) => {}
1682 _ => panic!(),
1683 }
1684 match vector_val {
1685 RValue::Vector(_) => {}
1686 _ => panic!(),
1687 }
1688 match matrix_val {
1689 RValue::Matrix(_) => {}
1690 _ => panic!(),
1691 }
1692 match df_val {
1693 RValue::DataFrame(_) => {}
1694 _ => panic!(),
1695 }
1696 }
1697
1698 #[test]
1699 fn test_r_unsupported_model_type() {
1700 let training_data = RMatrix {
1701 data: vec![1.0, 2.0, 3.0, 4.0],
1702 nrow: 2,
1703 ncol: 2,
1704 byrow: false,
1705 r_type: RType::Numeric,
1706 };
1707
1708 let response_var = RVector {
1709 data: vec![1.0, 2.0],
1710 length: 2,
1711 r_type: RType::Numeric,
1712 };
1713
1714 let hyperparams = HashMap::new();
1715
1716 let result = RInterop::create_r_ml_script(
1717 "unsupported_model",
1718 &training_data,
1719 &response_var,
1720 &hyperparams,
1721 );
1722
1723 assert!(result.is_err());
1724 assert!(result
1725 .unwrap_err()
1726 .to_string()
1727 .contains("Unsupported R model type"));
1728 }
1729
1730 #[test]
1731 fn test_r_unsupported_analysis_type() {
1732 let data = RMatrix {
1733 data: vec![1.0, 2.0, 3.0, 4.0],
1734 nrow: 2,
1735 ncol: 2,
1736 byrow: false,
1737 r_type: RType::Numeric,
1738 };
1739
1740 let result = RInterop::create_r_statistical_analysis(&data, "unsupported_analysis");
1741 assert!(result.is_err());
1742 assert!(result
1743 .unwrap_err()
1744 .to_string()
1745 .contains("Unsupported analysis type"));
1746 }
1747}