Skip to main content

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//!
18//! ## All power units
19//!
20//! ```rust
21//! use qtty_core::power::*;
22//! use qtty_core::Quantity;
23//!
24//! macro_rules! touch {
25//!     ($T:ty, $v:expr) => {{ let q = <$T>::new($v); let _c = q; assert!(q == q); }};
26//! }
27//!
28//! touch!(Watts, 1.0);
29//! touch!(Kilowatts, 1.0);   touch!(Megawatts, 1.0);  touch!(Gigawatts, 1.0);
30//! touch!(Milliwatts, 1.0);  touch!(Microwatts, 1.0);
31//! touch!(HorsepowerMetrics, 1.0); touch!(HorsepowerElectrics, 1.0);
32//! touch!(SolarLuminosities, 1.0);
33//! let erg = Quantity::<ErgPerSecond>::new(1.0);
34//! assert!(erg == erg);
35//! ```
36
37use crate::{Quantity, Unit};
38use qtty_derive::Unit;
39
40/// Re-export from the dimension module.
41pub use crate::dimension::Power;
42
43/// Marker trait for power units.
44pub trait PowerUnit: Unit<Dim = Power> {}
45impl<T: Unit<Dim = Power>> PowerUnit for T {}
46
47/// Watt (SI coherent derived unit).
48#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
49#[unit(symbol = "W", dimension = Power, ratio = 1.0)]
50pub struct Watt;
51/// Type alias shorthand for [`Watt`].
52pub type W = Watt;
53/// A quantity measured in watts.
54pub type Watts = Quantity<W>;
55/// One watt.
56pub const WATT: Watts = Watts::new(1.0);
57
58macro_rules! si_watt {
59    ($name:ident, $sym:literal, $ratio:expr, $alias:ident, $qty:ident, $one:ident) => {
60        #[doc = concat!("SI-prefixed watt unit (", stringify!($ratio), " W).")]
61        #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
62        #[unit(symbol = $sym, dimension = Power, ratio = $ratio)]
63        pub struct $name;
64        #[doc = concat!("Type alias shorthand for [`", stringify!($name), "`].")]
65        pub type $alias = $name;
66        #[doc = concat!("A quantity measured in ", stringify!($name), "s.")]
67        pub type $qty = Quantity<$alias>;
68        #[doc = concat!("One ", stringify!($name), ".")]
69        pub const $one: $qty = $qty::new(1.0);
70    };
71}
72
73// Full SI prefix ladder on watt
74si_watt!(Yoctowatt, "yW", 1e-24, Yw, Yoctowatts, YW);
75si_watt!(Zeptowatt, "zW", 1e-21, Zw, Zeptowatts, ZW);
76si_watt!(Attowatt, "aW", 1e-18, Aw, Attowatts, AW);
77si_watt!(Femtowatt, "fW", 1e-15, Fw, Femtowatts, FW);
78si_watt!(Picowatt, "pW", 1e-12, Pw, Picowatts, PW);
79si_watt!(Nanowatt, "nW", 1e-9, Nw, Nanowatts, NW);
80si_watt!(Microwatt, "µW", 1e-6, Uw, Microwatts, UW);
81si_watt!(Milliwatt, "mW", 1e-3, Mw, Milliwatts, MW_1);
82
83si_watt!(Deciwatt, "dW", 1e-1, Dw, Deciwatts, DW);
84si_watt!(Decawatt, "daW", 1e1, Daw, Decawatts, DAW);
85si_watt!(Hectowatt, "hW", 1e2, Hw, Hectowatts, HW);
86si_watt!(Kilowatt, "kW", 1e3, Kw, Kilowatts, KW);
87si_watt!(Megawatt, "MW", 1e6, MW, Megawatts, MEGAWATT);
88si_watt!(Gigawatt, "GW", 1e9, GW, Gigawatts, GW_1);
89si_watt!(Terawatt, "TW", 1e12, TW, Terawatts, TW_1);
90si_watt!(Petawatt, "PW", 1e15, PW, Petawatts, PETAWATT);
91si_watt!(Exawatt, "EW", 1e18, EW, Exawatts, EW_1);
92si_watt!(Zettawatt, "ZW", 1e21, ZW, Zettawatts, ZW_1);
93si_watt!(Yottawatt, "YW", 1e24, YW, Yottawatts, YW_1);
94
95/// Erg per second (`erg/s`).
96///
97/// Exact: `1 erg = 1e-7 J`, so `1 erg/s = 1e-7 W`.
98#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
99#[unit(symbol = "erg/s", dimension = Power, ratio = 1e-7)]
100pub struct ErgPerSecond;
101/// One erg/s.
102pub const ERG_PER_S: Quantity<ErgPerSecond> = Quantity::new(1.0);
103
104/// Metric horsepower (`PS`), defined as exactly `735.49875 W`.
105#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
106#[unit(symbol = "PS", dimension = Power, ratio = 73_549_875.0 / 100_000.0)]
107pub struct HorsepowerMetric;
108/// A quantity measured in metric horsepower.
109pub type HorsepowerMetrics = Quantity<HorsepowerMetric>;
110/// One metric horsepower.
111pub const PS: HorsepowerMetrics = HorsepowerMetrics::new(1.0);
112
113/// Electric horsepower (`hp_e`), defined as exactly `746 W`.
114#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
115#[unit(symbol = "hp_e", dimension = Power, ratio = 746.0)]
116pub struct HorsepowerElectric;
117/// A quantity measured in electric horsepower.
118pub type HorsepowerElectrics = Quantity<HorsepowerElectric>;
119/// One electric horsepower.
120pub const HP_E: HorsepowerElectrics = HorsepowerElectrics::new(1.0);
121
122/// Solar luminosity (IAU nominal constant; watts per L☉).
123///
124/// This is a *nominal reference* value intended for consistent conversion.
125#[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Unit)]
126#[unit(symbol = "L☉", dimension = Power, ratio = 3.828e26)]
127pub struct SolarLuminosity;
128/// A quantity measured in solar luminosities.
129pub type SolarLuminosities = Quantity<SolarLuminosity>;
130/// One solar luminosity.
131pub const L_SUN: SolarLuminosities = SolarLuminosities::new(1.0);
132
133// Generate all bidirectional From implementations between power units.
134crate::impl_unit_from_conversions!(
135    Watt,
136    Yoctowatt,
137    Zeptowatt,
138    Attowatt,
139    Femtowatt,
140    Picowatt,
141    Nanowatt,
142    Microwatt,
143    Milliwatt,
144    Deciwatt,
145    Decawatt,
146    Hectowatt,
147    Kilowatt,
148    Megawatt,
149    Gigawatt,
150    Terawatt,
151    Petawatt,
152    Exawatt,
153    Zettawatt,
154    Yottawatt,
155    ErgPerSecond,
156    HorsepowerMetric,
157    HorsepowerElectric,
158    SolarLuminosity
159);
160
161// Optional cross-unit operator support (`==`, `<`, etc.).
162#[cfg(feature = "cross-unit-ops")]
163crate::impl_unit_cross_unit_ops!(
164    Watt,
165    Yoctowatt,
166    Zeptowatt,
167    Attowatt,
168    Femtowatt,
169    Picowatt,
170    Nanowatt,
171    Microwatt,
172    Milliwatt,
173    Deciwatt,
174    Decawatt,
175    Hectowatt,
176    Kilowatt,
177    Megawatt,
178    Gigawatt,
179    Terawatt,
180    Petawatt,
181    Exawatt,
182    Zettawatt,
183    Yottawatt,
184    ErgPerSecond,
185    HorsepowerMetric,
186    HorsepowerElectric,
187    SolarLuminosity
188);
189
190#[cfg(test)]
191mod tests {
192    use super::*;
193    use approx::assert_relative_eq;
194    use proptest::prelude::*;
195
196    // ─────────────────────────────────────────────────────────────────────────────
197    // Basic conversions
198    // ─────────────────────────────────────────────────────────────────────────────
199
200    #[test]
201    fn solar_luminosity_to_watts() {
202        let sol = SolarLuminosities::new(1.0);
203        let w = sol.to::<Watt>();
204        // 1 L☉ = 3.828e26 W
205        assert_relative_eq!(w.value(), 3.828e26, max_relative = 1e-9);
206    }
207
208    #[test]
209    fn watts_to_solar_luminosity() {
210        let w = Watts::new(3.828e26);
211        let sol = w.to::<SolarLuminosity>();
212        assert_relative_eq!(sol.value(), 1.0, max_relative = 1e-9);
213    }
214
215    #[test]
216    fn multiple_solar_luminosities() {
217        let sol = SolarLuminosities::new(3.0);
218        let w = sol.to::<Watt>();
219        assert_relative_eq!(w.value(), 3.0 * 3.828e26, max_relative = 1e-9);
220    }
221
222    // ─────────────────────────────────────────────────────────────────────────────
223    // Solar luminosity sanity checks
224    // ─────────────────────────────────────────────────────────────────────────────
225
226    #[test]
227    fn solar_luminosity_ratio_sanity() {
228        // RATIO should be 3.828e26
229        assert_relative_eq!(SolarLuminosity::RATIO, 3.828e26, max_relative = 1e-9);
230    }
231
232    #[test]
233    fn solar_luminosity_order_of_magnitude() {
234        let sun = SolarLuminosities::new(1.0);
235        let w = sun.to::<Watt>();
236        // Should be between 1e26 and 1e27
237        assert!(w.value() > 1e26);
238        assert!(w.value() < 1e27);
239    }
240
241    // ─────────────────────────────────────────────────────────────────────────────
242    // Roundtrip conversions
243    // ─────────────────────────────────────────────────────────────────────────────
244
245    #[test]
246    fn roundtrip_w_sol() {
247        let original = Watts::new(1e26);
248        let converted = original.to::<SolarLuminosity>();
249        let back = converted.to::<Watt>();
250        assert_relative_eq!(back.value(), original.value(), max_relative = 1e-12);
251    }
252
253    // ─────────────────────────────────────────────────────────────────────────────
254    // Property-based tests
255    // ─────────────────────────────────────────────────────────────────────────────
256
257    proptest! {
258        #[test]
259        fn prop_roundtrip_w_sol(w in 1e20..1e30f64) {
260            let original = Watts::new(w);
261            let converted = original.to::<SolarLuminosity>();
262            let back = converted.to::<Watt>();
263            prop_assert!((back.value() - original.value()).abs() / original.value() < 1e-12);
264        }
265    }
266
267    // ─── SI-prefixed watt units ──────────────────────────────────────────────
268
269    #[test]
270    fn kilowatt_to_watt() {
271        let kw = Kilowatts::new(1.0);
272        let w = kw.to::<Watt>();
273        assert_relative_eq!(w.value(), 1_000.0, max_relative = 1e-12);
274    }
275
276    #[test]
277    fn megawatt_to_kilowatt() {
278        let mw = Megawatts::new(1.0);
279        let kw = mw.to::<Kilowatt>();
280        assert_relative_eq!(kw.value(), 1_000.0, max_relative = 1e-12);
281    }
282
283    #[test]
284    fn milliwatt_to_watt() {
285        let mw = Milliwatts::new(1000.0);
286        let w = mw.to::<Watt>();
287        assert_relative_eq!(w.value(), 1.0, max_relative = 1e-12);
288    }
289
290    // ─── Non-SI power units ──────────────────────────────────────────────────
291
292    #[test]
293    fn erg_per_second_to_watt() {
294        let erg_s = Quantity::<ErgPerSecond>::new(1e7);
295        let w = erg_s.to::<Watt>();
296        // 1e7 erg/s = 1 W
297        assert_relative_eq!(w.value(), 1.0, max_relative = 1e-9);
298    }
299
300    #[test]
301    fn metric_horsepower_to_watt() {
302        let ps = HorsepowerMetrics::new(1.0);
303        let w = ps.to::<Watt>();
304        // 1 PS = 735.49875 W
305        assert_relative_eq!(w.value(), 735.498_75, max_relative = 1e-9);
306    }
307
308    #[test]
309    fn electric_horsepower_to_watt() {
310        let hp = HorsepowerElectrics::new(1.0);
311        let w = hp.to::<Watt>();
312        // 1 hp_e = 746 W (exact)
313        assert_relative_eq!(w.value(), 746.0, max_relative = 1e-12);
314    }
315
316    #[test]
317    fn symbols_are_correct() {
318        assert_eq!(Watt::SYMBOL, "W");
319        assert_eq!(Kilowatt::SYMBOL, "kW");
320        assert_eq!(Megawatt::SYMBOL, "MW");
321        assert_eq!(HorsepowerMetric::SYMBOL, "PS");
322        assert_eq!(ErgPerSecond::SYMBOL, "erg/s");
323    }
324}