Skip to content

Commit

Permalink
Change SPICE kernel singleton structure (#167)
Browse files Browse the repository at this point in the history
* Change SPICE kernel singleton structure

* doc test fix
  • Loading branch information
dahlend authored Jan 8, 2025
1 parent 54d92a6 commit df3dab1
Show file tree
Hide file tree
Showing 15 changed files with 71 additions and 102 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed

- Comet Magnitude estimates now accepts two phase correction values instead of 1.
- Restructured SPICE kernel memory management to make entire class of bugs impossible.

### Fixed

Expand Down
19 changes: 9 additions & 10 deletions src/kete/rust/propagation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use itertools::Itertools;
use kete_core::{
errors::Error,
propagation::{self, moid, NonGravModel},
spice::{self, get_spk_singleton},
spice::{self, LOADED_SPK},
state::State,
time::{scales::TDB, Time},
};
Expand All @@ -27,15 +27,14 @@ use crate::{nongrav::PyNonGravModel, time::PyTime};
#[pyfunction]
#[pyo3(name = "moid", signature = (state_a, state_b=None))]
pub fn moid_py(state_a: PyState, state_b: Option<PyState>) -> PyResult<f64> {
let state_b =
state_b
.map(|x| x.0)
.unwrap_or(get_spk_singleton().read().unwrap().try_get_state(
399,
state_a.0.jd,
10,
state_a.0.frame,
)?);
let state_b = state_b
.map(|x| x.0)
.unwrap_or(LOADED_SPK.read().unwrap().try_get_state(
399,
state_a.0.jd,
10,
state_a.0.frame,
)?);
Ok(moid(state_a.0, state_b)?)
}

Expand Down
12 changes: 6 additions & 6 deletions src/kete/rust/spice/pck.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
use kete_core::frames::ecef_to_geodetic_lat_lon;
use kete_core::spice::{get_pck_singleton, get_spk_singleton};
use kete_core::spice::{LOADED_PCK, LOADED_SPK};
use kete_core::{constants, prelude::*};
use pyo3::{pyfunction, PyResult};

Expand All @@ -9,7 +9,7 @@ use crate::state::PyState;
#[pyfunction]
#[pyo3(name = "pck_load")]
pub fn pck_load_py(filenames: Vec<String>) -> PyResult<()> {
let mut singleton = get_pck_singleton().write().unwrap();
let mut singleton = LOADED_PCK.write().unwrap();
for filename in filenames.iter() {
let load = (*singleton).load_file(filename);
if let Err(err) = load {
Expand Down Expand Up @@ -52,13 +52,13 @@ pub fn pck_earth_frame_py(
None => Desig::Empty,
}
};
let pcks = get_pck_singleton().try_read().unwrap();
let pcks = LOADED_PCK.try_read().unwrap();
let frame = pcks.try_get_orientation(3000, jd)?;

let mut state = State::new(desig, jd, pos.into(), [0.0, 0.0, 0.0].into(), frame, 399);
state.try_change_frame_mut(Frame::Ecliptic)?;

let spks = get_spk_singleton().try_read().unwrap();
let spks = &LOADED_SPK.try_read().unwrap();
spks.try_change_center(&mut state, new_center)?;
Ok(PyState(state))
}
Expand All @@ -78,7 +78,7 @@ pub fn pck_earth_frame_py(
#[pyfunction]
#[pyo3(name = "state_to_earth_pos")]
pub fn pck_state_to_earth(state: PyState) -> PyResult<(f64, f64, f64)> {
let pcks = get_pck_singleton().try_read().unwrap();
let pcks = LOADED_PCK.try_read().unwrap();
let state = state.change_center(399)?.as_ecliptic()?;
let frame = pcks.try_get_orientation(3000, state.jd())?;
let mut state = state.0;
Expand All @@ -98,5 +98,5 @@ pub fn pck_state_to_earth(state: PyState) -> PyResult<(f64, f64, f64)> {
#[pyfunction]
#[pyo3(name = "pck_reset")]
pub fn pck_reset_py() {
get_pck_singleton().write().unwrap().reset()
LOADED_PCK.write().unwrap().reset()
}
14 changes: 7 additions & 7 deletions src/kete/rust/spice/spk.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use kete_core::spice::{get_spk_singleton, try_name_from_id};
use kete_core::spice::{try_name_from_id, LOADED_SPK};
use pyo3::{pyfunction, PyResult, Python};

use crate::frame::PyFrames;
Expand All @@ -9,7 +9,7 @@ use crate::time::PyTime;
#[pyfunction]
#[pyo3(name = "spk_load")]
pub fn spk_load_py(py: Python<'_>, filenames: Vec<String>) -> PyResult<()> {
let mut singleton = get_spk_singleton().write().unwrap();
let mut singleton = LOADED_SPK.write().unwrap();
if filenames.len() > 100 {
eprintln!("Loading {} spk files...", filenames.len());
}
Expand All @@ -30,7 +30,7 @@ pub fn spk_load_py(py: Python<'_>, filenames: Vec<String>) -> PyResult<()> {
#[pyfunction]
#[pyo3(name = "spk_available_info")]
pub fn spk_available_info_py(naif_id: i64) -> Vec<(f64, f64, i64, PyFrames, i32)> {
let singleton = get_spk_singleton().try_read().unwrap();
let singleton = &LOADED_SPK.try_read().unwrap();
singleton
.available_info(naif_id)
.into_iter()
Expand All @@ -42,7 +42,7 @@ pub fn spk_available_info_py(naif_id: i64) -> Vec<(f64, f64, i64, PyFrames, i32)
#[pyfunction]
#[pyo3(name = "spk_loaded")]
pub fn spk_loaded_objects_py() -> Vec<i64> {
let spk = get_spk_singleton().try_read().unwrap();
let spk = &LOADED_SPK.try_read().unwrap();
let loaded = spk.loaded_objects(false);
let mut loaded: Vec<i64> = loaded.into_iter().collect();
loaded.sort();
Expand All @@ -61,7 +61,7 @@ pub fn spk_get_name_from_id_py(id: i64) -> String {
#[pyfunction]
#[pyo3(name = "spk_reset")]
pub fn spk_reset_py() {
get_spk_singleton().write().unwrap().reset()
LOADED_SPK.write().unwrap().reset()
}

/// Calculate the state of a given object in the target frame.
Expand All @@ -82,7 +82,7 @@ pub fn spk_reset_py() {
#[pyo3(name = "spk_state")]
pub fn spk_state_py(id: i64, jd: PyTime, center: i64, frame: PyFrames) -> PyResult<PyState> {
let jd = jd.jd();
let spk = get_spk_singleton().try_read().unwrap();
let spk = &LOADED_SPK.try_read().unwrap();
let mut state = spk.try_get_state(id, jd, center, frame.into())?;
let _ = state.try_naif_id_to_name();
Ok(PyState(state))
Expand All @@ -102,6 +102,6 @@ pub fn spk_state_py(id: i64, jd: PyTime, center: i64, frame: PyFrames) -> PyResu
#[pyo3(name = "spk_raw_state")]
pub fn spk_raw_state_py(id: i64, jd: PyTime) -> PyResult<PyState> {
let jd = jd.jd();
let spk = get_spk_singleton().try_read().unwrap();
let spk = &LOADED_SPK.try_read().unwrap();
Ok(PyState(spk.try_get_raw_state(id, jd)?))
}
2 changes: 1 addition & 1 deletion src/kete/rust/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ impl PyState {
/// If the desired state is not a known NAIF id this will raise an exception.
pub fn change_center(&self, naif_id: i64) -> PyResult<Self> {
let mut state = self.0.clone();
let spk = prelude::get_spk_singleton().try_read().unwrap();
let spk = prelude::LOADED_SPK.try_read().unwrap();
spk.try_change_center(&mut state, naif_id)?;
Ok(Self(state))
}
Expand Down
2 changes: 1 addition & 1 deletion src/kete_core/benches/propagation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ fn prop_n_body_radau(state: State, dt: f64) {
}

fn prop_n_body_vec_radau(mut state: State, dt: f64) {
let spk = get_spk_singleton().read().unwrap();
let spk = &LOADED_SPK.read().unwrap();
spk.try_change_center(&mut state, 10).unwrap();
state.try_change_frame_mut(Frame::Ecliptic).unwrap();
let states = vec![state.clone(); 100];
Expand Down
10 changes: 5 additions & 5 deletions src/kete_core/benches/spice.rs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
extern crate criterion;
use criterion::{black_box, criterion_group, criterion_main, Criterion};
use kete_core::{spice::get_spk_singleton, state::State};
use kete_core::{spice::LOADED_SPK, state::State};
use pprof::criterion::{Output, PProfProfiler};

fn spice_get_raw_state(jd: f64) {
let spice = get_spk_singleton().try_read().unwrap();
let spice = &LOADED_SPK.try_read().unwrap();
for _ in 0..1000 {
let _ = spice.try_get_raw_state(5, jd).unwrap();
}
}

fn spice_change_center(mut state: State) {
let spice = get_spk_singleton().try_read().unwrap();
let spice = &LOADED_SPK.try_read().unwrap();
for _ in 0..500 {
spice.try_change_center(&mut state, 10).unwrap();
spice.try_change_center(&mut state, 0).unwrap();
}
}

fn spice_get_state(jd: f64) {
let spice = get_spk_singleton().try_read().unwrap();
let spice = &LOADED_SPK.try_read().unwrap();
for _ in 0..1000 {
let _ = spice
.try_get_state(5, jd, 10, kete_core::frames::Frame::Ecliptic)
Expand All @@ -28,7 +28,7 @@ fn spice_get_state(jd: f64) {
}

pub fn spice_benchmark(c: &mut Criterion) {
let spice = get_spk_singleton().try_read().unwrap();
let spice = &LOADED_SPK.try_read().unwrap();
let state = spice
.try_get_state(5, 2451545.0, 10, kete_core::frames::Frame::Ecliptic)
.unwrap();
Expand Down
2 changes: 1 addition & 1 deletion src/kete_core/src/fov/fov_like.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ pub trait FovLike: Sync + Sized {
/// This will fail silently if the object is not found.
fn check_spks(&self, obj_ids: &[i64]) -> Vec<Option<SimultaneousStates>> {
let obs = self.observer();
let spk = get_spk_singleton().try_read().unwrap();
let spk = &LOADED_SPK.try_read().unwrap();

let mut visible: Vec<Vec<State>> = vec![Vec::new(); self.n_patches()];

Expand Down
2 changes: 1 addition & 1 deletion src/kete_core/src/fov/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ mod tests {
fn test_check_omni_visible() {
// Build an observer, and check the observability of ceres with different offsets from the observer time.
// this will exercise the position, velocity, and time offsets due to light delay.
let spk = get_spk_singleton().read().unwrap();
let spk = &LOADED_SPK.read().unwrap();
let observer = State::new(
Desig::Empty,
2451545.0,
Expand Down
2 changes: 1 addition & 1 deletion src/kete_core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,6 @@ pub mod prelude {
pub use crate::frames::Frame;
pub use crate::propagation::{propagate_n_body_spk, propagate_two_body};
pub use crate::simult_states::SimultaneousStates;
pub use crate::spice::{get_pck_singleton, get_spk_singleton};
pub use crate::spice::{LOADED_PCK, LOADED_SPK};
pub use crate::state::{Desig, State};
}
6 changes: 3 additions & 3 deletions src/kete_core/src/propagation/acceleration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
//! information should be recorded.
//!
use crate::prelude::KeteResult;
use crate::spice::get_spk_singleton;
use crate::spice::LOADED_SPK;
use crate::{constants::*, errors::Error, frames::Frame, propagation::nongrav::NonGravModel};
use nalgebra::allocator::Allocator;
use nalgebra::{DefaultAllocator, Dim, Matrix3, OVector, Vector3, U1, U2};
Expand Down Expand Up @@ -135,7 +135,7 @@ pub fn spk_accel(
}
}

let spk = get_spk_singleton().try_read().unwrap();
let spk = &LOADED_SPK.try_read().unwrap();

for grav_params in meta.massive_obj.iter() {
let id = grav_params.naif_id;
Expand Down Expand Up @@ -297,7 +297,7 @@ mod tests {

#[test]
fn check_accelerations_equal() {
let spk = get_spk_singleton().try_read().unwrap();
let spk = &LOADED_SPK.try_read().unwrap();
let jd = 2451545.0;
let mut pos: Vec<f64> = Vec::new();
let mut vel: Vec<f64> = Vec::new();
Expand Down
6 changes: 3 additions & 3 deletions src/kete_core/src/propagation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use crate::constants::{MASSES, PLANETS, SIMPLE_PLANETS};
use crate::errors::Error;
use crate::frames::Frame;
use crate::prelude::{Desig, KeteResult};
use crate::spice::get_spk_singleton;
use crate::spice::LOADED_SPK;
use crate::state::State;
use nalgebra::{DVector, Vector3};

Expand Down Expand Up @@ -75,7 +75,7 @@ pub fn propagate_n_body_spk(
) -> KeteResult<State> {
let center = state.center_id;
let frame = state.frame;
let spk = get_spk_singleton().try_read().unwrap();
let spk = &LOADED_SPK.try_read().unwrap();
spk.try_change_center(&mut state, 0)?;
state.try_change_frame_mut(Frame::Equatorial)?;

Expand Down Expand Up @@ -164,7 +164,7 @@ pub fn propagate_n_body_vec(
let mut desigs: Vec<Desig> = Vec::new();

let planet_states = planet_states.unwrap_or_else(|| {
let spk = get_spk_singleton().try_read().unwrap();
let spk = &LOADED_SPK.try_read().unwrap();
let mut planet_states = Vec::with_capacity(SIMPLE_PLANETS.len());
for obj in SIMPLE_PLANETS.iter() {
let planet = spk
Expand Down
38 changes: 12 additions & 26 deletions src/kete_core/src/spice/pck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,9 @@ use super::pck_segments::PckSegment;
use crate::errors::{Error, KeteResult};
use crate::frames::Frame;
use crossbeam::sync::ShardedLock;
use lazy_static::lazy_static;

use std::io::Cursor;
use std::mem::MaybeUninit;
use std::sync::Once;

const PRELOAD_PCK: &[&[u8]] = &[
include_bytes!("../../data/earth_000101_240215_231123.bpc"),
Expand Down Expand Up @@ -78,28 +77,15 @@ impl PckCollection {
}
}

/// Get the PCK singleton.
/// This is a RwLock protected PCKCollection, and must be `.try_read().unwrapped()` for any
/// read-only cases.
pub fn get_pck_singleton() -> &'static PckSingleton {
// Create an uninitialized static
static mut SINGLETON: MaybeUninit<PckSingleton> = MaybeUninit::uninit();
static ONCE: Once = Once::new();

unsafe {
ONCE.call_once(|| {
let mut files = PckCollection {
segments: Vec::new(),
};
files.reset();
let singleton: PckSingleton = ShardedLock::new(files);
// Store it to the static var, i.e. initialize it
#[allow(static_mut_refs)]
let _ = SINGLETON.write(singleton);
});

// Now we give out a shared reference to the data, which is safe to use concurrently.
#[allow(static_mut_refs)]
SINGLETON.assume_init_ref()
}
lazy_static! {
/// PCK singleton.
/// This is a RwLock protected PCKCollection, and must be `.try_read().unwrapped()` for any
/// read-only cases.
pub static ref LOADED_PCK: PckSingleton = {
let mut files = PckCollection {
segments: Vec::new(),
};
files.reset();
ShardedLock::new(files)
};
}
Loading

0 comments on commit df3dab1

Please sign in to comment.