use crate::cosmic::Frame;
use crate::errors::TargetingError;
use log::error;
use std::default::Default;
use std::f64::consts::{FRAC_PI_2, FRAC_PI_8, PI};
use std::fmt;
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Vary {
PositionX,
PositionY,
PositionZ,
VelocityX,
VelocityY,
VelocityZ,
MnvrAlpha,
MnvrAlphaDot,
MnvrAlphaDDot,
MnvrDelta,
MnvrDeltaDot,
MnvrDeltaDDot,
StartEpoch,
Duration,
EndEpoch,
ThrustX,
ThrustY,
ThrustZ,
ThrustLevel,
ThrustRateX,
ThrustRateY,
ThrustRateZ,
ThrustAccelX,
ThrustAccelY,
ThrustAccelZ,
}
impl Vary {
#[allow(clippy::nonminimal_bool)]
pub fn is_finite_burn(&self) -> bool {
*self == Self::MnvrAlpha
|| *self == Self::MnvrAlphaDDot
|| *self == Self::MnvrAlphaDDot
|| *self == Self::MnvrDelta
|| *self == Self::MnvrDeltaDDot
|| *self == Self::MnvrDeltaDDot
|| *self == Self::StartEpoch
|| *self == Self::Duration
|| *self == Self::EndEpoch
|| *self == Self::ThrustX
|| *self == Self::ThrustY
|| *self == Self::ThrustZ
|| *self == Self::ThrustLevel
|| *self == Self::ThrustRateX
|| *self == Self::ThrustRateY
|| *self == Self::ThrustRateZ
|| *self == Self::ThrustAccelX
|| *self == Self::ThrustAccelY
|| *self == Self::ThrustAccelZ
}
#[allow(clippy::nonminimal_bool)]
pub fn vec_index(&self) -> usize {
match self {
Self::PositionX | Self::ThrustX | Self::MnvrAlphaDDot | Self::MnvrDeltaDDot => 0,
Self::PositionY | Self::ThrustY | Self::MnvrAlphaDot | Self::MnvrDeltaDot => 1,
Self::PositionZ | Self::ThrustZ | Self::MnvrAlpha | Self::MnvrDelta => 2,
Self::VelocityX | Self::ThrustRateX => 3,
Self::VelocityY | Self::ThrustRateY => 4,
Self::VelocityZ | Self::ThrustRateZ => 5,
Self::StartEpoch | Self::ThrustAccelX => 6,
Self::Duration | Self::EndEpoch | Self::ThrustAccelY => 7,
Self::ThrustAccelZ => 8,
_ => unreachable!(),
}
}
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct Variable {
pub component: Vary,
pub perturbation: f64,
pub init_guess: f64,
pub max_step: f64,
pub max_value: f64,
pub min_value: f64,
pub frame: Option<Frame>,
}
impl Variable {
#[allow(clippy::result_large_err)]
pub fn valid(&self) -> Result<(), TargetingError> {
if self.max_step < 0.0 {
let msg = format!(
"{:?}: max step is negative: {}",
self.component, self.max_step
);
error!("{msg}");
return Err(TargetingError::VariableError { msg });
}
if self.max_value < 0.0 {
let msg = format!(
"{:?}: max value is negative: {}",
self.component, self.max_value
);
error!("{msg}");
return Err(TargetingError::VariableError { msg });
}
if self.min_value > self.max_value {
let msg = format!(
"{:?}: min value is greater than max value: {} > {}",
self.component, self.min_value, self.max_value
);
error!("{msg}");
return Err(TargetingError::VariableError { msg });
}
Ok(())
}
pub fn with_initial_guess(mut self, guess: f64) -> Self {
self.init_guess = guess;
self
}
pub fn with_pert(mut self, pert: f64) -> Self {
self.perturbation = pert;
self
}
pub fn with_min(mut self, min_val: f64) -> Self {
self.min_value = min_val;
self
}
pub fn with_max(mut self, max_val: f64) -> Self {
self.max_value = max_val;
self
}
pub fn apply_bounds(&self, val: f64) -> f64 {
if val > self.max_value {
self.max_value
} else if val < self.min_value {
self.min_value
} else {
val
}
}
pub fn ensure_bounds(&self, val: &mut f64) {
*val = self.check_bounds(*val).0;
}
pub fn check_bounds(&self, val: f64) -> (f64, bool) {
if val > self.max_value {
(self.max_value, false)
} else if val < self.min_value {
(self.min_value, false)
} else {
(val, true)
}
}
}
impl Default for Variable {
fn default() -> Self {
Self {
component: Vary::VelocityX,
perturbation: 0.0001,
init_guess: 0.0,
max_step: 0.2,
max_value: 5.0,
min_value: -5.0,
frame: None,
}
}
}
impl From<Vary> for Variable {
fn from(vary: Vary) -> Self {
match vary {
Vary::PositionX
| Vary::PositionY
| Vary::PositionZ
| Vary::VelocityX
| Vary::VelocityY
| Vary::VelocityZ => Self {
component: vary,
..Default::default()
},
Vary::MnvrAlpha | Vary::MnvrAlphaDot | Vary::MnvrAlphaDDot => Self {
component: vary,
perturbation: 0.1 * PI,
max_step: FRAC_PI_8,
max_value: PI,
min_value: 0.0,
..Default::default()
},
Vary::MnvrDelta | Vary::MnvrDeltaDot | Vary::MnvrDeltaDDot => Self {
component: vary,
perturbation: 0.1 * PI,
max_step: FRAC_PI_8,
max_value: FRAC_PI_2,
min_value: -FRAC_PI_2,
..Default::default()
},
Vary::StartEpoch | Vary::EndEpoch => Self {
component: vary,
perturbation: 0.5,
max_step: 60.0,
max_value: 600.0,
min_value: -600.0,
..Default::default()
},
Vary::Duration => Self {
component: vary,
perturbation: 1.0,
max_step: 60.0,
max_value: 600.0,
min_value: 0.0,
..Default::default()
},
Vary::ThrustX | Vary::ThrustY | Vary::ThrustZ => Self {
component: vary,
max_value: 1.0,
min_value: -1.0,
..Default::default()
},
Vary::ThrustRateX | Vary::ThrustRateY | Vary::ThrustRateZ => Self {
component: vary,
perturbation: 1e-10,
max_value: 1.0,
min_value: -1.0,
..Default::default()
},
Vary::ThrustAccelX | Vary::ThrustAccelY | Vary::ThrustAccelZ => Self {
component: vary,
perturbation: 1e-15,
max_value: 1.0,
min_value: -1.0,
..Default::default()
},
Vary::ThrustLevel => Self {
component: vary,
perturbation: -0.0001, min_value: 0.0001,
max_value: 1.0,
init_guess: 1.0,
..Default::default()
},
}
}
}
impl fmt::Display for Variable {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"{}{:?} = {} ± {:} ∈ [{}; {}]",
match self.frame {
Some(f) => format!("{f}"),
None => "".to_string(),
},
self.component,
self.init_guess,
self.perturbation,
self.min_value,
self.max_value
)
}
}