#![allow(dead_code)]
#[derive(Debug, Clone)]
pub struct ThermoMechanical {
pub youngs_modulus: f64,
pub poisson_ratio: f64,
pub cte: f64,
pub thermal_conductivity: f64,
pub density: f64,
pub specific_heat: f64,
pub t_ref: f64,
}
impl ThermoMechanical {
pub fn steel() -> Self {
Self {
youngs_modulus: 200e9,
poisson_ratio: 0.3,
cte: 12e-6,
thermal_conductivity: 50.0,
density: 7850.0,
specific_heat: 500.0,
t_ref: 293.15,
}
}
#[allow(clippy::too_many_arguments)]
pub fn new(
youngs_modulus: f64,
poisson_ratio: f64,
cte: f64,
thermal_conductivity: f64,
density: f64,
specific_heat: f64,
t_ref: f64,
) -> Self {
Self {
youngs_modulus,
poisson_ratio,
cte,
thermal_conductivity,
density,
specific_heat,
t_ref,
}
}
pub fn thermal_strain(&self, temperature: f64) -> f64 {
self.cte * (temperature - self.t_ref)
}
pub fn thermal_stress_uniaxial(&self, temperature: f64) -> f64 {
-self.youngs_modulus * self.thermal_strain(temperature)
}
pub fn thermal_diffusivity(&self) -> f64 {
self.thermal_conductivity / (self.density * self.specific_heat)
}
pub fn lame_lambda(&self) -> f64 {
self.youngs_modulus * self.poisson_ratio
/ ((1.0 + self.poisson_ratio) * (1.0 - 2.0 * self.poisson_ratio))
}
pub fn lame_mu(&self) -> f64 {
self.youngs_modulus / (2.0 * (1.0 + self.poisson_ratio))
}
pub fn hydrostatic_thermal_stress(&self, temperature: f64) -> f64 {
let k_bulk = self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio));
-3.0 * k_bulk * self.cte * (temperature - self.t_ref)
}
pub fn thermoelastic_dissipation(&self, strain_rate: f64, temperature: f64) -> f64 {
let k_bulk = self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio));
-3.0 * k_bulk * self.cte * temperature * strain_rate
}
}
#[derive(Debug, Clone)]
pub struct PiezoElectric {
pub compliance: [f64; 4],
pub d_matrix: [f64; 3],
pub permittivity: [f64; 2],
pub name: String,
}
impl PiezoElectric {
pub fn pzt5h() -> Self {
Self {
compliance: [16.5e-12, -4.78e-12, 20.7e-12, 43.5e-12],
d_matrix: [-274e-12, 593e-12, 741e-12],
permittivity: [3130.0 * 8.854e-12, 3400.0 * 8.854e-12],
name: "PZT-5H".to_string(),
}
}
pub fn pvdf() -> Self {
Self {
compliance: [365e-12, -145e-12, 472e-12, 1600e-12],
d_matrix: [23e-12, -33e-12, -27e-12],
permittivity: [12.0 * 8.854e-12, 12.0 * 8.854e-12],
name: "PVDF".to_string(),
}
}
pub fn direct_effect_d33(&self, stress_33: f64) -> f64 {
self.d_matrix[1] * stress_33
}
pub fn converse_effect_d33(&self, e_field_3: f64) -> f64 {
self.d_matrix[1] * e_field_3
}
pub fn coupling_k33(&self) -> f64 {
let d33 = self.d_matrix[1];
let s33 = self.compliance[2];
let eps33 = self.permittivity[1];
d33 / (s33 * eps33).sqrt()
}
pub fn stiffened_modulus_33(&self) -> f64 {
let d33 = self.d_matrix[1];
let s33 = self.compliance[2];
let eps33 = self.permittivity[1];
(s33 - d33 * d33 / eps33).recip()
}
}
#[derive(Debug, Clone)]
pub struct Magnetostrictive {
pub lambda_s: f64,
pub m_saturation: f64,
pub youngs_modulus: f64,
pub d_coeff: f64,
pub coercive_field: f64,
}
impl Magnetostrictive {
pub fn terfenol_d() -> Self {
Self {
lambda_s: 1750e-6,
m_saturation: 7.65e5,
youngs_modulus: 30e9,
d_coeff: 1.67e-8,
coercive_field: 10e3,
}
}
pub fn magnetostriction(&self, magnetization: f64) -> f64 {
let m_norm = (magnetization / self.m_saturation).clamp(-1.0, 1.0);
1.5 * self.lambda_s * m_norm * m_norm
}
pub fn villari_effect(&self, stress: f64, h_field: f64) -> f64 {
self.d_coeff * stress * h_field / self.coercive_field
}
pub fn preisach_hysteron(&self, h: f64, alpha: f64, beta: f64, state: bool) -> bool {
if h > alpha {
true
} else if h < beta {
false
} else {
state
}
}
pub fn magnetization_curve(&self, h_field: f64) -> f64 {
let a = self.coercive_field;
let x = h_field / a;
self.m_saturation * x.tanh()
}
}
#[derive(Debug, Clone)]
pub struct ElectrochemicalMaterial {
pub exchange_current_density: f64,
pub alpha: f64,
pub diffusion_coefficient: f64,
pub partial_molar_volume: f64,
pub youngs_modulus: f64,
pub poisson_ratio: f64,
pub faraday: f64,
}
impl ElectrochemicalMaterial {
const F: f64 = 96485.0;
const R: f64 = 8.314;
pub fn lithium_graphite() -> Self {
Self {
exchange_current_density: 2.0,
alpha: 0.5,
diffusion_coefficient: 3.9e-14,
partial_molar_volume: 3.497e-6,
youngs_modulus: 10e9,
poisson_ratio: 0.3,
faraday: Self::F,
}
}
pub fn butler_volmer(&self, overpotential: f64, temperature: f64) -> f64 {
let f_rt = Self::F / (Self::R * temperature);
self.exchange_current_density
* ((self.alpha * f_rt * overpotential).exp()
- (-(1.0 - self.alpha) * f_rt * overpotential).exp())
}
pub fn diffusion_flux(&self, concentration_gradient: f64) -> f64 {
-self.diffusion_coefficient * concentration_gradient
}
pub fn intercalation_stress(&self, concentration: f64, c_avg: f64) -> f64 {
let prefactor =
self.partial_molar_volume * self.youngs_modulus / (3.0 * (1.0 - self.poisson_ratio));
-prefactor * (concentration - c_avg)
}
pub fn nernst_shift(&self, c_norm: f64, temperature: f64) -> f64 {
let c = c_norm.clamp(1e-10, 1.0 - 1e-10);
Self::R * temperature / Self::F * (c / (1.0 - c)).ln()
}
}
#[derive(Debug, Clone)]
pub struct PoroElastic {
pub youngs_modulus: f64,
pub poisson_ratio: f64,
pub biot_coefficient: f64,
pub permeability: f64,
pub fluid_viscosity: f64,
pub biot_modulus: f64,
}
impl PoroElastic {
pub fn saturated_clay() -> Self {
Self {
youngs_modulus: 20e6,
poisson_ratio: 0.35,
biot_coefficient: 0.9,
permeability: 1e-13,
fluid_viscosity: 1e-3,
biot_modulus: 1e9,
}
}
pub fn sandstone() -> Self {
Self {
youngs_modulus: 15e9,
poisson_ratio: 0.25,
biot_coefficient: 0.7,
permeability: 1e-13,
fluid_viscosity: 1e-3,
biot_modulus: 20e9,
}
}
pub fn consolidation_coefficient(&self) -> f64 {
self.permeability * self.biot_modulus / self.fluid_viscosity
}
pub fn effective_stress(&self, total_stress: f64, pore_pressure: f64) -> f64 {
total_stress - self.biot_coefficient * pore_pressure
}
pub fn darcy_velocity(&self, pressure_gradient: f64) -> f64 {
-(self.permeability / self.fluid_viscosity) * pressure_gradient
}
pub fn skempton_coefficient(&self) -> f64 {
let k_bulk = self.youngs_modulus / (3.0 * (1.0 - 2.0 * self.poisson_ratio));
let b = self.biot_coefficient;
let m = self.biot_modulus;
b * m / (k_bulk + b * b * m)
}
}
#[derive(Debug, Clone)]
pub struct SwellingMaterial {
pub hygroscopic_coeff: f64,
pub osmotic_coeff: f64,
pub crosslink_density: f64,
pub flory_chi: f64,
pub temperature: f64,
}
impl SwellingMaterial {
const R: f64 = 8.314;
pub fn hydrogel() -> Self {
Self {
hygroscopic_coeff: 0.002,
osmotic_coeff: 2479.0, crosslink_density: 100.0,
flory_chi: 0.5,
temperature: 298.15,
}
}
pub fn hygroscopic_strain(&self, delta_rh: f64) -> f64 {
self.hygroscopic_coeff * delta_rh
}
pub fn osmotic_pressure(&self, concentration: f64) -> f64 {
concentration * Self::R * self.temperature
}
pub fn flory_rehner_elastic_energy(&self, swelling_ratio: f64) -> f64 {
let nu = self.crosslink_density;
let kt = Self::R * self.temperature / 6.022e23;
1.5 * nu * kt * (swelling_ratio * swelling_ratio - 1.0 - swelling_ratio.ln())
}
pub fn swelling_equilibrium_condition(&self, phi: f64) -> f64 {
let phi = phi.clamp(1e-5, 1.0 - 1e-5);
let mixing = (1.0 - phi).ln() + phi + self.flory_chi * phi * phi;
let elastic = self.crosslink_density * (phi.powf(1.0 / 3.0) - 0.5 * phi);
mixing + elastic
}
}
#[derive(Debug, Clone)]
pub struct RadiationDamage {
pub threshold_energy_ev: f64,
pub atomic_density: f64,
pub yield_strength_initial: f64,
pub dpa: f64,
pub void_fraction: f64,
pub helium_conc_appm: f64,
}
impl RadiationDamage {
pub fn ss316l() -> Self {
Self {
threshold_energy_ev: 40.0,
atomic_density: 8.5e28,
yield_strength_initial: 220e6,
dpa: 0.0,
void_fraction: 0.0,
helium_conc_appm: 0.0,
}
}
pub fn compute_dpa(&self, flux: f64, cross_section: f64, time: f64) -> f64 {
flux * cross_section * time
/ (self.atomic_density * 2.0 * self.threshold_energy_ev * 1.6e-19)
}
pub fn irradiation_hardening(&self, k_hardening: f64) -> f64 {
k_hardening * self.dpa.sqrt()
}
pub fn yield_strength(&self, k_hardening: f64) -> f64 {
self.yield_strength_initial + self.irradiation_hardening(k_hardening)
}
pub fn void_swelling_rate(&self, temperature_k: f64, a_coeff: f64, b_coeff: f64) -> f64 {
a_coeff * (-b_coeff / temperature_k).exp()
}
pub fn bubble_radius(&self, bubble_density: f64) -> f64 {
let c_he = self.helium_conc_appm * 1e-6 * self.atomic_density;
(3.0 * c_he / (4.0 * std::f64::consts::PI * bubble_density)).cbrt()
}
pub fn dbtt_shift_k(&self) -> f64 {
20.0 * self.dpa
}
}
#[derive(Debug, Clone)]
pub struct PhaseTransformMaterial {
pub ea: f64,
pub em: f64,
pub max_strain: f64,
pub as_temp: f64,
pub af_temp: f64,
pub ms_temp: f64,
pub mf_temp: f64,
pub xi: f64,
}
impl PhaseTransformMaterial {
pub fn nitinol() -> Self {
Self {
ea: 75e9,
em: 30e9,
max_strain: 0.08,
as_temp: 291.0,
af_temp: 307.0,
ms_temp: 291.0,
mf_temp: 271.0,
xi: 0.0,
}
}
pub fn effective_modulus(&self) -> f64 {
self.ea + self.xi * (self.em - self.ea)
}
pub fn martensite_fraction_cooling(&self, temperature: f64) -> f64 {
if temperature >= self.ms_temp {
0.0
} else if temperature <= self.mf_temp {
1.0
} else {
let b_m = std::f64::consts::PI / (self.ms_temp - self.mf_temp);
0.5 * (1.0 - (b_m * (temperature - (self.ms_temp + self.mf_temp) / 2.0)).cos())
}
}
pub fn martensite_fraction_heating(&self, temperature: f64) -> f64 {
if temperature >= self.af_temp {
0.0
} else if temperature <= self.as_temp {
self.xi } else {
let b_a = std::f64::consts::PI / (self.af_temp - self.as_temp);
self.xi * 0.5 * (1.0 + (b_a * (temperature - self.as_temp)).cos())
}
}
pub fn transformation_stress(&self, strain: f64) -> f64 {
let eps = strain.clamp(0.0, self.max_strain);
let xi_new = eps / self.max_strain;
let e_eff = self.ea + xi_new * (self.em - self.ea);
e_eff * (eps - xi_new * self.max_strain)
}
}
#[derive(Debug, Clone)]
pub struct ElectroMagnetic {
pub rel_permeability: f64,
pub conductivity: f64,
pub steinmetz_k_h: f64,
pub steinmetz_n: f64,
pub eddy_current_k_e: f64,
pub frequency: f64,
}
impl ElectroMagnetic {
const MU0: f64 = 1.256_637_062e-6;
pub fn silicon_steel() -> Self {
Self {
rel_permeability: 5000.0,
conductivity: 2e6,
steinmetz_k_h: 40.0,
steinmetz_n: 1.8,
eddy_current_k_e: 0.5,
frequency: 50.0,
}
}
pub fn permeability(&self) -> f64 {
Self::MU0 * self.rel_permeability
}
pub fn skin_depth(&self) -> f64 {
let omega = 2.0 * std::f64::consts::PI * self.frequency;
(2.0 / (omega * self.conductivity * self.permeability())).sqrt()
}
pub fn hysteresis_loss(&self, b_max: f64) -> f64 {
self.steinmetz_k_h * self.frequency * b_max.powf(self.steinmetz_n)
}
pub fn eddy_current_loss(&self, b_max: f64) -> f64 {
self.eddy_current_k_e * self.frequency.powi(2) * b_max.powi(2)
}
pub fn total_core_loss(&self, b_max: f64) -> f64 {
self.hysteresis_loss(b_max) + self.eddy_current_loss(b_max)
}
pub fn inductance_per_length(&self, n_turns_per_m: f64, area_m2: f64) -> f64 {
self.permeability() * n_turns_per_m * n_turns_per_m * area_m2
}
}
pub struct CoupledSolver {
pub tolerance: f64,
pub max_iter: usize,
pub relaxation: f64,
}
impl CoupledSolver {
pub fn new(tolerance: f64, max_iter: usize, relaxation: f64) -> Self {
Self {
tolerance,
max_iter,
relaxation,
}
}
pub fn operator_split<FA, FB>(
&self,
a0: f64,
b0: f64,
solve_a: FA,
solve_b: FB,
) -> (f64, f64, usize)
where
FA: Fn(f64) -> f64,
FB: Fn(f64) -> f64,
{
let mut a = a0;
let mut b = b0;
for iter in 0..self.max_iter {
let a_new = solve_a(b);
let b_new = solve_b(a_new);
let err = ((a_new - a).powi(2) + (b_new - b).powi(2)).sqrt();
a = a * (1.0 - self.relaxation) + a_new * self.relaxation;
b = b * (1.0 - self.relaxation) + b_new * self.relaxation;
if err < self.tolerance {
return (a, b, iter + 1);
}
}
(a, b, self.max_iter)
}
pub fn staggered<F>(&self, state0: &[f64], step_fn: F) -> (Vec<f64>, usize)
where
F: Fn(&[f64]) -> Vec<f64>,
{
let mut state = state0.to_vec();
for iter in 0..self.max_iter {
let state_new = step_fn(&state);
let err = state
.iter()
.zip(state_new.iter())
.map(|(a, b)| (a - b).powi(2))
.sum::<f64>()
.sqrt();
let state_relaxed: Vec<f64> = state
.iter()
.zip(state_new.iter())
.map(|(a, b)| a * (1.0 - self.relaxation) + b * self.relaxation)
.collect();
state = state_relaxed;
if err < self.tolerance {
return (state, iter + 1);
}
}
(state, self.max_iter)
}
pub fn monolithic_newton<F>(&self, x0: &[f64], residual: F) -> (Vec<f64>, usize)
where
F: Fn(&[f64]) -> Vec<f64>,
{
let n = x0.len();
let mut x = x0.to_vec();
let eps = 1e-7;
for iter in 0..self.max_iter {
let r = residual(&x);
let r_norm = r.iter().map(|v| v * v).sum::<f64>().sqrt();
if r_norm < self.tolerance {
return (x, iter);
}
let mut j = vec![0.0_f64; n * n];
for j_col in 0..n {
let mut x_pert = x.clone();
x_pert[j_col] += eps;
let r_pert = residual(&x_pert);
for i in 0..n {
j[i * n + j_col] = (r_pert[i] - r[i]) / eps;
}
}
let dx = self.gaussian_solve(&j, &r.iter().map(|&v| -v).collect::<Vec<_>>(), n);
for i in 0..n {
x[i] += self.relaxation * dx[i];
}
}
(x, self.max_iter)
}
fn gaussian_solve(&self, a: &[f64], b: &[f64], n: usize) -> Vec<f64> {
let mut aug: Vec<f64> = (0..n)
.flat_map(|i| {
let mut row: Vec<f64> = a[i * n..(i + 1) * n].to_vec();
row.push(b[i]);
row
})
.collect();
for col in 0..n {
let mut max_row = col;
let mut max_val = aug[col * (n + 1) + col].abs();
for row in col + 1..n {
let v = aug[row * (n + 1) + col].abs();
if v > max_val {
max_val = v;
max_row = row;
}
}
for j in 0..=n {
aug.swap(col * (n + 1) + j, max_row * (n + 1) + j);
}
let pivot = aug[col * (n + 1) + col];
if pivot.abs() < 1e-15 {
continue;
}
for row in col + 1..n {
let factor = aug[row * (n + 1) + col] / pivot;
for j in col..=n {
let v = aug[col * (n + 1) + j];
aug[row * (n + 1) + j] -= factor * v;
}
}
}
let mut x = vec![0.0_f64; n];
for i in (0..n).rev() {
let mut sum = aug[i * (n + 1) + n];
for j in i + 1..n {
sum -= aug[i * (n + 1) + j] * x[j];
}
let diag = aug[i * (n + 1) + i];
x[i] = if diag.abs() > 1e-15 { sum / diag } else { 0.0 };
}
x
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_thermo_mechanical_thermal_strain() {
let mat = ThermoMechanical::steel();
let eps = mat.thermal_strain(393.15); assert!((eps - 12e-6 * 100.0).abs() < 1e-15);
}
#[test]
fn test_thermo_mechanical_diffusivity() {
let mat = ThermoMechanical::steel();
let diff = mat.thermal_diffusivity();
assert!((diff - 50.0 / (7850.0 * 500.0)).abs() < 1e-15);
}
#[test]
fn test_thermo_mechanical_lame() {
let mat = ThermoMechanical::steel();
assert!(mat.lame_lambda() > 0.0);
assert!(mat.lame_mu() > 0.0);
}
#[test]
fn test_thermo_mechanical_hydrostatic_stress() {
let mat = ThermoMechanical::steel();
let sigma = mat.hydrostatic_thermal_stress(393.15);
assert!(sigma < 0.0); }
#[test]
fn test_piezo_pzt5h_coupling() {
let pz = PiezoElectric::pzt5h();
let k33 = pz.coupling_k33();
assert!(k33 > 0.0 && k33 < 1.0);
}
#[test]
fn test_piezo_direct_effect() {
let pz = PiezoElectric::pzt5h();
let d = pz.direct_effect_d33(1e6); assert!(d.abs() > 0.0);
}
#[test]
fn test_piezo_converse_effect() {
let pz = PiezoElectric::pzt5h();
let s = pz.converse_effect_d33(1e6); assert!(s.abs() > 0.0);
}
#[test]
fn test_piezo_pvdf_name() {
let pz = PiezoElectric::pvdf();
assert_eq!(pz.name, "PVDF");
}
#[test]
fn test_magnetostrictive_magnetostriction() {
let ms = Magnetostrictive::terfenol_d();
let eps = ms.magnetostriction(ms.m_saturation);
assert!((eps - 1.5 * ms.lambda_s).abs() < 1e-15);
}
#[test]
fn test_magnetostrictive_curve() {
let ms = Magnetostrictive::terfenol_d();
let m = ms.magnetization_curve(0.0);
assert!(m.abs() < 1e-10); }
#[test]
fn test_electrochemical_butler_volmer() {
let ec = ElectrochemicalMaterial::lithium_graphite();
let j = ec.butler_volmer(0.0, 298.0);
assert!(j.abs() < 1e-10); }
#[test]
fn test_electrochemical_butler_volmer_positive_eta() {
let ec = ElectrochemicalMaterial::lithium_graphite();
let j = ec.butler_volmer(0.05, 298.0);
assert!(j > 0.0); }
#[test]
fn test_electrochemical_intercalation_stress() {
let ec = ElectrochemicalMaterial::lithium_graphite();
let sigma = ec.intercalation_stress(1000.0, 800.0);
assert!(sigma < 0.0); }
#[test]
fn test_poroelastic_consolidation_coeff() {
let pe = PoroElastic::saturated_clay();
let cv = pe.consolidation_coefficient();
assert!(cv > 0.0);
}
#[test]
fn test_poroelastic_effective_stress() {
let pe = PoroElastic::saturated_clay();
let sigma_eff = pe.effective_stress(-100e3, 50e3);
assert!((sigma_eff - (-100e3 - 0.9 * 50e3)).abs() < 1.0);
}
#[test]
fn test_poroelastic_darcy_velocity() {
let pe = PoroElastic::saturated_clay();
let q = pe.darcy_velocity(1000.0); assert!(q < 0.0); }
#[test]
fn test_swelling_hygroscopic_strain() {
let sw = SwellingMaterial::hydrogel();
let eps = sw.hygroscopic_strain(0.5); assert!((eps - 0.002 * 0.5).abs() < 1e-15);
}
#[test]
fn test_swelling_osmotic_pressure() {
let sw = SwellingMaterial::hydrogel();
let pi = sw.osmotic_pressure(1.0); assert!((pi - 1.0 * 8.314 * 298.15).abs() < 1.0);
}
#[test]
fn test_radiation_damage_dpa() {
let rd = RadiationDamage::ss316l();
let dpa = rd.compute_dpa(1e15, 1e-28, 3.15e7); assert!(dpa > 0.0);
}
#[test]
fn test_radiation_damage_hardening() {
let mut rd = RadiationDamage::ss316l();
rd.dpa = 10.0;
let delta_sy = rd.irradiation_hardening(100e6);
assert!((delta_sy - 100e6 * 10.0_f64.sqrt()).abs() < 1.0);
}
#[test]
fn test_radiation_damage_dbtt() {
let mut rd = RadiationDamage::ss316l();
rd.dpa = 5.0;
let shift = rd.dbtt_shift_k();
assert!((shift - 100.0).abs() < 1e-10);
}
#[test]
fn test_phase_transform_martensite_fraction() {
let sma = PhaseTransformMaterial::nitinol();
let xi = sma.martensite_fraction_cooling(261.0); assert!((xi - 1.0).abs() < 1e-10);
let xi2 = sma.martensite_fraction_cooling(310.0); assert!(xi2.abs() < 1e-10);
}
#[test]
fn test_phase_transform_effective_modulus() {
let mut sma = PhaseTransformMaterial::nitinol();
sma.xi = 0.0;
assert!((sma.effective_modulus() - sma.ea).abs() < 1.0);
sma.xi = 1.0;
assert!((sma.effective_modulus() - sma.em).abs() < 1.0);
}
#[test]
fn test_electromagnetic_skin_depth() {
let em = ElectroMagnetic::silicon_steel();
let delta = em.skin_depth();
assert!(delta > 0.0 && delta < 1.0); }
#[test]
fn test_electromagnetic_hysteresis_loss() {
let em = ElectroMagnetic::silicon_steel();
let ph = em.hysteresis_loss(1.5); assert!(ph > 0.0);
}
#[test]
fn test_electromagnetic_total_loss_increases_with_b() {
let em = ElectroMagnetic::silicon_steel();
let p1 = em.total_core_loss(1.0);
let p2 = em.total_core_loss(1.5);
assert!(p2 > p1);
}
#[test]
fn test_coupled_solver_operator_split() {
let solver = CoupledSolver::new(1e-6, 100, 1.0);
let (a, b, _iter) = solver.operator_split(0.0, 0.0, |b| b + 1.0, |a| a - 1.0);
assert!((a - b - 1.0).abs() < 1e-5);
}
#[test]
fn test_coupled_solver_staggered() {
let solver = CoupledSolver::new(1e-8, 100, 0.5);
let (state, _iter) = solver.staggered(&[1.0, 1.0], |s| vec![s[0] * 0.5, s[1] * 0.5]);
assert!(state[0].abs() < 0.01);
assert!(state[1].abs() < 0.01);
}
#[test]
fn test_coupled_solver_newton_linear() {
let solver = CoupledSolver::new(1e-10, 20, 1.0);
let (x, _) = solver.monolithic_newton(&[0.0], |s| vec![s[0] - 3.0]);
assert!((x[0] - 3.0).abs() < 1e-6);
}
}