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}