nyx_space/od/msr/
two_way.rs1use anise::constants::SPEED_OF_LIGHT_KM_S;
20use anise::constants::celestial_objects::SUN;
21use anise::constants::frames::SUN_J2000;
22use anise::constants::orientations::ICRS;
23use anise::errors::AlmanacPhysicsSnafu;
24use anise::frames::Frame;
25use anise::prelude::Almanac;
26use hifitime::{Duration, Epoch, TimeUnits};
27use nalgebra::Vector3;
28
29use crate::Spacecraft;
30use crate::md::prelude::Traj;
31use crate::od::ground_station::GroundStation;
32use crate::od::{ODAlmanacSnafu, ODError, ODPlanetaryDataSnafu, ODTrajSnafu};
33use snafu::ResultExt;
34
35const SUN_ICRS: Frame = Frame::new(SUN, ICRS);
36
37#[derive(Copy, Clone, Debug, PartialEq)]
38pub struct LightTimeLeg {
39 pub start_epoch: Epoch,
40 pub end_epoch: Epoch,
41 pub r_start_icrf_km: Vector3<f64>,
42 pub r_end_icrf_km: Vector3<f64>,
43 pub light_time: Duration,
44}
45
46impl LightTimeLeg {
47 pub fn range_km(&self) -> f64 {
48 (self.r_start_icrf_km - self.r_end_icrf_km).norm()
49 }
50}
51
52#[derive(Copy, Clone, Debug, PartialEq)]
63pub struct TwoWaySolution {
64 pub t1_transmit: Epoch,
65 pub t2_bounce: Epoch,
66 pub t3_receive: Epoch,
67 pub uplink: LightTimeLeg,
68 pub downlink: LightTimeLeg,
69}
70
71impl TwoWaySolution {
72 pub fn total_delay(&self) -> Duration {
73 self.uplink.light_time + self.downlink.light_time
74 }
75
76 pub fn range_km(&self) -> f64 {
77 0.5 * (self.t3_receive - self.t1_transmit).to_seconds() * SPEED_OF_LIGHT_KM_S
78 }
80}
81
82pub(crate) fn solve_two_way_picard(
86 t3: Epoch,
87 station: &GroundStation,
88 traj: &Traj<Spacecraft>,
89 almanac: &Almanac,
90) -> Result<TwoWaySolution, ODError> {
91 let sun_mu_km3_s2 = if station.relativistic_corrections {
92 Some(
93 almanac
94 .frame_info(SUN_J2000)
95 .context(ODPlanetaryDataSnafu {
96 action: "fetching Sun grav param for Shapiro delay",
97 })?
98 .mu_km3_s2()
99 .context(AlmanacPhysicsSnafu {
100 action: "Sun mu not defined",
101 })
102 .context(ODAlmanacSnafu {
103 action: "fetching Sun grav param for Shapiro delay",
104 })?,
105 )
106 } else {
107 None
108 };
109 let gs_rx_orbit = station.to_orbit(t3, almanac).context(ODAlmanacSnafu {
111 action: "building ground station orbit at t3",
112 })?;
113 let gs_rx_icrf = almanac
114 .transform_to(gs_rx_orbit, SUN_ICRS, None)
115 .context(ODAlmanacSnafu {
116 action: "transforming station at t3 to ICRF",
117 })?;
118 let r3_icrf_km = gs_rx_icrf.radius_km;
119
120 let mut tau_down = Duration::ZERO;
123 let mut r2_icrf_km = Vector3::zeros();
124 let mut t2 = t3;
125
126 for _ in 0..3 {
128 t2 = t3 - tau_down;
129 let sc_state = traj.at(t2).with_context(|_| ODTrajSnafu {
131 details: format!("interpolating spacecraft state at bounce epoch {t2}"),
132 })?;
133
134 let sc_icrf = almanac
136 .transform_to(sc_state.orbit, SUN_ICRS, None)
137 .context(ODAlmanacSnafu {
138 action: "transforming spacecraft at t2 to ICRF",
139 })?;
140
141 r2_icrf_km = sc_icrf.radius_km;
142 let dist_down_km = (r2_icrf_km - r3_icrf_km).norm();
143 let tau_geometric = dist_down_km / SPEED_OF_LIGHT_KM_S;
145 let tau_shapiro = if let Some(sun_mu) = sun_mu_km3_s2 {
146 shapiro_delay_s(&r2_icrf_km, &r3_icrf_km, dist_down_km, sun_mu)
147 } else {
148 0.0
149 };
150 tau_down = (tau_geometric + tau_shapiro).seconds();
151 }
152
153 let downlink = LightTimeLeg {
154 start_epoch: t2,
155 end_epoch: t3,
156 r_start_icrf_km: r2_icrf_km,
157 r_end_icrf_km: r3_icrf_km,
158 light_time: tau_down,
159 };
160
161 let mut tau_up = tau_down; let mut r1_icrf_km = Vector3::zeros();
166 let mut t1 = t2 - tau_up;
167
168 for _ in 0..3 {
169 t1 = t2 - tau_up;
170 let gs_tx_orbit = station.to_orbit(t1, almanac).context(ODAlmanacSnafu {
171 action: "building ground station orbit at t1",
172 })?;
173
174 let gs_tx_icrf =
175 almanac
176 .transform_to(gs_tx_orbit, SUN_ICRS, None)
177 .context(ODAlmanacSnafu {
178 action: "transforming station at t1 to ICRF",
179 })?;
180
181 r1_icrf_km = gs_tx_icrf.radius_km;
182 let dist_up_km = (r2_icrf_km - r1_icrf_km).norm();
183 let tau_geometric = dist_up_km / SPEED_OF_LIGHT_KM_S;
184 let tau_shapiro = if let Some(sun_mu) = sun_mu_km3_s2 {
185 shapiro_delay_s(&r1_icrf_km, &r2_icrf_km, dist_up_km, sun_mu)
186 } else {
187 0.0
188 };
189 tau_up = (tau_geometric + tau_shapiro).seconds();
190 }
191
192 let uplink = LightTimeLeg {
193 start_epoch: t1,
194 end_epoch: t2,
195 r_start_icrf_km: r1_icrf_km,
196 r_end_icrf_km: r2_icrf_km,
197 light_time: tau_up,
198 };
199
200 Ok(TwoWaySolution {
202 t1_transmit: t1,
203 t2_bounce: t2,
204 t3_receive: t3,
205 uplink,
206 downlink,
207 })
208}
209
210fn shapiro_delay_s(
215 r_start_icrf_km: &Vector3<f64>,
216 r_end_icrf_km: &Vector3<f64>,
217 rho_km: f64,
218 sun_mu_km3_s2: f64,
219) -> f64 {
220 let num = r_start_icrf_km.norm() + r_end_icrf_km.norm() + rho_km;
221 let denom = r_start_icrf_km.norm() + r_end_icrf_km.norm() - rho_km;
222
223 if denom <= 0.0 || num <= 0.0 {
224 return 0.0;
225 }
226
227 let factor = (2.0 * sun_mu_km3_s2) / SPEED_OF_LIGHT_KM_S.powi(3);
229 factor * (num / denom).ln()
230}