diff --git a/Cargo.toml b/Cargo.toml index cc142cbd..565addf8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,7 +26,7 @@ exclude = [ ] [workspace.dependencies] -hifitime = "4.0.1" +hifitime = "4.0.2" memmap2 = "0.9.4" crc32fast = "1.4.2" der = { version = "0.7.8", features = ["derive", "alloc", "real"] } @@ -40,9 +40,9 @@ zerocopy = { version = "0.8.0", features = ["derive"] } bytes = "1.6.0" snafu = { version = "0.8.0", features = ["backtrace"] } rstest = "0.23.0" -pyo3 = { version = "0.22", features = ["multiple-pymethods"] } -pyo3-log = "0.11" -numpy = "0.22" +pyo3 = { version = "0.23", features = ["multiple-pymethods"] } +pyo3-log = "0.12" +numpy = "0.23" ndarray = ">= 0.15, < 0.17" anise = { version = "0.5.1", path = "anise", default-features = false } diff --git a/anise-py/src/astro.rs b/anise-py/src/astro.rs index f3b5d3c4..3ad502ef 100644 --- a/anise-py/src/astro.rs +++ b/anise-py/src/astro.rs @@ -20,7 +20,7 @@ use anise::frames::Frame; use super::constants::register_constants; pub(crate) fn register_astro(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { - let sm = PyModule::new_bound(parent_module.py(), "astro")?; + let sm = PyModule::new(parent_module.py(), "astro")?; sm.add_class::()?; sm.add_class::()?; sm.add_class::()?; diff --git a/anise-py/src/constants.rs b/anise-py/src/constants.rs index 1a6737ac..ea24ae15 100644 --- a/anise-py/src/constants.rs +++ b/anise-py/src/constants.rs @@ -190,7 +190,7 @@ impl UsualConstants { } pub(crate) fn register_constants(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { - let sm = PyModule::new_bound(parent_module.py(), "astro.constants")?; + let sm = PyModule::new(parent_module.py(), "astro.constants")?; sm.add_class::()?; sm.add_class::()?; sm.add_class::()?; diff --git a/anise-py/src/lib.rs b/anise-py/src/lib.rs index 37878d32..3b0e299e 100644 --- a/anise-py/src/lib.rs +++ b/anise-py/src/lib.rs @@ -40,7 +40,7 @@ fn anise(m: &Bound<'_, PyModule>) -> PyResult<()> { /// Reexport hifitime as anise.time fn register_time_module(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { - let sm = PyModule::new_bound(parent_module.py(), "time")?; + let sm = PyModule::new(parent_module.py(), "time")?; sm.add_class::()?; sm.add_class::()?; diff --git a/anise-py/src/rotation.rs b/anise-py/src/rotation.rs index dfc4d467..985607d9 100644 --- a/anise-py/src/rotation.rs +++ b/anise-py/src/rotation.rs @@ -13,7 +13,7 @@ use pyo3::prelude::*; use pyo3::py_run; pub(crate) fn register_rotation(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { - let sm = PyModule::new_bound(parent_module.py(), "rotation")?; + let sm = PyModule::new(parent_module.py(), "rotation")?; sm.add_class::()?; Python::with_gil(|py| { diff --git a/anise-py/src/utils.rs b/anise-py/src/utils.rs index 34446991..cebfe0ad 100644 --- a/anise-py/src/utils.rs +++ b/anise-py/src/utils.rs @@ -15,7 +15,7 @@ use anise::structure::dataset::DataSetError; use pyo3::{prelude::*, py_run}; pub(crate) fn register_utils(parent_module: &Bound<'_, PyModule>) -> PyResult<()> { - let sm = PyModule::new_bound(parent_module.py(), "utils")?; + let sm = PyModule::new(parent_module.py(), "utils")?; sm.add_function(wrap_pyfunction!(convert_fk, &sm)?)?; sm.add_function(wrap_pyfunction!(convert_tpc, &sm)?)?; @@ -35,6 +35,7 @@ pub(crate) fn register_utils(parent_module: &Bound<'_, PyModule>) -> PyResult<() /// :type overwrite: bool, optional /// :rtype: None #[pyfunction] +#[pyo3(signature = (fk_file_path, anise_output_path, show_comments=None, overwrite=None))] fn convert_fk( fk_file_path: String, anise_output_path: String, @@ -60,6 +61,7 @@ fn convert_fk( /// :type overwrite: bool, optional /// :rtype: None #[pyfunction] +#[pyo3(signature = (pck_file_path, gm_file_path, anise_output_path, overwrite=None))] fn convert_tpc( pck_file_path: String, gm_file_path: String, diff --git a/anise/Cargo.toml b/anise/Cargo.toml index c36534fe..dd5fe5dc 100644 --- a/anise/Cargo.toml +++ b/anise/Cargo.toml @@ -44,8 +44,8 @@ regex = { version = "1.10.5", optional = true } [dev-dependencies] rust-spice = "0.7.6" -parquet = "53.0.0" -arrow = "53.0.0" +parquet = "54.0.0" +arrow = "54.0.0" criterion = "0.5" iai = "0.1" pretty_env_logger = { workspace = true } diff --git a/anise/src/almanac/metaload/metaalmanac.rs b/anise/src/almanac/metaload/metaalmanac.rs index 54a2dba3..d21b0e4e 100644 --- a/anise/src/almanac/metaload/metaalmanac.rs +++ b/anise/src/almanac/metaload/metaalmanac.rs @@ -57,9 +57,13 @@ impl MetaAlmanac { } /// Fetch all of the URIs and return a loaded Almanac - pub(crate) fn _process(&mut self, autodelete: bool) -> AlmanacResult { + /// When downloading the data, ANISE will create a temporarily lock file to prevent race conditions + /// where multiple processes download the data at the same time. Set `autodelete` to true to delete + /// this lock file if a dead lock is detected after 10 seconds. Set this flag to false if you have + /// more than ten processes which may attempt to download files in parallel. + pub fn process(&mut self, autodelete: bool) -> AlmanacResult { for (fno, file) in self.files.iter_mut().enumerate() { - file._process(autodelete).context(MetaSnafu { + file.process(autodelete).context(MetaSnafu { fno, file: file.clone(), })?; @@ -72,16 +76,6 @@ impl MetaAlmanac { Ok(ctx) } - /// Fetch all of the URIs and return a loaded Almanac - /// When downloading the data, ANISE will create a temporarily lock file to prevent race conditions - /// where multiple processes download the data at the same time. Set `autodelete` to true to delete - /// this lock file if a dead lock is detected after 10 seconds. Set this flag to false if you have - /// more than ten processes which may attempt to download files in parallel. - #[cfg(not(feature = "python"))] - pub fn process(&mut self, autodelete: bool) -> AlmanacResult { - self._process(autodelete) - } - /// Returns an Almanac loaded from the latest NAIF data via the `default` MetaAlmanac. /// The MetaAlmanac will download the DE440s.bsp file, the PCK0008.PCA, the full Moon Principal Axis BPC (moon_pa_de440_200625) and the latest high precision Earth kernel from JPL. /// @@ -95,7 +89,6 @@ impl MetaAlmanac { /// /// Note that the `earth_latest_high_prec.bpc` file is regularly updated daily (or so). As such, /// if queried at some future time, the Earth rotation parameters may have changed between two queries. - #[cfg(not(feature = "python"))] pub fn latest() -> AlmanacResult { Self::default().process(true) } @@ -144,6 +137,7 @@ impl MetaAlmanac { impl MetaAlmanac { /// Loads the provided path as a Dhall file. If no path is provided, creates an empty MetaAlmanac that can store MetaFiles. #[new] + #[pyo3(signature=(maybe_path=None))] pub fn py_new(maybe_path: Option) -> Result { match maybe_path { Some(path) => Self::new(path), @@ -179,13 +173,15 @@ impl MetaAlmanac { /// :type autodelete: bool, optional /// :rtype: MetaAlmanac #[classmethod] - fn latest( + #[pyo3(name = "latest")] + #[pyo3(signature=(autodelete=None))] + fn py_latest( _cls: &Bound<'_, PyType>, py: Python, autodelete: Option, ) -> AlmanacResult { let mut meta = Self::default(); - py.allow_threads(|| match meta._process(autodelete.unwrap_or(false)) { + py.allow_threads(|| match meta.process(autodelete.unwrap_or(false)) { Ok(almanac) => Ok(almanac), Err(e) => Err(e), }) @@ -199,8 +195,10 @@ impl MetaAlmanac { /// /// :type autodelete: bool, optional /// :rtype: Almanac - pub fn process(&mut self, py: Python, autodelete: Option) -> AlmanacResult { - py.allow_threads(|| self._process(autodelete.unwrap_or(true))) + #[pyo3(name = "process")] + #[pyo3(signature=(autodelete=None))] + pub fn py_process(&mut self, py: Python, autodelete: Option) -> AlmanacResult { + py.allow_threads(|| self.process(autodelete.unwrap_or(true))) } fn __str__(&self) -> String { diff --git a/anise/src/almanac/metaload/metafile.rs b/anise/src/almanac/metaload/metafile.rs index 90681200..7378ca7e 100644 --- a/anise/src/almanac/metaload/metafile.rs +++ b/anise/src/almanac/metaload/metafile.rs @@ -56,12 +56,7 @@ impl MetaFile { /// Processes this MetaFile by downloading it if it's a URL and sets this structure's `uri` field to the local path /// /// This function modified `self` and changes the URI to be the path to the downloaded file. - #[cfg(not(feature = "python"))] pub fn process(&mut self, autodelete: bool) -> Result<(), MetaAlmanacError> { - self._process(autodelete) - } - - pub(crate) fn _process(&mut self, autodelete: bool) -> Result<(), MetaAlmanacError> { // First, parse environment variables if any. self.uri = replace_env_vars(&self.uri); match Url::parse(&self.uri) { @@ -268,6 +263,7 @@ impl MetaFile { impl MetaFile { /// Builds a new MetaFile from the provided URI and optionally its CRC32 checksum. #[new] + #[pyo3(signature=(uri, crc32=None))] pub fn py_new(uri: String, crc32: Option) -> Self { Self { uri, crc32 } } @@ -296,12 +292,13 @@ impl MetaFile { /// /// :type autodelete: bool, optional /// :rtype: None - pub fn process( + #[pyo3(name = "process", signature=(autodelete=None))] + pub fn py_process( &mut self, py: Python, autodelete: Option, ) -> Result<(), MetaAlmanacError> { - py.allow_threads(|| self._process(autodelete.unwrap_or(false))) + py.allow_threads(|| self.process(autodelete.unwrap_or(false))) } /// :rtype: str @@ -347,28 +344,28 @@ mod ut_metafile { uri: "C:\\Users\\me\\meta.dhall".to_string(), crc32: None, }; - assert!(window_path._process(true).is_ok()); + assert!(window_path.process(true).is_ok()); assert_eq!(window_path.uri, "C:\\Users\\me\\meta.dhall".to_string()); let mut file_prefix_path = MetaFile { uri: "fIlE:///Users/me/meta.dhall".to_string(), crc32: None, }; - assert!(file_prefix_path._process(true).is_ok()); + assert!(file_prefix_path.process(true).is_ok()); assert_eq!(file_prefix_path.uri, "/Users/me/meta.dhall".to_string()); let mut unix_abs_path = MetaFile { uri: "/Users/me/meta.dhall".to_string(), crc32: None, }; - assert!(unix_abs_path._process(true).is_ok()); + assert!(unix_abs_path.process(true).is_ok()); assert_eq!(unix_abs_path.uri, "/Users/me/meta.dhall".to_string()); let mut unix_rel_path = MetaFile { uri: "../Users/me/meta.dhall".to_string(), crc32: None, }; - assert!(unix_rel_path._process(true).is_ok()); + assert!(unix_rel_path.process(true).is_ok()); assert_eq!(unix_rel_path.uri, "../Users/me/meta.dhall".to_string()); } @@ -379,14 +376,14 @@ mod ut_metafile { uri: "env:USER/.cargo/env".to_string(), crc32: None, }; - user_path._process(false).unwrap(); + user_path.process(false).unwrap(); assert_eq!(user_path.uri, env::var("USER").unwrap() + "/.cargo/env"); let mut unknown_path = MetaFile { uri: "env:BLAH_BLAH_NO_EXIST/.cargo/env".to_string(), crc32: None, }; - unknown_path._process(false).unwrap(); + unknown_path.process(false).unwrap(); assert_eq!( unknown_path.uri, "env:BLAH_BLAH_NO_EXIST/.cargo/env".to_string() diff --git a/anise/src/almanac/metaload/mod.rs b/anise/src/almanac/metaload/mod.rs index 9ecd5f8c..e84a28dc 100644 --- a/anise/src/almanac/metaload/mod.rs +++ b/anise/src/almanac/metaload/mod.rs @@ -54,25 +54,23 @@ pub enum MetaAlmanacError { } impl Almanac { - /// Load from the provided MetaFile. - fn _load_from_metafile(&self, mut metafile: MetaFile, autodelete: bool) -> AlmanacResult { - metafile._process(autodelete).context(MetaSnafu { + /// Load from the provided MetaFile, downloading it if necessary. + /// Set autodelete to true to automatically delete lock files. Lock files are important in multi-threaded loads. + pub fn load_from_metafile( + &self, + mut metafile: MetaFile, + autodelete: bool, + ) -> AlmanacResult { + metafile.process(autodelete).context(MetaSnafu { fno: 0_usize, file: metafile.clone(), })?; self.load(&metafile.uri) } - - /// Load from the provided MetaFile, downloading it if necessary. - /// Set autodelete to true to automatically delete lock files. Lock files are important in multi-threaded loads. - #[cfg(not(feature = "python"))] - pub fn load_from_metafile(&self, metafile: MetaFile, autodelete: bool) -> AlmanacResult { - self._load_from_metafile(metafile, autodelete) - } } #[cfg(feature = "python")] -#[cfg_attr(feature = "python", pymethods)] +#[pymethods] impl Almanac { /// Load from the provided MetaFile, downloading it if necessary. /// Set autodelete to true to automatically delete lock files. Lock files are important in multi-threaded loads. @@ -81,13 +79,14 @@ impl Almanac { /// :type metafile: Metafile /// :type autodelete: bool /// :rtype: Almanac - fn load_from_metafile( + #[pyo3(name = "load_from_metafile")] + fn py_load_from_metafile( &mut self, py: Python, metafile: MetaFile, autodelete: bool, ) -> AlmanacResult { - py.allow_threads(|| self._load_from_metafile(metafile, autodelete)) + py.allow_threads(|| self.load_from_metafile(metafile, autodelete)) } } @@ -105,15 +104,15 @@ mod meta_test { let mut meta = MetaAlmanac::default(); println!("{meta:?}"); - let almanac = meta._process(true).unwrap(); + let almanac = meta.process(true).unwrap(); // Shows everything in this Almanac almanac.describe(None, None, None, None, None, None); // Process again to confirm that the CRC check works - assert!(meta._process(true).is_ok()); + assert!(meta.process(true).is_ok()); // Test that loading from an invalid URI reports an error assert!(almanac - ._load_from_metafile( + .load_from_metafile( MetaFile { uri: "http://example.com/non/existing.pca".to_string(), crc32: None diff --git a/anise/src/astro/mod.rs b/anise/src/astro/mod.rs index ae0f65da..849c6197 100644 --- a/anise/src/astro/mod.rs +++ b/anise/src/astro/mod.rs @@ -79,6 +79,7 @@ impl AzElRange { impl AzElRange { /// Initializes a new AzElRange instance #[new] + #[pyo3(signature=(epoch, azimuth_deg, elevation_deg, range_km, range_rate_km_s, obstructed_by=None))] pub fn py_new( epoch: Epoch, azimuth_deg: f64, diff --git a/anise/src/frames/frame.rs b/anise/src/frames/frame.rs index 92a821eb..58238aeb 100644 --- a/anise/src/frames/frame.rs +++ b/anise/src/frames/frame.rs @@ -98,6 +98,7 @@ impl Frame { impl Frame { /// Initializes a new [Frame] provided its ephemeris and orientation identifiers, and optionally its gravitational parameter (in km^3/s^2) and optionally its shape (cf. [Ellipsoid]). #[new] + #[pyo3(signature=(ephemeris_id, orientation_id, mu_km3_s2=None, shape=None))] pub fn py_new( ephemeris_id: NaifId, orientation_id: NaifId, diff --git a/anise/src/math/cartesian_py.rs b/anise/src/math/cartesian_py.rs index aba709e6..9a617927 100644 --- a/anise/src/math/cartesian_py.rs +++ b/anise/src/math/cartesian_py.rs @@ -167,7 +167,7 @@ impl CartesianState { let state = Array1::from_iter(data); - Ok(PyArray1::::from_owned_array_bound(py, state)) + Ok(PyArray1::::from_owned_array(py, state)) } fn __str__(&self) -> String { diff --git a/anise/src/math/rotation/dcm_py.rs b/anise/src/math/rotation/dcm_py.rs index de6a40a6..ee2ab4af 100644 --- a/anise/src/math/rotation/dcm_py.rs +++ b/anise/src/math/rotation/dcm_py.rs @@ -23,6 +23,7 @@ use pyo3::types::PyType; #[pymethods] impl DCM { #[new] + #[pyo3(signature=(np_rot_mat, from_id, to_id, np_rot_mat_dt=None))] pub fn py_new<'py>( np_rot_mat: PyReadonlyArray2<'py, f64>, from_id: NaifId, @@ -129,7 +130,7 @@ impl DCM { // Create an ndarray Array2 (row-major order) let rot_mat = Array2::from_shape_vec((3, 3), data).unwrap(); - let py_rot_mat = PyArray2::::from_owned_array_bound(py, rot_mat); + let py_rot_mat = PyArray2::::from_owned_array(py, rot_mat); Ok(py_rot_mat) } @@ -152,7 +153,7 @@ impl DCM { // Create an ndarray Array2 (row-major order) let rot_mat_dt = Array2::from_shape_vec((3, 3), data).unwrap(); - let py_rot_mat_dt = PyArray2::::from_owned_array_bound(py, rot_mat_dt); + let py_rot_mat_dt = PyArray2::::from_owned_array(py, rot_mat_dt); Ok(Some(py_rot_mat_dt)) } @@ -179,7 +180,7 @@ impl DCM { // Create an ndarray Array2 (row-major order) let state_dcm = Array2::from_shape_vec((6, 6), data).unwrap(); - let pt_state_dcm = PyArray2::::from_owned_array_bound(py, state_dcm); + let pt_state_dcm = PyArray2::::from_owned_array(py, state_dcm); Ok(pt_state_dcm) } diff --git a/anise/src/naif/daf/data_types.rs b/anise/src/naif/daf/data_types.rs index 6985decb..d437e5df 100644 --- a/anise/src/naif/daf/data_types.rs +++ b/anise/src/naif/daf/data_types.rs @@ -19,7 +19,7 @@ use super::DAFError; #[cfg(feature = "python")] use pyo3::prelude::*; -#[cfg_attr(feature = "python", pyclass)] +#[cfg_attr(feature = "python", pyclass(eq, eq_int))] #[derive(Copy, Clone, Debug, PartialEq)] #[repr(u8)] pub enum DataType { diff --git a/anise/src/naif/kpl/parser.rs b/anise/src/naif/kpl/parser.rs index 425c80cf..8607cbc5 100644 --- a/anise/src/naif/kpl/parser.rs +++ b/anise/src/naif/kpl/parser.rs @@ -359,19 +359,16 @@ pub fn convert_fk + fmt::Debug>( } // Build the quaternion from the Euler matrices let from = id; - let mut to = item.data[&Parameter::Center].to_i32().unwrap(); + let to = item.data[&Parameter::Center].to_i32().unwrap(); if let Some(class) = item.data.get(&Parameter::Class) { if class.to_i32().unwrap() == 4 { // This is a relative frame. let relative_to = item.data.get(&Parameter::Relative).ok_or(DataSetError::Conversion { action: format!("frame {id} is class 4 relative to, but the RELATIVE_TO token was not found"), })?.to_string().unwrap(); - if let Ok(parent) = dataset.get_by_name(&relative_to) { - to = parent.to; - } else { - // Not found yet, let's mark it as an ID to revisit. - ids_to_update.push((id, relative_to.clone())); - } + + // Always mark as something to update later. + ids_to_update.push((id, relative_to.clone())); } } @@ -441,9 +438,10 @@ pub fn convert_fk + fmt::Debug>( let parent_id = dataset.data[(*parent_idx) as usize].to; // Modify this EP. - let this_idx = dataset.lut.by_id[&id]; - let mut this_q = dataset.data[this_idx as usize]; - this_q.from = parent_id; + let index = dataset.lut.by_id.get(&id).unwrap(); + // Grab the data + let this_q = dataset.data.get_mut(*index as usize).unwrap(); + this_q.to = parent_id; } dataset.set_crc32(); diff --git a/anise/src/structure/lookuptable.rs b/anise/src/structure/lookuptable.rs index 14dd8389..1f1a0676 100644 --- a/anise/src/structure/lookuptable.rs +++ b/anise/src/structure/lookuptable.rs @@ -47,7 +47,7 @@ pub enum LutError { /// _Both_ the IDs and the name MUST be unique in the look up table. #[derive(Clone, Default, Debug, PartialEq, Eq)] pub struct LookUpTable { - /// Unique IDs of each item in the + /// Unique IDs of each item in the LUT pub by_id: FnvIndexMap, /// Corresponding index for each hash pub by_name: FnvIndexMap, u32, ENTRIES>, diff --git a/anise/src/structure/planetocentric/ellipsoid.rs b/anise/src/structure/planetocentric/ellipsoid.rs index 34a7bb47..ca2bb89b 100644 --- a/anise/src/structure/planetocentric/ellipsoid.rs +++ b/anise/src/structure/planetocentric/ellipsoid.rs @@ -74,6 +74,7 @@ impl Ellipsoid { /// All units are in kilometers. If the semi minor equatorial radius is not provided, a bi-axial spheroid will be created using the semi major equatorial radius as /// the equatorial radius and using the provided polar axis radius. If only the semi major equatorial radius is provided, a perfect sphere will be built. #[new] + #[pyo3(signature=(semi_major_equatorial_radius_km, polar_radius_km=None, semi_minor_equatorial_radius_km=None))] fn py_new( semi_major_equatorial_radius_km: f64, polar_radius_km: Option, diff --git a/anise/tests/orientations/mod.rs b/anise/tests/orientations/mod.rs index b4c95434..a7e493bb 100644 --- a/anise/tests/orientations/mod.rs +++ b/anise/tests/orientations/mod.rs @@ -2,11 +2,11 @@ use std::path::PathBuf; use anise::constants::frames::{ EARTH_ITRF93, EARTH_J2000, EME2000, IAU_JUPITER_FRAME, IAU_MOON_FRAME, - JUPITER_BARYCENTER_J2000, MOON_J2000, MOON_ME_DE440_ME421_FRAME, MOON_ME_FRAME, - MOON_PA_DE421_FRAME, MOON_PA_DE440_FRAME, MOON_PA_FRAME, + JUPITER_BARYCENTER_J2000, MOON_J2000, MOON_ME_DE440_ME421_FRAME, MOON_PA_DE421_FRAME, + MOON_PA_DE440_FRAME, MOON_PA_FRAME, }; use anise::constants::orientations::{ - ECLIPJ2000, IAU_JUPITER, IAU_MOON, ITRF93, J2000, MOON_PA, MOON_PA_DE421, MOON_PA_DE440, + ECLIPJ2000, IAU_JUPITER, IAU_MOON, ITRF93, J2000, MOON_PA_DE421, MOON_PA_DE440, }; use anise::math::rotation::DCM; use anise::math::Matrix3;