Skip to main content

hydra_common/
quantity.rs

1//! Quantity contract: engine-declared physical quantities and their two
2//! display-system renderings (spec §5).
3//!
4//! Values crossing an engine boundary for a quantity-bearing field are in
5//! that quantity's **SI display unit**; applications convert for display
6//! and convert back on input using only the descriptor. Engines never
7//! format, and applications never hardcode a conversion — so a quantity
8//! this layer has never heard of (a rainfall intensity, an infiltration
9//! rate) costs an application nothing to support.
10
11use serde::Serialize;
12
13/// Descriptor of one physical quantity in an engine's catalog (spec §5).
14///
15/// Quantity keys are engine-scoped: two engines may both declare a `flow`
16/// quantity without their descriptors agreeing, because no value ever
17/// crosses between engines.
18#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
19#[serde(rename_all = "camelCase")]
20pub struct QuantityDescriptor {
21    /// Stable quantity identifier, opaque to this layer; referenced by
22    /// attribute schemas (spec §4.4) and result variables (spec §6).
23    pub key: &'static str,
24    /// Unit text in the SI display system (e.g. "m", "L/s", "mm/hr").
25    pub si_label: &'static str,
26    /// Unit text in the US-customary display system (e.g. "ft", "gpm").
27    pub us_label: &'static str,
28    /// Scale of the affine SI→US display conversion:
29    /// `us = si * si_to_us_scale + si_to_us_offset`.
30    pub si_to_us_scale: f64,
31    /// Offset of the affine SI→US display conversion. Zero for all but
32    /// temperature-like quantities.
33    pub si_to_us_offset: f64,
34    /// Suggested display precision in the SI system. Advisory.
35    pub si_decimals: u8,
36    /// Suggested display precision in the US system. Advisory.
37    pub us_decimals: u8,
38}
39
40impl QuantityDescriptor {
41    /// Convert a value from the SI display unit to the US display unit.
42    pub fn si_to_us(&self, si: f64) -> f64 {
43        si * self.si_to_us_scale + self.si_to_us_offset
44    }
45
46    /// Convert a value from the US display unit back to the SI display
47    /// unit — the exact inverse of [`Self::si_to_us`].
48    pub fn us_to_si(&self, us: f64) -> f64 {
49        (us - self.si_to_us_offset) / self.si_to_us_scale
50    }
51}
52
53#[cfg(test)]
54mod tests {
55    use super::*;
56
57    const TEMP: QuantityDescriptor = QuantityDescriptor {
58        key: "temperature",
59        si_label: "°C",
60        us_label: "°F",
61        si_to_us_scale: 1.8,
62        si_to_us_offset: 32.0,
63        si_decimals: 1,
64        us_decimals: 1,
65    };
66
67    #[test]
68    fn affine_conversion_round_trips() {
69        assert_eq!(TEMP.si_to_us(100.0), 212.0);
70        assert_eq!(TEMP.us_to_si(212.0), 100.0);
71        assert_eq!(TEMP.us_to_si(TEMP.si_to_us(37.5)), 37.5);
72    }
73}