nyx_space/od/simulator/scheduler.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181
/*
Nyx, blazing fast astrodynamics
Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published
by the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
use crate::io::{
duration_from_str, duration_to_str, maybe_duration_from_str, maybe_duration_to_str,
};
pub use crate::State;
use hifitime::{Duration, Unit};
use serde::Deserialize;
use serde_derive::Serialize;
use std::fmt::Debug;
use typed_builder::TypedBuilder;
/// Defines the handoff from a current ground station to the next one that is visible to prevent overlapping of measurements
#[derive(Copy, Clone, Debug, Deserialize, PartialEq, Serialize)]
pub enum Handoff {
/// If a new station is in visibility of the spacecraft, the "Eager" station will immediately stop tracking and switch over (default)
Eager,
/// If a new station is in visibility of the spacecraft, the "Greedy" station will continue to tracking until the vehicle is below its elevation mask
Greedy,
/// If a new station is in visibility of the spacecraft, the "Overlap" station will continue tracking, and so will the other one
Overlap,
}
impl Default for Handoff {
fn default() -> Self {
Self::Eager
}
}
/// A scheduler allows building a scheduling of spaceraft tracking for a set of ground stations.
#[derive(Copy, Clone, Debug, Default, Deserialize, PartialEq, Serialize, TypedBuilder)]
#[builder(doc)]
pub struct Scheduler {
/// Handoff strategy if two trackers see the vehicle at the same time
#[builder(default)]
pub handoff: Handoff,
/// On/off cadence of this scheduler
#[builder(default)]
pub cadence: Cadence,
/// Minimum number of samples for a valid arc, i.e. if there are less than this many samples during a pass, the strand is discarded.
#[builder(default = 10)]
pub min_samples: u32,
/// Round the time of the samples to the provided duration. For example, if the vehicle is above the horizon at 01:02:03.456 and the alignment
/// is set to 01 seconds, then this will cause the tracking to start at 01:02:03 as it is rounded to the nearest second.
#[builder(default = Some(Unit::Second * 1.0), setter(strip_option))]
#[serde(
serialize_with = "maybe_duration_to_str",
deserialize_with = "maybe_duration_from_str"
)]
pub sample_alignment: Option<Duration>,
}
/// Determines whether tracking is continuous or intermittent.
#[derive(Copy, Clone, Deserialize, PartialEq, Serialize)]
pub enum Cadence {
Continuous,
/// An intermittent schedule has On and Off durations.
Intermittent {
#[serde(
serialize_with = "duration_to_str",
deserialize_with = "duration_from_str"
)]
on: Duration,
#[serde(
serialize_with = "duration_to_str",
deserialize_with = "duration_from_str"
)]
off: Duration,
},
}
impl Default for Cadence {
fn default() -> Self {
Self::Continuous
}
}
impl Debug for Cadence {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Continuous => write!(f, "Continuous"),
Self::Intermittent { on, off } => f
.debug_struct("Intermittent")
.field("on", &format!("{on}"))
.field("off", &format!("{off}"))
.finish(),
}
}
}
#[cfg(test)]
mod scheduler_ut {
use process::simulator::scheduler::Handoff;
use crate::od::prelude::*;
use super::Scheduler;
#[test]
fn serde_cadence() {
use hifitime::TimeUnits;
use serde_yml;
let cont: Cadence = serde_yml::from_str("!Continuous").unwrap();
assert_eq!(cont, Cadence::Continuous);
let int: Cadence =
serde_yml::from_str("!Intermittent {on: 1 h 35 min, off: 15 h 02 min 3 s}").unwrap();
assert_eq!(
int,
Cadence::Intermittent {
on: 1.hours() + 35.0.minutes(),
off: 15.hours() + 2.minutes() + 3.seconds()
}
);
assert_eq!(
format!("{int:?}"),
r#"Intermittent { on: "1 h 35 min", off: "15 h 2 min 3 s" }"#
);
let serialized = serde_yml::to_string(&int).unwrap();
let deserd: Cadence = serde_yml::from_str(&serialized).unwrap();
assert_eq!(deserd, int);
}
#[test]
fn api_and_serde_scheduler() {
use hifitime::TimeUnits;
use serde_yml;
let scheduler = Scheduler::default();
let serialized = serde_yml::to_string(&scheduler).unwrap();
assert_eq!(
serialized,
"handoff: Eager\ncadence: Continuous\nmin_samples: 0\nsample_alignment: null\n"
);
let deserd: Scheduler = serde_yml::from_str(&serialized).unwrap();
assert_eq!(deserd, scheduler);
let scheduler = Scheduler::builder()
.handoff(Handoff::Eager)
.cadence(Cadence::Intermittent {
on: 0.2.hours(),
off: 17.hours() + 5.minutes(),
})
.build();
let serialized = serde_yml::to_string(&scheduler).unwrap();
assert_eq!(
serialized,
"handoff: Eager\ncadence: !Intermittent\n 'on': '12 min'\n 'off': '17 h 5 min'\nmin_samples: 10\nsample_alignment: '1 s'\n"
);
let deserd: Scheduler = serde_yml::from_str(&serialized).unwrap();
assert_eq!(deserd, scheduler);
}
#[test]
fn defaults() {
let sched = Scheduler::default();
assert_eq!(sched.cadence, Cadence::Continuous);
assert_eq!(sched.handoff, Handoff::Eager);
}
}