nyx_space/od/simulator/
scheduler.rs

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