Skip to main content

conspire/units/scale/
mod.rs

1//! The scales a quantity may be named in.
2//!
3//! A unit says what kind of thing a number is; a scale says how big the number
4//! is against that kind. Only the kind is worth carrying, so a scale is spent
5//! where the quantity is built and the value is held in the base scale
6//! thereafter. Every constructor and reader below is a `const fn` over a factor
7//! known at compile time, so naming a scale costs nothing at run time.
8//!
9//! A quantity is read back the way it was written, by naming a scale rather
10//! than supplying one, so the base scale is only ever a matter between the
11//! quantity and itself.
12//!
13//! The base scales are SI, coherent throughout: metres, kilograms, seconds and
14//! kelvin, so pascals, newtons, joules and watts follow. A factor is only
15//! correct against the rest of that set, which is what the tests check by
16//! crossing from one unit to another rather than within one.
17
18#[cfg(test)]
19mod test;
20
21use super::{
22    Action, Amount, Area, Charge, Dimensionless, Energy, Entropy, Force, ForcePerLength, Length,
23    MolarEnergy, MolarEntropy, PowerPerLengthTemperature, Rate, ReciprocalAmount,
24    ReciprocalTemperature, Stress, StressPerLength, Temperature, Time, Velocity, Viscosity, Volume,
25};
26use crate::math::{Quantity, TensorRank0};
27
28/// The kelvin a celsius is measured from.
29const ZERO_CELSIUS: TensorRank0 = 273.15;
30
31/// A scale named as the constructor that spends it.
32pub type Scale<U> = fn(TensorRank0) -> Quantity<U>;
33
34/// The scale a length is named in, where it is named in words rather than in a
35/// type.
36///
37/// A file that says what its numbers are in has to say it as text, so the words
38/// are matched here and nowhere else. A word that is not among them is nothing
39/// rather than a guess, since a length taken in the wrong scale is wrong by a
40/// factor rather than by a little, and a file that troubled to say something is
41/// the last place to stop listening.
42pub fn length_scale(label: &str) -> Option<Scale<Length>> {
43    Some(match label.trim().to_lowercase().as_str() {
44        "m" | "meter" | "meters" | "metre" | "metres" => Length::meters,
45        "mm" | "millimeter" | "millimeters" | "millimetre" | "millimetres" => Length::millimeters,
46        "cm" | "centimeter" | "centimeters" | "centimetre" | "centimetres" => Length::centimeters,
47        "um" | "\u{b5}m" | "\u{3bc}m" | "micrometer" | "micrometers" | "micrometre"
48        | "micrometres" | "micron" | "microns" => Length::micrometers,
49        "nm" | "nanometer" | "nanometers" | "nanometre" | "nanometres" => Length::nanometers,
50        "km" | "kilometer" | "kilometers" | "kilometre" | "kilometres" => Length::kilometers,
51        "in" | "inch" | "inches" => Length::inches,
52        "ft" | "foot" | "feet" => Length::feet,
53        _ => return None,
54    })
55}
56
57//
58// A reader is named alongside the constructor it undoes rather than derived
59// from it, since deriving one identifier from another is what a dependency
60// would be for and this crate has none.
61//
62macro_rules! scales {
63    ($($unit:ident { $($name:ident / $reader:ident = $factor:expr, $doc:expr),+ $(,)? })+) => {
64        $(
65            impl $unit {
66                $(
67                    #[doc = concat!("A quantity of ", $doc, ".")]
68                    pub const fn $name(value: TensorRank0) -> Quantity<$unit> {
69                        Quantity::new(value * $factor)
70                    }
71                )+
72            }
73            impl Quantity<$unit> {
74                $(
75                    #[doc = concat!("How many ", $doc, " the quantity is.")]
76                    pub const fn $reader(&self) -> TensorRank0 {
77                        self.value() / $factor
78                    }
79                )+
80            }
81        )+
82    };
83}
84
85scales!(
86    Length {
87        meters / in_meters = 1.0, "metres",
88        millimeters / in_millimeters = 1e-3, "millimetres",
89        micrometers / in_micrometers = 1e-6, "micrometres",
90        nanometers / in_nanometers = 1e-9, "nanometres",
91        centimeters / in_centimeters = 1e-2, "centimetres",
92        kilometers / in_kilometers = 1e3, "kilometres",
93        inches / in_inches = 2.54e-2, "inches",
94        feet / in_feet = 3.048e-1, "feet",
95    }
96    Area {
97        square_meters / in_square_meters = 1.0, "square metres",
98        square_millimeters / in_square_millimeters = 1e-6, "square millimetres",
99        square_centimeters / in_square_centimeters = 1e-4, "square centimetres",
100    }
101    Volume {
102        cubic_meters / in_cubic_meters = 1.0, "cubic metres",
103        cubic_millimeters / in_cubic_millimeters = 1e-9, "cubic millimetres",
104        cubic_centimeters / in_cubic_centimeters = 1e-6, "cubic centimetres",
105        liters / in_liters = 1e-3, "litres",
106    }
107    Time {
108        seconds / in_seconds = 1.0, "seconds",
109        milliseconds / in_milliseconds = 1e-3, "milliseconds",
110        microseconds / in_microseconds = 1e-6, "microseconds",
111        minutes / in_minutes = 6e1, "minutes",
112        hours / in_hours = 3.6e3, "hours",
113    }
114    Rate {
115        per_second / in_per_second = 1.0, "reciprocal seconds",
116        per_minute / in_per_minute = 1.0 / 6e1, "reciprocal minutes",
117        per_hour / in_per_hour = 1.0 / 3.6e3, "reciprocal hours",
118        hertz / in_hertz = 1.0, "hertz",
119    }
120    Stress {
121        pascals / in_pascals = 1.0, "pascals",
122        kilopascals / in_kilopascals = 1e3, "kilopascals",
123        megapascals / in_megapascals = 1e6, "megapascals",
124        gigapascals / in_gigapascals = 1e9, "gigapascals",
125        bars / in_bars = 1e5, "bars",
126        psi / in_psi = 6.894_757_293_168_361e3, "pounds per square inch",
127        ksi / in_ksi = 6.894_757_293_168_361e6, "kips per square inch",
128    }
129    Force {
130        newtons / in_newtons = 1.0, "newtons",
131        millinewtons / in_millinewtons = 1e-3, "millinewtons",
132        kilonewtons / in_kilonewtons = 1e3, "kilonewtons",
133        meganewtons / in_meganewtons = 1e6, "meganewtons",
134        pounds_force / in_pounds_force = 4.448_221_615_260_5, "pounds force",
135    }
136    Energy {
137        joules / in_joules = 1.0, "joules",
138        millijoules / in_millijoules = 1e-3, "millijoules",
139        kilojoules / in_kilojoules = 1e3, "kilojoules",
140        electronvolts / in_electronvolts = 1.602_176_634e-19, "electronvolts",
141    }
142    Entropy {
143        joules_per_kelvin / in_joules_per_kelvin = 1.0, "joules per kelvin",
144    }
145    Action {
146        joule_seconds / in_joule_seconds = 1.0, "joule seconds",
147    }
148    Amount {
149        moles / in_moles = 1.0, "moles",
150        millimoles / in_millimoles = 1e-3, "millimoles",
151    }
152    ReciprocalAmount {
153        per_mole / in_per_mole = 1.0, "reciprocal moles",
154    }
155    MolarEntropy {
156        joules_per_mole_kelvin / in_joules_per_mole_kelvin = 1.0, "joules per mole kelvin",
157    }
158    MolarEnergy {
159        joules_per_mole / in_joules_per_mole = 1.0, "joules per mole",
160        kilojoules_per_mole / in_kilojoules_per_mole = 1e3, "kilojoules per mole",
161    }
162    ForcePerLength {
163        newtons_per_meter / in_newtons_per_meter = 1.0, "newtons per metre",
164        piconewtons_per_nanometer / in_piconewtons_per_nanometer = 1e-3,
165            "piconewtons per nanometre",
166    }
167    Velocity {
168        meters_per_second / in_meters_per_second = 1.0, "metres per second",
169        millimeters_per_second / in_millimeters_per_second = 1e-3, "millimetres per second",
170        kilometers_per_hour / in_kilometers_per_hour = 1.0 / 3.6, "kilometres per hour",
171    }
172    Charge {
173        coulombs / in_coulombs = 1.0, "coulombs",
174    }
175    Viscosity {
176        pascal_seconds / in_pascal_seconds = 1.0, "pascal seconds",
177        poise / in_poise = 1e-1, "poise",
178        centipoise / in_centipoise = 1e-3, "centipoise",
179    }
180    Temperature {
181        kelvin / in_kelvin = 1.0, "kelvin",
182    }
183    ReciprocalTemperature {
184        per_kelvin / in_per_kelvin = 1.0, "reciprocal kelvin",
185        per_celsius / in_per_celsius = 1.0, "reciprocal degrees celsius",
186    }
187    StressPerLength {
188        pascals_per_meter / in_pascals_per_meter = 1.0, "pascals per metre",
189        megapascals_per_millimeter / in_megapascals_per_millimeter = 1e9,
190            "megapascals per millimetre",
191    }
192    PowerPerLengthTemperature {
193        watts_per_meter_kelvin / in_watts_per_meter_kelvin = 1.0, "watts per metre kelvin",
194    }
195    Dimensionless {
196        percent / in_percent = 1e-2, "percent",
197    }
198);
199
200impl Dimensionless {
201    /// A quantity of no unit at all.
202    ///
203    /// Named here rather than in the table, since a row names a reader as well
204    /// and there is no word to read a ratio back in. A quantity of no unit is
205    /// the number it holds, which [`value`](Quantity::value) already gives.
206    pub const fn of(value: TensorRank0) -> Quantity<Dimensionless> {
207        Quantity::new(value)
208    }
209}
210
211impl Temperature {
212    /// A quantity of degrees celsius.
213    ///
214    /// A celsius is a kelvin offset rather than scaled, so this names a
215    /// temperature and not a difference between two. A difference is the same
216    /// number of either, and is the [`kelvin`](Self::kelvin) it already is.
217    pub const fn celsius(value: TensorRank0) -> Quantity<Temperature> {
218        Quantity::new(value + ZERO_CELSIUS)
219    }
220    /// A quantity of degrees fahrenheit.
221    ///
222    /// Offset as well as scaled, so the same caution applies as for
223    /// [`celsius`](Self::celsius).
224    pub const fn fahrenheit(value: TensorRank0) -> Quantity<Temperature> {
225        Quantity::new((value - 32.0) * 5.0 / 9.0 + ZERO_CELSIUS)
226    }
227}
228
229impl Quantity<Temperature> {
230    /// How many degrees celsius the temperature is.
231    pub const fn in_celsius(&self) -> TensorRank0 {
232        self.value() - ZERO_CELSIUS
233    }
234    /// How many degrees fahrenheit the temperature is.
235    pub const fn in_fahrenheit(&self) -> TensorRank0 {
236        (self.value() - ZERO_CELSIUS) * 9.0 / 5.0 + 32.0
237    }
238}