use crate::error::{PhysicsError, PhysicsResult};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Dimensions {
pub length: i8,
pub mass: i8,
pub time: i8,
pub current: i8,
pub temperature: i8,
pub amount: i8,
pub luminosity: i8,
}
impl Dimensions {
pub const fn dimensionless() -> Self {
Self {
length: 0,
mass: 0,
time: 0,
current: 0,
temperature: 0,
amount: 0,
luminosity: 0,
}
}
pub fn equals(&self, other: &Self) -> bool {
self.length == other.length
&& self.mass == other.mass
&& self.time == other.time
&& self.current == other.current
&& self.temperature == other.temperature
&& self.amount == other.amount
&& self.luminosity == other.luminosity
}
pub fn is_dimensionless(&self) -> bool {
self.equals(&Self::dimensionless())
}
}
pub struct DimensionalAnalyzer {
unit_dimensions: HashMap<String, Dimensions>,
}
impl DimensionalAnalyzer {
pub fn new() -> Self {
let mut analyzer = Self {
unit_dimensions: HashMap::new(),
};
analyzer.initialize_si_units();
analyzer
}
fn initialize_si_units(&mut self) {
self.register_unit(
"m",
Dimensions {
length: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"kg",
Dimensions {
mass: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"s",
Dimensions {
time: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"A",
Dimensions {
current: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"K",
Dimensions {
temperature: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"mol",
Dimensions {
amount: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"cd",
Dimensions {
luminosity: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"N",
Dimensions {
length: 1,
mass: 1,
time: -2,
..Dimensions::dimensionless()
},
); self.register_unit(
"Pa",
Dimensions {
length: -1,
mass: 1,
time: -2,
..Dimensions::dimensionless()
},
); self.register_unit(
"J",
Dimensions {
length: 2,
mass: 1,
time: -2,
..Dimensions::dimensionless()
},
); self.register_unit(
"W",
Dimensions {
length: 2,
mass: 1,
time: -3,
..Dimensions::dimensionless()
},
);
self.register_unit(
"V",
Dimensions {
length: 2,
mass: 1,
time: -3,
current: -1,
..Dimensions::dimensionless()
},
); self.register_unit(
"Ω",
Dimensions {
length: 2,
mass: 1,
time: -3,
current: -2,
..Dimensions::dimensionless()
},
); self.register_unit(
"C",
Dimensions {
time: 1,
current: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"°C",
Dimensions {
temperature: 1,
..Dimensions::dimensionless()
},
);
self.register_unit(
"W/(m*K)",
Dimensions {
length: 1,
mass: 1,
time: -3,
temperature: -1,
..Dimensions::dimensionless()
},
); self.register_unit(
"J/(kg*K)",
Dimensions {
length: 2,
time: -2,
temperature: -1,
..Dimensions::dimensionless()
},
); self.register_unit(
"kg/m^3",
Dimensions {
length: -3,
mass: 1,
..Dimensions::dimensionless()
},
);
self.register_unit("dimensionless", Dimensions::dimensionless());
self.register_unit("1", Dimensions::dimensionless());
}
pub fn register_unit(&mut self, unit: &str, dimensions: Dimensions) {
self.unit_dimensions.insert(unit.to_string(), dimensions);
}
pub fn get_dimensions(&self, unit: &str) -> Option<&Dimensions> {
self.unit_dimensions.get(unit)
}
pub fn check_consistency(&self, unit1: &str, unit2: &str) -> PhysicsResult<bool> {
let dim1 = self
.get_dimensions(unit1)
.ok_or_else(|| PhysicsError::UnitConversion(format!("Unknown unit: {}", unit1)))?;
let dim2 = self
.get_dimensions(unit2)
.ok_or_else(|| PhysicsError::UnitConversion(format!("Unknown unit: {}", unit2)))?;
Ok(dim1.equals(dim2))
}
pub fn is_dimensionless(&self, unit: &str) -> PhysicsResult<bool> {
let dim = self
.get_dimensions(unit)
.ok_or_else(|| PhysicsError::UnitConversion(format!("Unknown unit: {}", unit)))?;
Ok(dim.is_dimensionless())
}
}
impl Default for DimensionalAnalyzer {
fn default() -> Self {
Self::new()
}
}
pub struct UnitChecker {
analyzer: DimensionalAnalyzer,
}
impl UnitChecker {
pub fn new() -> Self {
Self {
analyzer: DimensionalAnalyzer::new(),
}
}
pub fn check_compatibility(&self, unit1: &str, unit2: &str) -> PhysicsResult<bool> {
self.analyzer.check_consistency(unit1, unit2)
}
pub fn validate_unit(&self, unit: &str) -> PhysicsResult<()> {
if self.analyzer.get_dimensions(unit).is_none() {
return Err(PhysicsError::UnitConversion(format!(
"Unknown or unsupported unit: {}",
unit
)));
}
Ok(())
}
pub fn canonical_form(&self, unit: &str) -> PhysicsResult<String> {
self.validate_unit(unit)?;
Ok(unit.to_string())
}
pub fn can_convert(&self, from: &str, to: &str) -> PhysicsResult<bool> {
self.check_compatibility(from, to)
}
}
impl Default for UnitChecker {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dimensional_consistency() {
let analyzer = DimensionalAnalyzer::new();
assert!(analyzer
.check_consistency("m", "m")
.expect("should succeed"));
assert!(analyzer
.check_consistency("kg", "kg")
.expect("should succeed"));
assert!(!analyzer
.check_consistency("m", "kg")
.expect("should succeed"));
assert!(!analyzer
.check_consistency("J", "Pa")
.expect("should succeed"));
}
#[test]
fn test_derived_units() {
let analyzer = DimensionalAnalyzer::new();
let newton = analyzer.get_dimensions("N").expect("should succeed");
assert_eq!(newton.mass, 1);
assert_eq!(newton.length, 1);
assert_eq!(newton.time, -2);
let joule = analyzer.get_dimensions("J").expect("should succeed");
assert_eq!(joule.mass, 1);
assert_eq!(joule.length, 2);
assert_eq!(joule.time, -2);
let watt = analyzer.get_dimensions("W").expect("should succeed");
assert_eq!(watt.mass, 1);
assert_eq!(watt.length, 2);
assert_eq!(watt.time, -3);
}
#[test]
fn test_dimensionless() {
let analyzer = DimensionalAnalyzer::new();
assert!(analyzer
.is_dimensionless("dimensionless")
.expect("should succeed"));
assert!(analyzer.is_dimensionless("1").expect("should succeed"));
assert!(!analyzer.is_dimensionless("m").expect("should succeed"));
}
#[test]
fn test_unit_checker() {
let checker = UnitChecker::new();
assert!(checker.validate_unit("m").is_ok());
assert!(checker.validate_unit("kg").is_ok());
assert!(checker.validate_unit("N").is_ok());
assert!(checker.validate_unit("invalid_unit").is_err());
assert!(checker
.check_compatibility("m", "m")
.expect("should succeed"));
assert!(!checker
.check_compatibility("m", "kg")
.expect("should succeed"));
}
#[test]
fn test_thermal_units() {
let analyzer = DimensionalAnalyzer::new();
let k = analyzer.get_dimensions("W/(m*K)").expect("should succeed");
assert_eq!(k.length, 1);
assert_eq!(k.mass, 1);
assert_eq!(k.time, -3);
assert_eq!(k.temperature, -1);
let cp = analyzer.get_dimensions("J/(kg*K)").expect("should succeed");
assert_eq!(cp.length, 2);
assert_eq!(cp.mass, 0);
assert_eq!(cp.time, -2);
assert_eq!(cp.temperature, -1);
}
}