use crate::math::constants;
pub const FARADAY: f64 = 96485.0;
pub fn arrhenius_rate(pre_exponential: f64, activation_energy: f64, temperature: f64) -> f64 {
assert!(temperature > 0.0, "temperature must be positive");
pre_exponential * (-activation_energy / (constants::R * temperature)).exp()
}
pub fn half_life_first_order(rate_constant: f64) -> f64 {
assert!(rate_constant > 0.0, "rate_constant must be positive");
f64::ln(2.0) / rate_constant
}
pub fn concentration_first_order(c0: f64, rate_constant: f64, time: f64) -> f64 {
c0 * (-rate_constant * time).exp()
}
pub fn concentration_second_order(c0: f64, rate_constant: f64, time: f64) -> f64 {
assert!(c0 > 0.0, "initial concentration must be positive");
1.0 / (1.0 / c0 + rate_constant * time)
}
pub fn reaction_rate(k: f64, concentrations: &[f64], orders: &[f64]) -> f64 {
assert_eq!(
concentrations.len(),
orders.len(),
"concentrations and orders must have equal length"
);
let product: f64 = concentrations
.iter()
.zip(orders.iter())
.map(|(c, n)| c.powf(*n))
.product();
k * product
}
pub fn gibbs_free_energy(enthalpy: f64, temperature: f64, entropy: f64) -> f64 {
enthalpy - temperature * entropy
}
pub fn equilibrium_constant_from_gibbs(delta_g: f64, temperature: f64) -> f64 {
assert!(temperature > 0.0, "temperature must be positive");
(-delta_g / (constants::R * temperature)).exp()
}
pub fn vant_hoff(k1: f64, delta_h: f64, t1: f64, t2: f64) -> f64 {
assert!(t1 > 0.0, "t1 must be positive");
assert!(t2 > 0.0, "t2 must be positive");
let exponent = -delta_h / constants::R * (1.0 / t2 - 1.0 / t1);
k1 * exponent.exp()
}
pub fn hess_law(enthalpies: &[f64], coefficients: &[f64]) -> f64 {
assert_eq!(
enthalpies.len(),
coefficients.len(),
"enthalpies and coefficients must have equal length"
);
enthalpies
.iter()
.zip(coefficients.iter())
.map(|(h, c)| c * h)
.sum()
}
pub fn nernst_potential(
e_standard: f64,
temperature: f64,
n_electrons: f64,
reaction_quotient: f64,
) -> f64 {
assert!(n_electrons > 0.0, "n_electrons must be positive");
assert!(reaction_quotient > 0.0, "reaction_quotient must be positive");
e_standard
- (constants::R * temperature / (n_electrons * FARADAY)) * reaction_quotient.ln()
}
pub fn cell_potential(e_cathode: f64, e_anode: f64) -> f64 {
e_cathode - e_anode
}
pub fn faraday_electrolysis(
current: f64,
time: f64,
molar_mass: f64,
n_electrons: f64,
) -> f64 {
assert!(n_electrons > 0.0, "n_electrons must be positive");
(current * time * molar_mass) / (n_electrons * FARADAY)
}
pub fn ph(h_concentration: f64) -> f64 {
assert!(h_concentration > 0.0, "h_concentration must be positive");
-h_concentration.log10()
}
pub fn poh(oh_concentration: f64) -> f64 {
assert!(oh_concentration > 0.0, "oh_concentration must be positive");
-oh_concentration.log10()
}
pub fn h_from_ph(ph: f64) -> f64 {
10.0_f64.powf(-ph)
}
pub fn osmotic_pressure(molarity: f64, temperature: f64, i_factor: f64) -> f64 {
i_factor * molarity * constants::R * temperature
}
pub fn molarity(moles: f64, volume_liters: f64) -> f64 {
assert!(volume_liters > 0.0, "volume_liters must be positive");
moles / volume_liters
}
pub fn dilution(c1: f64, v1: f64, v2: f64) -> f64 {
assert!(v2 > 0.0, "v2 must be positive");
c1 * v1 / v2
}
#[cfg(test)]
mod tests {
use super::*;
const TOLERANCE: f64 = 1e-6;
fn approx(a: f64, b: f64) -> bool {
(a - b).abs() < TOLERANCE
}
fn approx_rel(a: f64, b: f64, rel_tol: f64) -> bool {
if b == 0.0 {
a.abs() < TOLERANCE
} else {
((a - b) / b).abs() < rel_tol
}
}
#[test]
fn test_arrhenius_rate() {
let k = arrhenius_rate(1e13, 75000.0, 300.0);
assert!(approx_rel(k, 0.874_168, 1e-4));
}
#[test]
fn test_half_life_first_order() {
let t = half_life_first_order(0.05);
assert!(approx_rel(t, 13.862_944, 1e-4));
}
#[test]
fn test_concentration_first_order() {
let c = concentration_first_order(1.0, 0.1, 10.0);
assert!(approx_rel(c, 0.367_879_441, 1e-6));
}
#[test]
fn test_concentration_second_order() {
let c = concentration_second_order(1.0, 0.5, 2.0);
assert!(approx(c, 0.5));
}
#[test]
fn test_reaction_rate() {
let r = reaction_rate(0.5, &[2.0, 3.0], &[1.0, 2.0]);
assert!(approx(r, 9.0));
}
#[test]
#[should_panic(expected = "concentrations and orders must have equal length")]
fn test_reaction_rate_mismatched_lengths() {
reaction_rate(1.0, &[1.0, 2.0], &[1.0]);
}
#[test]
fn test_gibbs_free_energy() {
let dg = gibbs_free_energy(-100000.0, 298.15, -200.0);
assert!(approx(dg, -40370.0));
}
#[test]
fn test_equilibrium_constant_from_gibbs() {
let k = equilibrium_constant_from_gibbs(0.0, 298.15);
assert!(approx(k, 1.0));
}
#[test]
fn test_equilibrium_constant_negative_dg() {
let k = equilibrium_constant_from_gibbs(-5000.0, 298.15);
assert!(approx_rel(k, 7.5158, 1e-3));
}
#[test]
fn test_vant_hoff() {
let k2 = vant_hoff(1.0, -40000.0, 300.0, 350.0);
assert!(approx_rel(k2, 0.10117, 1e-3));
}
#[test]
fn test_hess_law() {
let dh = hess_law(&[-100.0, 50.0, -200.0], &[1.0, -2.0, 1.0]);
assert!(approx(dh, -400.0));
}
#[test]
#[should_panic(expected = "enthalpies and coefficients must have equal length")]
fn test_hess_law_mismatched_lengths() {
hess_law(&[1.0], &[1.0, 2.0]);
}
#[test]
fn test_nernst_potential_standard_conditions() {
let e = nernst_potential(0.76, 298.15, 2.0, 1.0);
assert!(approx(e, 0.76));
}
#[test]
fn test_nernst_potential_nonstandard() {
let e = nernst_potential(0.76, 298.15, 2.0, 0.01);
assert!(approx_rel(e, 0.819_15, 1e-3));
}
#[test]
fn test_cell_potential() {
let e = cell_potential(0.34, -0.76);
assert!(approx(e, 1.10));
}
#[test]
fn test_faraday_electrolysis() {
let m = faraday_electrolysis(10.0, 3600.0, 63.546, 2.0);
assert!(approx_rel(m, 11.8553, 1e-3));
}
#[test]
fn test_ph() {
let p = ph(1e-7);
assert!(approx(p, 7.0));
}
#[test]
fn test_poh() {
let p = poh(1e-7);
assert!(approx(p, 7.0));
}
#[test]
fn test_h_from_ph() {
let h = h_from_ph(7.0);
assert!(approx_rel(h, 1e-7, 1e-9));
}
#[test]
fn test_ph_h_roundtrip() {
let original_ph = 4.5;
let h = h_from_ph(original_ph);
let recovered = ph(h);
assert!(approx(recovered, original_ph));
}
#[test]
fn test_osmotic_pressure() {
let pi = osmotic_pressure(0.1, 298.15, 2.0);
assert!(approx_rel(pi, 495.79, 1e-3));
}
#[test]
fn test_molarity() {
let m = molarity(0.5, 2.0);
assert!(approx(m, 0.25));
}
#[test]
fn test_dilution() {
let c2 = dilution(1.0, 0.5, 2.0);
assert!(approx(c2, 0.25));
}
#[test]
fn test_approx_rel_zero_b() {
assert!(approx_rel(0.0, 0.0, 0.01));
assert!(!approx_rel(1.0, 0.0, 0.01));
}
}