qtty_core/units/
velocity.rs

1//! Velocity unit aliases (`Length / Time`).
2//!
3//! This module defines velocity units as *pure type aliases* over [`Per`] using
4//! length and time units already defined elsewhere in the crate.
5//!
6//! No standalone velocity units are introduced: every velocity is represented as
7//! `Length / Time` at the type level.
8//!
9//! ## Design notes
10//!
11//! - The velocity *dimension* is [`Velocity`] = [`Length`] / [`Time`].
12//! - All velocity units are zero-cost type aliases.
13//! - Conversions are handled automatically via the underlying length and time units.
14//! - No assumptions are made about reference frames, relativistic effects, or media.
15//!
16//! ## Examples
17//!
18//! ```rust
19//! use qtty_core::length::{Kilometer, Kilometers};
20//! use qtty_core::time::{Second, Seconds};
21//! use qtty_core::velocity::Velocity;
22//!
23//! let d = Kilometers::new(42.0);
24//! let t = Seconds::new(2.0);
25//! let v: Velocity<Kilometer, Second> = d / t;
26//! assert!((v.value() - 21.0).abs() < 1e-12);
27//! ```
28//!
29//! ```rust
30//! use qtty_core::length::{Meter, Meters};
31//! use qtty_core::time::{Hour, Hours};
32//! use qtty_core::velocity::Velocity;
33//!
34//! let v: Velocity<Meter, Hour> = Meters::new(3_600.0) / Hours::new(1.0);
35//! assert!((v.value() - 3_600.0).abs() < 1e-12);
36//! ```
37
38use crate::units::length::Length;
39use crate::units::time::Time;
40use crate::{DivDim, Per, Quantity, Unit};
41
42/// Dimension alias for velocities (`Length / Time`).
43pub type VelocityDim = DivDim<Length, Time>;
44
45/// Marker trait for any unit whose dimension is [`VelocityDim`].
46pub trait VelocityUnit: Unit<Dim = VelocityDim> {}
47impl<T: Unit<Dim = VelocityDim>> VelocityUnit for T {}
48
49/// A velocity quantity parameterized by length and time units.
50///
51/// # Examples
52///
53/// ```rust
54/// use qtty_core::length::{Kilometer, Meter};
55/// use qtty_core::time::{Second, Hour};
56/// use qtty_core::velocity::Velocity;
57///
58/// let v1: Velocity<Meter, Second> = Velocity::new(10.0);
59/// let v2: Velocity<Kilometer, Hour> = Velocity::new(36.0);
60/// ```
61pub type Velocity<N, D> = Quantity<Per<N, D>>;
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66    use crate::units::length::{Au, Kilometer, Kilometers, Meter};
67    use crate::units::time::{Day, Hour, Second, Seconds};
68    use crate::Per;
69    use approx::{assert_abs_diff_eq, assert_relative_eq};
70    use proptest::prelude::*;
71
72    // ─────────────────────────────────────────────────────────────────────────────
73    // Basic velocity conversions
74    // ─────────────────────────────────────────────────────────────────────────────
75
76    #[test]
77    fn km_per_s_to_m_per_s() {
78        let v: Velocity<Kilometer, Second> = Velocity::new(1.0);
79        let v_mps: Velocity<Meter, Second> = v.to();
80        assert_abs_diff_eq!(v_mps.value(), 1000.0, epsilon = 1e-9);
81    }
82
83    #[test]
84    fn m_per_s_to_km_per_s() {
85        let v: Velocity<Meter, Second> = Velocity::new(1000.0);
86        let v_kps: Velocity<Kilometer, Second> = v.to();
87        assert_abs_diff_eq!(v_kps.value(), 1.0, epsilon = 1e-12);
88    }
89
90    #[test]
91    fn km_per_h_to_m_per_s() {
92        let v: Velocity<Kilometer, Hour> = Velocity::new(3.6);
93        let v_mps: Velocity<Meter, Second> = v.to();
94        // 3.6 km/h = 1 m/s
95        assert_abs_diff_eq!(v_mps.value(), 1.0, epsilon = 1e-12);
96    }
97
98    #[test]
99    fn km_per_h_to_km_per_s() {
100        let v: Velocity<Kilometer, Hour> = Velocity::new(3600.0);
101        let v_kps: Velocity<Kilometer, Second> = v.to();
102        // 3600 km/h = 1 km/s
103        assert_abs_diff_eq!(v_kps.value(), 1.0, epsilon = 1e-12);
104    }
105
106    #[test]
107    fn au_per_day_to_km_per_s() {
108        let v: Velocity<Au, Day> = Velocity::new(1.0);
109        let v_kps: Velocity<Kilometer, Second> = v.to();
110        // 1 AU/day = 149,597,870.7 km / 86400 s ≈ 1731.5 km/s
111        assert_relative_eq!(v_kps.value(), 1731.5, max_relative = 1e-3);
112    }
113
114    // ─────────────────────────────────────────────────────────────────────────────
115    // Per ratio behavior
116    // ─────────────────────────────────────────────────────────────────────────────
117
118    #[test]
119    fn per_ratio_km_s() {
120        // Per<Kilometer, Second> should have RATIO = 1000 / 1 = 1000
121        let ratio = <Per<Kilometer, Second>>::RATIO;
122        // Kilometer::RATIO = 1000, Second::RATIO = 1.0
123        // So Per ratio = 1000 / 1.0 = 1000
124        assert_relative_eq!(ratio, 1000.0, max_relative = 1e-12);
125    }
126
127    #[test]
128    fn per_ratio_m_s() {
129        // Per<Meter, Second> has RATIO = 1 / 1 = 1
130        let ratio = <Per<Meter, Second>>::RATIO;
131        assert_relative_eq!(ratio, 1.0, max_relative = 1e-12);
132    }
133
134    // ─────────────────────────────────────────────────────────────────────────────
135    // Velocity * Time = Length
136    // ─────────────────────────────────────────────────────────────────────────────
137
138    #[test]
139    fn velocity_times_time() {
140        let v: Velocity<Kilometer, Second> = Velocity::new(10.0);
141        let t: Seconds = Seconds::new(5.0);
142        let d: Kilometers = v * t;
143        assert_abs_diff_eq!(d.value(), 50.0, epsilon = 1e-9);
144    }
145
146    #[test]
147    fn time_times_velocity() {
148        let v: Velocity<Kilometer, Second> = Velocity::new(10.0);
149        let t: Seconds = Seconds::new(5.0);
150        let d: Kilometers = t * v;
151        assert_abs_diff_eq!(d.value(), 50.0, epsilon = 1e-9);
152    }
153
154    // ─────────────────────────────────────────────────────────────────────────────
155    // Length / Time = Velocity
156    // ─────────────────────────────────────────────────────────────────────────────
157
158    #[test]
159    fn length_div_time() {
160        let d: Kilometers = Kilometers::new(100.0);
161        let t: Seconds = Seconds::new(10.0);
162        let v: Velocity<Kilometer, Second> = d / t;
163        assert_abs_diff_eq!(v.value(), 10.0, epsilon = 1e-9);
164    }
165
166    // ─────────────────────────────────────────────────────────────────────────────
167    // Roundtrip conversions
168    // ─────────────────────────────────────────────────────────────────────────────
169
170    #[test]
171    fn roundtrip_mps_kps() {
172        let original: Velocity<Meter, Second> = Velocity::new(500.0);
173        let converted: Velocity<Kilometer, Second> = original.to();
174        let back: Velocity<Meter, Second> = converted.to();
175        assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-9);
176    }
177
178    // ─────────────────────────────────────────────────────────────────────────────
179    // Property-based tests
180    // ─────────────────────────────────────────────────────────────────────────────
181
182    proptest! {
183        #[test]
184        fn prop_roundtrip_mps_kps(v in 1e-6..1e6f64) {
185            let original: Velocity<Meter, Second> = Velocity::new(v);
186            let converted: Velocity<Kilometer, Second> = original.to();
187            let back: Velocity<Meter, Second> = converted.to();
188            prop_assert!((back.value() - original.value()).abs() < 1e-9 * v.abs().max(1.0));
189        }
190
191        #[test]
192        fn prop_mps_kps_ratio(v in 1e-6..1e6f64) {
193            let mps: Velocity<Meter, Second> = Velocity::new(v);
194            let kps: Velocity<Kilometer, Second> = mps.to();
195            // 1000 m/s = 1 km/s
196            prop_assert!((mps.value() / kps.value() - 1000.0).abs() < 1e-9);
197        }
198
199        #[test]
200        fn prop_velocity_time_roundtrip(
201            v_val in 1e-3..1e3f64,
202            t_val in 1e-3..1e3f64
203        ) {
204            let v: Velocity<Kilometer, Second> = Velocity::new(v_val);
205            let t: Seconds = Seconds::new(t_val);
206            let d: Kilometers = v * t;
207            // d / t should give back v
208            let v_back: Velocity<Kilometer, Second> = d / t;
209            prop_assert!((v_back.value() - v.value()).abs() / v.value() < 1e-12);
210        }
211    }
212}