Skip to main content

hpr_aero/
error.rs

1//! Error types for the aerodynamic models.
2
3use hpr_core::CoreError;
4use hpr_design::DesignError;
5use thiserror::Error;
6
7/// An error from the aerodynamic models: an input outside the models' domain, a design they can't
8/// describe, or an error from the design itself.
9#[derive(Debug, Clone, PartialEq, Error)]
10#[non_exhaustive]
11pub enum AeroError {
12    /// An input outside its domain, such as a negative span or a non-finite angle.
13    #[error("{what} is outside its domain: {value}")]
14    Domain {
15        /// What the value is.
16        what: &'static str,
17        /// The offending value.
18        value: f64,
19    },
20    /// A Mach number past the range of the model asked: `0 ≤ M < 5` for the normal force
21    /// ([`crate::model::NORMAL_FORCE_MACH_LIMIT`]) and the drag buildup
22    /// ([`crate::drag::BUILDUP_MACH_LIMIT`]); an override table takes any Mach number.
23    #[error("Mach {mach} is outside {model}'s range [0, {limit})")]
24    Mach {
25        /// The Mach number.
26        mach: f64,
27        /// The top of the model's range, which it doesn't reach.
28        limit: f64,
29        /// The model that refused it, such as "the drag buildup".
30        model: &'static str,
31    },
32    /// A part the models have no cited method for, such as tube fins.
33    #[error("no aerodynamic model: {0}")]
34    Unsupported(String),
35    /// A layout that doesn't hold together for the aerodynamic models, such as a fin set without
36    /// the radius of its body tube.
37    #[error("inconsistent layout: {0}")]
38    Layout(String),
39    /// An error in one component, with its id.
40    #[error("{id}: {source}")]
41    InComponent {
42        /// The component's id.
43        id: String,
44        /// The error.
45        source: Box<AeroError>,
46    },
47    /// An error from a drag model of a program's own ([`crate::custom::DragModel`]), as it gave
48    /// it: its own refusal, or one of hpr's it passed on.
49    #[error("drag model: {source}")]
50    DragModel {
51        /// The error.
52        source: Box<AeroError>,
53    },
54    /// An error from the design model, such as a profile whose volume integral fails.
55    #[error(transparent)]
56    Design(#[from] DesignError),
57    /// CSV text that doesn't read as a table, with its 1-based line (0 when the text has no
58    /// rows).
59    #[error("CSV line {line}: {message}")]
60    Csv {
61        /// The line, counting from 1.
62        line: usize,
63        /// What is wrong.
64        message: String,
65    },
66    /// A table that doesn't hold together, such as Mach numbers that don't increase.
67    #[error(transparent)]
68    Table(#[from] CoreError),
69}
70
71/// Checks that `value` is finite and positive (or non-negative when `allow_zero`).
72pub(crate) fn check_dimension(
73    what: &'static str,
74    value: f64,
75    allow_zero: bool,
76) -> Result<(), AeroError> {
77    let ok = value.is_finite() && (value > 0.0 || (allow_zero && value == 0.0));
78    if ok {
79        Ok(())
80    } else {
81        Err(AeroError::Domain { what, value })
82    }
83}
84
85/// Checks a Mach number in `[0, limit)` for `model`.
86pub(crate) fn check_mach(mach: f64, limit: f64, model: &'static str) -> Result<(), AeroError> {
87    if mach.is_finite() && (0.0..limit).contains(&mach) {
88        Ok(())
89    } else {
90        Err(AeroError::Mach { mach, limit, model })
91    }
92}