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_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#[cfg(test)]
143mod tests {
144    use super::*;
145    use approx::assert_relative_eq;
146    use proptest::prelude::*;
147
148    // ─────────────────────────────────────────────────────────────────────────────
149    // Basic conversions
150    // ─────────────────────────────────────────────────────────────────────────────
151
152    #[test]
153    fn solar_luminosity_to_watts() {
154        let sol = SolarLuminosities::new(1.0);
155        let w = sol.to::<Watt>();
156        // 1 L☉ = 3.828e26 W
157        assert_relative_eq!(w.value(), 3.828e26, max_relative = 1e-9);
158    }
159
160    #[test]
161    fn watts_to_solar_luminosity() {
162        let w = Watts::new(3.828e26);
163        let sol = w.to::<SolarLuminosity>();
164        assert_relative_eq!(sol.value(), 1.0, max_relative = 1e-9);
165    }
166
167    #[test]
168    fn multiple_solar_luminosities() {
169        let sol = SolarLuminosities::new(3.0);
170        let w = sol.to::<Watt>();
171        assert_relative_eq!(w.value(), 3.0 * 3.828e26, max_relative = 1e-9);
172    }
173
174    // ─────────────────────────────────────────────────────────────────────────────
175    // Solar luminosity sanity checks
176    // ─────────────────────────────────────────────────────────────────────────────
177
178    #[test]
179    fn solar_luminosity_ratio_sanity() {
180        // RATIO should be 3.828e26
181        assert_relative_eq!(SolarLuminosity::RATIO, 3.828e26, max_relative = 1e-9);
182    }
183
184    #[test]
185    fn solar_luminosity_order_of_magnitude() {
186        let sun = SolarLuminosities::new(1.0);
187        let w = sun.to::<Watt>();
188        // Should be between 1e26 and 1e27
189        assert!(w.value() > 1e26);
190        assert!(w.value() < 1e27);
191    }
192
193    // ─────────────────────────────────────────────────────────────────────────────
194    // Roundtrip conversions
195    // ─────────────────────────────────────────────────────────────────────────────
196
197    #[test]
198    fn roundtrip_w_sol() {
199        let original = Watts::new(1e26);
200        let converted = original.to::<SolarLuminosity>();
201        let back = converted.to::<Watt>();
202        assert_relative_eq!(back.value(), original.value(), max_relative = 1e-12);
203    }
204
205    // ─────────────────────────────────────────────────────────────────────────────
206    // Property-based tests
207    // ─────────────────────────────────────────────────────────────────────────────
208
209    proptest! {
210        #[test]
211        fn prop_roundtrip_w_sol(w in 1e20..1e30f64) {
212            let original = Watts::new(w);
213            let converted = original.to::<SolarLuminosity>();
214            let back = converted.to::<Watt>();
215            prop_assert!((back.value() - original.value()).abs() / original.value() < 1e-12);
216        }
217    }
218}