use core::f32;
use std::error::Error;
use std::fmt::{self, Display, Formatter, LowerExp};
use std::iter::{once, Product};
use std::ops::{Div, Mul};
pub use std::sync::LazyLock;
use crate::bareiss_eliminator::{BareissEliminatorError, RectangularMatrix};
#[derive(Debug, Clone, PartialEq)]
pub enum ConversionExponentError {
NoNonZeroExponentPair {
left_dimension: Dimension,
right_dimension: Dimension,
},
InconsistentExponentRatio {
left_dimension: Dimension,
right_dimension: Dimension,
right: f64,
left: f64,
expected: f64,
found: f64,
},
}
impl Display for ConversionExponentError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::NoNonZeroExponentPair { left_dimension, right_dimension } =>
write!(f, "No exponent pair was found where both sides are nonzero: {left_dimension} and {right_dimension}"),
Self::InconsistentExponentRatio { left_dimension, right_dimension, right, left, expected, found } =>
write!(f, "Inconsistent ratio exponent between {left_dimension} and {right_dimension}: expected {right}/{left} to be {expected} but found {found}"),
}
}
}
impl Error for ConversionExponentError {}
#[derive(Debug, Clone, PartialEq)]
pub struct UnconvertableDimensionsError {
base_dimensions: Vec<Dimension>,
target_dimension: Dimension,
bareiss_eliminator_error: BareissEliminatorError,
}
impl Display for UnconvertableDimensionsError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let Self { base_dimensions, target_dimension, bareiss_eliminator_error } = self;
write!(f, "Couldn't convert from {} to {target_dimension}. {bareiss_eliminator_error}", Dimensions(base_dimensions))
}
}
impl Error for UnconvertableDimensionsError {}
pub enum Prefix {
Quetta,
Ronna,
Yotta,
Zetta,
Exa,
Peta,
Tera,
Giga,
Mega,
Kilo,
Hecto,
Deca,
Deci,
Centi,
Milli,
Micro,
Nano,
Pico,
Femto,
Atto,
Zepto,
Yocto,
Ronto,
Quecto,
}
#[derive(Debug, Clone)]
pub struct Dimension {
scaling_factor: f64,
exponents: Box<[(String, f64)]>,
}
impl Dimension {
#[must_use]
pub const fn scaling_factor(&self) -> &f64 {
&self.scaling_factor
}
#[must_use]
pub const fn exponents(&self) -> &[(String, f64)] {
&self.exponents
}
#[must_use]
pub fn new(name: &str) -> Self {
Self {
scaling_factor: 1.0,
exponents: [(name.to_string(), 1.0)].into()
}
}
#[must_use]
pub fn power<T: Into<f64> + Copy>(&self, exponent: T) -> Self {
Self {
scaling_factor: self.scaling_factor.powf(exponent.into()),
exponents: self.exponents.iter().map(|current_exponent| {
let scaled = current_exponent.1 * exponent.into();
(current_exponent.0.clone(), if scaled.abs() < 1e-12 { 0.0 } else { scaled })
}).collect(),
}
}
#[must_use]
pub fn cube(&self) -> Self {
self.power(3)
}
#[must_use]
pub fn square(&self) -> Self {
self.power(2)
}
#[must_use]
pub fn square_root(&self) -> Self {
self.power(0.5)
}
#[must_use]
pub fn inverse(&self) -> Self {
self.power(-1)
}
#[must_use]
pub fn portion<T: Into<f64>>(&self, divisor: T) -> Self {
Self { scaling_factor: self.scaling_factor / divisor.into(), ..self.clone() }
}
#[must_use]
pub fn scale<T: Into<f64>>(&self, scaling_factor: T) -> Self {
Self { scaling_factor: self.scaling_factor * scaling_factor.into(), ..self.clone() }
}
#[must_use]
pub fn prefix(&self, prefix: &Prefix) -> Self {
self.scale(match prefix {
Prefix::Quetta=> 1e30,
Prefix::Ronna => 1e27,
Prefix::Yotta => 1e24,
Prefix::Zetta => 1e21,
Prefix::Exa => 1e18,
Prefix::Peta => 1e15,
Prefix::Tera => 1e12,
Prefix::Giga => 1e9,
Prefix::Mega => 1e6,
Prefix::Kilo => 1e3,
Prefix::Hecto => 1e2,
Prefix::Deca => 1e1,
Prefix::Deci => 1e-1,
Prefix::Centi => 1e-2,
Prefix::Milli => 1e-3,
Prefix::Micro => 1e-6,
Prefix::Nano => 1e-9,
Prefix::Pico => 1e-12,
Prefix::Femto => 1e-15,
Prefix::Atto => 1e-18,
Prefix::Zepto => 1e-21,
Prefix::Yocto => 1e-24,
Prefix::Ronto => 1e-27,
Prefix::Quecto=> 1e-30,
})
}
pub fn get_conversion_exponent(&self, other: &Self) -> Result<f64, ConversionExponentError> {
let exponents = [self, other].exponents();
let mut unit_iter = exponents[0].iter().zip(exponents[1].iter());
let exponent: f64 = loop {
match unit_iter.next() {
Some((left, right)) => {
if left.abs() > f64::from(f32::EPSILON)
&& right.abs() > f64::from(f32::EPSILON) {
break right / left
}
if left.abs() > f64::from(f32::EPSILON)
|| right.abs() > f64::from(f32::EPSILON) {
return Err(ConversionExponentError::NoNonZeroExponentPair {
left_dimension: self.clone(),
right_dimension: other.clone()
})
}
}
None => {
return Err(ConversionExponentError::NoNonZeroExponentPair {
left_dimension: self.clone(),
right_dimension: other.clone()
});
}
}
};
for (&left, &right) in unit_iter {
if (left.abs() > f64::from(f32::EPSILON)
|| right.abs() > f64::from(f32::EPSILON))
&& ((right / left / exponent) - 1.).abs() > f64::from(f32::EPSILON)
{
return Err(ConversionExponentError::InconsistentExponentRatio {
left_dimension: self.clone(),
right_dimension: other.clone(),
right,
left,
expected: exponent,
found: right / left
});
}
};
Ok(exponent)
}
}
impl PartialEq for Dimension {
fn eq(&self, other: &Self) -> bool {
let exponents = [self, other].exponents();
(exponents[0] == exponents[1]) && (self.scaling_factor / other.scaling_factor - 1.0).abs() < (f64::from(f32::EPSILON))
}
}
pub struct Dimensions<'a>(pub &'a Vec<Dimension>);
impl Display for Dimensions<'_> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "[{}]", self
.0
.iter()
.map(|d| format!("{d}"))
.collect::<Vec<_>>()
.join(", ")
)
}
}
impl LowerExp for Dimension {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let scaling_factor = self.scaling_factor;
let scaling_iterator = once(Some(format!("{scaling_factor:e}"))
.filter(|_| (scaling_factor - 1.0).abs() > f64::from(f32::EPSILON)))
.flatten();
let exponent_iterator = self
.exponents
.iter()
.filter(|(_, unit)| unit.abs() > f64::from(f32::EPSILON))
.map(|(name, unit)| {
if (unit - 1.0).abs() < f64::from(f32::EPSILON) {
name.clone()
} else {
format!("{name}^{unit}")
}
});
let output = scaling_iterator.chain(exponent_iterator);
let output: Vec<_> = output.collect();
let output = output.join(" ");
write!(f, "{output}")
}
}
impl Display for Dimension {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
let scaling_factor = self.scaling_factor;
let scaling_iterator = once(Some(format!("{scaling_factor}"))
.filter(|_| (scaling_factor - 1.0).abs() > f64::from(f32::EPSILON)))
.flatten();
let exponent_iterator = self
.exponents
.iter()
.filter(|(_, unit)| unit.abs() > f64::from(f32::EPSILON))
.map(|(name, unit)| {
if (unit - 1.0).abs() < f64::from(f32::EPSILON) {
name.clone()
} else {
format!("{name}^{unit}")
}
});
let output = scaling_iterator.chain(exponent_iterator);
let output: Vec<_> = output.collect();
let output = output.join(" ");
write!(f, "{output}")
}
}
impl Mul for &Dimension {
type Output = Dimension;
fn mul(self, rhs: Self) -> Self::Output {
let mut exponents = self.exponents.clone().into_vec();
for (name, exponent) in &rhs.exponents {
if let Some((_, existing_exp)) =
exponents.iter_mut().find(|(n, _)| n == name)
{
*existing_exp += exponent;
} else {
exponents.push((name.clone(), *exponent));
}
}
Self::Output {
scaling_factor: self.scaling_factor * rhs.scaling_factor,
exponents: exponents.into(),
}
}
}
impl Div for &Dimension {
type Output = Dimension;
fn div(self, rhs: Self) -> Self::Output {
let mut exponents = self.exponents.clone().into_vec();
for (name, exponent) in &rhs.exponents {
if let Some((_, existing_exp)) =
exponents.iter_mut().find(|(n, _)| n == name)
{
*existing_exp -= exponent;
} else {
exponents.push((name.clone(), -*exponent));
}
}
Self::Output {
scaling_factor: self.scaling_factor / rhs.scaling_factor,
exponents: exponents.into(),
}
}
}
impl Product for Dimension {
fn product<I: Iterator<Item = Self>>(iter: I) -> Self {
iter.fold(DIMENSIONLESS.clone(), |acc, x| &acc * &x)
}
}
pub trait DeepDereferrenceable {
fn get(&self) -> Box<[&Dimension]>;
}
impl DeepDereferrenceable for LazyLock<Box<[&LazyLock<Dimension>]>> {
fn get(&self) -> Box<[&Dimension]> {
self.iter().map(|&dimension| &**dimension).collect()
}
}
pub trait DimensionalAnalysable {
fn exponents(&self) -> Box<[Box<[f64]>]>;
fn have_same_exponents(&self) -> bool;
fn exponents_to(&self, other: &Dimension) -> Result<Box<[f64]>, UnconvertableDimensionsError>;
fn all_exponents_to(&self, others: &[&Dimension]) -> Result<Box<[Box<[f64]>]>, UnconvertableDimensionsError>;
fn product_of_powers(&self, others: &[f64]) -> Dimension;
fn coherent_system7(&self) -> Result<Box<[Box<[f64]>]>, UnconvertableDimensionsError>;
fn coherent_system5(&self) -> Result<Box<[Box<[f64]>]>, UnconvertableDimensionsError>;
fn coherent_system3(&self) -> Result<Box<[Box<[f64]>]>, UnconvertableDimensionsError>;
}
use crate::dimensions::le_systeme_international_d_unites::base_units::{AMPERE, CANDELA, KELVIN, KILOGRAM, METER, MOLE, SECOND};
use crate::{dimension};
impl DimensionalAnalysable for [&Dimension] {
fn exponents(&self) -> Box<[Box<[f64]>]> {
let mut exponent_names: Vec<&str> = Vec::new();
let mut exponent_matrix: Vec<Vec<f64>> = Vec::new();
let mut width: usize = 0;
for (row, exponents) in self.iter().map(|dimension| &dimension.exponents).enumerate() {
exponent_matrix.push(vec![0.0; width]);
for (name, exponent) in exponents {
if let Some(index) =
exponent_names.iter().position(|n| n == name)
{
exponent_matrix[row][index] = *exponent;
} else {
for exponent_row in &mut exponent_matrix[0..row] {
exponent_row.push(0.0);
}
exponent_matrix[row].push(*exponent);
exponent_names.push(name);
width += 1;
}
}
}
exponent_matrix.into_iter().map(Into::into).collect()
}
fn have_same_exponents(&self) -> bool {
if self.is_empty() {
return true
}
let all_exponents = self.exponents();
let first_exponents = &all_exponents[0];
all_exponents.iter().all(|exponents| exponents == first_exponents)
}
fn exponents_to(&self, other: &Dimension) -> Result<Box<[f64]>, UnconvertableDimensionsError> {
let dimension: Box<[&Dimension]> = self.iter().chain(once(&other)).copied().collect();
let rows: &[Box<[f64]>] = &dimension.exponents();
macro_rules! unconvertable_dimensions_error {
() => {
|error|
UnconvertableDimensionsError {
base_dimensions: self.iter().copied().cloned().collect(),
target_dimension: other.clone(),
bareiss_eliminator_error: error.into(),
}
};
}
RectangularMatrix::try_from(rows)
.map_err(unconvertable_dimensions_error!())?
.switch_dimensions()
.bareiss_solve()
.map_err(unconvertable_dimensions_error!())
}
fn all_exponents_to(&self, others: &[&Dimension]) -> Result<Box<[Box<[f64]>]>, UnconvertableDimensionsError> {
others.iter().map(|other| self.exponents_to(other)).collect()
}
fn product_of_powers(&self, rows: &[f64]) -> Dimension {
rows.iter().enumerate().map(|(index, &power)| self[index].power(power)).product()
}
fn coherent_system7(&self) -> Result<Box<[Box<[f64]>]>, UnconvertableDimensionsError> {
self.all_exponents_to(&[&KILOGRAM, &METER, &SECOND, &ERE, &KELVIN, &MOLE, &CANDELA])
}
fn coherent_system5(&self) -> Result<Box<[Box<[f64]>]>, UnconvertableDimensionsError> {
self.all_exponents_to(&[&KILOGRAM, &METER, &SECOND, &ERE, &KELVIN])
}
fn coherent_system3(&self) -> Result<Box<[Box<[f64]>]>, UnconvertableDimensionsError> {
self.all_exponents_to(&[&KILOGRAM, &METER, &SECOND])
}
}
#[cfg(test)]
mod tests {
use crate::{
debug_println,
{dimension::Prefix::Kilo, dimensions::{
centimeter_gram_second_units::base_units::GRAM, le_systeme_international_d_unites::base_units::{
KILOGRAM, METER, SECOND
}
}}
};
#[test]
fn meters_per_second_squared_comma_meters_per_second_per_second_and_meters_per_second_seconds() {
let lhs = &*METER / &SECOND.square();
let mhs = &(&*METER / &*SECOND) / &*SECOND;
let rhs = &*METER / &(&*SECOND * &*SECOND);
debug_println!("lhs:{}", lhs);
debug_println!("mhs:{}", mhs);
debug_println!("rhs:{}", rhs);
assert_eq!(lhs, mhs);
assert_eq!(mhs, rhs);
}
#[test]
fn kilograms_and_grams() {
let lhs = &*KILOGRAM;
let rhs = &GRAM.prefix(&Kilo);
debug_println!("lhs:{}", lhs);
debug_println!("rhs:{}", rhs);
assert_eq!(lhs, rhs);
}
}
#[macro_export]
macro_rules! product_of_powers {
(
$( ;$scaling_factor:expr; )?
$( ,$divisor:expr, )? ->
$( $( ,$prefix:ident )? $rest:ident $( ^$rest_exp:literal )? )*
) => {{
use std::ops::Mul;
$crate::dimension::DIMENSIONLESS
$(
.mul(
&$rest
$(.prefix(&$crate::dimension::Prefix::$prefix))?
$(.power($rest_exp))?
)
)*
$(.scale($scaling_factor))?
$(.portion($divisor))?
}};
}
#[macro_export]
macro_rules! dim {
(
$( ;$scaling_factor:expr; )?
$( ,$divisor:expr, )?
$( $( ,$prefix:ident )? $rest:ident $( ^$rest_exp:literal )? )*
) => {
&$crate::product_of_powers!(
$( ;$scaling_factor; )?
$( ,$divisor, )? ->
$( $( ,$prefix )? $rest $( ^$rest_exp )? )*
)
};
}
#[macro_export]
macro_rules! dimension {
($name:ident $($doc:literal)?) => {
dimension!($name = $crate::dimension::Dimension::new(&stringify!($name).to_lowercase()) $(=> $doc)?);
};
(
$( ,$divisor:expr, )?
$name:ident =
$( ;$scaling_factor:expr; )?
$( $( ,$prefix:ident )? $rest:ident $( ^$rest_exp:literal )? )*
$($doc:literal)?
) => {
dimension!(
$name =
$crate::product_of_powers!(
$( ;$scaling_factor; )?
$( ,$divisor, )? ->
$( $( ,$prefix )? $rest $( ^$rest_exp )? )*
)
$(=> $doc)?
);
};
($name:ident = $unit:expr $(=> $doc:literal)?) => {
$(#[doc=$doc])?
#[allow(unused)]
pub static $name: std::sync::LazyLock<$crate::dimension::Dimension> = std::sync::LazyLock::new(|| $unit);
};
}
dimension!(DIMENSIONLESS = Dimension {
scaling_factor: 1.0,
exponents: Vec::new().into(),
} => "The [`Dimension`] of a plain number");