use anise::almanac::Almanac;
use anise::analysis::AnalysisError;
use anise::structure::LocationDataSet;
use hifitime::{Duration, Epoch, TimeSeries, TimeUnits};
use log::{info, warn};
use num::integer::gcd;
use rand::SeedableRng;
use rand::rngs::SysRng;
use rand_pcg::Pcg64Mcg;
use crate::Spacecraft;
use crate::State;
use crate::io::ConfigError;
use crate::md::trajectory::Interpolatable;
use crate::od::GroundStation;
use crate::od::msr::TrackingDataArc;
use crate::od::prelude::Strand;
use crate::od::simulator::Cadence;
use crate::{linalg::DefaultAllocator, md::prelude::Traj};
use crate::{linalg::allocator::Allocator, od::TrackingDevice};
use std::collections::BTreeMap;
use std::fmt::Display;
use std::marker::PhantomData;
use super::{Handoff, TrkConfig};
#[derive(Clone)]
pub struct TrackingArcSim<MsrIn, D>
where
D: TrackingDevice<MsrIn>,
MsrIn: State,
MsrIn: Interpolatable,
DefaultAllocator: Allocator<<MsrIn as State>::Size>
+ Allocator<<MsrIn as State>::Size, <MsrIn as State>::Size>
+ Allocator<<MsrIn as State>::VecLength>,
{
pub devices: BTreeMap<String, D>,
pub trajectory: Traj<MsrIn>,
pub configs: BTreeMap<String, TrkConfig>,
rng: Pcg64Mcg,
time_series: TimeSeries,
_msr_in: PhantomData<MsrIn>,
}
impl<MsrIn, D> TrackingArcSim<MsrIn, D>
where
D: TrackingDevice<MsrIn>,
MsrIn: State,
MsrIn: Interpolatable,
DefaultAllocator: Allocator<<MsrIn as State>::Size>
+ Allocator<<MsrIn as State>::Size, <MsrIn as State>::Size>
+ Allocator<<MsrIn as State>::VecLength>,
{
pub fn with_rng(
devices: BTreeMap<String, D>,
trajectory: Traj<MsrIn>,
configs: BTreeMap<String, TrkConfig>,
rng: Pcg64Mcg,
) -> Result<Self, ConfigError> {
let mut sampling_rates_ns = Vec::with_capacity(devices.len());
for name in devices.keys() {
if let Some(cfg) = configs.get(name) {
if let Err(e) = cfg.sanity_check() {
warn!("Ignoring device {name}: {e}");
continue;
}
sampling_rates_ns.push(cfg.sampling.truncated_nanoseconds());
} else {
warn!("Ignoring device {name}: no associated tracking configuration",);
continue;
}
}
if sampling_rates_ns.is_empty() {
return Err(ConfigError::InvalidConfig {
msg: "None of the devices are properly configured".to_string(),
});
}
let common_sampling_rate_ns = sampling_rates_ns
.iter()
.fold(sampling_rates_ns[0], |a, &b| gcd(a, b));
let time_series = TimeSeries::inclusive(
trajectory.first().epoch(),
trajectory.last().epoch(),
Duration::from_truncated_nanoseconds(common_sampling_rate_ns),
);
let me = Self {
devices,
trajectory,
configs,
rng,
time_series,
_msr_in: PhantomData,
};
info!("{me}");
Ok(me)
}
pub fn with_seed(
devices: BTreeMap<String, D>,
trajectory: Traj<MsrIn>,
configs: BTreeMap<String, TrkConfig>,
seed: u64,
) -> Result<Self, ConfigError> {
let rng = Pcg64Mcg::new(seed as u128);
Self::with_rng(devices, trajectory, configs, rng)
}
pub fn new(
devices: BTreeMap<String, D>,
trajectory: Traj<MsrIn>,
configs: BTreeMap<String, TrkConfig>,
) -> Result<Self, ConfigError> {
let rng = Pcg64Mcg::try_from_rng(&mut SysRng).unwrap();
Self::with_rng(devices, trajectory, configs, rng)
}
pub fn generate_measurements(
&mut self,
almanac: &Almanac,
) -> Result<TrackingDataArc, ConfigError> {
let mut measurements = Vec::new();
for (name, device) in self.devices.iter_mut() {
if let Some(cfg) = self.configs.get(name) {
if cfg.scheduler.is_some() {
if cfg.strands.is_none() {
return Err(ConfigError::InvalidConfig {
msg: format!(
"schedule for {name} must be built before generating measurements"
),
});
} else {
warn!(
"scheduler for {name} is ignored, using the defined tracking strands instead"
)
}
}
let init_msr_count = measurements.len();
let tick = Epoch::now().unwrap();
match cfg.strands.as_ref() {
Some(strands) => {
'strands: for (ii, strand) in strands.iter().enumerate() {
for epoch in
TimeSeries::inclusive(strand.start, strand.end, cfg.sampling)
{
match device.measure(
epoch,
&self.trajectory,
Some(&mut self.rng),
almanac,
) {
Ok(msr_opt) => {
if let Some(msr) = msr_opt {
measurements.push(msr);
}
}
Err(e) => {
if epoch != strand.end {
warn!(
"Skipping the remaining strand #{ii} ending on {}: {e}",
strand.end
);
}
continue 'strands;
}
}
}
}
info!(
"Simulated {} measurements for {name} for {} tracking strands in {}",
measurements.len() - init_msr_count,
strands.len(),
(Epoch::now().unwrap() - tick).round(1.0_f64.milliseconds())
);
}
None => {
warn!("No tracking strands defined for {name}, skipping");
}
}
}
}
let mut trk_data = TrackingDataArc {
measurements,
source: None,
moduli: None,
force_reject: false,
};
trk_data.sort();
Ok(trk_data)
}
}
impl<MsrIn, D> Display for TrackingArcSim<MsrIn, D>
where
D: TrackingDevice<MsrIn>,
MsrIn: Interpolatable,
DefaultAllocator: Allocator<<MsrIn as State>::Size>
+ Allocator<<MsrIn as State>::Size, <MsrIn as State>::Size>
+ Allocator<<MsrIn as State>::VecLength>,
{
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"Tracking Arc Simulator on {} with devices {:?} over {}",
self.trajectory,
self.devices.keys(),
self.time_series
)
}
}
impl TrackingArcSim<Spacecraft, GroundStation> {
pub fn generate_schedule(
&self,
almanac: &Almanac,
) -> Result<BTreeMap<String, TrkConfig>, AnalysisError> {
let mut loc_dataset = LocationDataSet::default();
let mut loc_ids = Vec::with_capacity(self.devices.len());
for (dno, (name, device)) in self.devices.iter().enumerate() {
let loc_id = dno as i32 + 1_000;
loc_dataset
.push(device.location.clone(), Some(loc_id), Some(name))
.map_err(|e| AnalysisError::GenericAnalysisError {
err: format!("{e}"),
})?;
loc_ids.push((name.clone(), loc_id));
}
let almanac = (*almanac).clone();
let almanac = almanac.with_location_data(loc_dataset);
let mut built_cfg = self.configs.clone();
let traj = &self.trajectory;
for (name, loc_id) in &loc_ids {
if let Some(cfg) = self.configs.get(name)
&& let Some(scheduler) = cfg.scheduler
{
info!("Building schedule for {name}");
if scheduler.handoff == Handoff::Overlap {
warn!("Overlap unsupported for {name} on identical epochs.");
}
built_cfg.get_mut(name).unwrap().scheduler = None;
built_cfg.get_mut(name).unwrap().strands = Some(Vec::new());
let visibilty_arcs = almanac.report_visibility_arcs(
traj,
*loc_id,
traj.first().epoch(),
traj.last().epoch(),
cfg.sampling,
None,
)?;
for arc in visibilty_arcs {
let strand_start = arc.rise.orbit.epoch;
let strand_end = arc.fall.orbit.epoch;
if strand_end - strand_start < cfg.sampling * i64::from(scheduler.min_samples) {
warn!(
"Dropped [{strand_start}, {strand_end}]: < {} samples @ {}",
scheduler.min_samples, cfg.sampling
);
continue;
}
let mut strand_range = Strand {
start: strand_start,
end: strand_end,
};
if let Some(alignment) = scheduler.sample_alignment {
strand_range.start = strand_range.start.round(alignment);
strand_range.end = strand_range.end.round(alignment);
}
if let Cadence::Intermittent { on, off } = scheduler.cadence {
if let Some(prev_strand) = built_cfg[name].strands.as_ref().unwrap().last()
&& prev_strand.end + off > strand_range.start
{
strand_range.start = prev_strand.end + off;
if strand_range.start > strand_end {
warn!(
"Dropped {name} [{strand_start}, {strand_end}]: cadence {:?}",
scheduler.cadence
);
continue;
}
}
if strand_range.end - strand_range.start > on {
strand_range.end = strand_range.start + on;
}
}
built_cfg
.get_mut(name)
.unwrap()
.strands
.as_mut()
.unwrap()
.push(strand_range);
}
info!(
"Built {} tracking strands for {name}",
built_cfg[name].strands.as_ref().unwrap().len()
);
}
}
let mut cfg_as_vec = Vec::new();
for (name, cfg) in &built_cfg {
if let Some(strands) = &cfg.strands {
for (ii, strand) in strands.iter().enumerate() {
cfg_as_vec.push((name.clone(), ii, *strand));
}
}
}
cfg_as_vec.sort_by_key(|(_, _, strand)| strand.start);
for (ii, (this_name, this_pos, this_strand)) in
cfg_as_vec.iter().take(cfg_as_vec.len() - 1).enumerate()
{
if let Some(config) = self.configs[this_name].scheduler.as_ref() {
if let Some((next_name, next_pos, next_strand)) = cfg_as_vec.get(ii + 1) {
if config.handoff == Handoff::Greedy && this_strand.end >= next_strand.start {
let next_config = built_cfg.get_mut(next_name).unwrap();
let new_start = this_strand.end + next_config.sampling;
next_config.strands.as_mut().unwrap()[*next_pos].start = new_start;
info!("Greedy handoff for {this_name}: {next_name} delayed to {new_start}");
} else if config.handoff == Handoff::Eager
&& this_strand.end >= next_strand.start
{
let this_config = built_cfg.get_mut(this_name).unwrap();
let new_end = next_strand.start - this_config.sampling;
this_config.strands.as_mut().unwrap()[*this_pos].end = new_end;
info!("Eager handoff for {this_name}: {this_name} terminated at {new_end}");
}
} else {
break;
}
}
}
Ok(built_cfg)
}
pub fn build_schedule(&mut self, almanac: &Almanac) -> Result<(), AnalysisError> {
self.configs = self.generate_schedule(almanac)?;
Ok(())
}
}