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::{Per, Quantity, Unit};
39
40/// Re-export the velocity dimension from the dimension module.
41pub use crate::dimension::VelocityDim;
42
43/// Marker trait for any unit whose dimension is [`VelocityDim`].
44pub trait VelocityUnit: Unit<Dim = VelocityDim> {}
45impl<T: Unit<Dim = VelocityDim>> VelocityUnit for T {}
46
47/// A velocity quantity parameterized by length and time units.
48///
49/// # Examples
50///
51/// ```rust
52/// use qtty_core::length::{Kilometer, Meter};
53/// use qtty_core::time::{Second, Hour};
54/// use qtty_core::velocity::Velocity;
55///
56/// let v1: Velocity<Meter, Second> = Velocity::new(10.0);
57/// let v2: Velocity<Kilometer, Hour> = Velocity::new(36.0);
58/// ```
59pub type Velocity<N, D> = Quantity<Per<N, D>>;
60
61#[cfg(test)]
62mod tests {
63 use super::*;
64 use crate::units::length::{Au, Kilometer, Kilometers, Meter};
65 use crate::units::time::{Day, Hour, Second, Seconds};
66 use crate::Per;
67 use approx::{assert_abs_diff_eq, assert_relative_eq};
68 use proptest::prelude::*;
69
70 // ─────────────────────────────────────────────────────────────────────────────
71 // Basic velocity conversions
72 // ─────────────────────────────────────────────────────────────────────────────
73
74 #[test]
75 fn km_per_s_to_m_per_s() {
76 let v: Velocity<Kilometer, Second> = Velocity::new(1.0);
77 let v_mps: Velocity<Meter, Second> = v.to();
78 assert_abs_diff_eq!(v_mps.value(), 1000.0, epsilon = 1e-9);
79 }
80
81 #[test]
82 fn m_per_s_to_km_per_s() {
83 let v: Velocity<Meter, Second> = Velocity::new(1000.0);
84 let v_kps: Velocity<Kilometer, Second> = v.to();
85 assert_abs_diff_eq!(v_kps.value(), 1.0, epsilon = 1e-12);
86 }
87
88 #[test]
89 fn km_per_h_to_m_per_s() {
90 let v: Velocity<Kilometer, Hour> = Velocity::new(3.6);
91 let v_mps: Velocity<Meter, Second> = v.to();
92 // 3.6 km/h = 1 m/s
93 assert_abs_diff_eq!(v_mps.value(), 1.0, epsilon = 1e-12);
94 }
95
96 #[test]
97 fn km_per_h_to_km_per_s() {
98 let v: Velocity<Kilometer, Hour> = Velocity::new(3600.0);
99 let v_kps: Velocity<Kilometer, Second> = v.to();
100 // 3600 km/h = 1 km/s
101 assert_abs_diff_eq!(v_kps.value(), 1.0, epsilon = 1e-12);
102 }
103
104 #[test]
105 fn au_per_day_to_km_per_s() {
106 let v: Velocity<Au, Day> = Velocity::new(1.0);
107 let v_kps: Velocity<Kilometer, Second> = v.to();
108 // 1 AU/day = 149,597,870.7 km / 86400 s ≈ 1731.5 km/s
109 assert_relative_eq!(v_kps.value(), 1731.5, max_relative = 1e-3);
110 }
111
112 // ─────────────────────────────────────────────────────────────────────────────
113 // Per ratio behavior
114 // ─────────────────────────────────────────────────────────────────────────────
115
116 #[test]
117 fn per_ratio_km_s() {
118 // Per<Kilometer, Second> should have RATIO = 1000 / 1 = 1000
119 let ratio = <Per<Kilometer, Second>>::RATIO;
120 // Kilometer::RATIO = 1000, Second::RATIO = 1.0
121 // So Per ratio = 1000 / 1.0 = 1000
122 assert_relative_eq!(ratio, 1000.0, max_relative = 1e-12);
123 }
124
125 #[test]
126 fn per_ratio_m_s() {
127 // Per<Meter, Second> has RATIO = 1 / 1 = 1
128 let ratio = <Per<Meter, Second>>::RATIO;
129 assert_relative_eq!(ratio, 1.0, max_relative = 1e-12);
130 }
131
132 // ─────────────────────────────────────────────────────────────────────────────
133 // Velocity * Time = Length
134 // ─────────────────────────────────────────────────────────────────────────────
135
136 #[test]
137 fn velocity_times_time() {
138 let v: Velocity<Kilometer, Second> = Velocity::new(10.0);
139 let t: Seconds = Seconds::new(5.0);
140 let d: Kilometers = (v * t).to();
141 assert_abs_diff_eq!(d.value(), 50.0, epsilon = 1e-9);
142 }
143
144 #[test]
145 fn time_times_velocity() {
146 let v: Velocity<Kilometer, Second> = Velocity::new(10.0);
147 let t: Seconds = Seconds::new(5.0);
148 let d: Kilometers = (t * v).to();
149 assert_abs_diff_eq!(d.value(), 50.0, epsilon = 1e-9);
150 }
151
152 // ─────────────────────────────────────────────────────────────────────────────
153 // Length / Time = Velocity
154 // ─────────────────────────────────────────────────────────────────────────────
155
156 #[test]
157 fn length_div_time() {
158 let d: Kilometers = Kilometers::new(100.0);
159 let t: Seconds = Seconds::new(10.0);
160 let v: Velocity<Kilometer, Second> = d / t;
161 assert_abs_diff_eq!(v.value(), 10.0, epsilon = 1e-9);
162 }
163
164 // ─────────────────────────────────────────────────────────────────────────────
165 // Roundtrip conversions
166 // ─────────────────────────────────────────────────────────────────────────────
167
168 #[test]
169 fn roundtrip_mps_kps() {
170 let original: Velocity<Meter, Second> = Velocity::new(500.0);
171 let converted: Velocity<Kilometer, Second> = original.to();
172 let back: Velocity<Meter, Second> = converted.to();
173 assert_abs_diff_eq!(back.value(), original.value(), epsilon = 1e-9);
174 }
175
176 // ─────────────────────────────────────────────────────────────────────────────
177 // Property-based tests
178 // ─────────────────────────────────────────────────────────────────────────────
179
180 proptest! {
181 #[test]
182 fn prop_roundtrip_mps_kps(v in 1e-6..1e6f64) {
183 let original: Velocity<Meter, Second> = Velocity::new(v);
184 let converted: Velocity<Kilometer, Second> = original.to();
185 let back: Velocity<Meter, Second> = converted.to();
186 prop_assert!((back.value() - original.value()).abs() < 1e-9 * v.abs().max(1.0));
187 }
188
189 #[test]
190 fn prop_mps_kps_ratio(v in 1e-6..1e6f64) {
191 let mps: Velocity<Meter, Second> = Velocity::new(v);
192 let kps: Velocity<Kilometer, Second> = mps.to();
193 // 1000 m/s = 1 km/s
194 prop_assert!((mps.value() / kps.value() - 1000.0).abs() < 1e-9);
195 }
196
197 #[test]
198 fn prop_velocity_time_roundtrip(
199 v_val in 1e-3..1e3f64,
200 t_val in 1e-3..1e3f64
201 ) {
202 let v: Velocity<Kilometer, Second> = Velocity::new(v_val);
203 let t: Seconds = Seconds::new(t_val);
204 let d: Kilometers = (v * t).to();
205 // d / t should give back v
206 let v_back: Velocity<Kilometer, Second> = d / t;
207 prop_assert!((v_back.value() - v.value()).abs() / v.value() < 1e-12);
208 }
209 }
210}