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