use core::fmt;
use core::ops::{Add, Div, Mul, Neg, Sub};
macro_rules! impl_unit_type {
($name:ident, $unit:expr) => {
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct $name(pub f64);
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.4} {}", self.0, $unit)
}
}
impl Add for $name {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self(self.0 + rhs.0)
}
}
impl Sub for $name {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self(self.0 - rhs.0)
}
}
impl Mul<f64> for $name {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self(self.0 * rhs)
}
}
impl Mul<$name> for f64 {
type Output = $name;
fn mul(self, rhs: $name) -> $name {
$name(self * rhs.0)
}
}
impl Div<f64> for $name {
type Output = Self;
fn div(self, rhs: f64) -> Self {
Self(self.0 / rhs)
}
}
impl Neg for $name {
type Output = Self;
fn neg(self) -> Self {
Self(-self.0)
}
}
};
}
impl_unit_type!(Voltage, "V");
impl_unit_type!(Current, "A");
impl_unit_type!(Power, "W");
impl_unit_type!(ReactivePower, "VAr");
impl_unit_type!(Frequency, "Hz");
impl_unit_type!(PerUnit, "p.u.");
impl Voltage {}
impl Current {}
impl Power {}
impl ReactivePower {}
impl Frequency {}
impl PerUnit {}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
pub struct Impedance {
pub r: f64,
pub x: f64,
}
impl Impedance {
pub fn new(r: f64, x: f64) -> Self {
Self { r, x }
}
pub fn magnitude(&self) -> f64 {
(self.r * self.r + self.x * self.x).sqrt()
}
pub fn to_complex(&self) -> num_complex::Complex64 {
num_complex::Complex64::new(self.r, self.x)
}
pub fn to_admittance(&self) -> num_complex::Complex64 {
let z = self.to_complex();
if z.norm() < 1e-15 {
num_complex::Complex64::new(0.0, 0.0)
} else {
1.0 / z
}
}
}
impl fmt::Display for Impedance {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:.4} + j{:.4} \u{03a9}", self.r, self.x)
}
}
impl Add for Impedance {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
r: self.r + rhs.r,
x: self.x + rhs.x,
}
}
}
impl Sub for Impedance {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
r: self.r - rhs.r,
x: self.x - rhs.x,
}
}
}
impl Mul<f64> for Impedance {
type Output = Self;
fn mul(self, rhs: f64) -> Self {
Self {
r: self.r * rhs,
x: self.x * rhs,
}
}
}
impl Voltage {
pub fn to_per_unit(self, base: Voltage) -> PerUnit {
PerUnit(self.0 / base.0)
}
pub fn from_per_unit(pu: PerUnit, base: Voltage) -> Voltage {
Voltage(pu.0 * base.0)
}
}
impl Power {
pub fn to_per_unit(self, base: Power) -> PerUnit {
PerUnit(self.0 / base.0)
}
pub fn from_per_unit(pu: PerUnit, base: Power) -> Power {
Power(pu.0 * base.0)
}
}
impl ReactivePower {
pub fn to_per_unit(self, base: Power) -> PerUnit {
PerUnit(self.0 / base.0)
}
pub fn from_per_unit(pu: PerUnit, base: Power) -> ReactivePower {
ReactivePower(pu.0 * base.0)
}
}
impl Current {
pub fn to_per_unit(self, base: Current) -> PerUnit {
PerUnit(self.0 / base.0)
}
pub fn from_per_unit(pu: PerUnit, base: Current) -> Current {
Current(pu.0 * base.0)
}
}
impl core::ops::Mul<Current> for Voltage {
type Output = Power;
fn mul(self, rhs: Current) -> Power {
Power(self.0 * rhs.0)
}
}
impl core::ops::Mul<Voltage> for Current {
type Output = Power;
fn mul(self, rhs: Voltage) -> Power {
Power(self.0 * rhs.0)
}
}
impl Power {
pub fn to_energy_wh(self, dt_hours: f64) -> crate::units::Energy {
crate::units::Energy(self.0 * dt_hours)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[cfg(test)]
mod proptest_tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn prop_voltage_per_unit_roundtrip(v in 0.1f64..1000.0, base in 0.1f64..1000.0) {
let voltage = Voltage(v);
let base_v = Voltage(base);
let pu = voltage.to_per_unit(base_v);
let back = Voltage::from_per_unit(pu, base_v);
prop_assert!((back.0 - v).abs() < 1e-9);
}
#[test]
fn prop_power_per_unit_roundtrip(p in 0.1f64..1000.0, base in 0.1f64..1000.0) {
let power = Power(p);
let base_p = Power(base);
let pu = power.to_per_unit(base_p);
let back = Power::from_per_unit(pu, base_p);
prop_assert!((back.0 - p).abs() < 1e-9);
}
#[test]
fn prop_voltage_add_commutative(a in -1000.0f64..1000.0, b in -1000.0f64..1000.0) {
let va = Voltage(a);
let vb = Voltage(b);
prop_assert_eq!((va + vb).0, (vb + va).0);
}
#[test]
fn prop_voltage_scale_identity(v in -1000.0f64..1000.0) {
let voltage = Voltage(v);
prop_assert_eq!((voltage * 1.0).0, v);
}
}
}
#[test]
fn test_voltage_arithmetic() {
let v1 = Voltage(100.0);
let v2 = Voltage(50.0);
assert_eq!((v1 + v2).0, 150.0);
assert_eq!((v1 - v2).0, 50.0);
assert_eq!((v1 * 2.0).0, 200.0);
assert_eq!((v1 / 2.0).0, 50.0);
assert_eq!((-v1).0, -100.0);
}
#[test]
fn test_per_unit_conversion() {
let v = Voltage(115.0);
let base = Voltage(230.0);
let pu = v.to_per_unit(base);
assert!((pu.0 - 0.5).abs() < 1e-10);
let v_back = Voltage::from_per_unit(pu, base);
assert!((v_back.0 - 115.0).abs() < 1e-10);
}
#[test]
fn test_impedance() {
let z = Impedance::new(3.0, 4.0);
assert!((z.magnitude() - 5.0).abs() < 1e-10);
}
#[test]
fn test_display() {
let v = Voltage(230.0);
assert_eq!(format!("{v}"), "230.0000 V");
}
}