celestial_coords/
lighttime.rs1use crate::Distance;
2use celestial_core::Vector3;
3
4const C_AU_PER_DAY: f64 = 173.1446326846693;
5
6pub struct LightTimeCorrection {
7 light_time_days: f64,
8}
9
10impl LightTimeCorrection {
11 pub fn from_distance(distance: Distance) -> Self {
12 let distance_au = distance.au();
13 let light_time_days = distance_au / C_AU_PER_DAY;
14
15 Self { light_time_days }
16 }
17
18 pub fn from_position_vector(pos: Vector3) -> Self {
19 let distance_au = libm::sqrt(pos.x.powi(2) + pos.y.powi(2) + pos.z.powi(2));
20 let light_time_days = distance_au / C_AU_PER_DAY;
21
22 Self { light_time_days }
23 }
24
25 pub fn light_time_days(&self) -> f64 {
26 self.light_time_days
27 }
28
29 pub fn light_time_seconds(&self) -> f64 {
30 self.light_time_days * celestial_core::constants::SECONDS_PER_DAY_F64
31 }
32
33 pub fn apply_proper_motion(
34 &self,
35 pm_ra_mas_per_year: f64,
36 pm_dec_mas_per_year: f64,
37 ) -> (f64, f64) {
38 let years = self.light_time_days / 365.25;
39
40 let delta_ra_mas = pm_ra_mas_per_year * years;
41 let delta_dec_mas = pm_dec_mas_per_year * years;
42
43 (delta_ra_mas, delta_dec_mas)
44 }
45
46 pub fn apply_radial_velocity(&self, radial_velocity_km_s: f64) -> f64 {
47 radial_velocity_km_s * self.light_time_seconds()
48 }
49
50 pub fn correct_position_vector(pos: Vector3, vel: Vector3) -> Vector3 {
59 let lt = Self::from_position_vector(pos);
60 let t = lt.light_time_days();
61
62 Vector3::new(pos.x - vel.x * t, pos.y - vel.y * t, pos.z - vel.z * t)
63 }
64
65 pub fn iterate_correction(pos: Vector3, vel: Vector3, max_iterations: usize) -> Vector3 {
75 let mut corrected = pos;
76
77 for _ in 0..max_iterations {
78 let lt = Self::from_position_vector(corrected);
79 let t = lt.light_time_days();
80
81 let new_corrected =
82 Vector3::new(pos.x - vel.x * t, pos.y - vel.y * t, pos.z - vel.z * t);
83
84 let delta = libm::sqrt(
85 (new_corrected.x - corrected.x).powi(2)
86 + (new_corrected.y - corrected.y).powi(2)
87 + (new_corrected.z - corrected.z).powi(2),
88 );
89
90 corrected = new_corrected;
91
92 if delta < 1e-12 {
93 break;
94 }
95 }
96
97 corrected
98 }
99}
100
101#[cfg(test)]
102mod tests {
103 use super::*;
104
105 #[test]
106 fn test_light_time_from_distance() {
107 let distance = Distance::from_au(1.0).unwrap();
108 let lt = LightTimeCorrection::from_distance(distance);
109
110 assert!((lt.light_time_days() - 1.0 / C_AU_PER_DAY).abs() < 1e-12);
111 assert!((lt.light_time_seconds() - 499.00478).abs() < 1.0);
112 }
113
114 #[test]
115 fn test_proper_motion_correction() {
116 let distance = Distance::from_parsecs(10.0).unwrap();
117 let lt = LightTimeCorrection::from_distance(distance);
118
119 let (delta_ra, delta_dec) = lt.apply_proper_motion(100.0, 50.0);
120
121 assert!(delta_ra > 0.0);
122 assert!(delta_dec > 0.0);
123 }
124
125 #[test]
126 fn test_radial_velocity_correction() {
127 let distance = Distance::from_au(1.0).unwrap();
128 let lt = LightTimeCorrection::from_distance(distance);
129
130 let distance_change_km = lt.apply_radial_velocity(30.0);
131
132 assert!(distance_change_km > 0.0);
133 }
134
135 #[test]
136 fn test_position_vector_correction() {
137 let pos = Vector3::new(1.0, 0.0, 0.0);
138 let vel = Vector3::new(0.1, 0.0, 0.0);
139
140 let corrected = LightTimeCorrection::correct_position_vector(pos, vel);
141
142 assert!(corrected.x < pos.x);
143 }
144
145 #[test]
146 fn test_iterative_correction() {
147 let pos = Vector3::new(5.0, 0.0, 0.0);
148 let vel = Vector3::new(0.01, 0.0, 0.0);
149
150 let corrected = LightTimeCorrection::iterate_correction(pos, vel, 10);
151
152 assert!(corrected.x < pos.x);
153 }
154
155 #[test]
156 fn test_jupiter_light_time() {
157 let jupiter_distance = Distance::from_au(5.2).unwrap();
158 let lt = LightTimeCorrection::from_distance(jupiter_distance);
159
160 let expected_minutes = 5.2 * 499.0 / 60.0;
161 let actual_minutes = lt.light_time_seconds() / 60.0;
162
163 assert!((actual_minutes - expected_minutes).abs() < 1.0);
164 }
165
166 #[test]
167 fn test_romer_jupiter_moons_1676() {
168 let near_opposition = Distance::from_au(5.2 - 1.0).unwrap();
169 let far_opposition = Distance::from_au(5.2 + 1.0).unwrap();
170
171 let lt_near = LightTimeCorrection::from_distance(near_opposition);
172 let lt_far = LightTimeCorrection::from_distance(far_opposition);
173
174 let delay_seconds = lt_far.light_time_seconds() - lt_near.light_time_seconds();
175 let delay_minutes = delay_seconds / 60.0;
176
177 assert!((delay_minutes - 16.6).abs() < 1.0);
178 }
179
180 #[test]
181 fn test_barnards_star_proper_motion() {
182 let distance = Distance::from_parsecs(1.83).unwrap();
183 let lt = LightTimeCorrection::from_distance(distance);
184
185 let pm_ra = -798.58;
186 let pm_dec = 10328.12;
187
188 let (delta_ra, delta_dec) = lt.apply_proper_motion(pm_ra, pm_dec);
189
190 let light_years = lt.light_time_days() / 365.25;
191 assert!((delta_ra - (pm_ra * light_years)).abs() < 0.01);
192 assert!((delta_dec - (pm_dec * light_years)).abs() < 0.01);
193 }
194
195 #[test]
196 fn test_proxima_centauri_radial_velocity() {
197 let distance = Distance::from_parsecs(1.30).unwrap();
198 let lt = LightTimeCorrection::from_distance(distance);
199
200 let rv_km_s = -22.2;
201
202 let distance_change_km = lt.apply_radial_velocity(rv_km_s);
203
204 let expected_km = rv_km_s * lt.light_time_seconds();
205 assert!((distance_change_km - expected_km).abs() < 1e-6);
206 }
207
208 #[test]
209 fn test_sun_earth_light_time() {
210 let earth_sun = Distance::from_au(1.0).unwrap();
211 let lt = LightTimeCorrection::from_distance(earth_sun);
212
213 assert!((lt.light_time_seconds() - 499.0).abs() < 1.0);
214 assert!((lt.light_time_days() - 0.00577).abs() < 0.0001);
215 }
216
217 #[test]
218 fn test_mercury_transit_timing() {
219 let mercury_near = Distance::from_au(0.31).unwrap();
220 let mercury_far = Distance::from_au(0.47).unwrap();
221
222 let lt_near = LightTimeCorrection::from_distance(mercury_near);
223 let lt_far = LightTimeCorrection::from_distance(mercury_far);
224
225 let timing_difference = (lt_far.light_time_seconds() - lt_near.light_time_seconds()).abs();
226
227 assert!(timing_difference > 0.0);
228 assert!(timing_difference < 100.0);
229 }
230
231 #[test]
232 fn test_convergence_high_velocity_object() {
233 let pos = Vector3::new(100.0, 0.0, 0.0);
234 let vel = Vector3::new(1.0, 0.0, 0.0);
235
236 let single = LightTimeCorrection::correct_position_vector(pos, vel);
237 let iterated = LightTimeCorrection::iterate_correction(pos, vel, 10);
238
239 let improvement = (iterated.x - single.x).abs();
240 assert!(improvement > 0.0);
241 }
242
243 #[test]
244 fn test_aberration_effect_simulation() {
245 let distance = Distance::from_au(1.0).unwrap();
246 let lt = LightTimeCorrection::from_distance(distance);
247
248 let earth_orbital_velocity = 29.78;
249 let distance_change_km = lt.apply_radial_velocity(earth_orbital_velocity);
250
251 let distance_change_au = distance_change_km / 1.495978707e8;
252 assert!(distance_change_au.abs() < 0.1);
253 }
254}