-
-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Better error logging with snafu (#115)
# Summary **This is a major breaking change.** Closes #32 . ## Architectural changes - DAF no longer carries an explicit copy of FileRecord or NameRecord. These were 1024 bytes each. Now, they are parsed on demand from the borrowed underlying bytes. - DataSet now uses Bytes instead of &[u8]. This means the data can be loaded from a static byte array or copied to the heap. - Almanac now moves the DAF datasets (SPK and BPC) into itself. However, it will still unload them magically when going out of scope. Thanks to the DAF change above, this means that the Almanac size in memory stays about the same going from 5008 bytes to 6320 bytes, but while providing a much more ergonomic usage. - `Context` renamed to `Almanac`. Too many libraries use the term "context" and Almanac refers to ancient astronomy, so it's relevant. - `FrameTrait` is removed in favor of the concrete `Frame` structure. This structure now includes a number of optional fields. - We're no longer trying to be no-std at the moment. It's a lot of work if we want errors to provide context to the user. As much as possible, I've used `&'static str` and add a lifetime, but there were lifetime conflicts between `'static` and the other lifetime I was using. ## New features - `snafu` replaces `thiserror` everywhere. - New `FrameUid` structure. It is designed to be a unique identifier of Frames. This is useful to retrieve the frame information from the loaded Almanac. - CartesianState is also exported as an Orbit for simplicity. - Orbital computations require planetary data to be loaded from a converted SPICE TPC file. All computations checked for validity before returning. ### Logging `AniseError` no longer exists. Instead, we now follow the SNAFU philosophy of (almost) one error type per module. In practice, these error types are `pub(crate)` because a given error may happen in many different contexts. #### Example In `de440s_translation_verif_venus2emb`, change the epoch year from `2002` to `1800` and print the error, you'll now get something somewhat useful: ``` ERROR anise::almanac::spk > Almanach: No summary 2 valid at epoch 1801-01-01T00:00:00 UTC when searching for SPK summary caused DAF/SPK: summary 2 not present or does not cover requested epoch of 1801-01-01T00:00:00 UTC ``` ## Modifications ### Frame printing This is the previous printing, and it isn't short enough for most usages: ``` [Earth orientation 1 (μ = 398600.435436096 km3/s, eq. radius = 6378.14 km, polar radius = 6356.75 km, f = 0.0033536422844278)] 2000-01-01T13:39:27.999998123 UTC position = [7085.562191, -411.115207, 1249.629706] km velocity = [0.423308, 7.340402, 1.066334] km/s ``` This is from a Hermite BSP generated by GMAT. For some reason, the reference frame is `1`, and I need to figure out why it isn't `0`. ``` SPK Summary for TGT=-10000001 CTR=399 FRM=1 from 2000-01-01T12:00:32.183927295 ET to 2000-01-01T15:20:32.183931521 ET ```
- Loading branch information
1 parent
6724626
commit 3789ea3
Showing
96 changed files
with
3,935 additions
and
3,827 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,6 @@ | ||
/* | ||
* ANISE Toolkit | ||
* Copyright (C) 2021-2022 Christopher Rabotin <[email protected]> et al. (cf. AUTHORS.md) | ||
* Copyright (C) 2021-2023 Christopher Rabotin <[email protected]> et al. (cf. AUTHORS.md) | ||
* This Source Code Form is subject to the terms of the Mozilla Public | ||
* License, v. 2.0. If a copy of the MPL was not distributed with this | ||
* file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||
|
@@ -10,16 +10,17 @@ | |
|
||
use hifitime::Epoch; | ||
|
||
use crate::errors::AniseError; | ||
use crate::naif::daf::DAFError; | ||
use crate::naif::pck::BPCSummaryRecord; | ||
use crate::naif::BPC; | ||
use crate::orientations::OrientationError; | ||
use log::error; | ||
|
||
use super::{Almanac, MAX_LOADED_BPCS}; | ||
|
||
impl<'a: 'b, 'b> Almanac<'a> { | ||
/// Loads a Binary Planetary Constants kernel. | ||
pub fn load_bpc(&self, bpc: &'b BPC) -> Result<Almanac<'b>, AniseError> { | ||
pub fn load_bpc(&self, bpc: BPC) -> Result<Almanac<'b>, OrientationError> { | ||
// This is just a bunch of pointers so it doesn't use much memory. | ||
let mut me = self.clone(); | ||
let mut data_idx = MAX_LOADED_BPCS; | ||
|
@@ -30,15 +31,17 @@ impl<'a: 'b, 'b> Almanac<'a> { | |
} | ||
} | ||
if data_idx == MAX_LOADED_BPCS { | ||
return Err(AniseError::StructureIsFull); | ||
return Err(OrientationError::StructureIsFull { | ||
max_slots: MAX_LOADED_BPCS, | ||
}); | ||
} | ||
me.bpc_data[data_idx] = Some(bpc); | ||
Ok(me) | ||
} | ||
|
||
pub fn num_loaded_bpc(&self) -> usize { | ||
let mut count = 0; | ||
for maybe in self.bpc_data { | ||
for maybe in &self.bpc_data { | ||
if maybe.is_none() { | ||
break; | ||
} else { | ||
|
@@ -54,93 +57,148 @@ impl<'a: 'b, 'b> Almanac<'a> { | |
&self, | ||
name: &str, | ||
epoch: Epoch, | ||
) -> Result<(&BPCSummaryRecord, usize, usize), AniseError> { | ||
) -> Result<(&BPCSummaryRecord, usize, usize), OrientationError> { | ||
for (no, maybe_bpc) in self | ||
.bpc_data | ||
.iter() | ||
.take(self.num_loaded_bpc()) | ||
.rev() | ||
.enumerate() | ||
{ | ||
let bpc = maybe_bpc.unwrap(); | ||
let bpc = maybe_bpc.as_ref().unwrap(); | ||
if let Ok((summary, idx_in_bpc)) = bpc.summary_from_name_at_epoch(name, epoch) { | ||
return Ok((summary, no, idx_in_bpc)); | ||
} | ||
} | ||
|
||
// If we're reached this point, there is no relevant summary at this epoch. | ||
error!("Context: No summary {name} valid at epoch {epoch}"); | ||
Err(AniseError::MissingInterpolationData(epoch)) | ||
error!("Almanac: No summary {name} valid at epoch {epoch}"); | ||
Err(OrientationError::BPC { | ||
action: "searching for BPC summary", | ||
source: DAFError::SummaryNameAtEpochError { | ||
kind: "BPC", | ||
name: name.to_string(), | ||
epoch, | ||
}, | ||
}) | ||
} | ||
|
||
/// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch | ||
pub fn bpc_summary_at_epoch( | ||
&self, | ||
id: i32, | ||
epoch: Epoch, | ||
) -> Result<(&BPCSummaryRecord, usize, usize), AniseError> { | ||
// TODO: Consider a return type here | ||
) -> Result<(&BPCSummaryRecord, usize, usize), OrientationError> { | ||
for (no, maybe_bpc) in self | ||
.bpc_data | ||
.iter() | ||
.take(self.num_loaded_bpc()) | ||
.rev() | ||
.enumerate() | ||
{ | ||
let bpc = maybe_bpc.unwrap(); | ||
let bpc = maybe_bpc.as_ref().unwrap(); | ||
if let Ok((summary, idx_in_bpc)) = bpc.summary_from_id_at_epoch(id, epoch) { | ||
// NOTE: We're iterating backward, so the correct BPC number is "total loaded" minus "current iteration". | ||
return Ok((summary, self.num_loaded_bpc() - no - 1, idx_in_bpc)); | ||
} | ||
} | ||
|
||
error!("Context: No summary {id} valid at epoch {epoch}"); | ||
error!("Almanac: No summary {id} valid at epoch {epoch}"); | ||
// If we're reached this point, there is no relevant summary at this epoch. | ||
Err(AniseError::MissingInterpolationData(epoch)) | ||
Err(OrientationError::BPC { | ||
action: "searching for BPC summary", | ||
source: DAFError::SummaryIdAtEpochError { | ||
kind: "BPC", | ||
id, | ||
epoch, | ||
}, | ||
}) | ||
} | ||
|
||
/// Returns the summary given the name of the summary record. | ||
pub fn bpc_summary_from_name( | ||
&self, | ||
name: &str, | ||
) -> Result<(&BPCSummaryRecord, usize, usize), AniseError> { | ||
) -> Result<(&BPCSummaryRecord, usize, usize), OrientationError> { | ||
for (bpc_no, maybe_bpc) in self | ||
.bpc_data | ||
.iter() | ||
.take(self.num_loaded_bpc()) | ||
.rev() | ||
.enumerate() | ||
{ | ||
let bpc = maybe_bpc.unwrap(); | ||
let bpc = maybe_bpc.as_ref().unwrap(); | ||
if let Ok((summary, idx_in_bpc)) = bpc.summary_from_name(name) { | ||
return Ok((summary, bpc_no, idx_in_bpc)); | ||
} | ||
} | ||
|
||
// If we're reached this point, there is no relevant summary at this epoch. | ||
error!("Context: No summary {name} valid"); | ||
Err(AniseError::NoInterpolationData) | ||
error!("Almanac: No summary {name} valid"); | ||
Err(OrientationError::BPC { | ||
action: "searching for BPC summary", | ||
source: DAFError::SummaryNameError { | ||
kind: "BPC", | ||
name: name.to_string(), | ||
}, | ||
}) | ||
} | ||
|
||
/// Returns the summary given the name of the summary record if that summary has data defined at the requested epoch | ||
pub fn bpc_summary(&self, id: i32) -> Result<(&BPCSummaryRecord, usize, usize), AniseError> { | ||
// TODO: Consider a return type here | ||
pub fn bpc_summary( | ||
&self, | ||
id: i32, | ||
) -> Result<(&BPCSummaryRecord, usize, usize), OrientationError> { | ||
for (no, maybe_bpc) in self | ||
.bpc_data | ||
.iter() | ||
.take(self.num_loaded_bpc()) | ||
.rev() | ||
.enumerate() | ||
{ | ||
let bpc = maybe_bpc.unwrap(); | ||
let bpc = maybe_bpc.as_ref().unwrap(); | ||
if let Ok((summary, idx_in_bpc)) = bpc.summary_from_id(id) { | ||
// NOTE: We're iterating backward, so the correct BPC number is "total loaded" minus "current iteration". | ||
return Ok((summary, self.num_loaded_bpc() - no - 1, idx_in_bpc)); | ||
} | ||
} | ||
|
||
error!("Context: No summary {id} valid"); | ||
error!("Almanac: No summary {id} valid"); | ||
// If we're reached this point, there is no relevant summary | ||
Err(AniseError::NoInterpolationData) | ||
Err(OrientationError::BPC { | ||
action: "searching for BPC summary", | ||
source: DAFError::SummaryIdError { kind: "BPC", id }, | ||
}) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod ut_almanac_bpc { | ||
use crate::prelude::{Almanac, Epoch}; | ||
|
||
#[test] | ||
fn summaries_nothing_loaded() { | ||
let almanac = Almanac::default(); | ||
|
||
let e = Epoch::now().unwrap(); | ||
|
||
assert!( | ||
almanac.bpc_summary(0).is_err(), | ||
"empty Almanac should report an error" | ||
); | ||
assert!( | ||
almanac.bpc_summary_at_epoch(0, e).is_err(), | ||
"empty Almanac should report an error" | ||
); | ||
assert!( | ||
almanac.bpc_summary_from_name("invalid name").is_err(), | ||
"empty Almanac should report an error" | ||
); | ||
assert!( | ||
almanac | ||
.bpc_summary_from_name_at_epoch("invalid name", e) | ||
.is_err(), | ||
"empty Almanac should report an error" | ||
); | ||
} | ||
} |
Oops, something went wrong.