use std::fmt;
use std::ops;
use super::dim::*;
use super::qty::Qty;
use crate::prelude::Ex;
macro_rules! define_quantity {
(
$(#[$meta:meta])*
$name:ident, $dim:ty, $dim_name_str:literal, $dim_sym:literal
) => {
define_quantity!(@common $(#[$meta])* $name, $dim, $dim_name_str, $dim_sym);
impl From<Qty<$dim>> for $name {
fn from(q: Qty<$dim>) -> $name { $name(q.inner) }
}
impl $crate::units::qty::FromDimExpr<$dim> for $name {
fn from_dim_expr(qty: $crate::units::qty::Qty<$dim>) -> Self {
$name(qty.inner)
}
}
};
(
@no_qty_from
$(#[$meta:meta])*
$name:ident, $dim:ty, $dim_name_str:literal, $dim_sym:literal
) => {
define_quantity!(@common $(#[$meta])* $name, $dim, $dim_name_str, $dim_sym);
};
(
@common
$(#[$meta:meta])*
$name:ident, $dim:ty, $dim_name_str:literal, $dim_sym:literal
) => {
$(#[$meta])*
#[derive(Clone)]
pub struct $name(pub(crate) Ex);
impl $name {
pub fn from_ex(ex: impl $crate::units::qty::IntoEx) -> Self { $name(ex.into_ex()) }
pub fn checked_from_ex(
ex: impl $crate::units::qty::IntoEx,
dims: &$crate::units::inference::DimMap,
) -> Result<Self, String> {
let ex = ex.into_ex();
let inferred = $crate::units::inference::infer_dimension(&ex, dims)?;
if let Some(expected) = $crate::units::dim::ConstDim::from_name($dim_name_str) {
if !inferred.eq(expected) {
return Err(format!(
"Dimension mismatch: expected {} [{}], inferred {}",
$dim_name_str, $dim_sym, inferred
));
}
}
Ok($name(ex))
}
pub fn symbol(ctx: &$crate::api::context::Context, name: &str) -> Self {
$name(ctx.symbol(name))
}
pub fn constant(ctx: &$crate::api::context::Context, val: i64) -> Self {
$name(ctx.int(val))
}
pub fn rational(ctx: &$crate::api::context::Context, p: i64, q: i64) -> Self {
$name(ctx.rational(p, q))
}
pub fn zero(ctx: &$crate::api::context::Context) -> Self { $name(ctx.int(0)) }
pub fn into_inner(self) -> Ex { self.0 }
pub fn inner(&self) -> &Ex { &self.0 }
pub fn as_qty(self) -> Qty<$dim> { Qty::from_ex(self.0) }
pub fn dim_name_str() -> &'static str { $dim_name_str }
pub fn dim_symbol_str() -> &'static str { $dim_sym }
pub fn simplify(&self) -> Self { $name(self.0.simplify()) }
pub fn expand(&self) -> Self { $name(self.0.expand()) }
pub fn eval(&self) -> Self { $name(self.0.eval()) }
pub fn subs(&self, var: &impl AsRef<$crate::prelude::Ex>, val: &impl AsRef<$crate::prelude::Ex>) -> Self { $name(self.0.subs(var.as_ref(), val.as_ref())) }
pub fn simplify_full(&self) -> Self { $name(self.0.simplify()) }
pub fn simplify_trig(&self) -> Self { $name(self.0.simplify_trig()) }
pub fn simplify_powers(&self) -> Self { $name(self.0.simplify_powers()) }
pub fn simplify_rational(&self) -> Self { $name(self.0.simplify_rational()) }
pub fn expand_trig(&self) -> Self { $name(self.0.expand_trig()) }
pub fn expand_log(&self) -> Self { $name(self.0.expand_log()) }
pub fn log_combine(&self) -> Self { $name(self.0.log_combine()) }
pub fn trig_combine(&self) -> Self { $name(self.0.trig_combine()) }
pub fn factor(&self, var: &impl AsRef<$crate::prelude::Ex>) -> Self { $name(self.0.factor(var.as_ref())) }
pub fn collect(&self, var: &impl AsRef<$crate::prelude::Ex>) -> Self { $name(self.0.collect(var.as_ref())) }
pub fn cancel(&self, var: &Ex) -> Self { $name(self.0.cancel(var)) }
pub fn together(&self) -> Self { $name(self.0.together()) }
pub fn partial_fractions(&self, var: &impl AsRef<$crate::prelude::Ex>) -> Self { $name(self.0.partial_fractions(var.as_ref())) }
pub fn rationalize_denom(&self) -> Self { $name(self.0.rationalize_denom()) }
pub fn diff(&self, var: &impl AsRef<$crate::prelude::Ex>) -> Ex { self.0.diff(var.as_ref()) }
pub fn integrate(&self, var: &impl AsRef<$crate::prelude::Ex>) -> Ex { self.0.integrate(var.as_ref()) }
pub fn to_latex(&self) -> String { self.0.to_latex() }
pub fn free_symbols(&self) -> Vec<Ex> { self.0.free_symbols() }
pub fn contains(&self, other: &impl AsRef<$crate::prelude::Ex>) -> bool { self.0.contains(other.as_ref()) }
pub fn term_count(&self) -> usize { self.0.term_count() }
pub fn count_ops(&self) -> usize { self.0.count_ops() }
pub fn is_zero(&self) -> bool { self.0.is_zero().unwrap_or(false) }
pub fn equals(&self, other: &Self) -> bool { self.0.equals(&other.0).unwrap_or(false) }
pub fn eval_f64(&self) -> Result<f64, crate::base::errors::SymplexError> { self.0.eval_f64() }
pub fn eval_f64_with(&self, subs: &[(&Ex, i64)]) -> Result<f64, crate::base::errors::SymplexError> { self.0.eval_f64_with(subs) }
pub fn eval_decimal(&self, digits: u32) -> Result<String, crate::base::errors::SymplexError> { self.0.eval_decimal(digits) }
}
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{} [{}]", self.0, $dim_sym)
}
}
impl fmt::Debug for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}({})", stringify!($name), self.0)
}
}
impl ops::Add for $name {
type Output = $name;
fn add(self, rhs: $name) -> $name { $name(&self.0 + &rhs.0) }
}
impl ops::Add<&$name> for &$name {
type Output = $name;
fn add(self, rhs: &$name) -> $name { $name(&self.0 + &rhs.0) }
}
impl ops::Add<$name> for &$name {
type Output = $name;
fn add(self, rhs: $name) -> $name { $name(&self.0 + &rhs.0) }
}
impl ops::Add<&$name> for $name {
type Output = $name;
fn add(self, rhs: &$name) -> $name { $name(&self.0 + &rhs.0) }
}
impl ops::Sub for $name {
type Output = $name;
fn sub(self, rhs: $name) -> $name { $name(&self.0 - &rhs.0) }
}
impl ops::Sub<&$name> for &$name {
type Output = $name;
fn sub(self, rhs: &$name) -> $name { $name(&self.0 - &rhs.0) }
}
impl ops::Neg for $name {
type Output = $name;
fn neg(self) -> $name { $name(-&self.0) }
}
impl ops::Neg for &$name {
type Output = $name;
fn neg(self) -> $name { $name(-&self.0) }
}
impl ops::Mul<i64> for $name {
type Output = $name;
fn mul(self, rhs: i64) -> $name { $name(&self.0 * rhs) }
}
impl ops::Mul<i64> for &$name {
type Output = $name;
fn mul(self, rhs: i64) -> $name { $name(&self.0 * rhs) }
}
impl ops::Mul<$name> for i64 {
type Output = $name;
fn mul(self, rhs: $name) -> $name { $name(self * &rhs.0) }
}
impl ops::Mul<&$name> for i64 {
type Output = $name;
fn mul(self, rhs: &$name) -> $name { $name(self * &rhs.0) }
}
impl ops::Div<i64> for $name {
type Output = $name;
fn div(self, rhs: i64) -> $name { $name(&self.0 / rhs) }
}
impl ops::Mul<&Ex> for $name {
type Output = $name;
fn mul(self, rhs: &Ex) -> $name { $name(&self.0 * rhs) }
}
impl ops::Mul<&Ex> for &$name {
type Output = $name;
fn mul(self, rhs: &Ex) -> $name { $name(&self.0 * rhs) }
}
impl ops::Mul<$name> for &Ex {
type Output = $name;
fn mul(self, rhs: $name) -> $name { $name(self * &rhs.0) }
}
impl From<$name> for Qty<$dim> {
fn from(q: $name) -> Qty<$dim> { Qty::from_ex(q.0) }
}
impl AsRef<$crate::prelude::Ex> for $name {
fn as_ref(&self) -> &$crate::prelude::Ex { &self.0 }
}
};
}
define_quantity!(
Dimensionless, DimensionlessDim, "Dimensionless", "1"
);
define_quantity!(
@no_qty_from
Angle, AngleDim, "Angle", "rad"
);
define_quantity!(
Length, LengthDim, "Length", "m"
);
define_quantity!(
Mass, MassDim, "Mass", "kg"
);
define_quantity!(
Time, TimeDim, "Time", "s"
);
define_quantity!(
Current, CurrentDim, "Current", "A"
);
define_quantity!(
Temperature, TemperatureDim, "Temperature", "K"
);
define_quantity!(
Area, AreaDim, "Area", "m²"
);
define_quantity!(
Volume, VolumeDim, "Volume", "m³"
);
define_quantity!(
Velocity, VelocityDim, "Velocity", "m/s"
);
define_quantity!(
Acceleration, AccelerationDim, "Acceleration", "m/s²"
);
define_quantity!(
AngularVelocity, AngularVelocityDim, "AngularVelocity", "rad/s"
);
define_quantity!(
AngularAcceleration, AngularAccelerationDim, "AngularAcceleration", "rad/s²"
);
define_quantity!(
@no_qty_from
Frequency, FrequencyDim, "Frequency", "Hz"
);
define_quantity!(
Force, ForceDim, "Force", "N"
);
define_quantity!(
Energy, EnergyDim, "Energy", "J"
);
define_quantity!(
@no_qty_from
Torque, TorqueDim, "Torque", "N·m"
);
define_quantity!(
Power, PowerDim, "Power", "W"
);
define_quantity!(
Momentum, MomentumDim, "Momentum", "kg·m/s"
);
define_quantity!(
AngularMomentum, AngularMomentumDim, "AngularMomentum", "kg·m²/s"
);
define_quantity!(
MomentOfInertia, MomentOfInertiaDim, "MomentOfInertia", "kg·m²"
);
define_quantity!(
Pressure, PressureDim, "Pressure", "Pa"
);
define_quantity!(
Stiffness, StiffnessDim, "Stiffness", "N/m"
);
define_quantity!(
Damping, DampingDim, "Damping", "N·s/m"
);
define_quantity!(
Voltage, VoltageDim, "Voltage", "V"
);
define_quantity!(
Resistance, ResistanceDim, "Resistance", "Ω"
);
define_quantity!(
Inductance, InductanceDim, "Inductance", "H"
);
define_quantity!(
Capacitance, CapacitanceDim, "Capacitance", "F"
);
define_quantity!(
Charge, ChargeDim, "Charge", "C"
);
define_quantity!(
MagneticFlux, MagneticFluxDim, "MagneticFlux", "Wb"
);
impl Angle {
pub fn sin(&self) -> Dimensionless {
Dimensionless(self.0.sin())
}
pub fn cos(&self) -> Dimensionless {
Dimensionless(self.0.cos())
}
pub fn tan(&self) -> Dimensionless {
Dimensionless(self.0.tan())
}
}
impl From<Energy> for Torque {
fn from(e: Energy) -> Self {
Torque(e.0)
}
}
impl From<Torque> for Energy {
fn from(t: Torque) -> Self {
Energy(t.0)
}
}
impl From<Frequency> for AngularVelocity {
fn from(f: Frequency) -> Self {
AngularVelocity(f.0)
}
}
impl From<AngularVelocity> for Frequency {
fn from(w: AngularVelocity) -> Self {
Frequency(w.0)
}
}
impl Angle {
pub fn from_dimensionless(d: Dimensionless) -> Angle {
Angle(d.0)
}
}
impl Torque {
pub fn from_energy(e: Energy) -> Torque {
Torque(e.0)
}
}
impl Frequency {
pub fn from_angular_velocity(w: AngularVelocity) -> Frequency {
Frequency(w.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::api::context::Context;
#[test]
fn add_same_type() {
let ctx = Context::new();
let a = Force::symbol(&ctx, "F1");
let b = Force::symbol(&ctx, "F2");
let c = &a + &b;
assert_eq!(Force::dim_name_str(), "Force");
assert_eq!(Force::dim_symbol_str(), "N");
assert!(format!("{}", c).contains("[N]"));
}
#[test]
fn sub_same_type() {
let ctx = Context::new();
let a = Length::constant(&ctx, 10);
let b = Length::constant(&ctx, 3);
let c = a - b;
assert!(format!("{}", c).contains("[m]"));
}
#[test]
fn neg() {
let ctx = Context::new();
let v = Velocity::symbol(&ctx, "v");
let neg_v = -&v;
assert!(format!("{:?}", neg_v).starts_with("Velocity("));
}
#[test]
fn scalar_mul_i64() {
let ctx = Context::new();
let m = Mass::symbol(&ctx, "m");
let double = &m * 2;
assert!(format!("{}", double).contains("[kg]"));
let triple = 3 * &m;
assert!(format!("{}", triple).contains("[kg]"));
}
#[test]
fn scalar_div_i64() {
let ctx = Context::new();
let e = Energy::constant(&ctx, 100);
let half = e / 2;
assert!(format!("{}", half).contains("[J]"));
}
#[test]
fn scalar_mul_ex() {
let ctx = Context::new();
let f = Force::symbol(&ctx, "F");
let k = ctx.symbol("k");
let scaled = &f * &k;
assert!(format!("{}", scaled).contains("[N]"));
}
#[test]
fn display_and_debug() {
let ctx = Context::new();
let t = Temperature::symbol(&ctx, "T");
assert!(format!("{}", t).contains("[K]"));
assert!(format!("{:?}", t).starts_with("Temperature("));
}
#[test]
fn angle_trig() {
let ctx = Context::new();
let theta = Angle::symbol(&ctx, "theta");
let s = theta.sin();
assert_eq!(Dimensionless::dim_symbol_str(), "1");
assert!(format!("{}", s).contains("[1]"));
}
#[test]
fn cross_type_from_energy_torque() {
let ctx = Context::new();
let e = Energy::symbol(&ctx, "E");
let t: Torque = Torque::from(e);
assert!(format!("{}", t).contains("[N·m]"));
let t2 = Torque::symbol(&ctx, "tau");
let e2: Energy = Energy::from(t2);
assert!(format!("{}", e2).contains("[J]"));
}
#[test]
fn cross_type_from_freq_angular_vel() {
let ctx = Context::new();
let w = AngularVelocity::symbol(&ctx, "omega");
let f: Frequency = Frequency::from(w);
assert!(format!("{}", f).contains("[Hz]"));
}
#[test]
fn named_conversion_angle_from_dimensionless() {
let ctx = Context::new();
let d = Dimensionless::constant(&ctx, 1);
let a = Angle::from_dimensionless(d);
assert!(format!("{}", a).contains("[rad]"));
}
#[test]
fn named_conversion_torque_from_energy() {
let ctx = Context::new();
let e = Energy::symbol(&ctx, "W");
let t = Torque::from_energy(e);
assert_eq!(Torque::dim_name_str(), "Torque");
assert!(format!("{}", t).contains("[N·m]"));
}
#[test]
fn named_conversion_frequency_from_angular_velocity() {
let ctx = Context::new();
let w = AngularVelocity::symbol(&ctx, "omega");
let f = Frequency::from_angular_velocity(w);
assert_eq!(Frequency::dim_name_str(), "Frequency");
assert!(format!("{}", f).contains("[Hz]"));
}
#[test]
fn into_inner_roundtrip() {
let ctx = Context::new();
let raw = ctx.symbol("x");
let q = Pressure::from_ex(raw.clone());
let back = q.into_inner();
assert_eq!(format!("{}", back), format!("{}", raw));
}
#[test]
fn zero_and_constant() {
let ctx = Context::new();
let z = Charge::zero(&ctx);
assert!(format!("{}", z).contains("[C]"));
let c = Charge::constant(&ctx, 42);
assert!(format!("{}", c).contains("[C]"));
}
#[test]
fn qty_roundtrip() {
let ctx = Context::new();
let v = Voltage::symbol(&ctx, "V");
let q: Qty<VoltageDim> = v.into();
let v2: Voltage = q.into();
assert!(format!("{}", v2).contains("[V]"));
}
#[test]
fn simplify_expand_eval() {
let ctx = Context::new();
let x = Length::symbol(&ctx, "x");
let _ = x.clone().simplify();
let _ = x.clone().expand();
let _ = x.eval();
}
#[test]
fn subs() {
let ctx = Context::new();
let x_var = ctx.symbol("x");
let val = ctx.int(5);
let len = Length::symbol(&ctx, "x");
let result = len.subs(&x_var, &val);
assert!(format!("{}", result).contains("[m]"));
}
}