//! Comprehensive TDD tests for real scientific computing algorithms #![cfg(feature = "disabled_tests")] use crate::ScienceError; use crate::scientific_computing::*; use nalgebra as na; #[cfg(test)] mod scientific_tensor_tests { use super::*; #[test] fn test_scientific_tensor_creation() { let data = ndarray::Array2::from_elem((3, 3), 1.0); let tensor = ScientificTensor::from_array(data.into_dyn()); assert_eq!(tensor.shape(), &[3, 3]); assert_eq!(tensor.data().sum(), 9.0); } #[test] fn test_tensor_with_units() { let data = ndarray::Array1::from_vec(vec![1.0, 2.0, 3.0]); let tensor = ScientificTensor::with_units(data.into_dyn(), "meters"); assert_eq!(tensor.units(), Some("meters")); assert_eq!(tensor.shape(), &[3]); } #[test] fn test_tensor_zeros_ones() { let zeros = ScientificTensor::zeros(&[2, 3]); assert_eq!(zeros.shape(), &[2, 3]); assert_eq!(zeros.data().sum(), 0.0); let ones = ScientificTensor::ones(&[2, 3]); assert_eq!(ones.data().sum(), 6.0); } #[test] fn test_tensor_randn() { let tensor = ScientificTensor::randn(&[1000]); assert_eq!(tensor.shape(), &[1000]); let data = tensor.data().as_slice().unwrap(); let mean: f64 = data.iter().sum::() / data.len() as f64; let variance: f64 = data.iter().map(|x| (x - mean).powi(2)).sum::() / data.len() as f64; // Should approximate N(0,1) assert!(mean.abs() < 0.2); assert!(variance > 0.5 && variance < 2.0); } #[test] fn test_tensor_arithmetic() { let a = ScientificTensor::from_array(ndarray::Array1::from_vec(vec![1.0, 2.0, 3.0]).into_dyn()); let b = ScientificTensor::from_array(ndarray::Array1::from_vec(vec![4.0, 5.0, 6.0]).into_dyn()); let sum = a.add(&b).unwrap(); assert_eq!(sum.data().as_slice().unwrap(), &[5.0, 7.0, 9.0]); let product = a.mul(&b).unwrap(); assert_eq!(product.data().as_slice().unwrap(), &[4.0, 10.0, 18.0]); let scaled = a.mul_scalar(2.0); assert_eq!(scaled.data().as_slice().unwrap(), &[2.0, 4.0, 6.0]); } #[test] fn test_tensor_shape_mismatch() { let a = ScientificTensor::zeros(&[2, 3]); let b = ScientificTensor::zeros(&[3, 2]); let result = a.add(&b); assert!(result.is_err()); } #[test] fn test_tensor_matmul() { let a = ScientificTensor::from_array( ndarray::Array2::from_shape_vec((2, 3), vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0]) .unwrap() .into_dyn(), ); let b = ScientificTensor::from_array( ndarray::Array2::from_shape_vec((3, 2), vec![7.0, 8.0, 9.0, 10.0, 11.0, 12.0]) .unwrap() .into_dyn(), ); let result = a.matmul(&b).unwrap(); assert_eq!(result.shape(), &[2, 2]); // Verify matrix multiplication result let expected = ndarray::arr2(&[[58.0, 64.0], [139.0, 154.0]]); let result_array = result .data() .view() .into_dimensionality::() .unwrap(); assert_eq!(result_array, expected); } #[test] fn test_tensor_statistics() { let data = vec![1.0, 2.0, 3.0, 4.0, 5.0]; let tensor = ScientificTensor::from_array(ndarray::Array1::from_vec(data).into_dyn()); assert_eq!(tensor.mean(), 3.0); assert!((tensor.std() - 1.58).abs() < 0.1); assert!((tensor.var() - 2.5).abs() < 0.1); } #[test] fn test_tensor_fft() { let data = vec![1.0, 0.0, 1.0, 0.0]; let tensor = ScientificTensor::from_array(ndarray::Array1::from_vec(data).into_dyn()); let fft_result = tensor.fft().unwrap(); assert_eq!(fft_result.len(), 4); // Basic sanity check - FFT of alternating signal assert!(fft_result[0].re > 0.0); } #[test] fn test_tensor_gradient() { let data = vec![1.0, 4.0, 9.0, 16.0]; // x^2 for x = 1,2,3,4 let tensor = ScientificTensor::from_array(ndarray::Array1::from_vec(data).into_dyn()); let grad = tensor.gradient(0).unwrap(); assert_eq!(grad.shape(), tensor.shape()); // Gradient of x^2 should be approximately 2x } #[test] fn test_tensor_metadata() { let mut tensor = ScientificTensor::zeros(&[2, 2]); tensor.add_metadata("experiment", "test_001"); tensor.add_metadata("date", "2023-01-01"); assert_eq!( tensor.metadata.get("experiment"), Some(&"test_001".to_string()) ); assert_eq!(tensor.metadata.get("date"), Some(&"2023-01-01".to_string())); } } #[cfg(test)] mod physics_simulation_tests { use super::*; #[test] fn test_physics_simulation_creation() { let sim = PhysicsSimulation::new(0.01); assert_eq!(sim.time_step, 0.01); assert_eq!(sim.current_time, 0.0); } #[test] fn test_harmonic_oscillator() { let mut sim = PhysicsSimulation::new(0.001); let results = sim.simulate_harmonic_oscillator(1.0, 1.0, 1.0).unwrap(); assert!(!results.is_empty()); // Check that we have time, position, velocity data let (time, pos, vel) = results[0]; assert_eq!(time, 0.0); assert_eq!(pos, 1.0); // Initial position assert_eq!(vel, 0.0); // Initial velocity // Check energy conservation (approximately) let last_result = results.last().unwrap(); let initial_energy = 0.5 * 1.0 * 1.0; // 1/2 * k * x^2 at t=0 let final_energy = 0.5 * last_result.1.powi(2) + 0.5 * last_result.2.powi(2); assert!((initial_energy - final_energy).abs() < 0.1); } #[test] fn test_wave_equation() { let mut sim = PhysicsSimulation::new(0.001); let result = sim.simulate_wave_equation(1.0, 1.0, 0.1, 50); assert!(result.is_ok()); let solution = result.unwrap(); assert_eq!(solution.shape(), &[100, 50]); // nt x nx } #[test] fn test_wave_equation_cfl_violation() { let mut sim = PhysicsSimulation::new(0.1); // Large time step let result = sim.simulate_wave_equation(1.0, 1.0, 0.1, 10); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), ScienceError::Numerical { .. } )); } #[test] fn test_heat_equation() { let mut sim = PhysicsSimulation::new(0.001); let result = sim.simulate_heat_equation(0.1, 1.0, 0.1, 50); assert!(result.is_ok()); let solution = result.unwrap(); assert_eq!(solution.shape(), &[100, 50]); // nt x nx // Check that heat diffuses (temperature smooths out) let initial = solution.row(0); let final_row = solution.row(99); let initial_variance: f64 = initial.iter().map(|x| x.powi(2)).sum::() / initial.len() as f64; let final_variance: f64 = final_row.iter().map(|x| x.powi(2)).sum::() / final_row.len() as f64; assert!(final_variance < initial_variance); // Heat should diffuse } #[test] fn test_heat_equation_stability_violation() { let mut sim = PhysicsSimulation::new(0.1); // Large time step let result = sim.simulate_heat_equation(1.0, 1.0, 0.1, 10); assert!(result.is_err()); assert!(matches!( result.unwrap_err(), ScienceError::Numerical { .. } )); } } #[cfg(test)] mod chemistry_simulation_tests { use super::*; #[test] fn test_chemistry_simulation_creation() { let sim = ChemistrySimulation::new(298.15, 101325.0); assert_eq!(sim.temperature, 298.15); assert_eq!(sim.pressure, 101325.0); } #[test] fn test_water_molecule() { let mut sim = ChemistrySimulation::new(298.15, 101325.0); let atoms = vec![ Atom { element: "O".to_string(), atomic_number: 8, position: na::Vector3::new(0.0, 0.0, 0.0), charge: -0.8, }, Atom { element: "H".to_string(), atomic_number: 1, position: na::Vector3::new(0.96, 0.0, 0.0), charge: 0.4, }, Atom { element: "H".to_string(), atomic_number: 1, position: na::Vector3::new(-0.24, 0.93, 0.0), charge: 0.4, }, ]; let water = Molecule { formula: "H2O".to_string(), molecular_weight: 18.015, atoms, bonds: vec![], geometry: ndarray::Array2::zeros((3, 3)), }; sim.add_molecule("water", water); let properties = sim.calculate_molecular_properties("water").unwrap(); assert!(properties.contains_key("molecular_weight")); assert!(properties.contains_key("center_of_mass_x")); assert!(properties.contains_key("moment_of_inertia")); assert_eq!(properties["molecular_weight"], 18.015); } #[test] fn test_reaction_kinetics() { let mut sim = ChemistrySimulation::new(298.15, 101325.0); // Add a simple reaction: A -> B let reaction = ChemicalReaction { reactants: vec!["A".to_string()], products: vec!["B".to_string()], rate_constant: 0.1, activation_energy: 50000.0, // J/mol }; sim.reactions.push(reaction); let mut initial_concentrations = std::collections::HashMap::new(); initial_concentrations.insert("A".to_string(), 1.0); initial_concentrations.insert("B".to_string(), 0.0); let results = sim .simulate_reaction_kinetics(initial_concentrations, 1.0) .unwrap(); assert!(!results.is_empty()); // Check that A decreases and B increases let initial = &results[0]; let final_step = results.last().unwrap(); assert!(final_step["A"] < initial["A"]); assert!(final_step["B"] > initial["B"]); } #[test] fn test_atomic_mass_lookup() { let sim = ChemistrySimulation::new(298.15, 101325.0); assert_eq!(sim.get_atomic_mass("H").unwrap(), 1.008); assert_eq!(sim.get_atomic_mass("C").unwrap(), 12.011); assert_eq!(sim.get_atomic_mass("O").unwrap(), 15.999); assert!(sim.get_atomic_mass("Xx").is_err()); } } #[cfg(test)] mod materials_simulation_tests { use super::*; #[test] fn test_materials_simulation_creation() { let crystal = CrystalStructure { lattice_parameters: [4.0, 4.0, 4.0, 90.0, 90.0, 90.0], space_group: "Pm-3m".to_string(), atoms: vec![], }; let sim = MaterialsSimulation::new(crystal, 300.0, 101325.0); assert_eq!(sim.temperature, 300.0); assert_eq!(sim.pressure, 101325.0); } #[test] fn test_elastic_properties() { let crystal = CrystalStructure { lattice_parameters: [4.0, 4.0, 4.0, 90.0, 90.0, 90.0], space_group: "Pm-3m".to_string(), atoms: vec![], }; let sim = MaterialsSimulation::new(crystal, 300.0, 101325.0); let properties = sim.calculate_elastic_properties().unwrap(); assert!(properties.contains_key("bulk_modulus")); assert!(properties.contains_key("youngs_modulus")); assert!(properties.contains_key("poissons_ratio")); assert!(properties.contains_key("shear_modulus")); // Basic sanity checks let bulk_modulus = properties["bulk_modulus"]; let youngs_modulus = properties["youngs_modulus"]; let poissons_ratio = properties["poissons_ratio"]; assert!(bulk_modulus > 0.0); assert!(youngs_modulus > 0.0); assert!(poissons_ratio > 0.0 && poissons_ratio < 0.5); } #[test] fn test_thermal_properties() { let crystal = CrystalStructure { lattice_parameters: [3.5, 3.5, 3.5, 90.0, 90.0, 90.0], space_group: "Fd-3m".to_string(), atoms: vec![], }; let sim = MaterialsSimulation::new(crystal, 300.0, 101325.0); let properties = sim.calculate_thermal_properties().unwrap(); assert!(properties.contains_key("debye_temperature")); assert!(properties.contains_key("heat_capacity")); assert!(properties.contains_key("thermal_expansion")); assert!(properties.contains_key("thermal_conductivity")); // Basic sanity checks assert!(properties["debye_temperature"] > 0.0); assert!(properties["heat_capacity"] > 0.0); assert!(properties["thermal_expansion"] > 0.0); assert!(properties["thermal_conductivity"] > 0.0); } #[test] fn test_temperature_dependence() { let crystal = CrystalStructure { lattice_parameters: [4.0, 4.0, 4.0, 90.0, 90.0, 90.0], space_group: "Pm-3m".to_string(), atoms: vec![], }; let sim_low_t = MaterialsSimulation::new(crystal.clone(), 100.0, 101325.0); let sim_high_t = MaterialsSimulation::new(crystal, 1000.0, 101325.0); let props_low = sim_low_t.calculate_thermal_properties().unwrap(); let props_high = sim_high_t.calculate_thermal_properties().unwrap(); // Thermal conductivity should decrease with temperature assert!(props_high["thermal_conductivity"] < props_low["thermal_conductivity"]); } } #[cfg(test)] mod numerical_methods_tests { use super::*; #[test] fn test_linear_system_solve() { // Solve: 2x + 3y = 7, x + y = 3 let a = ndarray::arr2(&[[2.0, 3.0], [1.0, 1.0]]); let b = ndarray::arr1(&[7.0, 3.0]); let x = NumericalMethods::solve_linear_system(&a, &b).unwrap(); // Solution should be x=2, y=1 assert!((x[0] - 2.0).abs() < 1e-10); assert!((x[1] - 1.0).abs() < 1e-10); } #[test] fn test_eigenvalues() { // Simple 2x2 symmetric matrix let matrix = ndarray::arr2(&[[3.0, 1.0], [1.0, 3.0]]); let (eigenvals, _eigenvecs) = NumericalMethods::eigenvalues(&matrix).unwrap(); // Eigenvalues should be 2 and 4 let mut sorted_vals = eigenvals.to_vec(); sorted_vals.sort_by(|a, b| a.total_cmp(b)); assert!((sorted_vals[0] - 2.0).abs() < 1e-10); assert!((sorted_vals[1] - 4.0).abs() < 1e-10); } #[test] fn test_trapezoidal_integration() { // Integrate x^2 from 0 to 2 let x = ndarray::Array1::linspace(0.0, 2.0, 1000); let y = x.mapv(|val| val * val); let integral = NumericalMethods::integrate_trapezoidal(&x, &y).unwrap(); // Analytical result is 8/3 ≈ 2.667 assert!((integral - 8.0 / 3.0).abs() < 0.01); } #[test] fn test_runge_kutta_ode() { // Solve dy/dt = -y with y(0) = 1 // Analytical solution: y(t) = exp(-t) let f = |_t: f64, y: f64| -y; let (t, y) = NumericalMethods::runge_kutta_4(f, 1.0, (0.0, 2.0), 1000).unwrap(); // Check solution at t=1: should be e^(-1) ≈ 0.368 let idx = t.iter().position(|&x| (x - 1.0).abs() < 0.01).unwrap(); let analytical = (-1.0f64).exp(); assert!((y[idx] - analytical).abs() < 0.01); } #[test] fn test_newton_raphson() { // Find root of f(x) = x^2 - 4, which is x = 2 let f = |x: f64| x * x - 4.0; let df = |x: f64| 2.0 * x; let root = NumericalMethods::newton_raphson(f, df, 3.0, 1e-10, 100).unwrap(); assert!((root - 2.0).abs() < 1e-10); } #[test] fn test_newton_raphson_convergence_failure() { // Function with zero derivative let f = |x: f64| x * x; let df = |_x: f64| 0.0; // Always zero derivative let result = NumericalMethods::newton_raphson(f, df, 1.0, 1e-10, 100); assert!(result.is_err()); } #[test] fn test_integration_error_cases() { let x = ndarray::arr1(&[1.0, 2.0, 3.0]); let y = ndarray::arr1(&[1.0, 2.0]); // Mismatched length let result = NumericalMethods::integrate_trapezoidal(&x, &y); assert!(result.is_err()); let x_short = ndarray::arr1(&[1.0]); let y_short = ndarray::arr1(&[1.0]); let result = NumericalMethods::integrate_trapezoidal(&x_short, &y_short); assert!(result.is_err()); } } #[cfg(test)] mod integration_tests { use super::*; #[test] fn test_multiphysics_workflow() { // Test a combined physics-chemistry-materials workflow // 1. Physics: Simulate temperature distribution let mut physics_sim = PhysicsSimulation::new(0.001); let heat_solution = physics_sim .simulate_heat_equation(0.1, 1.0, 0.1, 50) .unwrap(); assert_eq!(heat_solution.shape(), &[100, 50]); // 2. Chemistry: Use temperature for reaction kinetics let temp = 350.0; // K let mut chem_sim = ChemistrySimulation::new(temp, 101325.0); let reaction = ChemicalReaction { reactants: vec!["A".to_string()], products: vec!["B".to_string()], rate_constant: 1e6, // Pre-exponential factor activation_energy: 50000.0, // J/mol }; chem_sim.reactions.push(reaction); let mut concentrations = std::collections::HashMap::new(); concentrations.insert("A".to_string(), 1.0); concentrations.insert("B".to_string(), 0.0); let kinetics_result = chem_sim .simulate_reaction_kinetics(concentrations, 0.1) .unwrap(); assert!(!kinetics_result.is_empty()); // 3. Materials: Calculate properties at this temperature let crystal = CrystalStructure { lattice_parameters: [4.0, 4.0, 4.0, 90.0, 90.0, 90.0], space_group: "Pm-3m".to_string(), atoms: vec![], }; let materials_sim = MaterialsSimulation::new(crystal, temp, 101325.0); let thermal_props = materials_sim.calculate_thermal_properties().unwrap(); assert!(thermal_props.contains_key("heat_capacity")); assert!(thermal_props["heat_capacity"] > 0.0); } #[test] fn test_scientific_tensor_physics_integration() { // Use scientific tensors in physics simulation // Create initial temperature distribution let temp_data = ndarray::Array1::linspace(100.0, 200.0, 50); let temp_tensor = ScientificTensor::with_units(temp_data.into_dyn(), "Kelvin"); assert_eq!(temp_tensor.units(), Some("Kelvin")); assert_eq!(temp_tensor.shape(), &[50]); // Apply some transformations let temp_celsius = temp_tensor.mul_scalar(1.0); // Simplified conversion let temp_squared = temp_celsius.mul(&temp_celsius).unwrap(); assert_eq!(temp_squared.shape(), &[50]); // Statistical analysis let mean_temp = temp_tensor.mean(); let std_temp = temp_tensor.std(); assert!(mean_temp > 100.0 && mean_temp < 200.0); assert!(std_temp > 0.0); } #[test] fn test_numerical_methods_physics_integration() { // Solve physics ODE using numerical methods // Simple harmonic oscillator: d²x/dt² + ω²x = 0 // Convert to system: dx/dt = v, dv/dt = -ω²x let omega = 1.0; let f = |_t: f64, y: f64| { // This is simplified - in reality would need system of ODEs -omega * omega * y }; let (t, x) = NumericalMethods::runge_kutta_4(f, 1.0, (0.0, 2.0 * std::f64::consts::PI), 1000) .unwrap(); // Should complete one full oscillation assert_eq!(t.len(), 1001); assert!((t[0] - 0.0).abs() < 1e-10); assert!((t[1000] - 2.0 * std::f64::consts::PI).abs() < 0.01); // Check that we return close to initial condition after one period assert!((x[1000] - x[0]).abs() < 0.1); } #[test] fn test_performance_benchmark() { // Performance test for large-scale scientific computing let size = 100; let start = std::time::Instant::now(); // Large tensor operations let a = ScientificTensor::randn(&[size, size]); let b = ScientificTensor::randn(&[size, size]); let c = a.add(&b).unwrap(); let d = a.matmul(&b).unwrap(); let tensor_time = start.elapsed(); let start = std::time::Instant::now(); // Physics simulation let mut physics_sim = PhysicsSimulation::new(0.01); let _oscillator = physics_sim .simulate_harmonic_oscillator(1.0, 1.0, 1.0) .unwrap(); let physics_time = start.elapsed(); let start = std::time::Instant::now(); // Numerical methods use rand::Rng; use rand_distr::{Distribution, StandardNormal}; let mut rng = rand::thread_rng(); let data: Vec = (0..2500).map(|_| StandardNormal.sample(&mut rng)).collect(); let matrix = ndarray::Array2::from_shape_vec((50, 50), data).unwrap(); let _eigenvals = NumericalMethods::eigenvalues(&matrix); let numerical_time = start.elapsed(); println!("Performance benchmark:"); println!("Tensor operations ({}x{}): {:?}", size, size, tensor_time); println!("Physics simulation: {:?}", physics_time); println!("Numerical methods (50x50 eigenvals): {:?}", numerical_time); // Basic performance expectations assert!(tensor_time.as_millis() < 1000); assert!(physics_time.as_millis() < 1000); assert!(numerical_time.as_millis() < 5000); } }