Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for meta almanac in Python and add serde to CartesianState #161

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ resolver = "2"
members = ["anise", "anise-cli", "anise-gui", "anise-py"]

[workspace.package]
version = "0.2.0"
version = "0.2.1"
edition = "2021"
authors = ["Christopher Rabotin <[email protected]>"]
description = "ANISE provides a toolkit and files for Attitude, Navigation, Instrument, Spacecraft, and Ephemeris data. It's a modern replacement of NAIF SPICE file."
Expand Down Expand Up @@ -34,7 +34,9 @@ log = "=0.4"
pretty_env_logger = "=0.5"
tabled = "=0.15"
const_format = "0.2"
nalgebra = "0.32"
nalgebra = { version = "0.32", default-features = true, features = [
"serde-serialize",
] }
approx = "=0.5.1"
zerocopy = { version = "0.7.26", features = ["derive"] }
bytes = "=1.5.0"
Expand All @@ -44,8 +46,11 @@ heapless = "0.8.0"
rstest = "0.18.2"
pyo3 = { version = "0.20.0", features = ["multiple-pymethods"] }
pyo3-log = "0.9.0"
serde = "1"
serde_derive = "1"
serde_dhall = "0.12"

anise = { version = "0.2.0", path = "anise", default-features = false }
anise = { version = "0.2.1", path = "anise", default-features = false }

[profile.bench]
debug = true
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# ANISE (Attitude, Navigation, Instrument, Spacecraft, Ephemeris)

ANISE, inspired by the iconic Dune universe, reimagines the functionalities of the NAIF SPICE toolkit with enhanced performance, precision, and ease of use, leveraging Rust's safety and speed.
ANISE is a rewrite of the core functionalities of the NAIF SPICE toolkit with enhanced performance, and ease of use, while leveraging Rust's safety and speed.

[**Please fill out our user survey**](https://7ug5imdtt8v.typeform.com/to/qYDB14Hj)

Expand Down Expand Up @@ -296,7 +296,7 @@ For more details, please see the [full text of the license](./LICENSE) or read [

## Acknowledgements

ANISE is heavily inspired by the NAIF SPICE toolkit and its excellent documentation
ANISE is heavily inspired by the NAIF SPICE toolkit and its excellent documentation.


## Contact
Expand Down
2 changes: 1 addition & 1 deletion anise-py/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "anise-py"
version = "0.1.0"
version = "0.2.1"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
Expand Down
126 changes: 124 additions & 2 deletions anise-py/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,128 @@
# ANISE Python
# ANISE (Attitude, Navigation, Instrument, Spacecraft, Ephemeris)

The Python interface to ANISE, a modern rewrite of NAIF SPICE.
ANISE is a rewrite of the core functionalities of the NAIF SPICE toolkit with enhanced performance, and ease of use, while leveraging Rust's safety and speed.

[**Please fill out our user survey**](https://7ug5imdtt8v.typeform.com/to/qYDB14Hj)

## Introduction

In the realm of space exploration, navigation, and astrophysics, precise and efficient computation of spacecraft position, orientation, and time is critical. ANISE, standing for "Attitude, Navigation, Instrument, Spacecraft, Ephemeris," offers a Rust-native approach to these challenges. This toolkit provides a suite of functionalities including but not limited to:

+ Loading SPK, BPC, PCK, FK, and TPC files.
+ High-precision translations, rotations, and their combination (rigid body transformations).
+ Comprehensive time system conversions using the hifitime library (including TT, TAI, ET, TDB, UTC, GPS time, and more).

ANISE stands validated against the traditional SPICE toolkit, ensuring accuracy and reliability, with translations achieving machine precision (2e-16) and rotations presenting minimal error (less than two arcseconds in the pointing of the rotation axis and less than one arcsecond in the angle about this rotation axis).

## Features

+ **High Precision**: Matches SPICE to machine precision in translations and minimal errors in rotations.
+ **Time System Conversions**: Extensive support for various time systems crucial in astrodynamics.
+ **Rust Efficiency**: Harnesses the speed and safety of Rust for space computations.
+ **Multi-threaded:** Yup! Forget about mutexes and race conditions you're used to in SPICE, ANISE _guarantees_ that you won't have any race conditions.
+ **Frame safety**: ANISE checks all frames translations or rotations are physically valid before performing any computation, even internally.

## Resources / Assets

For convenience, Nyx Space provides a few important SPICE files on a public bucket:

+ [de440s.bsp](http://public-data.nyxspace.com/anise/de440s.bsp): JPL's latest ephemeris dataset from 1900 until 20250
+ [de440.bsp](http://public-data.nyxspace.com/anise/de440.bsp): JPL's latest long-term ephemeris dataset
+ [pck08.pca](http://public-data.nyxspace.com/anise/pck08.pca): planetary constants ANISE (`pca`) kernel, built from the JPL gravitational data [gm_de431.tpc](http://public-data.nyxspace.com/anise/gm_de431.tpc) and JPL's plantary constants file [pck00008.tpc](http://public-data.nyxspace.com/anise/pck00008.tpc)

You may load any of these using the `load()` shortcut that will determine the file type upon loading, e.g. `let almanac = Almanac::default().load("pck08.pca").unwrap();`.

## Python Usage

In Python, start by adding anise to your project: `pip install anise`.

```python
from anise import Almanac, Aberration
from anise.astro.constants import Frames
from anise.astro import Orbit
from anise.time import Epoch

from pathlib import Path


def test_state_transformation():
"""
This is the Python equivalent to anise/tests/almanac/mod.rs
"""
data_path = Path(__file__).parent.joinpath("..", "..", "data")
# Must ensure that the path is a string
ctx = Almanac(str(data_path.joinpath("de440s.bsp")))
# Let's add another file here -- note that the Almanac will load into a NEW variable, so we must overwrite it!
# This prevents memory leaks (yes, I promise)
ctx = ctx.load(str(data_path.joinpath("pck08.pca"))).load(
str(data_path.joinpath("earth_latest_high_prec.bpc"))
)
eme2k = ctx.frame_info(Frames.EME2000)
assert eme2k.mu_km3_s2() == 398600.435436096
assert eme2k.shape.polar_radius_km == 6356.75
assert abs(eme2k.shape.flattening() - 0.0033536422844278) < 2e-16

epoch = Epoch("2021-10-29 12:34:56 TDB")

orig_state = Orbit.from_keplerian(
8_191.93,
1e-6,
12.85,
306.614,
314.19,
99.887_7,
epoch,
eme2k,
)

assert orig_state.sma_km() == 8191.93
assert orig_state.ecc() == 1.000000000361619e-06
assert orig_state.inc_deg() == 12.849999999999987
assert orig_state.raan_deg() == 306.614
assert orig_state.tlong_deg() == 0.6916999999999689

state_itrf93 = ctx.transform_to(
orig_state, Frames.EARTH_ITRF93, None
)

print(orig_state)
print(state_itrf93)

assert state_itrf93.geodetic_latitude_deg() == 10.549246868302738
assert state_itrf93.geodetic_longitude_deg() == 133.76889100913047
assert state_itrf93.geodetic_height_km() == 1814.503598063825

# Convert back
from_state_itrf93_to_eme2k = ctx.transform_to(
state_itrf93, Frames.EARTH_J2000, None
)

print(from_state_itrf93_to_eme2k)

assert orig_state == from_state_itrf93_to_eme2k

# Demo creation of a ground station
mean_earth_angular_velocity_deg_s = 0.004178079012116429
# Grab the loaded frame info
itrf93 = ctx.frame_info(Frames.EARTH_ITRF93)
paris = Orbit.from_latlongalt(
48.8566,
2.3522,
0.4,
mean_earth_angular_velocity_deg_s,
epoch,
itrf93,
)

assert abs(paris.geodetic_latitude_deg() - 48.8566) < 1e-3
assert abs(paris.geodetic_longitude_deg() - 2.3522) < 1e-3
assert abs(paris.geodetic_height_km() - 0.4) < 1e-3


if __name__ == "__main__":
test_state_transformation()

```

## Getting started as a developer

Expand Down
12 changes: 0 additions & 12 deletions anise-py/src/errors.rs

This file was deleted.

4 changes: 3 additions & 1 deletion anise-py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Documentation: https://nyxspace.com/
*/

use ::anise::almanac::meta::{MetaAlmanac, MetaFile};
use ::anise::almanac::metaload::{MetaAlmanac, MetaFile};
use ::anise::almanac::Almanac;
use ::anise::astro::Aberration;
use hifitime::leap_seconds::{LatestLeapSeconds, LeapSecondsFile};
Expand All @@ -19,12 +19,14 @@ use pyo3::prelude::*;
use pyo3::py_run;

mod astro;
mod utils;

/// A Python module implemented in Rust.
#[pymodule]
fn anise(py: Python, m: &PyModule) -> PyResult<()> {
register_time_module(py, m)?;
astro::register_astro(py, m)?;
utils::register_utils(py, m)?;
m.add_class::<Almanac>()?;
m.add_class::<Aberration>()?;
m.add_class::<MetaAlmanac>()?;
Expand Down
63 changes: 63 additions & 0 deletions anise-py/src/utils.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* ANISE Toolkit
* 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/.
*
* Documentation: https://nyxspace.com/
*/

use std::path::PathBuf;

use anise::naif::kpl::parser::{convert_fk as convert_fk_rs, convert_tpc as convert_tpc_rs};
use anise::structure::dataset::DataSetError;
use anise::structure::planetocentric::ellipsoid::Ellipsoid;
use pyo3::prelude::*;

pub(crate) fn register_utils(py: Python<'_>, parent_module: &PyModule) -> PyResult<()> {
let sm = PyModule::new(py, "_anise.utils")?;
sm.add_class::<Ellipsoid>()?;
sm.add_function(wrap_pyfunction!(convert_fk, sm)?)?;
sm.add_function(wrap_pyfunction!(convert_tpc, sm)?)?;
parent_module.add_submodule(sm)?;
Ok(())
}

/// Converts a KPL/FK file, that defines frame constants like fixed rotations, and frame name to ID mappings into the EulerParameterDataSet equivalent ANISE file.
/// KPL/FK files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.
#[pyfunction]
fn convert_fk(
fk_file_path: String,
anise_output_path: String,
show_comments: Option<bool>,
overwrite: Option<bool>,
) -> Result<(), DataSetError> {
let dataset = convert_fk_rs(fk_file_path, show_comments.unwrap_or(false))?;

dataset.save_as(
&PathBuf::from(anise_output_path),
overwrite.unwrap_or(false),
)?;

Ok(())
}

/// Converts two KPL/TPC files, one defining the planetary constants as text, and the other defining the gravity parameters, into the PlanetaryDataSet equivalent ANISE file.
/// KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.
#[pyfunction]
fn convert_tpc(
pck_file_path: String,
gm_file_path: String,
anise_output_path: String,
overwrite: Option<bool>,
) -> Result<(), DataSetError> {
let dataset = convert_tpc_rs(pck_file_path, gm_file_path)?;

dataset.save_as(
&PathBuf::from(anise_output_path),
overwrite.unwrap_or(false),
)?;

Ok(())
}
15 changes: 6 additions & 9 deletions anise-py/tests/test_almanac.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,12 @@ def test_state_transformation():
"""

if environ.get("CI", False):
# # Load from meta kernel to not use Git LFS quota
# data_path = Path(__file__).parent.joinpath("..", "..", "data", "default_meta.dhall")
# meta = MetaAlmanac(str(data_path))
# print(meta)
# # Process the files to be loaded
# ctx = meta.process()
print("I don't know where the files are in the Python CI")

return
# Load from meta kernel to not use Git LFS quota
data_path = Path(__file__).parent.joinpath("..", "..", "data", "ci_config.dhall")
meta = MetaAlmanac(str(data_path))
print(meta)
# Process the files to be loaded
ctx = meta.process()
else:
data_path = Path(__file__).parent.joinpath("..", "..", "data")
# Must ensure that the path is a string
Expand Down
16 changes: 5 additions & 11 deletions anise/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ rstest = { workspace = true }
pyo3 = { workspace = true, optional = true }
pyo3-log = { workspace = true, optional = true }
url = { version = "2.5.0", optional = true }
serde = { version = "1", optional = true }
serde_derive = { version = "1", optional = true }
serde_dhall = { version = "0.12", optional = true }
serde = { workspace = true }
serde_derive = { workspace = true }
serde_dhall = { workspace = true, optional = true }
reqwest = { version = "0.11.23", optional = true, features = ["blocking"] }
platform-dirs = { version = "0.3.0", optional = true }

Expand All @@ -41,20 +41,14 @@ criterion = "0.5"
iai = "0.1"
polars = { version = "0.36.2", features = ["lazy", "parquet"] }
rayon = "1.7"
serde_yaml = "0.9.30"

[features]
default = ["metaload"]
# Enabling this flag significantly increases compilation times due to Arrow and Polars.
spkezr_validation = []
python = ["pyo3", "pyo3-log"]
metaload = [
"url",
"serde",
"serde_derive",
"serde_dhall",
"reqwest/blocking",
"platform-dirs",
]
metaload = ["serde_dhall", "url", "reqwest/blocking", "platform-dirs"]

[[bench]]
name = "iai_jpl_ephemerides"
Expand Down
Loading
Loading