#![allow(dead_code)]
use crate::{bessel, erf, gamma, statistical};
use std::collections::HashMap;
pub struct MigrationGuide {
mappings: HashMap<String, FunctionMapping>,
reverse_mappings: HashMap<String, String>, }
#[derive(Clone)]
pub struct FunctionMapping {
pub scipy_name: String,
pub scirs2_name: String,
pub module_path: String,
pub signature_changes: Vec<String>,
pub notes: Vec<String>,
}
impl Default for MigrationGuide {
fn default() -> Self {
Self::new()
}
}
impl MigrationGuide {
pub fn new() -> Self {
let mut mappings = HashMap::new();
let mut reverse_mappings = HashMap::new();
mappings.insert(
"scipy.special.gamma".to_string(),
FunctionMapping {
scipy_name: "gamma".to_string(),
scirs2_name: "gamma".to_string(),
module_path: "scirs2_special::gamma".to_string(),
signature_changes: vec![],
notes: vec![
"Direct replacement, same signature".to_string(),
"Returns NaN for negative integers (poles)".to_string(),
],
},
);
mappings.insert(
"scipy.special.gammaln".to_string(),
FunctionMapping {
scipy_name: "gammaln".to_string(),
scirs2_name: "gammaln".to_string(),
module_path: "scirs2_special::gamma".to_string(),
signature_changes: vec![],
notes: vec!["Direct replacement".to_string()],
},
);
mappings.insert(
"scipy.special.beta".to_string(),
FunctionMapping {
scipy_name: "beta".to_string(),
scirs2_name: "beta".to_string(),
module_path: "scirs2_special::gamma".to_string(),
signature_changes: vec![],
notes: vec!["Direct replacement".to_string()],
},
);
mappings.insert(
"scipy.special.j0".to_string(),
FunctionMapping {
scipy_name: "j0".to_string(),
scirs2_name: "j0".to_string(),
module_path: "scirs2_special::bessel".to_string(),
signature_changes: vec![],
notes: vec!["Direct replacement".to_string()],
},
);
mappings.insert(
"scipy.special.jv".to_string(),
FunctionMapping {
scipy_name: "jv".to_string(),
scirs2_name: "jv".to_string(),
module_path: "scirs2_special::bessel".to_string(),
signature_changes: vec!["Order parameter v must implement Float trait".to_string()],
notes: vec!["Supports both integer and fractional orders".to_string()],
},
);
mappings.insert(
"scipy.special.erf".to_string(),
FunctionMapping {
scipy_name: "erf".to_string(),
scirs2_name: "erf".to_string(),
module_path: "scirs2_special::erf".to_string(),
signature_changes: vec![],
notes: vec![
"Direct replacement".to_string(),
"Complex version available as erf_complex".to_string(),
],
},
);
mappings.insert(
"scipy.special.erfc".to_string(),
FunctionMapping {
scipy_name: "erfc".to_string(),
scirs2_name: "erfc".to_string(),
module_path: "scirs2_special::erf".to_string(),
signature_changes: vec![],
notes: vec!["Direct replacement".to_string()],
},
);
mappings.insert(
"scipy.special.expit".to_string(),
FunctionMapping {
scipy_name: "expit".to_string(),
scirs2_name: "logistic".to_string(),
module_path: "scirs2_special::statistical".to_string(),
signature_changes: vec![],
notes: vec![
"Name change: expit -> logistic".to_string(),
"Same mathematical function: 1/(1+exp(-x))".to_string(),
],
},
);
mappings.insert(
"scipy.special.softmax".to_string(),
FunctionMapping {
scipy_name: "softmax".to_string(),
scirs2_name: "softmax".to_string(),
module_path: "scirs2_special::statistical".to_string(),
signature_changes: vec![
"Takes &[f64] slice instead of numpy array".to_string(),
"Returns Vec<f64> instead of numpy array".to_string(),
],
notes: vec!["Use ndarray for array operations".to_string()],
},
);
mappings.insert(
"scipy.special.legendre".to_string(),
FunctionMapping {
scipy_name: "legendre".to_string(),
scirs2_name: "legendre_p".to_string(),
module_path: "scirs2_special::orthogonal".to_string(),
signature_changes: vec![
"Returns polynomial value, not polynomial object".to_string(),
"Use legendre_p(n, x) for evaluation".to_string(),
],
notes: vec![
"SciPy returns polynomial object, SciRS2 evaluates directly".to_string()
],
},
);
mappings.insert(
"scipy.special.airy".to_string(),
FunctionMapping {
scipy_name: "airy".to_string(),
scirs2_name: "ai, bi, aip, bip".to_string(),
module_path: "scirs2_special::airy".to_string(),
signature_changes: vec![
"SciPy returns tuple (Ai, Aip, Bi, Bip)".to_string(),
"SciRS2 has separate functions for each".to_string(),
],
notes: vec!["Use individual functions: ai(x), bi(x), aip(x), bip(x)".to_string()],
},
);
mappings.insert(
"scipy.special.ellipk".to_string(),
FunctionMapping {
scipy_name: "ellipk".to_string(),
scirs2_name: "elliptic_k".to_string(),
module_path: "scirs2_special::elliptic".to_string(),
signature_changes: vec![],
notes: vec!["Name change: ellipk -> elliptic_k".to_string()],
},
);
mappings.insert(
"scipy.special.hyp1f1".to_string(),
FunctionMapping {
scipy_name: "hyp1f1".to_string(),
scirs2_name: "hyp1f1".to_string(),
module_path: "scirs2_special::hypergeometric".to_string(),
signature_changes: vec![],
notes: vec!["Direct replacement".to_string()],
},
);
mappings.insert(
"scipy.special.sph_harm".to_string(),
FunctionMapping {
scipy_name: "sph_harm".to_string(),
scirs2_name: "sph_harm_complex".to_string(),
module_path: "scirs2_special::spherical_harmonics".to_string(),
signature_changes: vec![
"Parameter order: (m, n, theta, phi) in SciPy".to_string(),
"Parameter order: (n, m, theta, phi) in SciRS2".to_string(),
],
notes: vec!["Watch out for parameter order change".to_string()],
},
);
for (scipy_name, mapping) in &mappings {
reverse_mappings.insert(mapping.scirs2_name.clone(), scipy_name.clone());
}
MigrationGuide {
mappings,
reverse_mappings,
}
}
pub fn get_mapping(&self, scipyfunc: &str) -> Option<&FunctionMapping> {
self.mappings.get(scipyfunc)
}
pub fn get_reverse_mapping(&self, scirs2func: &str) -> Option<&String> {
self.reverse_mappings.get(scirs2func)
}
pub fn list_all_mappings(&self) -> Vec<(&String, &FunctionMapping)> {
self.mappings.iter().collect()
}
pub fn generate_migration_report(&self, scipyfunctions: &[&str]) -> String {
let mut report = String::from("SciPy to SciRS2 Migration Report\n");
report.push_str("================================\n\n");
for &func in scipyfunctions {
if let Some(mapping) = self.get_mapping(func) {
report.push_str(&format!("## {func}\n"));
report.push_str(&format!("SciRS2 equivalent: `{}`\n", mapping.module_path));
if !mapping.signature_changes.is_empty() {
report.push_str("\nSignature changes:\n");
for change in &mapping.signature_changes {
report.push_str(&format!("- {change}\n"));
}
}
if !mapping.notes.is_empty() {
report.push_str("\nNotes:\n");
for note in &mapping.notes {
report.push_str(&format!("- {note}\n"));
}
}
report.push('\n');
} else {
report.push_str(&format!("## {func}\n"));
report.push_str(
"⚠️ No direct mapping found. May require custom implementation.\n\n",
);
}
}
report
}
}
pub mod compat {
use super::*;
use scirs2_core::ndarray::{Array1, ArrayView1};
pub fn gamma_array(x: &ArrayView1<f64>) -> Array1<f64> {
x.mapv(gamma::gamma)
}
pub fn erf_array(x: &ArrayView1<f64>) -> Array1<f64> {
x.mapv(erf::erf)
}
pub fn j0_array(x: &ArrayView1<f64>) -> Array1<f64> {
x.mapv(bessel::j0)
}
pub fn softmax_axis(x: &ArrayView1<f64>, _axis: Option<usize>) -> Vec<f64> {
match statistical::softmax(x.view()) {
Ok(result) => result.to_vec(),
Err(_) => vec![],
}
}
}
pub mod codegen {
use super::*;
#[cfg(feature = "python-interop")]
use regex::Regex;
pub fn generate_rust_equivalent(_scipycode: &str) -> Result<String, String> {
#[cfg(feature = "python-interop")]
{
generate_rust_equivalent_regex(_scipycode)
}
#[cfg(not(feature = "python-interop"))]
{
generate_rust_equivalent_simple(_scipycode)
}
}
#[cfg(feature = "python-interop")]
fn generate_rust_equivalent_regex(_scipycode: &str) -> Result<String, String> {
let guide = MigrationGuide::new();
let mut rust_code = String::new();
let mut imports = std::collections::HashSet::new();
let mut code_lines = Vec::new();
let patterns = vec![
(r"scipy\.special\.(\w+)\s*\(", "scipy.special."),
(r"from scipy\.special import (\w+(?:,\s*\w+)*)", ""),
(r"special\.(\w+)\s*\(", "scipy.special."),
];
let mut found_functions = Vec::new();
for (pattern, prefix) in patterns {
let re = Regex::new(pattern).map_err(|e| e.to_string())?;
for cap in re.captures_iter(_scipycode) {
if let Some(func_match) = cap.get(1) {
let funcs = func_match.as_str();
for func in funcs.split(',') {
let func = func.trim();
let full_name = format!("{prefix}{func}");
if let Some(mapping) = guide.get_mapping(&full_name) {
found_functions.push((func.to_string(), mapping.clone()));
imports.insert(mapping.module_path.clone());
}
}
}
}
}
for import in &imports {
rust_code.push_str(&format!("use {import};\n"));
}
if !imports.is_empty() {
rust_code.push('\n');
}
let mut transformed = _scipycode.to_string();
for (scipyfunc, mapping) in &found_functions {
code_lines.push(format!("// {} -> {}", scipyfunc, mapping.scirs2_name));
transformed =
transformed.replace(&format!("scipy.special.{scipyfunc}"), &mapping.scirs2_name);
transformed =
transformed.replace(&format!("special.{scipyfunc}"), &mapping.scirs2_name);
}
if !found_functions.is_empty() {
rust_code.push_str("// Transformed _code:\n");
rust_code.push_str(&format!("// {transformed}\n"));
rust_code.push_str("\n// Notes:\n");
for (_, mapping) in &found_functions {
for note in &mapping.notes {
rust_code.push_str(&format!("// - {note}\n"));
}
}
}
if rust_code.is_empty() {
Err("No recognized SciPy functions found".to_string())
} else {
Ok(rust_code)
}
}
fn generate_rust_equivalent_simple(_scipycode: &str) -> Result<String, String> {
let guide = MigrationGuide::new();
let mut rust_code = String::new();
let known_functions = vec!["gamma", "erf", "j0", "j1", "beta", "gammaln"];
for func in known_functions {
let scipy_pattern = format!("scipy.special.{func}");
if _scipycode.contains(&scipy_pattern) {
let full_name = format!("scipy.special.{func}");
if let Some(mapping) = guide.get_mapping(&full_name) {
let module_path = &mapping.module_path;
rust_code.push_str(&format!("use {module_path};\n"));
let scirs2_name = &mapping.scirs2_name;
rust_code.push_str(&format!("// Replace {scipy_pattern} with {scirs2_name}\n"));
}
}
}
if rust_code.is_empty() {
Err("No recognized SciPy functions found".to_string())
} else {
Ok(rust_code)
}
}
pub fn generate_imports(_scipyimports: &[&str]) -> String {
let mut _imports = String::from("// SciRS2 _imports\n");
for &import in _scipyimports {
match import {
"gamma" | "gammaln" | "beta" => {
_imports.push_str("use scirs2_special::gamma::{gamma, gammaln, beta};\n");
}
"j0" | "j1" | "jv" => {
_imports.push_str("use scirs2_special::bessel::{j0, j1, jv};\n");
}
"erf" | "erfc" => {
_imports.push_str("use scirs2_special::erf::{erf, erfc};\n");
}
_ => {}
}
}
_imports
}
}
pub mod performance {
pub struct PerformanceComparison {
pub function_name: String,
pub scipy_time_ms: f64,
pub scirs2_time_ms: f64,
pub speedup: f64,
pub accuracy_difference: f64,
}
impl PerformanceComparison {
pub fn report(&self) -> String {
format!(
"{}: SciRS2 is {:.1}x {} (accuracy diff: {:.2e})",
self.function_name,
if self.speedup > 1.0 {
self.speedup
} else {
1.0 / self.speedup
},
if self.speedup > 1.0 {
"faster"
} else {
"slower"
},
self.accuracy_difference
)
}
}
}
pub mod examples {
pub fn gamma_migration_example() -> String {
r#"
// SciPy Python code:
// from scipy.special import gamma
// result = gamma(5.5)
// SciRS2 Rust equivalent:
use scirs2_special::gamma;
let result = gamma(5.5_f64);
// For array operations:
use scirs2_core::ndarray::Array1;
let x = Array1::linspace(0.1, 10.0, 100);
let gamma_values = x.mapv(gamma);
"#
.to_string()
}
pub fn bessel_migration_example() -> String {
r#"
// SciPy Python code:
// from scipy.special import j0, jv
// y1 = j0(2.5)
// y2 = jv(1.5, 3.0)
// SciRS2 Rust equivalent:
use scirs2_special::bessel::{j0, jv};
let y1 = j0(2.5_f64);
let y2 = jv(1.5_f64, 3.0_f64);
"#
.to_string()
}
pub fn statistical_migration_example() -> String {
r#"
// SciPy Python code:
// from scipy.special import expit, softmax
// sigmoid = expit(x)
// probs = softmax(logits)
// SciRS2 Rust equivalent:
use scirs2_special::statistical::{logistic, softmax};
let sigmoid = logistic(x);
let probs = softmax(&logits); // Note: takes a slice
// For ndarray:
use scirs2_core::ndarray::Array1;
let x_array = Array1::from_vec(vec![1.0, 2.0, 3.0]);
let sigmoid_array = x_array.mapv(logistic);
"#
.to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_migration_guide() {
let guide = MigrationGuide::new();
let gamma_mapping = guide
.get_mapping("scipy.special.gamma")
.expect("Operation failed");
assert_eq!(gamma_mapping.scirs2_name, "gamma");
let expit_mapping = guide
.get_mapping("scipy.special.expit")
.expect("Operation failed");
assert_eq!(expit_mapping.scirs2_name, "logistic");
}
#[test]
fn test_migration_report() {
let guide = MigrationGuide::new();
let functions = vec![
"scipy.special.gamma",
"scipy.special.erf",
"scipy.special.unknown",
];
let report = guide.generate_migration_report(&functions);
assert!(report.contains("gamma"));
assert!(report.contains("erf"));
assert!(report.contains("No direct mapping found"));
}
#[test]
fn test_codegen() {
let _scipycode = "result = scipy.special.gamma(x)";
let rust_code = codegen::generate_rust_equivalent(_scipycode).expect("Operation failed");
assert!(rust_code.contains("use scirs2_special::gamma"));
}
#[test]
fn test_performance_comparison() {
let comparison = performance::PerformanceComparison {
function_name: "gamma".to_string(),
scipy_time_ms: 10.0,
scirs2_time_ms: 2.0,
speedup: 5.0,
accuracy_difference: 1e-15,
};
let report = comparison.report();
assert!(report.contains("5.0x faster"));
}
}