qtty_core/units/
power.rs

1//! Power units.
2//!
3//! The canonical scaling unit for this dimension is [`Watt`] (`Watt::RATIO == 1.0`).
4//!
5//! This module focuses on completeness without baking in avoidable precision loss:
6//! - Full SI prefix ladder on the watt (yocto … yotta).
7//! - A small set of widely used non-SI units with unambiguous definitions.
8//! - Nominal astronomical reference: solar luminosity (IAU).
9//!
10//! ```rust
11//! use qtty_core::power::{SolarLuminosities, Watt};
12//!
13//! let sol = SolarLuminosities::new(1.0);
14//! let w = sol.to::<Watt>();
15//! assert!((w.value() - 3.828e26).abs() < 1e18);
16//! ```
17
18use crate::{Dimension, Quantity, Unit};
19use qtty_derive::Unit;
20
21/// Fundamental dimension – power.
22pub enum Power {}
23impl Dimension for Power {}
24
25/// Marker trait for power units.
26pub trait PowerUnit: Unit<Dim = Power> {}
27impl<T: Unit<Dim = Power>> PowerUnit for T {}
28
29/// Watt (SI coherent derived unit).
30#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
31#[unit(symbol = "W", dimension = Power, ratio = 1.0)]
32pub struct Watt;
33/// Type alias shorthand for [`Watt`].
34pub type W = Watt;
35/// A quantity measured in watts.
36pub type Watts = Quantity<W>;
37/// One watt.
38pub const WATT: Watts = Watts::new(1.0);
39
40macro_rules! si_watt {
41    ($name:ident, $sym:literal, $ratio:expr, $alias:ident, $qty:ident, $one:ident) => {
42        #[doc = concat!("SI-prefixed watt unit (", stringify!($ratio), " W).")]
43        #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
44        #[unit(symbol = $sym, dimension = Power, ratio = $ratio)]
45        pub struct $name;
46        #[doc = concat!("Type alias shorthand for [`", stringify!($name), "`].")]
47        pub type $alias = $name;
48        #[doc = concat!("A quantity measured in ", stringify!($name), "s.")]
49        pub type $qty = Quantity<$alias>;
50        #[doc = concat!("One ", stringify!($name), ".")]
51        pub const $one: $qty = $qty::new(1.0);
52    };
53}
54
55// Full SI prefix ladder on watt
56si_watt!(Yoctowatt, "yW", 1e-24, Yw, Yoctowatts, YW);
57si_watt!(Zeptowatt, "zW", 1e-21, Zw, Zeptowatts, ZW);
58si_watt!(Attowatt, "aW", 1e-18, Aw, Attowatts, AW);
59si_watt!(Femtowatt, "fW", 1e-15, Fw, Femtowatts, FW);
60si_watt!(Picowatt, "pW", 1e-12, Pw, Picowatts, PW);
61si_watt!(Nanowatt, "nW", 1e-9, Nw, Nanowatts, NW);
62si_watt!(Microwatt, "µW", 1e-6, Uw, Microwatts, UW);
63si_watt!(Milliwatt, "mW", 1e-3, Mw, Milliwatts, MW_1);
64
65si_watt!(Deciwatt, "dW", 1e-1, Dw, Deciwatts, DW);
66si_watt!(Decawatt, "daW", 1e1, Daw, Decawatts, DAW);
67si_watt!(Hectowatt, "hW", 1e2, Hw, Hectowatts, HW);
68si_watt!(Kilowatt, "kW", 1e3, Kw, Kilowatts, KW);
69si_watt!(Megawatt, "MW", 1e6, MW, Megawatts, MEGAWATT);
70si_watt!(Gigawatt, "GW", 1e9, GW, Gigawatts, GW_1);
71si_watt!(Terawatt, "TW", 1e12, TW, Terawatts, TW_1);
72si_watt!(Petawatt, "PW", 1e15, PW, Petawatts, PETAWATT);
73si_watt!(Exawatt, "EW", 1e18, EW, Exawatts, EW_1);
74si_watt!(Zettawatt, "ZW", 1e21, ZW, Zettawatts, ZW_1);
75si_watt!(Yottawatt, "YW", 1e24, YW, Yottawatts, YW_1);
76
77/// Erg per second (`erg/s`).
78///
79/// Exact: `1 erg = 1e-7 J`, so `1 erg/s = 1e-7 W`.
80#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
81#[unit(symbol = "erg/s", dimension = Power, ratio = 1e-7)]
82pub struct ErgPerSecond;
83/// One erg/s.
84pub const ERG_PER_S: Quantity<ErgPerSecond> = Quantity::new(1.0);
85
86/// Metric horsepower (`PS`), defined as exactly `735.49875 W`.
87#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
88#[unit(symbol = "PS", dimension = Power, ratio = 73_549_875.0 / 100_000.0)]
89pub struct HorsepowerMetric;
90/// A quantity measured in metric horsepower.
91pub type HorsepowerMetrics = Quantity<HorsepowerMetric>;
92/// One metric horsepower.
93pub const PS: HorsepowerMetrics = HorsepowerMetrics::new(1.0);
94
95/// Electric horsepower (`hp_e`), defined as exactly `746 W`.
96#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
97#[unit(symbol = "hp_e", dimension = Power, ratio = 746.0)]
98pub struct HorsepowerElectric;
99/// A quantity measured in electric horsepower.
100pub type HorsepowerElectrics = Quantity<HorsepowerElectric>;
101/// One electric horsepower.
102pub const HP_E: HorsepowerElectrics = HorsepowerElectrics::new(1.0);
103
104/// Solar luminosity (IAU nominal constant; watts per L☉).
105///
106/// This is a *nominal reference* value intended for consistent conversion.
107#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
108#[unit(symbol = "L☉", dimension = Power, ratio = 3.828e26)]
109pub struct SolarLuminosity;
110/// A quantity measured in solar luminosities.
111pub type SolarLuminosities = Quantity<SolarLuminosity>;
112/// One solar luminosity.
113pub const L_SUN: SolarLuminosities = SolarLuminosities::new(1.0);
114
115// Generate all bidirectional From implementations between power units
116crate::impl_unit_conversions!(
117    Watt,
118    Yoctowatt,
119    Zeptowatt,
120    Attowatt,
121    Femtowatt,
122    Picowatt,
123    Nanowatt,
124    Microwatt,
125    Milliwatt,
126    Deciwatt,
127    Decawatt,
128    Hectowatt,
129    Kilowatt,
130    Megawatt,
131    Gigawatt,
132    Terawatt,
133    Petawatt,
134    Exawatt,
135    Zettawatt,
136    Yottawatt,
137    ErgPerSecond,
138    HorsepowerMetric,
139    HorsepowerElectric,
140    SolarLuminosity
141);
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use approx::assert_relative_eq;
147    use proptest::prelude::*;
148
149    // ─────────────────────────────────────────────────────────────────────────────
150    // Basic conversions
151    // ─────────────────────────────────────────────────────────────────────────────
152
153    #[test]
154    fn solar_luminosity_to_watts() {
155        let sol = SolarLuminosities::new(1.0);
156        let w = sol.to::<Watt>();
157        // 1 L☉ = 3.828e26 W
158        assert_relative_eq!(w.value(), 3.828e26, max_relative = 1e-9);
159    }
160
161    #[test]
162    fn watts_to_solar_luminosity() {
163        let w = Watts::new(3.828e26);
164        let sol = w.to::<SolarLuminosity>();
165        assert_relative_eq!(sol.value(), 1.0, max_relative = 1e-9);
166    }
167
168    #[test]
169    fn multiple_solar_luminosities() {
170        let sol = SolarLuminosities::new(3.0);
171        let w = sol.to::<Watt>();
172        assert_relative_eq!(w.value(), 3.0 * 3.828e26, max_relative = 1e-9);
173    }
174
175    // ─────────────────────────────────────────────────────────────────────────────
176    // Solar luminosity sanity checks
177    // ─────────────────────────────────────────────────────────────────────────────
178
179    #[test]
180    fn solar_luminosity_ratio_sanity() {
181        // RATIO should be 3.828e26
182        assert_relative_eq!(SolarLuminosity::RATIO, 3.828e26, max_relative = 1e-9);
183    }
184
185    #[test]
186    fn solar_luminosity_order_of_magnitude() {
187        let sun = SolarLuminosities::new(1.0);
188        let w = sun.to::<Watt>();
189        // Should be between 1e26 and 1e27
190        assert!(w.value() > 1e26);
191        assert!(w.value() < 1e27);
192    }
193
194    // ─────────────────────────────────────────────────────────────────────────────
195    // Roundtrip conversions
196    // ─────────────────────────────────────────────────────────────────────────────
197
198    #[test]
199    fn roundtrip_w_sol() {
200        let original = Watts::new(1e26);
201        let converted = original.to::<SolarLuminosity>();
202        let back = converted.to::<Watt>();
203        assert_relative_eq!(back.value(), original.value(), max_relative = 1e-12);
204    }
205
206    // ─────────────────────────────────────────────────────────────────────────────
207    // Property-based tests
208    // ─────────────────────────────────────────────────────────────────────────────
209
210    proptest! {
211        #[test]
212        fn prop_roundtrip_w_sol(w in 1e20..1e30f64) {
213            let original = Watts::new(w);
214            let converted = original.to::<SolarLuminosity>();
215            let back = converted.to::<Watt>();
216            prop_assert!((back.value() - original.value()).abs() / original.value() < 1e-12);
217        }
218    }
219}