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}