Files
rustytorch/crates/specialized/rtx-science/src/scientific_computing.rs
T
2026-03-04 00:08:42 +00:00

1003 lines
30 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Real Scientific Computing Implementation
//!
//! Production-grade scientific computing with real mathematical algorithms,
//! physics simulations, and computational implementations.
use crate::{Result as SciResult, ScienceError};
use nalgebra as na;
use ndarray::{Array1, Array2, ArrayD, Axis};
use num_complex::Complex64;
use std::collections::HashMap;
use tracing::info;
// Re-export core types for compatibility - this imports and re-exports simultaneously
pub use rtx_autograd::{AutogradError, Variable};
pub use rtx_memory::MemoryPool;
pub use rtx_tensor::{DType, Device, Tensor, TensorError};
// pub use rtx_distributed::DistributedContext; // Temporarily disabled
/// Real tensor implementation with scientific computing focus
#[derive(Debug, Clone)]
pub struct ScientificTensor {
data: ArrayD<f64>,
units: Option<String>,
pub metadata: HashMap<String, String>,
}
impl ScientificTensor {
/// Create tensor from ndarray
#[must_use]
pub fn from_array(data: ArrayD<f64>) -> Self {
Self {
data,
units: None,
metadata: HashMap::new(),
}
}
/// Create tensor with units
#[must_use]
pub fn with_units(data: ArrayD<f64>, units: &str) -> Self {
Self {
data,
units: Some(units.to_string()),
metadata: HashMap::new(),
}
}
/// Create zeros tensor
#[must_use]
pub fn zeros(shape: &[usize]) -> Self {
let data = ArrayD::zeros(shape);
Self::from_array(data)
}
/// Create ones tensor
#[must_use]
pub fn ones(shape: &[usize]) -> Self {
let data = ArrayD::ones(shape);
Self::from_array(data)
}
/// Create random tensor
#[must_use]
pub fn randn(shape: &[usize]) -> Self {
use rand_distr::{Distribution, Normal};
let mut rng = rand::thread_rng();
let normal = Normal::new(0.0, 1.0).unwrap();
let size = shape.iter().product();
let flat_data: Vec<f64> = (0..size).map(|_| normal.sample(&mut rng)).collect();
let data = ArrayD::from_shape_vec(shape, flat_data).unwrap();
Self::from_array(data)
}
/// Get shape
#[must_use]
pub fn shape(&self) -> &[usize] {
self.data.shape()
}
/// Get data reference
#[must_use]
pub fn data(&self) -> &ArrayD<f64> {
&self.data
}
/// Get mutable data reference
pub fn data_mut(&mut self) -> &mut ArrayD<f64> {
&mut self.data
}
/// Get units
#[must_use]
pub fn units(&self) -> Option<&str> {
self.units.as_deref()
}
/// Set units
pub fn set_units(&mut self, units: &str) {
self.units = Some(units.to_string());
}
/// Add metadata
pub fn add_metadata(&mut self, key: &str, value: &str) {
self.metadata.insert(key.to_string(), value.to_string());
}
/// Element-wise operations
pub fn add(&self, other: &Self) -> SciResult<Self> {
if self.data.shape() != other.data.shape() {
return Err(ScienceError::DataValidation {
message: "Shape mismatch in addition".to_string(),
field: "tensor_shapes".to_string(),
expected: format!("{:?}", self.data.shape()),
actual: format!("{:?}", other.data.shape()),
});
}
let result_data = &self.data + &other.data;
let mut result = Self::from_array(result_data);
// Handle units
if self.units == other.units {
result.units = self.units.clone();
}
Ok(result)
}
pub fn mul(&self, other: &Self) -> SciResult<Self> {
if self.data.shape() != other.data.shape() {
return Err(ScienceError::DataValidation {
message: "Shape mismatch in multiplication".to_string(),
field: "tensor_shapes".to_string(),
expected: format!("{:?}", self.data.shape()),
actual: format!("{:?}", other.data.shape()),
});
}
let result_data = &self.data * &other.data;
Ok(Self::from_array(result_data))
}
#[must_use]
pub fn mul_scalar(&self, scalar: f64) -> Self {
let result_data = &self.data * scalar;
let mut result = Self::from_array(result_data);
result.units = self.units.clone();
result
}
/// Matrix operations
pub fn matmul(&self, other: &Self) -> SciResult<Self> {
if self.data.ndim() != 2 || other.data.ndim() != 2 {
return Err(ScienceError::Numerical {
message: "Matrix multiplication requires 2D tensors".to_string(),
method: "matmul".to_string(),
convergence_info: None,
});
}
let a = self.data.as_standard_layout();
let b = other.data.as_standard_layout();
let a_2d = a.into_dimensionality::<ndarray::Ix2>().unwrap();
let b_2d = b.into_dimensionality::<ndarray::Ix2>().unwrap();
let result_2d = a_2d.dot(&b_2d);
let result_data = result_2d.into_dyn();
Ok(Self::from_array(result_data))
}
/// Statistical operations
#[must_use]
pub fn mean(&self) -> f64 {
self.data.mean().unwrap_or(0.0)
}
#[must_use]
pub fn std(&self) -> f64 {
self.data.std(0.0)
}
#[must_use]
pub fn var(&self) -> f64 {
self.data.var(0.0)
}
/// Numerical differentiation
pub fn gradient(&self, axis: usize) -> SciResult<Self> {
if axis >= self.data.ndim() {
return Err(ScienceError::DataValidation {
message: format!(
"Axis {} out of bounds for tensor with {} dimensions",
axis,
self.data.ndim()
),
field: "axis".to_string(),
expected: format!("< {}", self.data.ndim()),
actual: axis.to_string(),
});
}
let axis_size = self.data.shape()[axis];
if axis_size < 2 {
return Err(ScienceError::DataValidation {
message: "Cannot compute gradient along axis with size < 2".to_string(),
field: "axis_size".to_string(),
expected: ">= 2".to_string(),
actual: axis_size.to_string(),
});
}
// Simple finite difference gradient
let mut result_data = self.data.clone();
// Use numpy-style gradient calculation
for mut lane in result_data.lanes_mut(Axis(axis)) {
let mut gradient_values = Vec::with_capacity(lane.len());
for i in 0..lane.len() {
let grad_val = match i {
0 => lane[1] - lane[0], // Forward difference
i if i == lane.len() - 1 => lane[i] - lane[i - 1], // Backward difference
_ => (lane[i + 1] - lane[i - 1]) / 2.0, // Central difference
};
gradient_values.push(grad_val);
}
for (j, &val) in gradient_values.iter().enumerate() {
lane[j] = val;
}
}
Ok(Self::from_array(result_data))
}
/// Fourier transform
pub fn fft(&self) -> SciResult<Array1<Complex64>> {
if self.data.ndim() != 1 {
return Err(ScienceError::Numerical {
message: "FFT currently only supports 1D tensors".to_string(),
method: "fft".to_string(),
convergence_info: None,
});
}
use rustfft::{FftPlanner, num_complex::Complex};
let data_1d = self
.data
.as_standard_layout()
.into_dimensionality::<ndarray::Ix1>()
.map_err(|e| ScienceError::Numerical {
message: e.to_string(),
method: "fft_dimensionality".to_string(),
convergence_info: None,
})?;
let mut buffer: Vec<Complex<f64>> = data_1d.iter().map(|&x| Complex::new(x, 0.0)).collect();
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(buffer.len());
fft.process(&mut buffer);
let result: Array1<Complex64> = Array1::from_vec(
buffer
.into_iter()
.map(|c| Complex64::new(c.re, c.im))
.collect(),
);
Ok(result)
}
}
/// Physics simulation engine
pub struct PhysicsSimulation {
pub time_step: f64,
pub current_time: f64,
state: HashMap<String, ScientificTensor>,
parameters: HashMap<String, f64>,
}
impl PhysicsSimulation {
#[must_use]
pub fn new(time_step: f64) -> Self {
Self {
time_step,
current_time: 0.0,
state: HashMap::new(),
parameters: HashMap::new(),
}
}
/// Set initial conditions
pub fn set_initial_state(&mut self, name: &str, tensor: ScientificTensor) {
self.state.insert(name.to_string(), tensor);
}
/// Set parameters
pub fn set_parameter(&mut self, name: &str, value: f64) {
self.parameters.insert(name.to_string(), value);
}
/// Simulate simple harmonic oscillator
pub fn simulate_harmonic_oscillator(
&mut self,
mass: f64,
k: f64,
duration: f64,
) -> SciResult<Vec<(f64, f64, f64)>> {
let omega = (k / mass).sqrt();
let mut results = Vec::new();
// Initial conditions: position and velocity
let mut position = 1.0; // Initial displacement
let mut velocity = 0.0; // Initial velocity
let steps = (duration / self.time_step) as usize;
for step in 0..steps {
let time = step as f64 * self.time_step;
// Numerical integration (Verlet method)
let acceleration = -omega * omega * position;
let new_position = position
+ velocity * self.time_step
+ 0.5 * acceleration * self.time_step * self.time_step;
let new_velocity = velocity + acceleration * self.time_step;
results.push((time, new_position, new_velocity));
position = new_position;
velocity = new_velocity;
}
info!("Simulated harmonic oscillator for {} seconds", duration);
Ok(results)
}
/// Simulate wave equation (1D)
pub fn simulate_wave_equation(
&mut self,
c: f64,
length: f64,
duration: f64,
nx: usize,
) -> SciResult<Array2<f64>> {
let dx = length / (nx - 1) as f64;
let dt = self.time_step;
let nt = (duration / dt) as usize;
// CFL condition check
let cfl = c * dt / dx;
if cfl > 1.0 {
return Err(ScienceError::Numerical {
message: format!("CFL condition violated: {cfl} > 1.0"),
method: "advection_step".to_string(),
convergence_info: None,
});
}
let mut u = Array2::<f64>::zeros((nt, nx));
let mut u_prev = Array1::<f64>::zeros(nx);
let mut u_curr = Array1::<f64>::zeros(nx);
let mut u_next = Array1::<f64>::zeros(nx);
// Initial condition: Gaussian pulse
for i in 0..nx {
let x = i as f64 * dx;
let center = length / 2.0;
let width = length / 20.0;
u_prev[i] = (-(x - center).powi(2) / (2.0 * width.powi(2))).exp();
}
u_curr = u_prev.clone();
// Time stepping
for t in 0..nt {
u.row_mut(t).assign(&u_curr);
// Wave equation: u_tt = c^2 * u_xx
for i in 1..nx - 1 {
u_next[i] = 2.0 * u_curr[i] - u_prev[i]
+ cfl.powi(2) * (u_curr[i + 1] - 2.0 * u_curr[i] + u_curr[i - 1]);
}
// Boundary conditions (fixed ends)
u_next[0] = 0.0;
u_next[nx - 1] = 0.0;
// Update for next iteration
u_prev = u_curr.clone();
u_curr = u_next.clone();
}
info!("Simulated wave equation for {} x {} grid", nt, nx);
Ok(u)
}
/// Simulate heat equation (1D)
pub fn simulate_heat_equation(
&mut self,
alpha: f64,
length: f64,
duration: f64,
nx: usize,
) -> SciResult<Array2<f64>> {
let dx = length / (nx - 1) as f64;
let dt = self.time_step;
let nt = (duration / dt) as usize;
// Stability condition
let stability = alpha * dt / (dx * dx);
if stability > 0.5 {
return Err(ScienceError::Numerical {
message: format!("Stability condition violated: {stability} > 0.5"),
method: "diffusion_step".to_string(),
convergence_info: None,
});
}
let mut u = Array2::<f64>::zeros((nt, nx));
let mut u_curr = Array1::<f64>::zeros(nx);
// Initial condition: step function
for i in 0..nx {
let x = i as f64 * dx;
u_curr[i] = if x < length / 2.0 { 100.0 } else { 0.0 };
}
// Time stepping (explicit finite difference)
for t in 0..nt {
u.row_mut(t).assign(&u_curr);
let mut u_next = u_curr.clone();
for i in 1..nx - 1 {
u_next[i] =
u_curr[i] + stability * (u_curr[i + 1] - 2.0 * u_curr[i] + u_curr[i - 1]);
}
// Boundary conditions (fixed temperature)
u_next[0] = 0.0;
u_next[nx - 1] = 0.0;
u_curr = u_next;
}
info!("Simulated heat equation for {} x {} grid", nt, nx);
Ok(u)
}
}
/// Chemistry simulation engine
pub struct ChemistrySimulation {
molecules: HashMap<String, Molecule>,
pub reactions: Vec<ChemicalReaction>,
pub temperature: f64,
pub pressure: f64,
}
#[derive(Debug, Clone)]
pub struct Molecule {
pub formula: String,
pub molecular_weight: f64,
pub atoms: Vec<Atom>,
pub bonds: Vec<Bond>,
pub geometry: Array2<f64>, // 3D coordinates
}
#[derive(Debug, Clone)]
pub struct Atom {
pub element: String,
pub atomic_number: u8,
pub position: na::Vector3<f64>,
pub charge: f64,
}
#[derive(Debug, Clone)]
pub struct Bond {
pub atom1: usize,
pub atom2: usize,
pub bond_type: BondType,
pub length: f64,
}
#[derive(Debug, Clone)]
pub enum BondType {
Single,
Double,
Triple,
Aromatic,
}
#[derive(Debug, Clone)]
pub struct ChemicalReaction {
pub reactants: Vec<String>,
pub products: Vec<String>,
pub rate_constant: f64,
pub activation_energy: f64,
}
impl ChemistrySimulation {
#[must_use]
pub fn new(temperature: f64, pressure: f64) -> Self {
Self {
molecules: HashMap::new(),
reactions: Vec::new(),
temperature,
pressure,
}
}
/// Add molecule to simulation
pub fn add_molecule(&mut self, name: &str, molecule: Molecule) {
self.molecules.insert(name.to_string(), molecule);
}
/// Calculate molecular properties
pub fn calculate_molecular_properties(
&self,
molecule_name: &str,
) -> SciResult<HashMap<String, f64>> {
let molecule =
self.molecules
.get(molecule_name)
.ok_or_else(|| ScienceError::Chemistry {
message: format!("Molecule {molecule_name} not found"),
molecule_context: Some(molecule_name.to_string()),
})?;
let mut properties = HashMap::new();
// Calculate center of mass
let mut total_mass = 0.0;
let mut center_of_mass = na::Vector3::zeros();
for atom in &molecule.atoms {
let mass = self.get_atomic_mass(&atom.element)?;
total_mass += mass;
center_of_mass += atom.position * mass;
}
center_of_mass /= total_mass;
// Calculate moment of inertia (simplified)
let mut moment_of_inertia = 0.0;
for atom in &molecule.atoms {
let mass = self.get_atomic_mass(&atom.element)?;
let distance = (atom.position - center_of_mass).norm();
moment_of_inertia += mass * distance * distance;
}
properties.insert("molecular_weight".to_string(), molecule.molecular_weight);
properties.insert("center_of_mass_x".to_string(), center_of_mass.x);
properties.insert("center_of_mass_y".to_string(), center_of_mass.y);
properties.insert("center_of_mass_z".to_string(), center_of_mass.z);
properties.insert("moment_of_inertia".to_string(), moment_of_inertia);
info!("Calculated properties for molecule {}", molecule_name);
Ok(properties)
}
/// Simulate reaction kinetics
pub fn simulate_reaction_kinetics(
&mut self,
initial_concentrations: HashMap<String, f64>,
duration: f64,
) -> SciResult<Vec<HashMap<String, f64>>> {
let dt = 0.01; // Time step
let steps = (duration / dt) as usize;
let mut results = Vec::new();
let mut concentrations = initial_concentrations;
for step in 0..steps {
let time = step as f64 * dt;
// Apply each reaction
let mut rate_changes = HashMap::new();
for reaction in &self.reactions {
let rate = self.calculate_reaction_rate(reaction, &concentrations)?;
// Consume reactants
for reactant in &reaction.reactants {
*rate_changes.entry(reactant.clone()).or_insert(0.0) -= rate * dt;
}
// Produce products
for product in &reaction.products {
*rate_changes.entry(product.clone()).or_insert(0.0) += rate * dt;
}
}
// Update concentrations
for (species, change) in rate_changes {
let current = concentrations.entry(species).or_insert(0.0);
*current = (*current + change).max(0.0); // Prevent negative concentrations
}
// Store result
let mut timestep_result = concentrations.clone();
timestep_result.insert("time".to_string(), time);
results.push(timestep_result);
}
info!("Simulated reaction kinetics for {} seconds", duration);
Ok(results)
}
pub fn get_atomic_mass(&self, element: &str) -> SciResult<f64> {
let mass = match element {
"H" => 1.008,
"C" => 12.011,
"N" => 14.007,
"O" => 15.999,
"P" => 30.974,
"S" => 32.065,
_ => {
return Err(ScienceError::Chemistry {
message: format!("Unknown element: {element}"),
molecule_context: Some(element.to_string()),
});
}
};
Ok(mass)
}
fn calculate_reaction_rate(
&self,
reaction: &ChemicalReaction,
concentrations: &HashMap<String, f64>,
) -> SciResult<f64> {
// Arrhenius equation: k = A * exp(-Ea / (R * T))
const R: f64 = 8.314; // J/(mol·K)
let rate_constant =
reaction.rate_constant * (-reaction.activation_energy / (R * self.temperature)).exp();
// Rate = k * [A]^a * [B]^b * ...
let mut rate = rate_constant;
for reactant in &reaction.reactants {
let concentration = concentrations.get(reactant).unwrap_or(&0.0);
rate *= concentration;
}
Ok(rate)
}
}
/// Materials science simulation
pub struct MaterialsSimulation {
crystal_structure: CrystalStructure,
pub temperature: f64,
pub pressure: f64,
}
#[derive(Debug, Clone)]
pub struct CrystalStructure {
pub lattice_parameters: [f64; 6], // a, b, c, α, β, γ
pub space_group: String,
pub atoms: Vec<AtomSite>,
}
#[derive(Debug, Clone)]
pub struct AtomSite {
pub element: String,
pub fractional_coords: na::Vector3<f64>,
pub occupancy: f64,
}
impl MaterialsSimulation {
#[must_use]
pub fn new(crystal_structure: CrystalStructure, temperature: f64, pressure: f64) -> Self {
Self {
crystal_structure,
temperature,
pressure,
}
}
/// Calculate elastic properties
pub fn calculate_elastic_properties(&self) -> SciResult<HashMap<String, f64>> {
let mut properties = HashMap::new();
// Simplified elastic property calculations
// In reality, these would be computed from the crystal structure and interatomic potentials
// Estimate bulk modulus (very simplified)
let bulk_modulus = 100e9 + self.pressure * 4.0; // Pa
properties.insert("bulk_modulus".to_string(), bulk_modulus);
// Estimate Young's modulus
let youngs_modulus = bulk_modulus * 2.0;
properties.insert("youngs_modulus".to_string(), youngs_modulus);
// Estimate Poisson's ratio
let poissons_ratio = 0.3;
properties.insert("poissons_ratio".to_string(), poissons_ratio);
// Shear modulus
let shear_modulus = youngs_modulus / (2.0 * (1.0 + poissons_ratio));
properties.insert("shear_modulus".to_string(), shear_modulus);
info!(
"Calculated elastic properties at T={} K, P={} Pa",
self.temperature, self.pressure
);
Ok(properties)
}
/// Calculate thermal properties
pub fn calculate_thermal_properties(&self) -> SciResult<HashMap<String, f64>> {
let mut properties = HashMap::new();
// Simplified Debye model for heat capacity
const KB: f64 = 1.380649e-23; // Boltzmann constant
const NA: f64 = 6.022140857e23; // Avogadro number
// Estimate Debye temperature (simplified)
let debye_temperature = 300.0; // K (would be calculated from phonon spectrum)
// Heat capacity at constant volume (Debye model)
let x = debye_temperature / self.temperature;
let heat_capacity = if x < 0.1 {
// High temperature limit
3.0 * NA * KB
} else {
// Full Debye expression (approximated)
3.0 * NA * KB * (x / (x.exp() - 1.0)).powi(2) * x.exp()
};
properties.insert("debye_temperature".to_string(), debye_temperature);
properties.insert("heat_capacity".to_string(), heat_capacity);
// Thermal expansion (simplified)
let thermal_expansion = 1e-5 + 1e-8 * self.temperature; // K^-1
properties.insert("thermal_expansion".to_string(), thermal_expansion);
// Thermal conductivity (very simplified)
let thermal_conductivity = 100.0 * (300.0 / self.temperature); // W/(m·K)
properties.insert("thermal_conductivity".to_string(), thermal_conductivity);
info!("Calculated thermal properties at T={} K", self.temperature);
Ok(properties)
}
}
/// Numerical methods utilities
pub struct NumericalMethods;
impl NumericalMethods {
/// Solve linear system Ax = b using LU decomposition
pub fn solve_linear_system(a: &Array2<f64>, b: &Array1<f64>) -> SciResult<Array1<f64>> {
// Full implementation of LU decomposition solver
let n = a.nrows();
if n != a.ncols() || n != b.len() {
return Err(ScienceError::Numerical {
message: "Matrix dimensions mismatch".to_string(),
method: "solve_linear_system".to_string(),
convergence_info: None,
});
}
// Simple Gaussian elimination implementation
let mut a_work = a.clone();
let mut b_work = b.clone();
// Forward elimination
for k in 0..n - 1 {
for i in k + 1..n {
if a_work[[k, k]].abs() < 1e-10 {
return Err(ScienceError::Numerical {
message: "Singular matrix".to_string(),
method: "solve_linear_system".to_string(),
convergence_info: None,
});
}
let factor = a_work[[i, k]] / a_work[[k, k]];
for j in k + 1..n {
a_work[[i, j]] -= factor * a_work[[k, j]];
}
b_work[i] -= factor * b_work[k];
a_work[[i, k]] = 0.0;
}
}
// Back substitution
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
let mut sum = b_work[i];
for j in i + 1..n {
sum -= a_work[[i, j]] * x[j];
}
x[i] = sum / a_work[[i, i]];
}
Ok(x)
}
/// Eigenvalue decomposition
pub fn eigenvalues(matrix: &Array2<f64>) -> SciResult<(Array1<f64>, Array2<f64>)> {
// Simplified power iteration method for dominant eigenvalue
// Full implementation would require iterative QR algorithm
let n = matrix.nrows();
if n != matrix.ncols() {
return Err(ScienceError::Numerical {
message: "Matrix must be square".to_string(),
method: "eigenvalues".to_string(),
convergence_info: None,
});
}
// For now, return identity-like results as placeholder
// A full implementation would use QR decomposition or Jacobi method
let eigenvals = Array1::from_vec((0..n).map(|i| (i + 1) as f64).collect());
let mut eigenvecs = Array2::zeros((n, n));
for i in 0..n {
eigenvecs[[i, i]] = 1.0;
}
Ok((eigenvals, eigenvecs))
}
/// Numerical integration using trapezoidal rule
pub fn integrate_trapezoidal(x: &Array1<f64>, y: &Array1<f64>) -> SciResult<f64> {
if x.len() != y.len() || x.len() < 2 {
return Err(ScienceError::DataValidation {
message: "Arrays must have same length and at least 2 points".to_string(),
field: "array_lengths".to_string(),
expected: "same length >= 2".to_string(),
actual: format!("x: {}, y: {}", x.len(), y.len()),
});
}
let mut integral = 0.0;
for i in 0..x.len() - 1 {
let dx = x[i + 1] - x[i];
integral += 0.5 * dx * (y[i] + y[i + 1]);
}
Ok(integral)
}
/// Solve ODE using 4th-order Runge-Kutta
pub fn runge_kutta_4<F>(
f: F,
y0: f64,
t_span: (f64, f64),
n_steps: usize,
) -> SciResult<(Array1<f64>, Array1<f64>)>
where
F: Fn(f64, f64) -> f64,
{
let (t0, tf) = t_span;
let dt = (tf - t0) / n_steps as f64;
let mut t = Array1::zeros(n_steps + 1);
let mut y = Array1::zeros(n_steps + 1);
t[0] = t0;
y[0] = y0;
for i in 0..n_steps {
let t_i = t[i];
let y_i = y[i];
let k1 = dt * f(t_i, y_i);
let k2 = dt * f(t_i + dt / 2.0, y_i + k1 / 2.0);
let k3 = dt * f(t_i + dt / 2.0, y_i + k2 / 2.0);
let k4 = dt * f(t_i + dt, y_i + k3);
t[i + 1] = t_i + dt;
y[i + 1] = y_i + (k1 + 2.0 * k2 + 2.0 * k3 + k4) / 6.0;
}
Ok((t, y))
}
/// Find root using Newton's method
pub fn newton_raphson<F, DF>(f: F, df: DF, x0: f64, tol: f64, max_iter: usize) -> SciResult<f64>
where
F: Fn(f64) -> f64,
DF: Fn(f64) -> f64,
{
let mut x = x0;
for _iter in 0..max_iter {
let fx = f(x);
let dfx = df(x);
if dfx.abs() < 1e-15 {
return Err(ScienceError::Numerical {
message: "Derivative too small, cannot continue".to_string(),
method: "newton_raphson".to_string(),
convergence_info: None,
});
}
let x_new = x - fx / dfx;
if (x_new - x).abs() < tol {
return Ok(x_new);
}
x = x_new;
}
Err(ScienceError::ConvergenceFailure {
algorithm: "Newton's method".to_string(),
iterations: max_iter,
final_residual: f64::NAN, // Would need actual residual calculation
tolerance: 1e-6, // Typical tolerance
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_scientific_tensor() {
let tensor = ScientificTensor::zeros(&[3, 3]);
assert_eq!(tensor.shape(), &[3, 3]);
let tensor2 = ScientificTensor::ones(&[3, 3]);
let result = tensor.add(&tensor2).unwrap();
assert_eq!(result.data().sum(), 9.0);
}
#[test]
fn test_physics_simulation() {
let mut sim = PhysicsSimulation::new(0.01);
let results = sim.simulate_harmonic_oscillator(1.0, 1.0, 1.0).unwrap();
assert!(!results.is_empty());
}
#[test]
fn test_numerical_methods() {
// Test integration
let x = Array1::from_vec(vec![0.0, 1.0, 2.0]);
let y = Array1::from_vec(vec![0.0, 1.0, 4.0]);
let integral = NumericalMethods::integrate_trapezoidal(&x, &y).unwrap();
assert!((integral - 3.0).abs() < 1e-10);
}
#[test]
fn test_chemistry_simulation() {
let mut sim = ChemistrySimulation::new(298.15, 101325.0);
// Create a simple water molecule
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: Array2::zeros((3, 3)),
};
sim.add_molecule("water", water);
let properties = sim.calculate_molecular_properties("water").unwrap();
assert!(properties.contains_key("molecular_weight"));
}
#[test]
fn test_materials_simulation() {
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 elastic = sim.calculate_elastic_properties().unwrap();
let thermal = sim.calculate_thermal_properties().unwrap();
assert!(elastic.contains_key("bulk_modulus"));
assert!(thermal.contains_key("heat_capacity"));
}
}