use std::f64::consts::PI;
use serde::Serialize;
use crate::params::*;
use crate::hamiltonian::hamltn;
use crate::integrator::*;
#[derive(Serialize)]
pub struct TracePoint {
pub step: usize,
pub t: f64,
pub height_km: f64,
pub lat_deg: f64,
pub lon_deg: f64,
pub ground_range_km: f64,
pub group_path: f64,
pub phase_path: f64,
pub absorption: f64,
}
#[derive(Serialize)]
pub struct TraceResult {
pub points: Vec<TracePoint>,
pub max_height: f64,
pub ground_range_km: f64,
pub returned_to_ground: bool,
pub n_steps: usize,
}
pub fn trace_ray(
freq_mhz: f64, ray_mode: f64,
elevation_deg: f64, azimuth_deg: f64,
tx_lat_deg: f64,
int_mode: usize, step_size: f64, max_steps: usize,
e1max: f64, e1min: f64, e2max: f64,
p: &ModelParams,
print_every: usize,
) -> TraceResult {
let elev_rad = elevation_deg * PI / 180.0;
let az_rad = azimuth_deg * PI / 180.0;
let r0 = p.earth_r;
let theta0 = PID2 - tx_lat_deg * PI / 180.0;
let phi0 = 0.0;
let degs = 180.0 / PI;
let ground_range = |theta: f64, phi: f64| -> f64 {
let cos_ang = theta0.cos() * theta.cos() + theta0.sin() * theta.sin() * (phi - phi0).cos();
p.earth_r * cos_ang.clamp(-1.0, 1.0).acos()
};
let kr0 = elev_rad.sin();
let kth0 = elev_rad.cos() * (PI - az_rad).cos();
let kph0 = elev_rad.cos() * (PI - az_rad).sin();
let mut y = [r0, theta0, phi0, kr0, kth0, kph0, 0.0, 0.0];
let (_d0, _, new_kr, new_kth, new_kph) = hamltn(&y, freq_mhz, ray_mode, p, true);
y[3] = new_kr;
y[4] = new_kth;
y[5] = new_kph;
let mut state = IntegratorState::new(
&y, 0.0, step_size, int_mode,
e1max, e1min, e2max, 1.0e-8, 0.5,
);
let (d_init, _, _, _, _) = hamltn(&state.y, freq_mhz, ray_mode, p, false);
state.dydt = d_init;
for i in 0..NN {
state.fv[state.mm][i] = d_init[i];
}
let mut result = TraceResult {
points: Vec::with_capacity(max_steps / print_every + 5),
max_height: 0.0,
ground_range_km: 0.0,
returned_to_ground: false,
n_steps: 0,
};
result.points.push(TracePoint {
step: 0, t: 0.0, height_km: 0.0,
lat_deg: tx_lat_deg, lon_deg: 0.0,
ground_range_km: 0.0, group_path: 0.0,
phase_path: 0.0, absorption: 0.0,
});
let mut went_up = false;
for step_num in 1..=max_steps {
if state.mm < 4 || state.mode == 1 {
rk4_step(&mut state, freq_mhz, ray_mode, p);
} else {
am_step(&mut state, freq_mhz, ray_mode, p);
}
let h = state.y[0] - p.earth_r;
if h > result.max_height { result.max_height = h; }
if h > 10.0 { went_up = true; }
if h.is_nan() || h.abs() > 50000.0 { break; }
let ground = went_up && h < 0.0;
if ground { result.returned_to_ground = true; }
if step_num % print_every == 0 || ground {
let theta = state.y[1];
let phi = state.y[2];
let lat = (PID2 - theta) * degs;
let lon = phi * degs;
let gr = ground_range(theta, phi);
let gp = state.y[6]; result.points.push(TracePoint {
step: step_num,
t: state.t,
height_km: h,
lat_deg: lat,
lon_deg: lon,
ground_range_km: gr,
group_path: gp,
phase_path: state.y[6],
absorption: state.y[7],
});
if ground {
result.ground_range_km = gr;
}
}
result.n_steps = step_num;
if ground { break; }
}
result
}