//! Integration tests for [`autodj`] #![allow( clippy::default_numeric_fallback, clippy::expect_used, clippy::float_cmp, clippy::indexing_slicing )] mod ideal_gas { use autodj::fluid::Dual; fn calc_gas_state>([pressure, volume, temperature, moles]: [D; 4]) -> D { const UGC: f64 = 8.314; pressure * volume - temperature * moles * D::parameter(UGC) } const ATM: f64 = 101_325.; const GOLDEN: f64 = 1.618_033_988_75; const KELVIN: f64 = 273.15; const BODY: f64 = KELVIN + 36.6; #[test] fn moles() { use autodj::prelude::single::*; let pressure = ATM.into(); let volume = GOLDEN.into(); let temperature = BODY.into(); let moles_initial = 1.0; let scalar_func = |m| calc_gas_state([pressure, volume, temperature, m]); let initial = moles_initial.into_variable().map(scalar_func); let (f, df) = initial.decompose(); // newton iteration let moles = moles_initial - f / df; let state = moles.into_variable().map(scalar_func); println!( r#" Initial guess: r({moles_initial}) = {initial} Update-------: r({moles}) = {:e}"#, state.value() ); } #[test] fn moles_volume() { use autodj::prelude::array::*; const W: f64 = 1.5; const WEIGHTS: [f64; 2] = [W, 1. - W]; let pressure = ATM.into(); let temperature = BODY.into(); let moles_initial = GOLDEN; let volume_initial = 1.0; let vector_func = |&[moles, volume]: &[DualNumber; 2]| { calc_gas_state([pressure, volume, temperature, moles]) }; let initial = vector_func(&[moles_initial, volume_initial].into_variables()); // Newton-like iteration let moles = moles_initial - WEIGHTS[0] * initial.value() / initial.dual().as_ref()[0]; let volume = volume_initial - WEIGHTS[1] * initial.value() / initial.dual().as_ref()[1]; let update = vector_func(&[moles, volume].into_variables()); println!( r#" Initial guess: r({moles_initial}, {volume_initial}) = {initial:e} Update-------: r({moles}, {volume}) = {:e}"#, update.value() ); } } mod vector { use autodj::prelude::vector::*; use std::ops::{Add, Mul}; #[test] fn vector_multiple() { fn sqps(x: &[DualF64]) -> DualF64 { let add: DualF64 = 1.0.into(); x.iter() .map(|x| x.powf(2.0).add_impl(&add)) .reduce(Add::add) .expect("nonzero slice length") } let x = vec![1., 2., 3.]; let result = sqps(x.clone().into_variables().as_slice()); println!("f{x:?} ≈ {result:?}"); assert_eq!(result.value(), &17.0); assert_eq!(result.dual().as_ref(), &[2., 4., 6.]); } #[test] fn sum() { let x = vec![1., 2., 3., 4., 5.]; let reference: f64 = x.iter().sum(); println!("f({x:?}) = ∑ x_i = {reference}"); let result: DualF64 = x .clone() .into_variables() .into_iter() .reduce(Add::add) .expect("nonzero slice length"); println!("f({x:?}) = {result:?}",); assert_eq!(result.value(), &reference); } #[test] fn product() { let x = vec![1., 2., 3., 4., 5.]; let reference: f64 = x.iter().product(); println!("f({x:?}) = ∏ x_i = {reference}"); let result = x.clone().into_variables().into_iter().reduce(Mul::mul); println!("f({x:?}) = {result:?}",); assert_eq!( result.map(|result| result.value().to_owned()), Some(reference) ); } #[test] fn product_owned() { let zero = [1.0, 2.0, 3.0] .into_variables() .iter() .map(|x| x.sub_impl(&1.0.into())) .reduce(Mul::mul); assert_eq!(zero.map(|result| result.value().to_owned()), Some(0.0)); } #[test] fn shifted_partial() { fn shifted_product(x: &[DualF64], threshold: f64) -> Option { x.iter() .filter(|x| x.value() < &threshold) .map(|x| x.sub_impl(&1.0.into())) .reduce(Mul::mul) } let values: Vec = vec![2., 3., 5., 8.]; let variables = values.clone().into_variables(); let f = shifted_product(variables.as_slice(), 6.).expect("At least two variables"); println!("f({values:?}) = {f:?}"); assert_eq!(f.value(), &8.); assert_eq!(f.dual().as_ref().len(), variables.len()); assert_eq!(f.dual().as_ref().last(), Some(&0.)); } #[test] fn div() { let variables = [1., 2.].into_variables(); let x = &variables[0]; let y = &variables[1]; let (f, df) = (x.div_impl(y)).decompose(); assert_eq!(df.as_ref(), &[0.5, -0.25]); assert_eq!(f, 0.5); } }