use crate::dimension::{Dimension, Rational16};
use std::fmt;
#[derive(Clone, Debug, PartialEq)]
pub struct UnitComponent {
pub(crate) symbol: String,
pub(crate) dimension: Dimension,
pub(crate) scale: f64,
pub(crate) power: Rational16,
}
impl UnitComponent {
pub fn symbol(&self) -> &str {
&self.symbol
}
pub fn dimension(&self) -> Dimension {
self.dimension
}
pub fn scale(&self) -> f64 {
self.scale
}
pub fn power(&self) -> Rational16 {
self.power
}
}
impl UnitComponent {
pub fn new(
symbol: impl Into<String>,
dimension: Dimension,
scale: f64,
power: Rational16,
) -> Self {
UnitComponent {
symbol: symbol.into(),
dimension,
scale,
power,
}
}
pub fn effective_dimension(&self) -> Dimension {
self.dimension.pow(self.power)
}
pub fn effective_scale(&self) -> f64 {
self.scale.powf(self.power.to_f64())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct CompositeUnit {
pub(crate) scale: f64,
pub(crate) components: Vec<UnitComponent>,
}
impl CompositeUnit {
pub fn scale(&self) -> f64 {
self.scale
}
pub fn components(&self) -> &[UnitComponent] {
&self.components
}
}
impl CompositeUnit {
pub fn new(scale: f64, components: Vec<UnitComponent>) -> Self {
CompositeUnit { scale, components }
}
pub fn dimensionless(scale: f64) -> Self {
CompositeUnit {
scale,
components: Vec::new(),
}
}
pub fn from_base(symbol: impl Into<String>, dimension: Dimension, scale: f64) -> Self {
CompositeUnit {
scale: 1.0,
components: vec![UnitComponent::new(
symbol,
dimension,
scale,
Rational16::ONE,
)],
}
}
pub fn dimension(&self) -> Dimension {
let mut dim = Dimension::DIMENSIONLESS;
for comp in &self.components {
dim = dim.mul(&comp.effective_dimension());
}
dim
}
pub fn total_scale(&self) -> f64 {
let mut s = self.scale;
for comp in &self.components {
s *= comp.effective_scale();
}
s
}
pub fn mul(&self, other: &CompositeUnit) -> CompositeUnit {
let mut components = self.components.clone();
for other_comp in &other.components {
let mut found = false;
for comp in &mut components {
if comp.symbol == other_comp.symbol && comp.dimension == other_comp.dimension {
comp.power = comp.power + other_comp.power;
found = true;
break;
}
}
if !found {
components.push(other_comp.clone());
}
}
components.retain(|c| !c.power.is_zero());
CompositeUnit {
scale: self.scale * other.scale,
components,
}
}
pub fn div(&self, other: &CompositeUnit) -> CompositeUnit {
self.mul(&other.inv())
}
pub fn inv(&self) -> CompositeUnit {
CompositeUnit {
scale: 1.0 / self.scale,
components: self
.components
.iter()
.map(|c| UnitComponent {
symbol: c.symbol.clone(),
dimension: c.dimension,
scale: c.scale,
power: -c.power,
})
.collect(),
}
}
pub fn pow(&self, power: Rational16) -> CompositeUnit {
CompositeUnit {
scale: self.scale.powf(power.to_f64()),
components: self
.components
.iter()
.map(|c| UnitComponent {
symbol: c.symbol.clone(),
dimension: c.dimension,
scale: c.scale,
power: c.power * power,
})
.collect(),
}
}
}
impl fmt::Display for CompositeUnit {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.components.is_empty() {
if (self.scale - 1.0).abs() < 1e-15 {
return write!(f, "dimensionless");
} else {
return write!(f, "{}", self.scale);
}
}
let mut positive = Vec::new();
let mut negative = Vec::new();
for comp in &self.components {
if comp.power.numer > 0 {
positive.push(comp);
} else if comp.power.numer < 0 {
negative.push(comp);
}
}
let pos_str: Vec<String> = positive
.iter()
.map(|c| {
if c.power == Rational16::ONE {
c.symbol.clone()
} else {
format!("{}^{}", c.symbol, c.power)
}
})
.collect();
let neg_str: Vec<String> = negative
.iter()
.map(|c| {
let abs_power = Rational16::new(-c.power.numer, c.power.denom);
if abs_power == Rational16::ONE {
c.symbol.clone()
} else {
format!("{}^{}", c.symbol, abs_power)
}
})
.collect();
let scale_prefix = if (self.scale - 1.0).abs() < 1e-15 {
String::new()
} else {
format!("{} ", self.scale)
};
if negative.is_empty() {
write!(f, "{}{}", scale_prefix, pos_str.join(" "))
} else if positive.is_empty() {
write!(f, "{}1 / {}", scale_prefix, neg_str.join(" "))
} else {
write!(
f,
"{}{} / {}",
scale_prefix,
pos_str.join(" "),
neg_str.join(" ")
)
}
}
}