qtty_core/units/velocity/mod.rs
1// SPDX-License-Identifier: BSD-3-Clause
2// Copyright (C) 2026 Vallés Puig, Ramon
3
4//! Velocity unit aliases (`Length / Time`).
5//!
6//! This module defines velocity units as *pure type aliases* over [`Per`] using
7//! length and time units already defined elsewhere in the crate.
8//!
9//! No standalone velocity units are introduced: every velocity is represented as
10//! `Length / Time` at the type level.
11//!
12//! ## Design notes
13//!
14//! - The velocity *dimension* is [`Velocity`](crate::Velocity) = [`Length`] / [`Time`].
15//! - All velocity units are zero-cost type aliases.
16//! - Conversions are handled automatically via the underlying length and time units.
17//! - No assumptions are made about reference frames, relativistic effects, or media.
18//!
19//! ## Examples
20//!
21//! ```rust
22//! use qtty_core::length::{Kilometer, Kilometers};
23//! use qtty_core::time::{Second, Seconds};
24//! use qtty_core::velocity::Velocity;
25//!
26//! let d = Kilometers::new(42.0);
27//! let t = Seconds::new(2.0);
28//! let v: Velocity<Kilometer, Second> = d / t;
29//! assert!((v.value() - 21.0).abs() < 1e-12);
30//! ```
31//!
32//! ```rust
33//! use qtty_core::length::{Meter, Meters};
34//! use qtty_core::time::{Hour, Hours};
35//! use qtty_core::velocity::Velocity;
36//!
37//! let v: Velocity<Meter, Hour> = Meters::new(3_600.0) / Hours::new(1.0);
38//! assert!((v.value() - 3_600.0).abs() < 1e-12);
39//! ```
40
41use crate::{Per, Quantity, Unit};
42
43/// Re-export the velocity dimension from the dimension module.
44pub use crate::dimension::Velocity as VelocityDimension;
45
46/// Marker trait for any unit whose dimension is [`Velocity`](crate::Velocity).
47pub trait VelocityUnit: Unit<Dim = crate::Velocity> {}
48impl<T: Unit<Dim = crate::Velocity>> VelocityUnit for T {}
49
50/// A velocity quantity parameterized by length and time units.
51///
52/// # Examples
53///
54/// ```rust
55/// use qtty_core::length::{Kilometer, Meter};
56/// use qtty_core::time::{Second, Hour};
57/// use qtty_core::velocity::Velocity;
58///
59/// let v1: Velocity<Meter, Second> = Velocity::new(10.0);
60/// let v2: Velocity<Kilometer, Hour> = Velocity::new(36.0);
61/// ```
62pub type Velocity<N, D> = Quantity<Per<N, D>>;
63
64#[cfg(feature = "astro")]
65pub mod astro;
66#[cfg(feature = "astro")]
67pub use astro::AU_PER_DAY_C;
68#[cfg(feature = "astro")]
69pub use astro::*;
70
71#[cfg(all(test, feature = "std"))]
72mod tests {
73 use super::*;
74 use crate::units::length::{Kilometer, Kilometers, Meter};
75 use crate::units::time::{Hour, Second, Seconds};
76 use crate::Per;
77 use approx::{assert_abs_diff_eq, assert_relative_eq};
78 use proptest::prelude::*;
79
80 // ─────────────────────────────────────────────────────────────────────────────
81 // Basic velocity conversions
82 // ─────────────────────────────────────────────────────────────────────────────
83
84 #[test]
85 fn km_per_s_to_m_per_s() {
86 let v: Velocity<Kilometer, Second> = Velocity::new(1.0);
87 let v_mps: Velocity<Meter, Second> = v.to();
88 assert_abs_diff_eq!(v_mps.value(), 1000.0, epsilon = 1e-9);
89 }
90
91 #[test]
92 fn m_per_s_to_km_per_s() {
93 let v: Velocity<Meter, Second> = Velocity::new(1000.0);
94 let v_kps: Velocity<Kilometer, Second> = v.to();
95 assert_abs_diff_eq!(v_kps.value(), 1.0, epsilon = 1e-12);
96 }
97
98 #[test]
99 fn km_per_h_to_m_per_s() {
100 let v: Velocity<Kilometer, Hour> = Velocity::new(3.6);
101 let v_mps: Velocity<Meter, Second> = v.to();
102 // 3.6 km/h = 1 m/s
103 assert_abs_diff_eq!(v_mps.value(), 1.0, epsilon = 1e-12);
104 }
105
106 #[test]
107 fn km_per_h_to_km_per_s() {
108 let v: Velocity<Kilometer, Hour> = Velocity::new(3600.0);
109 let v_kps: Velocity<Kilometer, Second> = v.to();
110 // 3600 km/h = 1 km/s
111 assert_abs_diff_eq!(v_kps.value(), 1.0, epsilon = 1e-12);
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).to();
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).to();
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).to();
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}