nyx_space/dynamics/guidance/
finiteburns.rs

1use anise::prelude::Almanac;
2/*
3    Nyx, blazing fast astrodynamics
4    Copyright (C) 2018-onwards Christopher Rabotin <christopher.rabotin@gmail.com>
5
6    This program is free software: you can redistribute it and/or modify
7    it under the terms of the GNU Affero General Public License as published
8    by the Free Software Foundation, either version 3 of the License, or
9    (at your option) any later version.
10
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU Affero General Public License for more details.
15
16    You should have received a copy of the GNU Affero General Public License
17    along with this program.  If not, see <https://www.gnu.org/licenses/>.
18*/
19use hifitime::Epoch;
20
21use super::{GuidanceError, GuidanceLaw, Maneuver};
22use crate::cosmic::{GuidanceMode, Spacecraft};
23use crate::linalg::Vector3;
24use crate::State;
25use std::fmt;
26use std::sync::Arc;
27
28/// A guidance law for a set of pre-determined maneuvers.
29#[derive(Clone, Debug)]
30pub struct FiniteBurns {
31    /// Maneuvers should be provided in chronological order, first maneuver first in the list
32    pub mnvrs: Vec<Maneuver>,
33}
34
35impl FiniteBurns {
36    /// Builds a schedule from the vector of maneuvers, must be provided in chronological order.
37    pub fn from_mnvrs(mnvrs: Vec<Maneuver>) -> Arc<Self> {
38        Arc::new(Self { mnvrs })
39    }
40
41    /// Find the maneuver with the closest start epoch that is less than or equal to the current epoch
42    fn maneuver_at(&self, epoch: Epoch) -> Option<&Maneuver> {
43        let index = self.mnvrs.binary_search_by_key(&epoch, |mnvr| mnvr.start);
44        match index {
45            Err(0) => None, // No maneuvers start before the current epoch
46            Ok(index) => Some(&self.mnvrs[index]),
47            Err(index) => Some(&self.mnvrs[index - 1]), // Return the maneuver with the closest start epoch
48        }
49    }
50}
51
52impl fmt::Display for FiniteBurns {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        write!(f, "FiniteBurns with {} maneuvers", self.mnvrs.len())
55    }
56}
57
58impl GuidanceLaw for FiniteBurns {
59    fn direction(&self, osc: &Spacecraft) -> Result<Vector3<f64>, GuidanceError> {
60        // NOTE: We do not increment the mnvr number here. The power function is called first,
61        // so we let that function handle starting and stopping of the maneuver.
62        match osc.mode() {
63            GuidanceMode::Thrust => {
64                if let Some(next_mnvr) = self.maneuver_at(osc.epoch()) {
65                    if next_mnvr.start <= osc.epoch() {
66                        <Maneuver as GuidanceLaw>::direction(next_mnvr, osc)
67                    } else {
68                        Ok(Vector3::zeros())
69                    }
70                } else {
71                    Ok(Vector3::zeros())
72                }
73            }
74            _ => Ok(Vector3::zeros()),
75        }
76    }
77
78    fn throttle(&self, osc: &Spacecraft) -> Result<f64, GuidanceError> {
79        match osc.mode() {
80            GuidanceMode::Thrust => {
81                if let Some(next_mnvr) = self.maneuver_at(osc.epoch()) {
82                    if next_mnvr.start <= osc.epoch() {
83                        Ok(next_mnvr.thrust_prct)
84                    } else {
85                        Ok(0.0)
86                    }
87                } else {
88                    Ok(0.0)
89                }
90            }
91            _ => {
92                // We aren't in maneuver mode, so return 0% throttle
93                Ok(0.0)
94            }
95        }
96    }
97
98    fn next(&self, sc: &mut Spacecraft, _almanac: Arc<Almanac>) {
99        // Grab the last maneuver
100        if let Some(last_mnvr) = self.mnvrs.last() {
101            // If the last maneuver ends before the current epoch, switch back into coast
102            if last_mnvr.end < sc.epoch() {
103                sc.mut_mode(GuidanceMode::Coast)
104            } else {
105                // Get ready for the maneuver
106                sc.mut_mode(GuidanceMode::Thrust)
107            }
108        } else {
109            // There aren't any maneuvers
110            sc.mut_mode(GuidanceMode::Coast)
111        }
112    }
113}