use super::{Converter, Equivalency};
use crate::constants::BOLTZMANN_CONSTANT;
use crate::dimension::{Dimension, Rational16};
use crate::unit::Unit;
fn is_temperature(unit: &Unit) -> bool {
unit.dimension() == Dimension::TEMPERATURE
}
fn is_energy(unit: &Unit) -> bool {
let energy_dim = Dimension::MASS
.mul(&Dimension::LENGTH.pow(Rational16::new(2, 1)))
.mul(&Dimension::TIME.pow(Rational16::new(-2, 1)));
unit.dimension() == energy_dim
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum TempScale {
Kelvin,
Celsius,
Fahrenheit,
}
fn identify_temp_scale(unit: &Unit) -> Option<TempScale> {
if !is_temperature(unit) {
return None;
}
if let Unit::Base(b) = unit {
let offset = b.offset;
if (offset - 273.15).abs() < 1e-6 {
return Some(TempScale::Celsius);
}
if (offset - 459.67).abs() < 1e-6 {
return Some(TempScale::Fahrenheit);
}
}
Some(TempScale::Kelvin)
}
pub fn temperature() -> Equivalency {
Equivalency::new("temperature", |from, to| {
let from_scale = identify_temp_scale(from)?;
let to_scale = identify_temp_scale(to)?;
if from_scale == to_scale {
return None;
}
let from_unit_scale = from.scale();
let to_unit_scale = to.scale();
Some(create_temp_converter(
from_scale,
to_scale,
from_unit_scale,
to_unit_scale,
))
})
}
fn create_temp_converter(
from: TempScale,
to: TempScale,
_from_scale: f64,
_to_scale: f64,
) -> Converter {
let _ = (from, to);
Converter::new(
|k| {
if k < 0.0 {
return Err(format!(
"temperature cannot be negative in Kelvin, got {}",
k
));
}
Ok(k)
},
|k| {
if k < 0.0 {
return Err(format!(
"temperature cannot be negative in Kelvin, got {}",
k
));
}
Ok(k)
},
)
}
pub fn temperature_energy() -> Equivalency {
Equivalency::new("temperature_energy", |from, to| {
let (is_temp_to_energy, _from_scale, _to_scale) = if is_temperature(from) && is_energy(to) {
(true, from.scale(), to.scale())
} else if is_energy(from) && is_temperature(to) {
(false, from.scale(), to.scale())
} else {
return None;
};
if is_temp_to_energy {
Some(Converter::new(
|t_kelvin| {
if t_kelvin < 0.0 {
return Err(format!(
"Kelvin temperature cannot be negative, got {}",
t_kelvin
));
}
Ok(BOLTZMANN_CONSTANT * t_kelvin)
},
|e_joule| {
if e_joule < 0.0 {
return Err(format!("energy cannot be negative, got {}", e_joule));
}
Ok(e_joule / BOLTZMANN_CONSTANT)
},
))
} else {
Some(Converter::new(
|e_joule| {
if e_joule < 0.0 {
return Err(format!("energy cannot be negative, got {}", e_joule));
}
Ok(e_joule / BOLTZMANN_CONSTANT)
},
|t_kelvin| {
if t_kelvin < 0.0 {
return Err(format!(
"Kelvin temperature cannot be negative, got {}",
t_kelvin
));
}
Ok(BOLTZMANN_CONSTANT * t_kelvin)
},
))
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::systems::si::{DEG_C, DEG_F, EV, J, K};
#[test]
fn test_temperature_energy_room_temp() {
let temp = 300.0 * K.clone();
let energy = temp.to_equiv(&EV, temperature_energy()).unwrap();
let expected = BOLTZMANN_CONSTANT * 300.0 / 1.602176634e-19;
assert!((energy.value() - expected).abs() / expected < 1e-6);
}
#[test]
fn test_temperature_energy_1ev() {
let energy = 1.0 * EV.clone();
let temp = energy.to_equiv(&K, temperature_energy()).unwrap();
let expected = 1.602176634e-19 / BOLTZMANN_CONSTANT;
assert!((temp.value() - expected).abs() / expected < 1e-6);
}
#[test]
fn test_temperature_energy_roundtrip() {
let temp = 1000.0 * K.clone();
let energy = temp.to_equiv(&J, temperature_energy()).unwrap();
let temp_back = energy.to_equiv(&K, temperature_energy()).unwrap();
assert!((temp.value() - temp_back.value()).abs() / temp.value() < 1e-10);
}
#[test]
fn test_negative_kelvin_fails() {
let temp = -1.0 * K.clone();
let result = temp.to_equiv(&J, temperature_energy());
assert!(result.is_err());
}
#[test]
fn test_negative_energy_fails() {
let energy = -1.0 * J.clone();
let result = energy.to_equiv(&K, temperature_energy());
assert!(result.is_err());
}
#[test]
fn test_absolute_zero_ok() {
let temp = 0.0 * K.clone();
let result = temp.to_equiv(&J, temperature_energy());
assert!(result.is_ok());
assert!(result.unwrap().value().abs() < 1e-30);
}
#[test]
fn test_kelvin_to_celsius() {
let temp = 373.15 * K.clone();
let celsius = temp.to(&DEG_C).unwrap();
assert!((celsius.value() - 100.0).abs() < 1e-10);
}
#[test]
fn test_celsius_to_kelvin() {
let temp = 0.0 * DEG_C.clone();
let kelvin = temp.to(&K).unwrap();
assert!((kelvin.value() - 273.15).abs() < 1e-10);
}
#[test]
fn test_kelvin_to_fahrenheit() {
let temp = 373.15 * K.clone();
let fahrenheit = temp.to(&DEG_F).unwrap();
assert!((fahrenheit.value() - 212.0).abs() < 1e-6);
}
#[test]
fn test_fahrenheit_to_kelvin() {
let temp = 32.0 * DEG_F.clone();
let kelvin = temp.to(&K).unwrap();
assert!((kelvin.value() - 273.15).abs() < 1e-6);
}
#[test]
fn test_celsius_to_fahrenheit() {
let temp = 100.0 * DEG_C.clone();
let fahrenheit = temp.to(&DEG_F).unwrap();
assert!((fahrenheit.value() - 212.0).abs() < 1e-6);
}
#[test]
fn test_fahrenheit_to_celsius() {
let temp = 32.0 * DEG_F.clone();
let celsius = temp.to(&DEG_C).unwrap();
assert!(celsius.value().abs() < 1e-6);
}
#[test]
fn test_celsius_roundtrip() {
let temp = 37.0 * DEG_C.clone();
let kelvin = temp.to(&K).unwrap();
let back = kelvin.to(&DEG_C).unwrap();
assert!((back.value() - 37.0).abs() < 1e-10);
}
#[test]
fn test_fahrenheit_roundtrip() {
let temp = 98.6 * DEG_F.clone();
let kelvin = temp.to(&K).unwrap();
let back = kelvin.to(&DEG_F).unwrap();
assert!((back.value() - 98.6).abs() < 1e-10);
}
#[test]
fn test_celsius_fahrenheit_roundtrip() {
let temp = 37.0 * DEG_C.clone();
let f = temp.to(&DEG_F).unwrap();
let back = f.to(&DEG_C).unwrap();
assert!((back.value() - 37.0).abs() < 1e-10);
}
#[test]
fn test_absolute_zero_across_scales() {
let zero_k = 0.0 * K.clone();
let celsius = zero_k.to(&DEG_C).unwrap();
let fahrenheit = zero_k.to(&DEG_F).unwrap();
assert!((celsius.value() - (-273.15)).abs() < 1e-10);
assert!((fahrenheit.value() - (-459.67)).abs() < 1e-6);
}
#[test]
fn test_minus_40_identity() {
let c = -40.0 * DEG_C.clone();
let f = c.to(&DEG_F).unwrap();
assert!((f.value() - (-40.0)).abs() < 1e-6);
let f2 = -40.0 * DEG_F.clone();
let c2 = f2.to(&DEG_C).unwrap();
assert!((c2.value() - (-40.0)).abs() < 1e-6);
}
#[test]
fn test_temperature_equality_across_scales() {
let c = 100.0 * DEG_C.clone();
let f = 212.0 * DEG_F.clone();
let k = 373.15 * K.clone();
assert_eq!(c, k);
assert_eq!(c, f);
assert_eq!(f, k);
}
#[test]
fn test_celsius_decompose() {
let temp = 100.0 * DEG_C.clone();
let decomposed = temp.decompose();
assert!((decomposed.value() - 373.15).abs() < 1e-10);
}
#[test]
fn test_celsius_arithmetic() {
let a = 5.0 * DEG_C.clone();
let b = 3.0 * DEG_C.clone();
let sum = a + b;
assert!((sum.value() - 8.0).abs() < 1e-10);
}
#[test]
fn test_temperature_energy_from_celsius() {
let temp = 0.0 * DEG_C.clone();
let energy = temp.to_equiv(&J, temperature_energy()).unwrap();
let expected = BOLTZMANN_CONSTANT * 273.15;
assert!((energy.value() - expected).abs() / expected < 1e-6);
}
}