use crate::{UtilsError, UtilsResult};
use scirs2_core::ndarray::{Array1, Array2};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::ffi::{CStr, CString};
use std::fmt;
use std::os::raw::c_char;
pub struct PythonInterop;
impl PythonInterop {
pub fn array_to_python_buffer(array: &Array1<f64>) -> PyArrayBuffer {
PyArrayBuffer {
data: array.as_slice().expect("operation should succeed").to_vec(),
shape: vec![array.len()],
dtype: "float64".to_string(),
order: "C".to_string(),
}
}
pub fn array2_to_python_buffer(array: &Array2<f64>) -> PyArrayBuffer {
let (rows, cols) = array.dim();
PyArrayBuffer {
data: array.as_slice().expect("operation should succeed").to_vec(),
shape: vec![rows, cols],
dtype: "float64".to_string(),
order: "C".to_string(),
}
}
pub fn python_buffer_to_array(buffer: &PyArrayBuffer) -> UtilsResult<Array1<f64>> {
if buffer.shape.len() != 1 {
return Err(UtilsError::InvalidParameter(
"Expected 1D array".to_string(),
));
}
if buffer.dtype != "float64" {
return Err(UtilsError::InvalidParameter(format!(
"Unsupported dtype: {}",
buffer.dtype
)));
}
Array1::from_vec(buffer.data.clone())
.into_shape_with_order(buffer.shape[0])
.map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
}
pub fn python_buffer_to_array2(buffer: &PyArrayBuffer) -> UtilsResult<Array2<f64>> {
if buffer.shape.len() != 2 {
return Err(UtilsError::InvalidParameter(
"Expected 2D array".to_string(),
));
}
if buffer.dtype != "float64" {
return Err(UtilsError::InvalidParameter(format!(
"Unsupported dtype: {}",
buffer.dtype
)));
}
Array2::from_shape_vec((buffer.shape[0], buffer.shape[1]), buffer.data.clone())
.map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
}
pub fn generate_numpy_import_code(array_name: &str, buffer: &PyArrayBuffer) -> String {
format!(
r#"
import numpy as np
# Data generated by sklears-utils
{} = np.array({:?}, dtype='{}').reshape({:?})
"#,
array_name, buffer.data, buffer.dtype, buffer.shape
)
}
pub fn generate_function_call_template(
function_name: &str,
parameters: &[PythonParameter],
) -> String {
let param_strings: Vec<String> = parameters
.iter()
.map(|p| match &p.value {
PythonValue::String(s) => format!("{}='{}'", p.name, s),
PythonValue::Number(n) => format!("{}={}", p.name, n),
PythonValue::Boolean(b) => {
format!("{}={}", p.name, if *b { "True" } else { "False" })
}
PythonValue::Array(name) => format!("{}={}", p.name, name),
})
.collect();
format!("{}({})", function_name, param_strings.join(", "))
}
pub fn create_ml_script(
model_type: &str,
training_data: &PyArrayBuffer,
labels: &PyArrayBuffer,
hyperparameters: &HashMap<String, f64>,
) -> UtilsResult<String> {
let mut script = String::new();
script.push_str("import numpy as np\n");
script.push_str("from sklearn.model_selection import train_test_split\n");
match model_type {
"linear_regression" => {
script.push_str("from sklearn.linear_model import LinearRegression\n")
}
"random_forest" => {
script.push_str("from sklearn.ensemble import RandomForestRegressor\n")
}
"svm" => script.push_str("from sklearn.svm import SVC\n"),
_ => {
return Err(UtilsError::InvalidParameter(format!(
"Unsupported model type: {model_type}"
)))
}
}
script.push_str("\n# Data preparation\n");
script.push_str(&Self::generate_numpy_import_code("X", training_data));
script.push_str(&Self::generate_numpy_import_code("y", labels));
script.push_str("\n# Train-test split\n");
script.push_str("X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)\n");
script.push_str("\n# Model creation and training\n");
let model_creation = match model_type {
"linear_regression" => "model = LinearRegression()".to_string(),
"random_forest" => {
let n_estimators = hyperparameters.get("n_estimators").unwrap_or(&100.0);
format!(
"model = RandomForestRegressor(n_estimators={})",
*n_estimators as i32
)
}
"svm" => {
let c = hyperparameters.get("C").unwrap_or(&1.0);
format!("model = SVC(C={c})")
}
_ => {
return Err(UtilsError::InvalidParameter(format!(
"Unsupported model type: {model_type}"
)))
}
};
script.push_str(&model_creation);
script.push_str("\nmodel.fit(X_train, y_train)\n");
script.push_str("\n# Evaluation\n");
script.push_str("score = model.score(X_test, y_test)\n");
script.push_str("print(f'Model score: {score:.4f}')\n");
Ok(script)
}
}
pub struct WasmUtils;
impl WasmUtils {
pub fn generate_wasm_signature(
function_name: &str,
parameters: &[WasmParameter],
return_type: WasmType,
) -> String {
let param_strings: Vec<String> = parameters
.iter()
.map(|p| format!("{}: {}", p.name, p.param_type))
.collect();
format!(
"#[wasm_bindgen]\npub fn {}({}) -> {} {{",
function_name,
param_strings.join(", "),
return_type
)
}
pub fn generate_memory_helpers() -> String {
r#"
use wasm_bindgen::prelude::*;
// Memory management helpers for WASM
#[wasm_bindgen]
pub fn alloc(size: usize) -> *mut u8 {
let mut buf = Vec::with_capacity(size);
let ptr = buf.as_mut_ptr();
std::mem::forget(buf);
ptr
}
#[wasm_bindgen]
pub fn dealloc(ptr: *mut u8, size: usize) {
unsafe {
let _ = Vec::from_raw_parts(ptr, size, size);
}
}
// Array helpers
#[wasm_bindgen]
pub struct Float64Array {
data: Vec<f64>,
}
#[wasm_bindgen]
impl Float64Array {
#[wasm_bindgen(constructor)]
pub fn new(size: usize) -> Float64Array {
Float64Array {
data: vec![0.0; size],
}
}
#[wasm_bindgen(getter)]
pub fn length(&self) -> usize {
self.data.len()
}
#[wasm_bindgen]
pub fn get(&self, index: usize) -> f64 {
self.data.get(index).copied().unwrap_or(0.0)
}
#[wasm_bindgen]
pub fn set(&mut self, index: usize, value: f64) {
if index < self.data.len() {
self.data[index] = value;
}
}
#[wasm_bindgen]
pub fn as_ptr(&self) -> *const f64 {
self.data.as_ptr()
}
}
"#
.to_string()
}
pub fn generate_ml_bindings() -> String {
r#"
use wasm_bindgen::prelude::*;
// Linear algebra operations
#[wasm_bindgen]
pub fn dot_product(a: &[f64], b: &[f64]) -> f64 {
if a.len() != b.len() {
return 0.0;
}
a.iter().zip(b.iter()).map(|(x, y)| x * y).sum()
}
#[wasm_bindgen]
pub fn matrix_multiply(
a: &[f64], a_rows: usize, a_cols: usize,
b: &[f64], b_rows: usize, b_cols: usize,
result: &mut [f64]
) -> bool {
if a_cols != b_rows || result.len() != a_rows * b_cols {
return false;
}
for i in 0..a_rows {
for j in 0..b_cols {
let mut sum = 0.0;
for k in 0..a_cols {
sum += a[i * a_cols + k] * b[k * b_cols + j];
}
result[i * b_cols + j] = sum;
}
}
true
}
// Statistical functions
#[wasm_bindgen]
pub fn mean(data: &[f64]) -> f64 {
if data.is_empty() {
return 0.0;
}
data.iter().sum::<f64>() / data.len() as f64
}
#[wasm_bindgen]
pub fn variance(data: &[f64]) -> f64 {
if data.len() < 2 {
return 0.0;
}
let mean_val = mean(data);
let variance = data.iter()
.map(|x| (x - mean_val).powi(2))
.sum::<f64>() / (data.len() - 1) as f64;
variance
}
#[wasm_bindgen]
pub fn standard_deviation(data: &[f64]) -> f64 {
variance(data).sqrt()
}
"#
.to_string()
}
pub fn create_wasm_build_config() -> WasmBuildConfig {
WasmBuildConfig {
target: "wasm32-unknown-unknown".to_string(),
features: vec![
"wasm-bindgen".to_string(),
"console_error_panic_hook".to_string(),
],
optimization: WasmOptimization::Size,
debug: false,
typescript_bindings: true,
}
}
pub fn generate_package_json(project_name: &str, version: &str) -> String {
format!(
r#"{{
"name": "{project_name}",
"version": "{version}",
"description": "WASM bindings for sklears ML utilities",
"main": "index.js",
"types": "index.d.ts",
"scripts": {{
"build": "wasm-pack build --target web --out-dir pkg",
"build:nodejs": "wasm-pack build --target nodejs --out-dir pkg-node",
"test": "wasm-pack test --headless --chrome"
}},
"devDependencies": {{
"wasm-pack": "^0.12.0"
}},
"files": [
"pkg/"
],
"keywords": [
"wasm",
"machine-learning",
"sklears",
"linear-algebra"
]
}}"#
)
}
}
pub struct RInterop;
impl RInterop {
pub fn array_to_r_vector(array: &Array1<f64>) -> RVector {
RVector {
data: array.as_slice().expect("operation should succeed").to_vec(),
length: array.len(),
r_type: RType::Numeric,
}
}
pub fn array2_to_r_matrix(array: &Array2<f64>) -> RMatrix {
let (rows, cols) = array.dim();
let mut col_major_data = vec![0.0; rows * cols];
for i in 0..rows {
for j in 0..cols {
col_major_data[j * rows + i] = array[[i, j]];
}
}
RMatrix {
data: col_major_data,
nrow: rows,
ncol: cols,
byrow: false, r_type: RType::Numeric,
}
}
pub fn r_vector_to_array(vector: &RVector) -> UtilsResult<Array1<f64>> {
if vector.r_type != RType::Numeric {
return Err(UtilsError::InvalidParameter(format!(
"Expected numeric vector, got {:?}",
vector.r_type
)));
}
Array1::from_vec(vector.data.clone())
.into_shape_with_order(vector.length)
.map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
}
pub fn r_matrix_to_array2(matrix: &RMatrix) -> UtilsResult<Array2<f64>> {
if matrix.r_type != RType::Numeric {
return Err(UtilsError::InvalidParameter(format!(
"Expected numeric matrix, got {:?}",
matrix.r_type
)));
}
if matrix.byrow {
Array2::from_shape_vec((matrix.nrow, matrix.ncol), matrix.data.clone())
.map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
} else {
let mut row_major_data = vec![0.0; matrix.data.len()];
for i in 0..matrix.nrow {
for j in 0..matrix.ncol {
row_major_data[i * matrix.ncol + j] = matrix.data[j * matrix.nrow + i];
}
}
Array2::from_shape_vec((matrix.nrow, matrix.ncol), row_major_data)
.map_err(|e| UtilsError::InvalidParameter(format!("Shape error: {e}")))
}
}
pub fn generate_r_vector_code(vector_name: &str, vector: &RVector) -> String {
format!(
"{} <- c({})",
vector_name,
vector
.data
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ")
)
}
pub fn generate_r_matrix_code(matrix_name: &str, matrix: &RMatrix) -> String {
let data_str = matrix
.data
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
format!(
"{} <- matrix(c({}), nrow = {}, ncol = {}, byrow = {})",
matrix_name,
data_str,
matrix.nrow,
matrix.ncol,
if matrix.byrow { "TRUE" } else { "FALSE" }
)
}
pub fn generate_r_dataframe_code(
df_name: &str,
columns: &HashMap<String, RVector>,
) -> UtilsResult<String> {
let mut column_defs = Vec::new();
let first_length = columns.values().next().map(|v| v.length).unwrap_or(0);
for (name, vector) in columns {
if vector.length != first_length {
return Err(UtilsError::InvalidParameter(
"All columns must have the same length".to_string(),
));
}
let data_str = vector
.data
.iter()
.map(|x| x.to_string())
.collect::<Vec<_>>()
.join(", ");
column_defs.push(format!("{name} = c({data_str})"));
}
Ok(format!(
"{} <- data.frame({})",
df_name,
column_defs.join(", ")
))
}
pub fn generate_r_package_imports(packages: &[&str]) -> String {
let mut imports = String::new();
for package in packages {
imports.push_str(&format!("library({package})\n"));
}
imports
}
pub fn generate_r_function_call(function_name: &str, parameters: &[RParameter]) -> String {
let param_strings: Vec<String> = parameters
.iter()
.map(|p| match &p.value {
RValue::String(s) => format!("{} = \"{}\"", p.name, s),
RValue::Number(n) => format!("{} = {}", p.name, n),
RValue::Boolean(b) => format!("{} = {}", p.name, if *b { "TRUE" } else { "FALSE" }),
RValue::Vector(name) => format!("{} = {}", p.name, name),
RValue::Matrix(name) => format!("{} = {}", p.name, name),
RValue::DataFrame(name) => format!("{} = {}", p.name, name),
})
.collect();
format!("{}({})", function_name, param_strings.join(", "))
}
pub fn create_r_ml_script(
model_type: &str,
training_data: &RMatrix,
response_var: &RVector,
hyperparameters: &HashMap<String, f64>,
) -> UtilsResult<String> {
let mut script = String::new();
match model_type {
"linear_regression" => {
script.push_str("# Linear regression using base R\n");
}
"random_forest" => {
script.push_str(&Self::generate_r_package_imports(&["randomForest"]));
}
"svm" => {
script.push_str(&Self::generate_r_package_imports(&["e1071"]));
}
"glm" => {
script.push_str("# Generalized linear model using base R\n");
}
"tree" => {
script.push_str(&Self::generate_r_package_imports(&["tree"]));
}
_ => {
return Err(UtilsError::InvalidParameter(format!(
"Unsupported R model type: {model_type}"
)))
}
}
script.push_str("\n# Data preparation\n");
script.push_str(&Self::generate_r_matrix_code("X", training_data));
script.push('\n');
script.push_str(&Self::generate_r_vector_code("y", response_var));
script.push('\n');
if matches!(model_type, "glm" | "tree") {
script.push_str("\n# Create data frame\n");
script.push_str("df <- data.frame(y = y, X)\n");
script.push_str("colnames(df) <- c('response', paste0('X', 1:ncol(X)))\n");
}
script.push_str("\n# Train-test split\n");
script.push_str("set.seed(42)\n");
script.push_str("train_indices <- sample(1:nrow(X), size = 0.8 * nrow(X))\n");
script.push_str("X_train <- X[train_indices, ]\n");
script.push_str("X_test <- X[-train_indices, ]\n");
script.push_str("y_train <- y[train_indices]\n");
script.push_str("y_test <- y[-train_indices]\n");
script.push_str("\n# Model creation and training\n");
let model_creation = match model_type {
"linear_regression" => "model <- lm(y_train ~ X_train)".to_string(),
"random_forest" => {
let ntree = hyperparameters.get("ntree").unwrap_or(&500.0);
let mtry = hyperparameters.get("mtry").unwrap_or(&3.0);
format!(
"model <- randomForest(x = X_train, y = y_train, ntree = {}, mtry = {})",
*ntree as i32, *mtry as i32
)
}
"svm" => {
let cost = hyperparameters.get("cost").unwrap_or(&1.0);
let gamma = hyperparameters.get("gamma").unwrap_or(&0.1);
format!("model <- svm(x = X_train, y = y_train, cost = {cost}, gamma = {gamma})")
}
"glm" => {
let family = "gaussian"; format!("df_train <- df[train_indices, ]\nmodel <- glm(response ~ ., data = df_train, family = {family})")
}
"tree" => {
"df_train <- df[train_indices, ]\nmodel <- tree(response ~ ., data = df_train)"
.to_string()
}
_ => {
return Err(UtilsError::InvalidParameter(format!(
"Unsupported R model type: {model_type}"
)))
}
};
script.push_str(&model_creation);
script.push('\n');
script.push_str("\n# Prediction and evaluation\n");
let prediction_code = match model_type {
"linear_regression" => {
"predictions <- predict(model, data.frame(X_test))\nrmse <- sqrt(mean((predictions - y_test)^2))\ncat('RMSE:', rmse, '\\n')"
},
"random_forest" => {
"predictions <- predict(model, X_test)\nrmse <- sqrt(mean((predictions - y_test)^2))\ncat('RMSE:', rmse, '\\n')"
},
"svm" => {
"predictions <- predict(model, X_test)\nrmse <- sqrt(mean((predictions - y_test)^2))\ncat('RMSE:', rmse, '\\n')"
},
"glm" | "tree" => {
"df_test <- df[-train_indices, ]\npredictions <- predict(model, df_test)\nrmse <- sqrt(mean((predictions - y_test)^2))\ncat('RMSE:', rmse, '\\n')"
},
_ => {
return Err(UtilsError::InvalidParameter(format!(
"Unsupported R model type for prediction: {model_type}"
)))
}
};
script.push_str(prediction_code);
script.push('\n');
script.push_str("\n# Model summary\n");
script.push_str("print(summary(model))\n");
Ok(script)
}
pub fn create_r_statistical_analysis(
data: &RMatrix,
analysis_type: &str,
) -> UtilsResult<String> {
let mut script = String::new();
script.push_str("# Statistical analysis generated by sklears-utils\n");
script.push_str(&Self::generate_r_matrix_code("data", data));
script.push('\n');
match analysis_type {
"descriptive" => {
script.push_str("\n# Descriptive statistics\n");
script.push_str("summary(data)\n");
script.push_str("apply(data, 2, sd) # Standard deviations\n");
script.push_str("cor(data) # Correlation matrix\n");
}
"pca" => {
script.push_str("\n# Principal Component Analysis\n");
script.push_str("pca_result <- prcomp(data, center = TRUE, scale. = TRUE)\n");
script.push_str("summary(pca_result)\n");
script
.push_str("plot(pca_result$x[,1:2]) # Scatter plot of first two components\n");
}
"clustering" => {
script.push_str("\n# K-means clustering\n");
script.push_str("set.seed(42)\n");
script.push_str("kmeans_result <- kmeans(data, centers = 3)\n");
script.push_str("print(kmeans_result)\n");
script.push_str("plot(data, col = kmeans_result$cluster)\n");
}
"normality_test" => {
script.push_str("\n# Normality tests\n");
script.push_str("for(i in 1:ncol(data)) {\n");
script.push_str(" cat('Column', i, '\\n')\n");
script.push_str(" print(shapiro.test(data[,i]))\n");
script.push_str("}\n");
}
_ => {
return Err(UtilsError::InvalidParameter(format!(
"Unsupported analysis type: {analysis_type}"
)))
}
}
Ok(script)
}
pub fn convert_r_output(output: &str, expected_type: ROutputType) -> UtilsResult<ROutputValue> {
match expected_type {
ROutputType::Vector => {
let cleaned = output.replace("[1]", "").trim().to_string();
let values: Result<Vec<f64>, _> = cleaned
.split_whitespace()
.map(|s| s.parse::<f64>())
.collect();
match values {
Ok(v) => Ok(ROutputValue::Vector(v)),
Err(_) => Err(UtilsError::InvalidParameter(
"Failed to parse R vector output".to_string(),
)),
}
}
ROutputType::Scalar => {
let cleaned = output.replace("[1]", "");
let cleaned = cleaned.trim();
match cleaned.parse::<f64>() {
Ok(v) => Ok(ROutputValue::Scalar(v)),
Err(_) => Err(UtilsError::InvalidParameter(
"Failed to parse R scalar output".to_string(),
)),
}
}
ROutputType::String => Ok(ROutputValue::String(output.to_string())),
}
}
}
pub struct FFIUtils;
impl FFIUtils {
pub fn create_c_signature(
function_name: &str,
parameters: &[CParameter],
return_type: CType,
) -> String {
let param_strings: Vec<String> = parameters
.iter()
.map(|p| format!("{} {}", p.param_type, p.name))
.collect();
format!(
"extern \"C\" fn {}({}) -> {}",
function_name,
param_strings.join(", "),
return_type
)
}
pub fn generate_c_header(library_name: &str, functions: &[CFunctionSignature]) -> String {
let mut header = String::new();
let library_upper = library_name.to_uppercase();
header.push_str(&format!("#ifndef {library_upper}_H\n"));
header.push_str(&format!("#define {library_upper}_H\n\n"));
header.push_str("#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n");
for func in functions {
header.push_str(&format!("{};\n", func.signature));
}
header.push_str("\n#ifdef __cplusplus\n}\n#endif\n\n");
header.push_str(&format!("#endif // {library_upper}_H\n"));
header
}
pub fn rust_string_to_c(s: &str) -> UtilsResult<*mut c_char> {
let c_string = CString::new(s)
.map_err(|e| UtilsError::InvalidParameter(format!("Invalid C string: {e}")))?;
Ok(c_string.into_raw())
}
pub unsafe fn c_string_to_rust(ptr: *const c_char) -> UtilsResult<String> {
if ptr.is_null() {
return Err(UtilsError::InvalidParameter("Null pointer".to_string()));
}
let c_str = CStr::from_ptr(ptr);
c_str
.to_str()
.map(|s| s.to_string())
.map_err(|e| UtilsError::InvalidParameter(format!("Invalid UTF-8: {e}")))
}
pub fn create_array_transfer(data: &[f64]) -> ArrayTransfer {
ArrayTransfer {
data: data.as_ptr(),
length: data.len(),
capacity: data.len(),
}
}
pub fn generate_ffi_examples() -> String {
r#"
// Example FFI functions for machine learning operations
use std::os::raw::{c_double, c_int};
use std::slice;
#[repr(C)]
pub struct ArrayTransfer {
pub data: *const f64,
pub length: usize,
pub capacity: usize,
}
// Linear regression example
#[no_mangle]
pub extern "C" fn linear_regression_fit(
x_data: *const c_double,
y_data: *const c_double,
n_samples: c_int,
coefficients: *mut c_double,
intercept: *mut c_double,
) -> c_int {
if x_data.is_null() || y_data.is_null() || coefficients.is_null() || intercept.is_null() {
return -1; // Error: null pointer
}
unsafe {
let x_slice = slice::from_raw_parts(x_data, n_samples as usize);
let y_slice = slice::from_raw_parts(y_data, n_samples as usize);
// Simple linear regression calculation
let n = n_samples as f64;
let sum_x: f64 = x_slice.iter().sum();
let sum_y: f64 = y_slice.iter().sum();
let sum_xy: f64 = x_slice.iter().zip(y_slice.iter()).map(|(x, y)| x * y).sum();
let sum_xx: f64 = x_slice.iter().map(|x| x * x).sum();
let slope = (n * sum_xy - sum_x * sum_y) / (n * sum_xx - sum_x * sum_x);
let intercept_val = (sum_y - slope * sum_x) / n;
*coefficients = slope;
*intercept = intercept_val;
0 // Success
}
}
// Array operations example
#[no_mangle]
pub extern "C" fn array_mean(
data: *const c_double,
length: c_int,
result: *mut c_double,
) -> c_int {
if data.is_null() || result.is_null() || length <= 0 {
return -1;
}
unsafe {
let slice = slice::from_raw_parts(data, length as usize);
let mean = slice.iter().sum::<f64>() / length as f64;
*result = mean;
0
}
}
"#
.to_string()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PyArrayBuffer {
pub data: Vec<f64>,
pub shape: Vec<usize>,
pub dtype: String,
pub order: String,
}
#[derive(Debug, Clone)]
pub struct PythonParameter {
pub name: String,
pub value: PythonValue,
}
#[derive(Debug, Clone)]
pub enum PythonValue {
String(String),
Number(f64),
Boolean(bool),
Array(String), }
#[derive(Debug, Clone)]
pub struct WasmParameter {
pub name: String,
pub param_type: WasmType,
}
#[derive(Debug, Clone)]
pub enum WasmType {
F64,
F32,
I32,
U32,
Bool,
String,
}
impl fmt::Display for WasmType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WasmType::F64 => write!(f, "f64"),
WasmType::F32 => write!(f, "f32"),
WasmType::I32 => write!(f, "i32"),
WasmType::U32 => write!(f, "u32"),
WasmType::Bool => write!(f, "bool"),
WasmType::String => write!(f, "String"),
}
}
}
#[derive(Debug, Clone)]
pub struct WasmBuildConfig {
pub target: String,
pub features: Vec<String>,
pub optimization: WasmOptimization,
pub debug: bool,
pub typescript_bindings: bool,
}
#[derive(Debug, Clone)]
pub enum WasmOptimization {
None,
Size,
Speed,
}
#[derive(Debug, Clone)]
pub struct CParameter {
pub name: String,
pub param_type: CType,
}
#[derive(Debug, Clone)]
pub enum CType {
Int,
Double,
Float,
CharPtr,
VoidPtr,
ConstCharPtr,
ConstDoublePtr,
}
impl fmt::Display for CType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
CType::Int => write!(f, "int"),
CType::Double => write!(f, "double"),
CType::Float => write!(f, "float"),
CType::CharPtr => write!(f, "char*"),
CType::VoidPtr => write!(f, "void*"),
CType::ConstCharPtr => write!(f, "const char*"),
CType::ConstDoublePtr => write!(f, "const double*"),
}
}
}
#[derive(Debug, Clone)]
pub struct CFunctionSignature {
pub name: String,
pub signature: String,
pub description: String,
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct ArrayTransfer {
pub data: *const f64,
pub length: usize,
pub capacity: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RVector {
pub data: Vec<f64>,
pub length: usize,
pub r_type: RType,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RMatrix {
pub data: Vec<f64>,
pub nrow: usize,
pub ncol: usize,
pub byrow: bool,
pub r_type: RType,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum RType {
Numeric,
Integer,
Character,
Logical,
Factor,
}
#[derive(Debug, Clone)]
pub struct RParameter {
pub name: String,
pub value: RValue,
}
#[derive(Debug, Clone)]
pub enum RValue {
String(String),
Number(f64),
Boolean(bool),
Vector(String), Matrix(String), DataFrame(String), }
#[derive(Debug, Clone)]
pub enum ROutputType {
Vector,
Scalar,
String,
}
#[derive(Debug, Clone)]
pub enum ROutputValue {
Vector(Vec<f64>),
Scalar(f64),
String(String),
}
#[allow(non_snake_case)]
#[cfg(test)]
mod tests {
use super::*;
use scirs2_core::ndarray::array;
#[test]
fn test_python_array_conversion() {
let arr = array![1.0, 2.0, 3.0, 4.0];
let buffer = PythonInterop::array_to_python_buffer(&arr);
assert_eq!(buffer.data, vec![1.0, 2.0, 3.0, 4.0]);
assert_eq!(buffer.shape, vec![4]);
assert_eq!(buffer.dtype, "float64");
assert_eq!(buffer.order, "C");
let converted =
PythonInterop::python_buffer_to_array(&buffer).expect("operation should succeed");
assert_eq!(converted, arr);
}
#[test]
fn test_python_array2_conversion() {
let arr = array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
let buffer = PythonInterop::array2_to_python_buffer(&arr);
assert_eq!(buffer.data, vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
assert_eq!(buffer.shape, vec![3, 2]);
let converted =
PythonInterop::python_buffer_to_array2(&buffer).expect("operation should succeed");
assert_eq!(converted, arr);
}
#[test]
fn test_numpy_code_generation() {
let buffer = PyArrayBuffer {
data: vec![1.0, 2.0, 3.0],
shape: vec![3],
dtype: "float64".to_string(),
order: "C".to_string(),
};
let code = PythonInterop::generate_numpy_import_code("my_array", &buffer);
assert!(code.contains("import numpy as np"));
assert!(code.contains("my_array = np.array"));
assert!(code.contains("[1.0, 2.0, 3.0]"));
assert!(code.contains("dtype='float64'"));
assert!(code.contains("reshape([3])"));
}
#[test]
fn test_python_function_call_template() {
let params = vec![
PythonParameter {
name: "n_estimators".to_string(),
value: PythonValue::Number(100.0),
},
PythonParameter {
name: "random_state".to_string(),
value: PythonValue::Number(42.0),
},
PythonParameter {
name: "verbose".to_string(),
value: PythonValue::Boolean(true),
},
];
let call = PythonInterop::generate_function_call_template("RandomForestRegressor", ¶ms);
assert!(call.contains("RandomForestRegressor("));
assert!(call.contains("n_estimators=100"));
assert!(call.contains("random_state=42"));
assert!(call.contains("verbose=True"));
}
#[test]
fn test_ml_script_generation() {
let training_data = PyArrayBuffer {
data: vec![1.0, 2.0, 3.0, 4.0],
shape: vec![2, 2],
dtype: "float64".to_string(),
order: "C".to_string(),
};
let labels = PyArrayBuffer {
data: vec![0.0, 1.0],
shape: vec![2],
dtype: "float64".to_string(),
order: "C".to_string(),
};
let mut hyperparams = HashMap::new();
hyperparams.insert("n_estimators".to_string(), 50.0);
let script =
PythonInterop::create_ml_script("random_forest", &training_data, &labels, &hyperparams)
.expect("operation should succeed");
assert!(script.contains("import numpy as np"));
assert!(script.contains("from sklearn.ensemble import RandomForestRegressor"));
assert!(script.contains("train_test_split"));
assert!(script.contains("RandomForestRegressor(n_estimators=50)"));
assert!(script.contains("model.fit(X_train, y_train)"));
assert!(script.contains("model.score(X_test, y_test)"));
}
#[test]
fn test_wasm_signature_generation() {
let params = vec![
WasmParameter {
name: "a".to_string(),
param_type: WasmType::F64,
},
WasmParameter {
name: "b".to_string(),
param_type: WasmType::F64,
},
];
let signature = WasmUtils::generate_wasm_signature("add", ¶ms, WasmType::F64);
assert!(signature.contains("#[wasm_bindgen]"));
assert!(signature.contains("pub fn add(a: f64, b: f64) -> f64"));
}
#[test]
fn test_wasm_memory_helpers() {
let helpers = WasmUtils::generate_memory_helpers();
assert!(helpers.contains("pub fn alloc(size: usize)"));
assert!(helpers.contains("pub fn dealloc(ptr: *mut u8, size: usize)"));
assert!(helpers.contains("pub struct Float64Array"));
assert!(helpers.contains("#[wasm_bindgen]"));
}
#[test]
fn test_wasm_ml_bindings() {
let bindings = WasmUtils::generate_ml_bindings();
assert!(bindings.contains("pub fn dot_product"));
assert!(bindings.contains("pub fn matrix_multiply"));
assert!(bindings.contains("pub fn mean"));
assert!(bindings.contains("pub fn variance"));
assert!(bindings.contains("pub fn standard_deviation"));
}
#[test]
fn test_wasm_build_config() {
let config = WasmUtils::create_wasm_build_config();
assert_eq!(config.target, "wasm32-unknown-unknown");
assert!(config.features.contains(&"wasm-bindgen".to_string()));
assert!(config.typescript_bindings);
assert!(!config.debug);
}
#[test]
fn test_package_json_generation() {
let json = WasmUtils::generate_package_json("my-ml-wasm", "0.1.0");
assert!(json.contains("\"name\": \"my-ml-wasm\""));
assert!(json.contains("\"version\": \"0.1.0\""));
assert!(json.contains("wasm-pack"));
assert!(json.contains("\"machine-learning\""));
}
#[test]
fn test_c_signature_creation() {
let params = vec![
CParameter {
name: "data".to_string(),
param_type: CType::ConstDoublePtr,
},
CParameter {
name: "length".to_string(),
param_type: CType::Int,
},
];
let signature = FFIUtils::create_c_signature("compute_mean", ¶ms, CType::Double);
assert!(signature.contains("extern \"C\" fn compute_mean"));
assert!(signature.contains("const double* data"));
assert!(signature.contains("int length"));
assert!(signature.contains("-> double"));
}
#[test]
fn test_c_header_generation() {
let functions = vec![
CFunctionSignature {
name: "add".to_string(),
signature: "double add(double a, double b)".to_string(),
description: "Add two numbers".to_string(),
},
CFunctionSignature {
name: "multiply".to_string(),
signature: "double multiply(double a, double b)".to_string(),
description: "Multiply two numbers".to_string(),
},
];
let header = FFIUtils::generate_c_header("mylib", &functions);
assert!(header.contains("#ifndef MYLIB_H"));
assert!(header.contains("#define MYLIB_H"));
assert!(header.contains("extern \"C\" {"));
assert!(header.contains("double add(double a, double b);"));
assert!(header.contains("double multiply(double a, double b);"));
assert!(header.contains("#endif // MYLIB_H"));
}
#[test]
fn test_rust_to_c_string() {
let rust_str = "Hello, World!";
let c_ptr = FFIUtils::rust_string_to_c(rust_str).expect("operation should succeed");
let converted =
unsafe { FFIUtils::c_string_to_rust(c_ptr).expect("operation should succeed") };
assert_eq!(converted, rust_str);
unsafe {
let _ = CString::from_raw(c_ptr);
}
}
#[test]
fn test_array_transfer_creation() {
let data = vec![1.0, 2.0, 3.0, 4.0];
let transfer = FFIUtils::create_array_transfer(&data);
assert_eq!(transfer.length, 4);
assert_eq!(transfer.capacity, 4);
assert!(!transfer.data.is_null());
}
#[test]
fn test_ffi_examples_generation() {
let examples = FFIUtils::generate_ffi_examples();
assert!(examples.contains("linear_regression_fit"));
assert!(examples.contains("array_mean"));
assert!(examples.contains("#[no_mangle]"));
assert!(examples.contains("extern \"C\""));
assert!(examples.contains("ArrayTransfer"));
}
#[test]
fn test_python_value_variants() {
let string_val = PythonValue::String("test".to_string());
let number_val = PythonValue::Number(42.0);
let bool_val = PythonValue::Boolean(true);
let array_val = PythonValue::Array("my_array".to_string());
match string_val {
PythonValue::String(_) => {}
_ => panic!(),
}
match number_val {
PythonValue::Number(_) => {}
_ => panic!(),
}
match bool_val {
PythonValue::Boolean(_) => {}
_ => panic!(),
}
match array_val {
PythonValue::Array(_) => {}
_ => panic!(),
}
}
#[test]
fn test_wasm_type_display() {
assert_eq!(WasmType::F64.to_string(), "f64");
assert_eq!(WasmType::F32.to_string(), "f32");
assert_eq!(WasmType::I32.to_string(), "i32");
assert_eq!(WasmType::U32.to_string(), "u32");
assert_eq!(WasmType::Bool.to_string(), "bool");
assert_eq!(WasmType::String.to_string(), "String");
}
#[test]
fn test_c_type_display() {
assert_eq!(CType::Int.to_string(), "int");
assert_eq!(CType::Double.to_string(), "double");
assert_eq!(CType::Float.to_string(), "float");
assert_eq!(CType::CharPtr.to_string(), "char*");
assert_eq!(CType::VoidPtr.to_string(), "void*");
assert_eq!(CType::ConstCharPtr.to_string(), "const char*");
assert_eq!(CType::ConstDoublePtr.to_string(), "const double*");
}
#[test]
fn test_r_array_conversion() {
let arr = array![1.0, 2.0, 3.0, 4.0];
let r_vector = RInterop::array_to_r_vector(&arr);
assert_eq!(r_vector.data, vec![1.0, 2.0, 3.0, 4.0]);
assert_eq!(r_vector.length, 4);
assert_eq!(r_vector.r_type, RType::Numeric);
let converted = RInterop::r_vector_to_array(&r_vector).expect("operation should succeed");
assert_eq!(converted, arr);
}
#[test]
fn test_r_matrix_conversion() {
let arr = array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
let r_matrix = RInterop::array2_to_r_matrix(&arr);
assert_eq!(r_matrix.data, vec![1.0, 3.0, 5.0, 2.0, 4.0, 6.0]); assert_eq!(r_matrix.nrow, 3);
assert_eq!(r_matrix.ncol, 2);
assert!(!r_matrix.byrow);
assert_eq!(r_matrix.r_type, RType::Numeric);
let converted = RInterop::r_matrix_to_array2(&r_matrix).expect("operation should succeed");
assert_eq!(converted, arr);
}
#[test]
fn test_r_vector_code_generation() {
let r_vector = RVector {
data: vec![1.0, 2.0, 3.0],
length: 3,
r_type: RType::Numeric,
};
let code = RInterop::generate_r_vector_code("my_vector", &r_vector);
assert_eq!(code, "my_vector <- c(1, 2, 3)");
}
#[test]
fn test_r_matrix_code_generation() {
let r_matrix = RMatrix {
data: vec![1.0, 2.0, 3.0, 4.0],
nrow: 2,
ncol: 2,
byrow: false,
r_type: RType::Numeric,
};
let code = RInterop::generate_r_matrix_code("my_matrix", &r_matrix);
assert_eq!(
code,
"my_matrix <- matrix(c(1, 2, 3, 4), nrow = 2, ncol = 2, byrow = FALSE)"
);
}
#[test]
fn test_r_dataframe_code_generation() {
let mut columns = HashMap::new();
columns.insert(
"x".to_string(),
RVector {
data: vec![1.0, 2.0, 3.0],
length: 3,
r_type: RType::Numeric,
},
);
columns.insert(
"y".to_string(),
RVector {
data: vec![4.0, 5.0, 6.0],
length: 3,
r_type: RType::Numeric,
},
);
let code = RInterop::generate_r_dataframe_code("my_df", &columns)
.expect("operation should succeed");
let expected1 = "my_df <- data.frame(x = c(1, 2, 3), y = c(4, 5, 6))";
let expected2 = "my_df <- data.frame(y = c(4, 5, 6), x = c(1, 2, 3))";
assert!(code == expected1 || code == expected2);
}
#[test]
fn test_r_package_imports() {
let packages = &["randomForest", "e1071", "ggplot2"];
let imports = RInterop::generate_r_package_imports(packages);
assert!(imports.contains("library(randomForest)"));
assert!(imports.contains("library(e1071)"));
assert!(imports.contains("library(ggplot2)"));
}
#[test]
fn test_r_function_call_generation() {
let params = vec![
RParameter {
name: "ntree".to_string(),
value: RValue::Number(500.0),
},
RParameter {
name: "mtry".to_string(),
value: RValue::Number(3.0),
},
RParameter {
name: "importance".to_string(),
value: RValue::Boolean(true),
},
RParameter {
name: "x".to_string(),
value: RValue::Matrix("X_train".to_string()),
},
];
let call = RInterop::generate_r_function_call("randomForest", ¶ms);
assert!(call.contains("randomForest("));
assert!(call.contains("ntree = 500"));
assert!(call.contains("mtry = 3"));
assert!(call.contains("importance = TRUE"));
assert!(call.contains("x = X_train"));
}
#[test]
fn test_r_ml_script_generation() {
let training_data = RMatrix {
data: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
nrow: 3,
ncol: 2,
byrow: false,
r_type: RType::Numeric,
};
let response_var = RVector {
data: vec![1.0, 0.0, 1.0],
length: 3,
r_type: RType::Numeric,
};
let mut hyperparams = HashMap::new();
hyperparams.insert("ntree".to_string(), 100.0);
hyperparams.insert("mtry".to_string(), 1.0);
let script = RInterop::create_r_ml_script(
"random_forest",
&training_data,
&response_var,
&hyperparams,
)
.expect("operation should succeed");
assert!(script.contains("library(randomForest)"));
assert!(script.contains("X <- matrix"));
assert!(script.contains("y <- c"));
assert!(script.contains("randomForest(x = X_train, y = y_train, ntree = 100, mtry = 1)"));
assert!(script.contains("train_indices"));
assert!(script.contains("predictions <- predict"));
assert!(script.contains("summary(model)"));
}
#[test]
fn test_r_linear_regression_script() {
let training_data = RMatrix {
data: vec![1.0, 2.0, 3.0, 4.0],
nrow: 2,
ncol: 2,
byrow: false,
r_type: RType::Numeric,
};
let response_var = RVector {
data: vec![1.0, 2.0],
length: 2,
r_type: RType::Numeric,
};
let hyperparams = HashMap::new();
let script = RInterop::create_r_ml_script(
"linear_regression",
&training_data,
&response_var,
&hyperparams,
)
.expect("operation should succeed");
assert!(script.contains("# Linear regression using base R"));
assert!(script.contains("model <- lm(y_train ~ X_train)"));
assert!(!script.contains("library(")); }
#[test]
fn test_r_statistical_analysis() {
let data = RMatrix {
data: vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0],
nrow: 3,
ncol: 2,
byrow: false,
r_type: RType::Numeric,
};
let script = RInterop::create_r_statistical_analysis(&data, "descriptive")
.expect("operation should succeed");
assert!(script.contains("summary(data)"));
assert!(script.contains("apply(data, 2, sd)"));
assert!(script.contains("cor(data)"));
let script = RInterop::create_r_statistical_analysis(&data, "pca")
.expect("operation should succeed");
assert!(script.contains("prcomp(data, center = TRUE, scale. = TRUE)"));
assert!(script.contains("plot(pca_result$x[,1:2])"));
let script = RInterop::create_r_statistical_analysis(&data, "clustering")
.expect("operation should succeed");
assert!(script.contains("kmeans(data, centers = 3)"));
assert!(script.contains("plot(data, col = kmeans_result$cluster)"));
}
#[test]
fn test_r_output_conversion() {
let vector_output = "[1] 1.0 2.5 3.8";
let result = RInterop::convert_r_output(vector_output, ROutputType::Vector)
.expect("operation should succeed");
match result {
ROutputValue::Vector(v) => assert_eq!(v, vec![1.0, 2.5, 3.8]),
_ => panic!("Expected vector output"),
}
let scalar_output = "[1] 42.5";
let result = RInterop::convert_r_output(scalar_output, ROutputType::Scalar)
.expect("operation should succeed");
match result {
ROutputValue::Scalar(s) => assert_eq!(s, 42.5),
_ => panic!("Expected scalar output"),
}
let string_output = "This is a test string";
let result = RInterop::convert_r_output(string_output, ROutputType::String)
.expect("operation should succeed");
match result {
ROutputValue::String(s) => assert_eq!(s, "This is a test string"),
_ => panic!("Expected string output"),
}
}
#[test]
fn test_r_matrix_row_major_conversion() {
let r_matrix = RMatrix {
data: vec![1.0, 2.0, 3.0, 4.0],
nrow: 2,
ncol: 2,
byrow: true, r_type: RType::Numeric,
};
let converted = RInterop::r_matrix_to_array2(&r_matrix).expect("operation should succeed");
let expected = array![[1.0, 2.0], [3.0, 4.0]];
assert_eq!(converted, expected);
}
#[test]
fn test_r_type_variants() {
let numeric = RType::Numeric;
let integer = RType::Integer;
let character = RType::Character;
let logical = RType::Logical;
let factor = RType::Factor;
assert_eq!(numeric, RType::Numeric);
assert_eq!(integer, RType::Integer);
assert_eq!(character, RType::Character);
assert_eq!(logical, RType::Logical);
assert_eq!(factor, RType::Factor);
}
#[test]
fn test_r_value_variants() {
let string_val = RValue::String("test".to_string());
let number_val = RValue::Number(42.0);
let bool_val = RValue::Boolean(true);
let vector_val = RValue::Vector("my_vector".to_string());
let matrix_val = RValue::Matrix("my_matrix".to_string());
let df_val = RValue::DataFrame("my_df".to_string());
match string_val {
RValue::String(_) => {}
_ => panic!(),
}
match number_val {
RValue::Number(_) => {}
_ => panic!(),
}
match bool_val {
RValue::Boolean(_) => {}
_ => panic!(),
}
match vector_val {
RValue::Vector(_) => {}
_ => panic!(),
}
match matrix_val {
RValue::Matrix(_) => {}
_ => panic!(),
}
match df_val {
RValue::DataFrame(_) => {}
_ => panic!(),
}
}
#[test]
fn test_r_unsupported_model_type() {
let training_data = RMatrix {
data: vec![1.0, 2.0, 3.0, 4.0],
nrow: 2,
ncol: 2,
byrow: false,
r_type: RType::Numeric,
};
let response_var = RVector {
data: vec![1.0, 2.0],
length: 2,
r_type: RType::Numeric,
};
let hyperparams = HashMap::new();
let result = RInterop::create_r_ml_script(
"unsupported_model",
&training_data,
&response_var,
&hyperparams,
);
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unsupported R model type"));
}
#[test]
fn test_r_unsupported_analysis_type() {
let data = RMatrix {
data: vec![1.0, 2.0, 3.0, 4.0],
nrow: 2,
ncol: 2,
byrow: false,
r_type: RType::Numeric,
};
let result = RInterop::create_r_statistical_analysis(&data, "unsupported_analysis");
assert!(result.is_err());
assert!(result
.unwrap_err()
.to_string()
.contains("Unsupported analysis type"));
}
}