├── .gitmodules ├── anise-py ├── anise │ ├── py.typed │ ├── time.py │ ├── utils.py │ ├── astro │ │ ├── __init__.py │ │ └── constants.py │ ├── analysis.py │ ├── constants.py │ ├── rotation.py │ ├── utils.pyi │ ├── __init__.py │ └── constants.pyi ├── .cargo │ └── config.toml ├── tests │ ├── README.md │ └── test_hifitime.py ├── pyproject.toml ├── src │ ├── rotation.rs │ ├── astro.rs │ ├── bin.rs │ ├── utils.rs │ └── lib.rs ├── Cargo.toml └── .gitignore ├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── stakeholder-need.md │ ├── documentation.md │ └── maintenance.md ├── actions │ └── setup-anise │ │ └── action.yml ├── pull_request_template.md ├── workflows │ ├── gui.yaml │ └── benchmarks.yml └── CONTRIBUTING.md ├── .vscode ├── settings.json └── tasks.json ├── anise ├── fuzz │ ├── .gitignore │ ├── fuzz_targets │ │ ├── parse_bpc.rs │ │ ├── parse_spk.rs │ │ ├── fuzz_metadata.rs │ │ ├── planetary_dataset.rs │ │ ├── spacecraft_dataset.rs │ │ ├── euler_parameter_dataset.rs │ │ ├── rotation_mrp_normalize.rs │ │ ├── rotation_dcm_to_quaternion.rs │ │ ├── fkitem_extract_key.rs │ │ ├── tpcitem_extract_key.rs │ │ ├── rotation_dcm_partialeq.rs │ │ ├── rotation_mrp_partialeq.rs │ │ ├── fkitem_parse.rs │ │ ├── kpl_parse_bytes_fkitem.rs │ │ ├── kpl_parse_bytes_tpcitem.rs │ │ ├── orientations_paths_root.rs │ │ ├── try_find_ephemeris_root.rs │ │ ├── load_from_bytes.rs │ │ ├── rotation_quaternion_mul.rs │ │ ├── convert_fk_items.rs │ │ ├── orientations_rotations_rotate.rs │ │ ├── orientations_paths_path.rs │ │ ├── almanac_describe.rs │ │ ├── convert_tpc_items.rs │ │ └── common_ephemeris_path.rs │ ├── README.md │ └── Cargo.toml ├── tests │ ├── astro │ │ ├── mod.rs │ │ └── eclipsing.rs │ ├── test_analysis │ │ ├── requirements.txt │ │ ├── lro-occultation.py │ │ └── spk_validation_plots.py │ ├── frames │ │ ├── mod.rs │ │ └── format.rs │ ├── lib.rs │ └── ephemerides │ │ ├── mod.rs │ │ ├── validation │ │ ├── mod.rs │ │ ├── type03_chebyshev_jpl_de.rs │ │ ├── type09_lagrange.rs │ │ ├── type01_modified_diff.rs │ │ ├── type13_hermite.rs │ │ ├── type02_chebyshev_jpl_de.rs │ │ └── validate.rs │ │ ├── parent_translation_verif.rs │ │ └── paths.rs ├── src │ ├── naif │ │ ├── spk │ │ │ └── mod.rs │ │ ├── daf │ │ │ ├── datatypes │ │ │ │ ├── mod.rs │ │ │ │ └── posvel.rs │ │ │ ├── summary_record.rs │ │ │ └── name_record.rs │ │ ├── mod.rs │ │ └── pck │ │ │ └── mod.rs │ ├── frames │ │ ├── mod.rs │ │ └── frameuid.rs │ ├── structure │ │ ├── mod.rs │ │ ├── dataset │ │ │ ├── datatype.rs │ │ │ ├── error.rs │ │ │ └── pretty_print.rs │ │ ├── semver.rs │ │ └── spacecraft │ │ │ ├── drag.rs │ │ │ ├── srp.rs │ │ │ └── inertia.rs │ ├── math │ │ ├── angles.rs │ │ ├── interpolation │ │ │ ├── mod.rs │ │ │ ├── chebyshev.rs │ │ │ └── lagrange.rs │ │ ├── units.rs │ │ ├── rotation │ │ │ └── mod.rs │ │ └── mod.rs │ ├── orientations │ │ └── mod.rs │ ├── almanac │ │ └── embed.rs │ ├── ephemerides │ │ └── mod.rs │ ├── py_errors.rs │ ├── lib.rs │ └── astro │ │ └── orbit_equinoctial.rs ├── benches │ ├── crit_planetary_data.rs │ ├── iai_jpl_ephemeris.rs │ ├── iai_spacecraft_ephemeris.rs │ ├── crit_bpc_rotation.rs │ ├── crit_jpl_ephemerides.rs │ └── crit_spacecraft_ephemeris.rs ├── build.rs └── Cargo.toml ├── data ├── .gitattributes ├── moon_fk.epa ├── .gitignore ├── moon_fk_de440.epa ├── pck08.pca ├── pck11.pca ├── gmat-hermite.bsp ├── rename-test.bsp ├── gmat-lagrange.bsp ├── earth_latest_high_prec.bpc ├── gmat-hermite-big-endian.bsp ├── moon_pa_de440_200625.bpc ├── variable-seg-size-hermite.bsp ├── earth_2025_250826_2125_predict.bpc ├── earth_longterm_000101_251211_250915.bpc ├── local.dhall ├── ci_config.dhall ├── aer_regression.dhall ├── latest.dhall └── example_meta.dhall ├── ANISE-logo.png ├── anise-cpp └── README.md ├── anise-gui ├── icon.ico ├── icon-256.png ├── index.html ├── build.rs ├── Cargo.toml ├── src │ ├── main.rs │ └── epa.rs └── README.md ├── test_constants.tpc ├── AUTHORS.md ├── .gitattributes ├── .cargo └── config.toml ├── dev-env-setup.sh ├── .gitignore ├── anise-cli ├── Cargo.toml └── src │ ├── mod.rs │ └── args.rs ├── Cargo.toml └── download_test_data.sh /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /anise-py/anise/py.typed: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [nyx-space] 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "python.formatting.provider": "yapf" 3 | } -------------------------------------------------------------------------------- /anise/fuzz/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | corpus 3 | artifacts 4 | coverage 5 | -------------------------------------------------------------------------------- /anise/tests/astro/mod.rs: -------------------------------------------------------------------------------- 1 | mod aer; 2 | mod eclipsing; 3 | mod orbit; 4 | -------------------------------------------------------------------------------- /data/.gitattributes: -------------------------------------------------------------------------------- 1 | data/de438s.bsp filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /ANISE-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyx-space/anise/HEAD/ANISE-logo.png -------------------------------------------------------------------------------- /anise-cpp/README.md: -------------------------------------------------------------------------------- 1 | # ANISE C++ 2 | 3 | Plaeceholder for the C++ bindings to ANISE. -------------------------------------------------------------------------------- /data/moon_fk.epa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyx-space/anise/HEAD/data/moon_fk.epa -------------------------------------------------------------------------------- /anise-gui/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyx-space/anise/HEAD/anise-gui/icon.ico -------------------------------------------------------------------------------- /test_constants.tpc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyx-space/anise/HEAD/test_constants.tpc -------------------------------------------------------------------------------- /data/.gitignore: -------------------------------------------------------------------------------- 1 | !pck08.pca 2 | !pck11.pca 3 | !moon_fk.epa 4 | !moon_fk_de440.epa 5 | de*.bsp -------------------------------------------------------------------------------- /anise-gui/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyx-space/anise/HEAD/anise-gui/icon-256.png -------------------------------------------------------------------------------- /data/moon_fk_de440.epa: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nyx-space/anise/HEAD/data/moon_fk_de440.epa -------------------------------------------------------------------------------- /data/pck08.pca: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:cdca71964f9e248bfc421cf8ab72b2fbacb9f05106ad08abe16c1ff5488aa313 3 | size 33217 4 | -------------------------------------------------------------------------------- /data/pck11.pca: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:4bbe31dd7fa986324343150d0feeaf78a7a102cb9167818b5cd8748db4f3e6a2 3 | size 38067 4 | -------------------------------------------------------------------------------- /data/gmat-hermite.bsp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:be0e3585440d70fbba44dbba6ef6fff7fa50d3f16b007fe9edf26f4ed92191c3 3 | size 14336 4 | -------------------------------------------------------------------------------- /data/rename-test.bsp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:62e4ce41a6aa17910b5c0c41845cbca2f81884ba8f63fa66b975dcd059a33737 3 | size 80896 4 | -------------------------------------------------------------------------------- /data/gmat-lagrange.bsp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:80382ab44cf42d38c90cca12d24611b60d441f46e50ff3f051e88c8e1597de9c 3 | size 14336 4 | -------------------------------------------------------------------------------- /data/earth_latest_high_prec.bpc: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f03347c0b3ccba47e6e8a53c77a0381d695973fa9af8f409c282d111fc1ceb22 3 | size 4388864 4 | -------------------------------------------------------------------------------- /data/gmat-hermite-big-endian.bsp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:3db0d55ec61c9fb7e4beb0834f3e1b78a390628d45e6cee59f23827d19edeaaa 3 | size 14336 4 | -------------------------------------------------------------------------------- /data/moon_pa_de440_200625.bpc: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:60cd55aa401ea2ea97360636f567554bfe4e37bb829f901b4460a455dfaf783f 3 | size 12863488 4 | -------------------------------------------------------------------------------- /data/variable-seg-size-hermite.bsp: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:995ad203359e3acd7ceca2c883d16553b9d43af8fb45adbc3c9f3c2c921c33df 3 | size 82944 4 | -------------------------------------------------------------------------------- /AUTHORS.md: -------------------------------------------------------------------------------- 1 | # Authors 2 | 3 | + Christopher Rabotin 4 | + Chris de Claverie 5 | + Grégoire Henry -------------------------------------------------------------------------------- /data/earth_2025_250826_2125_predict.bpc: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:f5ed4d1f460dbcf40bf24f657ab9021718048e18fb723093bfd229066b4e3a49 3 | size 19179520 4 | -------------------------------------------------------------------------------- /data/earth_longterm_000101_251211_250915.bpc: -------------------------------------------------------------------------------- 1 | version https://git-lfs.github.com/spec/v1 2 | oid sha256:39d1f9907cbfdf989b8f8d39802fe355ad30fa93d00381f29fe847715fb83340 3 | size 4941824 4 | -------------------------------------------------------------------------------- /data/local.dhall: -------------------------------------------------------------------------------- 1 | -- Default Almanac 2 | { files = 3 | [ { crc32 = None Natural, uri = "../../data/de440s.bsp" } 4 | , { crc32 = None Natural, uri = "../../data/pck08.pca" } 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | data/*.bsp filter=lfs diff=lfs merge=lfs -text 2 | data/*.bpc filter=lfs diff=lfs merge=lfs -text 3 | data/*.pca filter=lfs diff=lfs merge=lfs -text 4 | *.ipynb linguist-documentation=true -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: cargo 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | time: "11:00" 8 | open-pull-requests-limit: 10 9 | -------------------------------------------------------------------------------- /anise-gui/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/parse_bpc.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::BPC; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _ = BPC::parse(data); 8 | }); 9 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/parse_spk.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::SPK; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _ = SPK::parse(data); 8 | }); 9 | -------------------------------------------------------------------------------- /anise-py/anise/time.py: -------------------------------------------------------------------------------- 1 | from anise._anise import time 2 | 3 | __all__ = [] 4 | 5 | for el in dir(time): 6 | if el and el[0] != "_": 7 | locals()[el] = getattr(time, el) 8 | __all__ += [el] 9 | -------------------------------------------------------------------------------- /anise-py/anise/utils.py: -------------------------------------------------------------------------------- 1 | from anise._anise import utils 2 | 3 | __all__ = [] 4 | 5 | for el in dir(utils): 6 | if el and el[0] != "_": 7 | locals()[el] = getattr(utils, el) 8 | __all__ += [el] 9 | -------------------------------------------------------------------------------- /anise-py/anise/astro/__init__.py: -------------------------------------------------------------------------------- 1 | from anise._anise import astro 2 | 3 | __all__ = [] 4 | 5 | for el in dir(astro): 6 | if el and el[0] != "_": 7 | locals()[el] = getattr(astro, el) 8 | __all__ += [el] 9 | -------------------------------------------------------------------------------- /anise-py/anise/analysis.py: -------------------------------------------------------------------------------- 1 | from anise._anise import analysis 2 | 3 | __all__ = [] 4 | 5 | for el in dir(analysis): 6 | if el and el[0] != "_": 7 | locals()[el] = getattr(analysis, el) 8 | __all__ += [el] 9 | -------------------------------------------------------------------------------- /anise-py/anise/constants.py: -------------------------------------------------------------------------------- 1 | from anise._anise import constants 2 | 3 | __all__ = [] 4 | 5 | for el in dir(constants): 6 | if el and el[0] != "_": 7 | locals()[el] = getattr(constants, el) 8 | __all__ += [el] 9 | -------------------------------------------------------------------------------- /anise-py/anise/rotation.py: -------------------------------------------------------------------------------- 1 | from anise._anise import rotation 2 | 3 | __all__ = [] 4 | 5 | for el in dir(rotation): 6 | if el and el[0] != "_": 7 | locals()[el] = getattr(rotation, el) 8 | __all__ += [el] 9 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/fuzz_metadata.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::structure::metadata::Metadata; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _ = Metadata::decode_header(data); 8 | }); 9 | -------------------------------------------------------------------------------- /anise-gui/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(windows)] 2 | fn main() { 3 | let mut res = tauri_winres::WindowsResource::new(); 4 | res.set_icon("icon.ico"); 5 | res.compile().unwrap(); 6 | } 7 | 8 | #[cfg(not(windows))] 9 | fn main() {} 10 | -------------------------------------------------------------------------------- /anise-py/anise/astro/constants.py: -------------------------------------------------------------------------------- 1 | from anise._anise import constants 2 | 3 | __all__ = [] 4 | 5 | for el in dir(constants): 6 | if el and el[0] != "_": 7 | locals()[el] = getattr(constants, el) 8 | __all__ += [el] 9 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/planetary_dataset.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::structure::PlanetaryDataSet; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _ = PlanetaryDataSet::try_from_bytes(data); 8 | }); 9 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/spacecraft_dataset.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::structure::SpacecraftDataSet; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _ = SpacecraftDataSet::try_from_bytes(data); 8 | }); 9 | -------------------------------------------------------------------------------- /anise-py/.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [target.x86_64-apple-darwin] 2 | rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] 3 | 4 | [target.aarch64-apple-darwin] 5 | rustflags = ["-C", "link-arg=-undefined", "-C", "link-arg=dynamic_lookup"] 6 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/euler_parameter_dataset.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::structure::EulerParameterDataSet; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | let _ = EulerParameterDataSet::try_from_bytes(data); 8 | }); 9 | -------------------------------------------------------------------------------- /.cargo/config.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | CSPICE_DIR = {value = "./cspice/", relative = true} 3 | 4 | # [target.x86_64-unknown-linux-gnu] 5 | # For flamegraph -- https://github.com/flamegraph-rs/flamegraph 6 | # linker = "/usr/bin/clang" 7 | # rustflags = ["-Clink-arg=-fuse-ld=lld", "-Clink-arg=-Wl,--no-rosegment"] 8 | -------------------------------------------------------------------------------- /dev-env-setup.sh: -------------------------------------------------------------------------------- 1 | # This shell script sets up the development environment on Linux 2 | curl https://naif.jpl.nasa.gov/pub/naif/toolkit//C/PC_Linux_GCC_64bit/packages/cspice.tar.Z --output cspice.tar.Z 3 | tar xzvf cspice.tar.Z 4 | cd cspice 5 | tcsh makeall.csh 6 | mv lib/cspice.a lib/libcspice.a 7 | cd .. 8 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/rotation_mrp_normalize.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::math::rotation::MRP; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use anise_fuzz::ArbitraryMRP; 7 | 8 | fuzz_target!(|data: ArbitraryMRP| { 9 | let mrp = MRP::from(data); 10 | let _ = mrp.normalize(); 11 | }); 12 | -------------------------------------------------------------------------------- /anise/tests/test_analysis/requirements.txt: -------------------------------------------------------------------------------- 1 | hifitime==4.1.3 2 | narwhals==2.0.1 3 | numpy #==2.3.2 # Install whatever works? 4 | packaging==25.0 5 | pandas==2.3.1 6 | plotly==6.2.0 7 | polars==1.32.0 8 | pyarrow==21.0.0 9 | python-dateutil==2.9.0.post0 10 | pytz==2025.2 11 | six==1.17.0 12 | tzdata==2025.2 13 | -------------------------------------------------------------------------------- /anise-py/tests/README.md: -------------------------------------------------------------------------------- 1 | # Python tests 2 | 3 | The sole purpose of the Python tests is to check the export of classes and functions, and check that they behave as expected. 4 | 5 | Unit testing, integration testing, and regression tests all happen in the Rust tests. Please refer to those [here](../../anise/tests/) for details. 6 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/rotation_dcm_to_quaternion.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::math::rotation::{Quaternion, DCM}; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use anise_fuzz::ArbitraryDCM; 7 | 8 | fuzz_target!(|data: ArbitraryDCM| { 9 | let dcm = DCM::from(data); 10 | let _ = Quaternion::from(dcm); 11 | }); 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | *.anis* 4 | cspice 5 | *.parquet 6 | *.svg 7 | perf.data* 8 | analysis/.venv 9 | cspice.tar.Z 10 | .venv 11 | *.pca 12 | *.sca 13 | *.epa 14 | anise-gui/dist/ 15 | anise-py/notebooks/.ipynb_checkpoints/ANISE Tutorial for querying SPK files-checkpoint.ipynb 16 | data/lro.bsp 17 | data/mro.bsp 18 | *.csv 19 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/fkitem_extract_key.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::kpl::{fk::FKItem, KPLItem}; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use anise_fuzz::ArbitraryAssignment; 7 | 8 | fuzz_target!(|data: ArbitraryAssignment| { 9 | let assignment = data.into(); 10 | let _ = FKItem::extract_key(&assignment); 11 | }); 12 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/tpcitem_extract_key.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::kpl::{tpc::TPCItem, KPLItem}; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use anise_fuzz::ArbitraryAssignment; 7 | 8 | fuzz_target!(|data: ArbitraryAssignment| { 9 | let assignment = data.into(); 10 | let _ = TPCItem::extract_key(&assignment); 11 | }); 12 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/rotation_dcm_partialeq.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::math::rotation::DCM; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use anise_fuzz::ArbitraryDCM; 7 | 8 | fuzz_target!(|data: (ArbitraryDCM, ArbitraryDCM)| { 9 | let dcm_0 = DCM::from(data.0); 10 | let dcm_1 = DCM::from(data.1); 11 | let _ = dcm_0 == dcm_1; 12 | }); 13 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/rotation_mrp_partialeq.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::math::rotation::MRP; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use anise_fuzz::ArbitraryMRP; 7 | 8 | fuzz_target!(|data: (ArbitraryMRP, ArbitraryMRP)| { 9 | let mrp_0 = MRP::from(data.0); 10 | let mrp_1 = MRP::from(data.1); 11 | let _ = mrp_0 == mrp_1; 12 | }); 13 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/fkitem_parse.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::kpl::{fk::FKItem, KPLItem}; 3 | 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | use anise_fuzz::ArbitraryAssignment; 7 | 8 | fuzz_target!(|data: ArbitraryAssignment| { 9 | let assignment = data.into(); 10 | let mut item = FKItem::default(); 11 | item.parse(assignment); 12 | }); 13 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/kpl_parse_bytes_fkitem.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::kpl::fk::FKItem; 3 | use anise::naif::kpl::parser::parse_bytes; 4 | use std::io::BufReader; 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | 8 | fuzz_target!(|data: &[u8]| { 9 | let mut reader = BufReader::new(data); 10 | let show_comments = false; 11 | let _ = parse_bytes::<_, FKItem>(&mut reader, show_comments); 12 | }); 13 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/kpl_parse_bytes_tpcitem.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::kpl::parser::parse_bytes; 3 | use anise::naif::kpl::tpc::TPCItem; 4 | use std::io::BufReader; 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | 8 | fuzz_target!(|data: &[u8]| { 9 | let mut reader = BufReader::new(data); 10 | let show_comments = false; 11 | let _ = parse_bytes::<_, TPCItem>(&mut reader, show_comments); 12 | }); 13 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/orientations_paths_root.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::almanac::Almanac; 3 | use bytes::BytesMut; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | let almanac = Almanac::default(); 9 | let data = BytesMut::from(data); 10 | if let Ok(almanac) = almanac.load_from_bytes(data) { 11 | let _ = almanac.try_find_orientation_root(); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/try_find_ephemeris_root.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::almanac::Almanac; 3 | use bytes::BytesMut; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | let almanac = Almanac::default(); 9 | let data = BytesMut::from(data); 10 | if let Ok(almanac) = almanac.load_from_bytes(data) { 11 | let _ = almanac.try_find_ephemeris_root(); 12 | } 13 | }); 14 | -------------------------------------------------------------------------------- /data/ci_config.dhall: -------------------------------------------------------------------------------- 1 | { files = 2 | [ { crc32 = Some 1921414410 3 | , uri = "http://public-data.nyxspace.com/anise/de440s.bsp" 4 | } 5 | , { crc32 = Some 0x92b7d662 6 | , uri = "http://public-data.nyxspace.com/anise/v0.7/pck08.pca" 7 | } 8 | , { crc32 = None Natural 9 | , uri = 10 | "http://public-data.nyxspace.com/anise/ci/earth_latest_high_prec-2023-09-08.bpc" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/load_from_bytes.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::almanac::Almanac; 3 | use bytes::BytesMut; 4 | use libfuzzer_sys::fuzz_target; 5 | 6 | fuzz_target!(|data: &[u8]| { 7 | // create default almanac to serve as test env 8 | let almanac = Almanac::default(); 9 | // convert fuzzed data into Bytes object, matching _load_from_bytes function 10 | let _ = almanac.load_from_bytes(BytesMut::from(data)); 11 | }); 12 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/rotation_quaternion_mul.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::math::rotation::Quaternion; 3 | use anise::math::Vector3; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | use anise_fuzz::{ArbitraryQuaternion, ArbitraryVector3}; 8 | 9 | fuzz_target!(|data: (ArbitraryQuaternion, ArbitraryVector3)| { 10 | let quaternion = Quaternion::from(data.0); 11 | let vector = Vector3::from(data.1); 12 | let _ = quaternion * vector; 13 | }); 14 | -------------------------------------------------------------------------------- /anise/tests/frames/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | mod format; 12 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/convert_fk_items.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::kpl::parser::convert_fk_items; 3 | use std::collections::HashMap; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | use anise_fuzz::ArbitraryFKItem; 8 | 9 | fuzz_target!(|data: HashMap| { 10 | let assignments = data 11 | .into_iter() 12 | .map(|(idx, item)| (idx, item.into())) 13 | .collect(); 14 | let _ = convert_fk_items(assignments); 15 | }); 16 | -------------------------------------------------------------------------------- /anise/src/naif/spk/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | // Defines how to read an SPK 12 | pub mod summary; 13 | -------------------------------------------------------------------------------- /anise/src/frames/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | mod frame; 12 | mod frameuid; 13 | 14 | pub use frame::Frame; 15 | pub use frameuid::FrameUid; 16 | -------------------------------------------------------------------------------- /anise/tests/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | #[macro_use] 12 | extern crate approx; 13 | mod almanac; 14 | mod astro; 15 | mod ephemerides; 16 | mod frames; 17 | mod orientations; 18 | -------------------------------------------------------------------------------- /anise-py/tests/test_hifitime.py: -------------------------------------------------------------------------------- 1 | from anise.time import * 2 | 3 | """ 4 | The time tests only make sure that we can call all of the functions that are re-exported. 5 | For comprehensive tests of the time, refer to the hifitime test suite 6 | """ 7 | 8 | 9 | def test_exports(): 10 | for cls in [ 11 | Epoch, 12 | TimeSeries, 13 | Duration, 14 | Unit, 15 | Ut1Provider, 16 | LatestLeapSeconds, 17 | LeapSecondsFile, 18 | ]: 19 | print(f"{cls} OK") 20 | 21 | 22 | if __name__ == "__main__": 23 | test_exports() 24 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | mod parent_translation_verif; 12 | mod paths; 13 | mod transform; 14 | mod translation; 15 | #[cfg(feature = "validation")] 16 | mod validation; 17 | -------------------------------------------------------------------------------- /data/aer_regression.dhall: -------------------------------------------------------------------------------- 1 | { files = 2 | [ { crc32 = Some 1921414410 3 | , uri = "http://public-data.nyxspace.com/anise/de440s.bsp" 4 | } 5 | , { crc32 = Some 0x92b7d662 6 | , uri = "http://public-data.nyxspace.com/anise/v0.7/pck08.pca" 7 | } 8 | , { crc32 = Some 0x256c8653 9 | , uri = 10 | "http://public-data.nyxspace.com/anise/ci/earth_latest_high_prec-2025-05-24.bpc" 11 | } 12 | , { crc32 = Some 0x9145a9b3 13 | , uri = 14 | "http://public-data.nyxspace.com/anise/ci/earth_longterm_000101_251211_250915.bpc" 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/orientations_rotations_rotate.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::almanac::Almanac; 3 | use anise::frames::Frame; 4 | use hifitime::Epoch; 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | 8 | use anise_fuzz::{ArbitraryEpoch, ArbitraryFrame}; 9 | 10 | fuzz_target!(|data: (ArbitraryFrame, ArbitraryFrame, ArbitraryEpoch)| { 11 | let from_frame = Frame::from(data.0); 12 | let to_frame = Frame::from(data.1); 13 | let epoch = Epoch::from(data.2); 14 | 15 | let almanac = Almanac::default(); 16 | 17 | let _ = almanac.rotate(from_frame, to_frame, epoch); 18 | }); 19 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/orientations_paths_path.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::almanac::Almanac; 3 | use anise::frames::Frame; 4 | use hifitime::Epoch; 5 | 6 | use libfuzzer_sys::fuzz_target; 7 | 8 | use anise_fuzz::{ArbitraryEpoch, ArbitraryFrame}; 9 | 10 | fuzz_target!(|data: (ArbitraryFrame, ArbitraryFrame, ArbitraryEpoch)| { 11 | let from_frame = Frame::from(data.0); 12 | let to_frame = Frame::from(data.1); 13 | let epoch = Epoch::from(data.2); 14 | 15 | let almanac = Almanac::default(); 16 | 17 | let _ = almanac.common_orientation_path(from_frame, to_frame, epoch); 18 | }); 19 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/validation/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | mod type01_modified_diff; 12 | mod type02_chebyshev_jpl_de; 13 | mod type03_chebyshev_jpl_de; 14 | mod type09_lagrange; 15 | mod type13_hermite; 16 | 17 | mod compare; 18 | mod validate; 19 | -------------------------------------------------------------------------------- /anise-py/pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = ["maturin>=1.7,<2.0", "numpy>=1.16.0"] 3 | build-backend = "maturin" 4 | 5 | [project] 6 | name = "anise" 7 | requires-python = ">=3.9" 8 | classifiers = [ 9 | "Programming Language :: Rust", 10 | "Programming Language :: Python :: Implementation :: CPython", 11 | "Programming Language :: Python :: Implementation :: PyPy", 12 | ] 13 | readme = "README.md" 14 | dynamic = ["version", "urls", "description", "authors", "license", "description_content_type", "summary"] 15 | [project.scripts] 16 | anise-gui = "anise:exec_gui" 17 | 18 | [tool.maturin] 19 | features = ["pyo3/extension-module"] 20 | module-name = "anise._anise" 21 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/almanac_describe.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::almanac::Almanac; 3 | use bytes::BytesMut; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | fuzz_target!(|data: &[u8]| { 8 | let almanac = Almanac::default(); 9 | let mut bytes = BytesMut::new(); 10 | bytes.reserve(data.len()); 11 | bytes.extend(data.iter()); 12 | if let Ok(almanac) = almanac.load_from_bytes(bytes) { 13 | almanac.describe( 14 | Some(true), 15 | Some(true), 16 | Some(true), 17 | Some(true), 18 | Some(true), 19 | Some(true), 20 | None, 21 | None, 22 | ); 23 | } 24 | }); 25 | -------------------------------------------------------------------------------- /anise/src/naif/daf/datatypes/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | pub mod chebyshev; 12 | pub mod chebyshev3; 13 | pub mod hermite; 14 | pub mod lagrange; 15 | pub mod modified_diff; 16 | pub mod posvel; 17 | 18 | pub use chebyshev::*; 19 | pub use chebyshev3::*; 20 | pub use hermite::*; 21 | pub use lagrange::*; 22 | -------------------------------------------------------------------------------- /anise-py/src/rotation.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use anise::math::rotation::{Quaternion, DCM}; 12 | use pyo3::prelude::*; 13 | 14 | #[pymodule] 15 | pub(crate) fn rotation(_py: Python, sm: &Bound) -> PyResult<()> { 16 | sm.add_class::()?; 17 | sm.add_class::()?; 18 | 19 | Ok(()) 20 | } 21 | -------------------------------------------------------------------------------- /anise-py/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anise-py" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | edition = { workspace = true } 6 | homepage = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | description = { workspace = true } 10 | 11 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 12 | [lib] 13 | name = "anise" 14 | crate-type = ["cdylib"] 15 | 16 | [dependencies] 17 | anise = { workspace = true, features = ["python", "metaload", "analysis"] } 18 | hifitime = { workspace = true, features = ["python"] } 19 | pyo3 = { workspace = true, features = ["extension-module"] } 20 | pyo3-log = { workspace = true } 21 | -------------------------------------------------------------------------------- /anise-cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anise-cli" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | edition = { workspace = true } 6 | homepage = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | description = "A command line interface for ANISE" 10 | 11 | [dependencies] 12 | anise = { workspace = true } 13 | clap = { version = "4", features = ["derive"] } 14 | pretty_env_logger = { workspace = true } 15 | bytes = { workspace = true } 16 | memmap2 = { workspace = true } 17 | snafu = { workspace = true } 18 | log = { workspace = true } 19 | zerocopy = { workspace = true } 20 | hifitime = { workspace = true } 21 | 22 | 23 | [[bin]] 24 | name = "anise-cli" 25 | path = "src/main.rs" 26 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/convert_tpc_items.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::naif::kpl::parser::convert_tpc_items; 3 | use std::collections::HashMap; 4 | 5 | use libfuzzer_sys::fuzz_target; 6 | 7 | use anise_fuzz::ArbitraryTPCItem; 8 | 9 | fuzz_target!(|data: ( 10 | HashMap, 11 | HashMap 12 | )| { 13 | let (planetary_data, gravity_data) = data; 14 | let planetary_data = planetary_data 15 | .into_iter() 16 | .map(|(idx, item)| (idx, item.into())) 17 | .collect(); 18 | let gravity_data = gravity_data 19 | .into_iter() 20 | .map(|(idx, item)| (idx, item.into())) 21 | .collect(); 22 | let _ = convert_tpc_items(planetary_data, gravity_data); 23 | }); 24 | -------------------------------------------------------------------------------- /anise/fuzz/fuzz_targets/common_ephemeris_path.rs: -------------------------------------------------------------------------------- 1 | #![no_main] 2 | use anise::almanac::Almanac; 3 | use anise::frames::Frame; 4 | use bytes::BytesMut; 5 | use hifitime::Epoch; 6 | 7 | use libfuzzer_sys::fuzz_target; 8 | 9 | use anise_fuzz::{ArbitraryEpoch, ArbitraryFrame}; 10 | 11 | fuzz_target!( 12 | |data: (&[u8], ArbitraryFrame, ArbitraryFrame, ArbitraryEpoch)| { 13 | let from_frame = Frame::from(data.1); 14 | let to_frame = Frame::from(data.2); 15 | let epoch = Epoch::from(data.3); 16 | let almanac = Almanac::default(); 17 | 18 | let bytes = BytesMut::from(data.0); 19 | if let Ok(almanac) = almanac.load_from_bytes(bytes) { 20 | let _ = almanac.common_ephemeris_path(from_frame, to_frame, epoch); 21 | } 22 | } 23 | ); 24 | -------------------------------------------------------------------------------- /data/latest.dhall: -------------------------------------------------------------------------------- 1 | -- Latest planetary ephemerides, planetary constants, high precision Moon rotation, and daily Earth orientation parameter 2 | { files = 3 | [ { crc32 = Some 0x7286750a 4 | , uri = "http://public-data.nyxspace.com/anise/de440s.bsp" 5 | } 6 | , { crc32 = Some 0x51f69e46 7 | , uri = "http://public-data.nyxspace.com/anise/v0.7/pck11.pca" 8 | } 9 | , { crc32 = Some 0x32c8f9d7 10 | , uri = "http://public-data.nyxspace.com/anise/v0.7/moon_fk_de440.epa" 11 | } 12 | , { crc32 = Some 0xcde5ca7d 13 | , uri = "http://public-data.nyxspace.com/anise/moon_pa_de440_200625.bpc" 14 | } 15 | , { crc32 = None Natural 16 | , uri = 17 | "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/pck/earth_latest_high_prec.bpc" 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /anise-py/anise/utils.pyi: -------------------------------------------------------------------------------- 1 | def convert_fk(fk_file_path: str, anise_output_path: str, show_comments: bool=None, overwrite: bool=None) -> None: 2 | """Converts a KPL/FK file, that defines frame constants like fixed rotations, and frame name to ID mappings into the EulerParameterDataSet equivalent ANISE file. 3 | KPL/FK files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" 4 | 5 | def convert_tpc(pck_file_path: str, gm_file_path: str, anise_output_path: str, overwrite: bool=None) -> None: 6 | """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. 7 | KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE.""" 8 | -------------------------------------------------------------------------------- /anise-py/anise/__init__.py: -------------------------------------------------------------------------------- 1 | from anise._anise import ( 2 | Aberration, 3 | Almanac, 4 | MetaAlmanac, 5 | MetaFile, 6 | LocationDhallSet, 7 | LocationDhallSetEntry, 8 | LocationDataSet, 9 | exec_gui, 10 | time, 11 | analysis, 12 | astro, 13 | constants, 14 | rotation, 15 | utils, 16 | __version__, 17 | __doc__, 18 | __author__, 19 | ) 20 | 21 | __all__ = [ 22 | # modules 23 | "analysis", 24 | "astro", 25 | "constants", 26 | "time", 27 | "rotation", 28 | "utils", 29 | # root 30 | "Aberration", 31 | "Almanac", 32 | "MetaAlmanac", 33 | "MetaFile", 34 | "LocationDhallSet", 35 | "LocationDhallSetEntry", 36 | "LocationDataSet", 37 | # functions 38 | "exec_gui", 39 | "__version__", 40 | "__doc__", 41 | "__author__", 42 | ] 43 | -------------------------------------------------------------------------------- /anise-cli/src/mod.rs: -------------------------------------------------------------------------------- 1 | extern crate clap; 2 | extern crate tabled; 3 | 4 | use snafu::prelude::*; 5 | use std::io; 6 | 7 | use crate::{ 8 | naif::daf::{file_record::FileRecordError, DAFError}, 9 | prelude::InputOutputError, 10 | structure::dataset::DataSetError, 11 | }; 12 | 13 | pub mod args; 14 | 15 | pub mod inspect; 16 | 17 | #[derive(Debug, Snafu)] 18 | #[snafu(visibility(pub))] 19 | pub enum CliErrors { 20 | /// File not found or unreadable 21 | FileNotFound { 22 | source: io::Error, 23 | }, 24 | /// ANISE error encountered" 25 | CliDAF { 26 | source: DAFError, 27 | }, 28 | CliFileRecord { 29 | source: FileRecordError, 30 | }, 31 | ArgumentError { 32 | arg: String, 33 | }, 34 | CliDataSet { 35 | source: DataSetError, 36 | }, 37 | AniseError { 38 | source: InputOutputError, 39 | }, 40 | } 41 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # Bug report 11 | 12 | ## Describe the bug 13 | A clear and concise description of what the bug is. 14 | 15 | ## To Reproduce 16 | Steps to reproduce the behavior: 17 | 1. Load this file '...' (attach if possible) 18 | 2. Execute this function '....' 19 | 3. Perform these instructions '....' 20 | 4. See error 21 | 22 | ## Expected behavior 23 | A clear and concise description of what you expected to happen. 24 | 25 | ## Code to reproduce the issue 26 | If possible, provide a short snippet of code that reproduces the bug. 27 | 28 | ## Platform 29 | Describe the platform you encountered the bug on, e.g. desktop running Linux and calling ANISE from FORTRAN. 30 | 31 | ## Additional context 32 | Add any other context about the problem here. 33 | -------------------------------------------------------------------------------- /anise/benches/crit_planetary_data.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use anise::{constants::frames::EARTH_ITRF93, naif::kpl::parser::convert_tpc, prelude::*}; 4 | use criterion::{criterion_group, criterion_main, Criterion}; 5 | use std::hint::black_box; 6 | 7 | fn benchmark_fetch(almanac: &Almanac, frame: Frame) { 8 | black_box(almanac.frame_info(frame).unwrap()); 9 | } 10 | 11 | pub fn criterion_benchmark(c: &mut Criterion) { 12 | let pca = PathBuf::from_str("pck11.pca").unwrap(); 13 | let planetary_data = convert_tpc("../data/pck00011.tpc", "../data/gm_de431.tpc").unwrap(); 14 | planetary_data.save_as(&pca, true).unwrap(); 15 | 16 | let almanac = Almanac::new("pck11.pca").unwrap(); 17 | 18 | c.bench_function("Frame fetch from planetary dataset", |b| { 19 | b.iter(|| benchmark_fetch(&almanac, EARTH_ITRF93)) 20 | }); 21 | } 22 | 23 | criterion_group!(pca, criterion_benchmark); 24 | criterion_main!(pca); 25 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/stakeholder-need.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Stakeholder need 3 | about: A Quality Assurance compliant template for stakeholder needs 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | # High level description 11 | 12 | Describe the need you have either with use cases or examples. 13 | 14 | # Requirements 15 | 16 | What does the system need to do? 17 | 18 | ## Test plans 19 | 20 | How do we test that these requirements are fulfilled correctly? What are some edge cases we should be aware of when developing the test code? 21 | 22 | # Design 23 | 24 | Document, discuss, and optionally upload design diagram into this section. 25 | 26 | 27 | ## Algorithm demonstration 28 | 29 | If this issue requires a change in an algorithm, it should be described here. This algorithm should be described thoroughly enough to be used as documentation, simply refer to an algorithm in the literature or in another piece of software that has been validated. 30 | -------------------------------------------------------------------------------- /anise/tests/test_analysis/lro-occultation.py: -------------------------------------------------------------------------------- 1 | import spiceypy as sp 2 | 3 | # Load kernels 4 | sp.furnsh('../../../data/lro.bsp') 5 | sp.furnsh('../../../data/de440s.bsp') 6 | sp.furnsh('../../../data/pck00008.tpc') 7 | 8 | # Define parameters 9 | occtyp = 'ANY' 10 | front = 'MOON' 11 | fshape = 'ELLIPSOID' 12 | fframe = 'IAU_MOON' 13 | back = 'SUN' 14 | bshape = 'ELLIPSOID' 15 | bframe = 'IAU_SUN' 16 | abcorr = 'NONE' 17 | obsrvr = '-85' # LRO 18 | stepsz = 5.0 # Step size in seconds 19 | cnfine = sp.stypes.SPICEDOUBLE_CELL(2) 20 | 21 | prenumbra_start_et = 757339371.1839062 # 2024-01-01T00:01:42 UTC 22 | prenumbra_end_et = 757339383.1839062 # 2024-01-01T00:01:54 UTC 23 | pre_prenumbra_start_et = prenumbra_start_et - 1.0 24 | 25 | umbra_et = 757339384.1839062 # 2024-01-01T00:01:55 UTC: 26 | 27 | for epoch, rslt in [(pre_prenumbra_start_et, 0), (prenumbra_start_et, 1), (prenumbra_end_et, 1), (umbra_et, 3)]: 28 | # Compute occultation 29 | occult = sp.occult(front, fshape, fframe, back, bshape, bframe, abcorr, obsrvr, epoch) 30 | assert occult == rslt, f"want {rslt} got {occult}" 31 | -------------------------------------------------------------------------------- /anise-py/.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | .pytest_cache/ 6 | *.py[cod] 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | .venv/ 14 | env/ 15 | bin/ 16 | build/ 17 | develop-eggs/ 18 | dist/ 19 | eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | include/ 26 | man/ 27 | venv/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | .ruff_cache 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | pip-selfcheck.json 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | 46 | # Translations 47 | *.mo 48 | 49 | # Mr Developer 50 | .mr.developer.cfg 51 | .project 52 | .pydevproject 53 | 54 | # Rope 55 | .ropeproject 56 | 57 | # Django stuff: 58 | *.log 59 | *.pot 60 | 61 | .DS_Store 62 | 63 | # Sphinx documentation 64 | docs/_build/ 65 | 66 | # PyCharm 67 | .idea/ 68 | 69 | # VSCode 70 | .vscode/ 71 | 72 | # Pyenv 73 | .python-version 74 | 75 | .ipynb_checkpoints.lka 76 | demo.py 77 | *.lka 78 | -------------------------------------------------------------------------------- /.github/actions/setup-anise/action.yml: -------------------------------------------------------------------------------- 1 | name: "Setup Anise Environment" 2 | description: "Installs Rust, CSPICE, and downloads test data with caching" 3 | inputs: 4 | components: 5 | description: "Rust components to install" 6 | required: false 7 | default: "" 8 | 9 | runs: 10 | using: "composite" 11 | steps: 12 | - name: Install stable toolchain 13 | uses: dtolnay/rust-toolchain@stable 14 | with: 15 | components: ${{ inputs.components }} 16 | 17 | - name: Install System Dependencies 18 | shell: bash 19 | run: | 20 | sudo apt-get update 21 | sudo apt-get install -y tcsh 22 | 23 | - name: Install CSPICE 24 | shell: bash 25 | run: sh dev-env-setup.sh 26 | 27 | - name: Cache Test Data 28 | id: cache-data 29 | uses: actions/cache@v3 30 | with: 31 | path: data 32 | # Update the hash if the download script changes 33 | key: test-data-${{ hashFiles('download_test_data.sh') }} 34 | 35 | - name: Download Data 36 | if: steps.cache-data.outputs.cache-hit != 'true' 37 | shell: bash 38 | run: sh download_test_data.sh 39 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/validation/type03_chebyshev_jpl_de.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use super::{compare::*, validate::Validation}; 12 | 13 | #[ignore = "Requires Rust SPICE -- must be executed serially"] 14 | #[test] 15 | fn validate_jplde_de440_type3_no_aberration() { 16 | let file_name = "spk-type3-validation-de440".to_string(); 17 | let comparator = CompareEphem::new( 18 | vec!["../data/de440_type3.bsp".to_string()], 19 | file_name.clone(), 20 | 1_000, 21 | None, 22 | ); 23 | 24 | let err_count = comparator.run(); 25 | 26 | assert_eq!(err_count, 0, "None of the queries should fail!"); 27 | 28 | let validator = Validation { 29 | file_name, 30 | ..Default::default() 31 | }; 32 | 33 | validator.validate(); 34 | } 35 | -------------------------------------------------------------------------------- /data/example_meta.dhall: -------------------------------------------------------------------------------- 1 | -- Example Dhall meta "kernel" 2 | let MetaFile 3 | : Type 4 | = { uri : Text, crc32 : Optional Natural } 5 | 6 | let NyxAsset 7 | : Text -> Text 8 | = \(file : Text) -> "http://public-data.nyxspace.com/anise/v0.7/${file}" 9 | 10 | let JplAsset 11 | : Text -> Text 12 | = \(file : Text) -> 13 | "https://naif.jpl.nasa.gov/pub/naif/generic_kernels/${file}" 14 | 15 | let buildNyxAsset 16 | : Text -> MetaFile 17 | = \(file : Text) -> 18 | let crc32 = None Natural 19 | 20 | let uri 21 | : Text 22 | = NyxAsset file 23 | 24 | let thisAsset 25 | : MetaFile 26 | = { uri, crc32 } 27 | 28 | in thisAsset 29 | 30 | let buildJplAsset 31 | : Text -> MetaFile 32 | = \(file : Text) -> 33 | let crc32 = None Natural 34 | 35 | let uri 36 | : Text 37 | = JplAsset file 38 | 39 | let thisAsset 40 | : MetaFile 41 | = { uri, crc32 } 42 | 43 | in thisAsset 44 | 45 | in { files = 46 | [ buildNyxAsset "de440s.bsp" 47 | , buildNyxAsset "pck08.pca" 48 | , buildJplAsset "pck/earth_latest_high_prec.bpc" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /anise-py/src/astro.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use anise::astro::{AzElRange, Location, Occultation, TerrainMask}; 12 | use anise::frames::FrameUid; 13 | use anise::structure::planetocentric::ellipsoid::Ellipsoid; 14 | use pyo3::prelude::*; 15 | 16 | use anise::astro::orbit::Orbit; 17 | use anise::frames::Frame; 18 | use pyo3::wrap_pymodule; 19 | 20 | #[pymodule] 21 | pub(crate) fn astro(_py: Python, sm: &Bound<'_, PyModule>) -> PyResult<()> { 22 | sm.add_class::()?; 23 | sm.add_class::()?; 24 | sm.add_class::()?; 25 | sm.add_class::()?; 26 | sm.add_class::()?; 27 | sm.add_class::()?; 28 | sm.add_class::()?; 29 | sm.add_class::()?; 30 | 31 | // Also add the constants as a submodule to astro for backward compatibility 32 | sm.add_wrapped(wrap_pymodule!(crate::constants::constants))?; 33 | 34 | Ok(()) 35 | } 36 | -------------------------------------------------------------------------------- /anise-gui/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anise-gui" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | edition = { workspace = true } 6 | homepage = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | description = "A graphical user interface for ANISE" 10 | build = "build.rs" 11 | 12 | [dependencies] 13 | anise = { workspace = true } 14 | hifitime = { workspace = true } 15 | log = { workspace = true } 16 | bytes = { workspace = true } 17 | pretty_env_logger = { workspace = true } 18 | eframe = { version = "0.33" } 19 | egui = { version = "0.33" } 20 | egui_extras = { version = "0.33", features = ["datepicker", "http", "image"] } 21 | rfd = { version = "0.16.0" } 22 | egui_logger = "0.9.0" 23 | 24 | [target.'cfg(target_arch = "wasm32")'.dependencies] 25 | wasm-bindgen-futures = "0.4" 26 | poll-promise = { version = "0.3.0", features = ["web"] } 27 | 28 | 29 | [target.'cfg(windows)'.build-dependencies] 30 | tauri-winres = "0.3" 31 | 32 | [package.metadata.tauri-winres] 33 | FileDescription = "Inspect SPICE SPK and PCK binary files" 34 | FileVersion = "0.4" 35 | InternalName = "ANISE-GUI.EXE" 36 | OriginalFilename = "ANISE-GUI.EXE" 37 | ProductName = "ANISE" 38 | ProductVersion = "0.4" 39 | LegalCopyright = "Copyright (C) 2021-onward Christopher Rabotin" 40 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | 2 | # Summary 3 | 4 | **Summarize the proposed changes** 5 | 6 | ## Architectural Changes 7 | 8 | 9 | 10 | No change 11 | 12 | ## New Features 13 | 14 | 15 | 16 | No change 17 | 18 | ## Improvements 19 | 20 | 21 | 22 | No change 23 | 24 | ## Bug Fixes 25 | 26 | 27 | 28 | No change 29 | 30 | ## Testing and validation 31 | 32 | 33 | 34 | **Detail the changes in tests, including new tests and validations** 35 | 36 | ## Documentation 37 | 38 | 39 | 40 | This PR does not primarily deal with documentation changes. 41 | 42 | -------------------------------------------------------------------------------- /anise/src/naif/daf/summary_record.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; 12 | 13 | use super::NAIFRecord; 14 | 15 | /// DAF Summary record is stored a 64-bit floats, but these are actually integers. 16 | #[derive(IntoBytes, Clone, Copy, Debug, Default, FromBytes, KnownLayout, Immutable)] 17 | #[repr(C)] 18 | pub struct SummaryRecord { 19 | next_record: f64, 20 | prev_record: f64, 21 | num_summaries: f64, 22 | } 23 | 24 | impl NAIFRecord for SummaryRecord {} 25 | 26 | impl SummaryRecord { 27 | pub fn next_record(&self) -> usize { 28 | self.next_record as usize 29 | } 30 | 31 | pub fn prev_record(&self) -> usize { 32 | self.prev_record as usize 33 | } 34 | 35 | pub fn num_summaries(&self) -> usize { 36 | self.num_summaries as usize 37 | } 38 | 39 | pub fn is_final_record(&self) -> bool { 40 | self.next_record() == 0 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /anise/benches/iai_jpl_ephemeris.rs: -------------------------------------------------------------------------------- 1 | use anise::{ 2 | constants::frames::{EARTH_J2000, MOON_J2000}, 3 | file2heap, 4 | prelude::*, 5 | }; 6 | use iai_callgrind::{library_benchmark, library_benchmark_group, main}; 7 | use std::hint::black_box; 8 | 9 | #[library_benchmark] 10 | fn benchmark_spice_single_hop_type2_cheby() { 11 | let epoch = Epoch::from_gregorian_at_noon(2025, 5, 25, TimeScale::ET); 12 | 13 | // SPICE load 14 | spice::furnsh("../data/de440s.bsp"); 15 | 16 | black_box(spice::spkezr( 17 | "EARTH", 18 | epoch.to_et_seconds(), 19 | "J2000", 20 | "NONE", 21 | "MOON", 22 | )); 23 | 24 | spice::unload("../data/de440s.bsp"); 25 | } 26 | 27 | #[library_benchmark] 28 | fn benchmark_anise_single_hop_type2_cheby() { 29 | let epoch = Epoch::from_gregorian_at_noon(2025, 5, 25, TimeScale::ET); 30 | 31 | let path = "../data/de440s.bsp"; 32 | let buf = file2heap!(path).unwrap(); 33 | let spk = SPK::parse(buf).unwrap(); 34 | let ctx = Almanac::from_spk(spk); 35 | 36 | black_box( 37 | ctx.translate_geometric(EARTH_J2000, MOON_J2000, epoch) 38 | .unwrap(), 39 | ); 40 | } 41 | 42 | library_benchmark_group!(name = bench_jpl_ephem; benchmarks = benchmark_anise_single_hop_type2_cheby, benchmark_spice_single_hop_type2_cheby); 43 | main!(library_benchmark_groups = bench_jpl_ephem); 44 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/documentation.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Documentation 3 | about: Recommend an update to the documentation 4 | title: '' 5 | labels: 'Documentation' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Description 11 | 12 | Please describe the issue or enhancement you want to report. Provide as much detail as possible to help us understand and address it. 13 | 14 | ## Documentation type 15 | 16 | Tell us which of the four Diátaxis documentation types this issue or enhancement relates to: 17 | 18 | - Tutorial 19 | - How-to guide 20 | - Technical reference / Math Spec 21 | - Explanation 22 | 23 | ## Affected area(s) 24 | 25 | For the selected documentation type, tell us what specific area(s) this issue relates to. Provide titles, page numbers, URLs or other locators. 26 | 27 | ## Expected or desired behavior 28 | 29 | Tell us what you expected to see in the documentation, or how you think it could be improved. For enhancements, describe the improvement you think could be made. 30 | 31 | ## Additional context 32 | 33 | Provide any other context or screenshots that would help us understand the issue or enhancement. 34 | 35 | ## Possible solutions (optional) 36 | 37 | If you have any suggestions for how to address the issue or implement the enhancement, provide them here. We appreciate any insights you have! 38 | 39 | ## Who should review this? 40 | 41 | Tag any individuals, teams, or roles that would likely need to review or address this issue for the specified documentation type. 42 | 43 | We will do our best to direct this to the appropriate people. Thank you for your feedback! 44 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/validation/type09_lagrange.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use super::{compare::*, validate::Validation}; 12 | use anise::almanac::metaload::MetaFile; 13 | use std::env; 14 | 15 | #[ignore = "Requires Rust SPICE -- must be executed serially"] 16 | #[test] 17 | fn validate_lagrange_type9_with_varying_segment_sizes() { 18 | if let Err(_) = env::var("LAGRANGE_BSP") { 19 | // Skip this test if the env var is not defined. 20 | return; 21 | } 22 | 23 | let mut lagrange_meta = MetaFile { 24 | uri: "http://public-data.nyxspace.com/anise/ci/env:LAGRANGE_BSP".to_string(), 25 | crc32: None, 26 | }; 27 | lagrange_meta.process(true).unwrap(); 28 | 29 | let file_name = "spk-type9-validation-variable-seg-size".to_string(); 30 | let comparator = CompareEphem::new(vec![lagrange_meta.uri], file_name.clone(), 10_000, None); 31 | 32 | let err_count = comparator.run(); 33 | 34 | assert_eq!(err_count, 0, "None of the queries should fail!"); 35 | 36 | let validator = Validation { 37 | file_name, 38 | max_q75_err: 5e-9, 39 | max_q99_err: 2e-7, 40 | max_abs_err: 0.05, 41 | }; 42 | 43 | validator.validate(); 44 | } 45 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "type": "cargo", 6 | "command": "test", 7 | "args": [ 8 | "validate_bpc_", 9 | "--", 10 | "--nocapture", 11 | "--include-ignored", 12 | "--test-threads", 13 | "1" 14 | ], 15 | "problemMatcher": [ 16 | "$rustc" 17 | ], 18 | "group": "none", 19 | "label": "ANISE: BPC validation" 20 | }, 21 | { 22 | "type": "cargo", 23 | "command": "test", 24 | "args": [ 25 | "validate_hermite_type13_", 26 | "--features", 27 | "spkezr_validation", 28 | "--release", 29 | "--", 30 | "--nocapture", 31 | "--include-ignored", 32 | "--test-threads", 33 | "1" 34 | ], 35 | "problemMatcher": [ 36 | "$rustc" 37 | ], 38 | "group": "none", 39 | "label": "ANISE: SPK Hermite validation" 40 | }, 41 | { 42 | "type": "cargo", 43 | "command": "test", 44 | "args": [ 45 | "validate_jplde", 46 | "--features", 47 | "spkezr_validation", 48 | "--release", 49 | "--", 50 | "--nocapture", 51 | "--include-ignored", 52 | "--test-threads", 53 | "1" 54 | ], 55 | "problemMatcher": [ 56 | "$rustc" 57 | ], 58 | "group": "none", 59 | "label": "ANISE: SPK Chebyshev validation" 60 | }, 61 | { 62 | "type": "cargo", 63 | "command": "build", 64 | "args": [ 65 | "--bin", 66 | "anise-gui", 67 | "--features", 68 | "gui", 69 | ], 70 | "problemMatcher": [ 71 | "$rustc" 72 | ], 73 | "group": { 74 | "kind": "build", 75 | "isDefault": true 76 | }, 77 | "label": "ANISE: Build GUI" 78 | } 79 | ] 80 | } -------------------------------------------------------------------------------- /anise/benches/iai_spacecraft_ephemeris.rs: -------------------------------------------------------------------------------- 1 | use anise::{constants::frames::EARTH_J2000, file2heap, prelude::*}; 2 | use iai_callgrind::{library_benchmark, library_benchmark_group, main}; 3 | use std::hint::black_box; 4 | 5 | #[library_benchmark] 6 | fn benchmark_spice_single_hop_type13_hermite() { 7 | let epoch = Epoch::from_gregorian_hms(2023, 12, 15, 14, 0, 0, TimeScale::UTC); 8 | 9 | // SPICE load 10 | spice::furnsh("../data/de440s.bsp"); 11 | spice::furnsh("../data/lro.bsp"); 12 | 13 | black_box(spice::spkezr( 14 | "-85", 15 | epoch.to_et_seconds(), 16 | "J2000", 17 | "NONE", 18 | "EARTH", 19 | )); 20 | 21 | spice::unload("../data/lro.bsp"); 22 | spice::unload("../data/de440s.bsp"); 23 | } 24 | 25 | #[library_benchmark] 26 | fn benchmark_anise_single_hop_type13_hermite() { 27 | let epoch = Epoch::from_gregorian_hms(2023, 12, 15, 14, 0, 0, TimeScale::UTC); 28 | 29 | let path = "../data/de440s.bsp"; 30 | let buf = file2heap!(path).unwrap(); 31 | let spk = SPK::parse(buf).unwrap(); 32 | 33 | let buf = file2heap!("../data/lro.bsp").unwrap(); 34 | let spacecraft = SPK::parse(buf).unwrap(); 35 | 36 | let ctx = Almanac::from_spk(spk).with_spk(spacecraft); 37 | 38 | let my_sc_j2k = Frame::from_ephem_j2000(-85); 39 | 40 | black_box( 41 | ctx.translate_geometric(my_sc_j2k, EARTH_J2000, epoch) 42 | .unwrap(), 43 | ); 44 | } 45 | 46 | library_benchmark_group!(name = bench_spacecraft_ephem; benchmarks = benchmark_anise_single_hop_type13_hermite, benchmark_spice_single_hop_type13_hermite); 47 | main!(library_benchmark_groups = bench_spacecraft_ephem); 48 | -------------------------------------------------------------------------------- /anise-gui/src/main.rs: -------------------------------------------------------------------------------- 1 | #![windows_subsystem = "windows"] 2 | #[allow(dead_code)] 3 | const LOG_VAR: &str = "ANISE_LOG"; 4 | 5 | mod ui; 6 | use ui::UiApp; 7 | 8 | mod bpc; 9 | mod epa; 10 | mod pca; 11 | mod spk; 12 | 13 | #[cfg(not(target_arch = "wasm32"))] 14 | fn main() { 15 | use std::env::{set_var, var}; 16 | 17 | if var(LOG_VAR).is_err() { 18 | set_var(LOG_VAR, "INFO"); 19 | } 20 | 21 | // Initialize the logger 22 | egui_logger::builder() 23 | .init() 24 | .expect("Error initializing logger"); 25 | 26 | let opts = eframe::NativeOptions { 27 | viewport: egui::ViewportBuilder::default() 28 | .with_inner_size([1024.0, 640.0]) 29 | .with_icon( 30 | eframe::icon_data::from_png_bytes(&include_bytes!("../icon-256.png")[..]).unwrap(), 31 | ), 32 | ..Default::default() 33 | }; 34 | 35 | let _ = eframe::run_native( 36 | "ANISE by Nyx Space", 37 | opts, 38 | Box::new(|cc| Ok(Box::new(UiApp::new(cc)))), 39 | ); 40 | } 41 | 42 | // Entrypoint for WebAssembly 43 | #[cfg(target_arch = "wasm32")] 44 | fn main() { 45 | use log::info; 46 | 47 | eframe::WebLogger::init(log::LevelFilter::Debug).ok(); 48 | let web_options = eframe::WebOptions::default(); 49 | 50 | info!("Starting ANISE in WebAssembly mode"); 51 | wasm_bindgen_futures::spawn_local(async { 52 | eframe::WebRunner::new() 53 | .start( 54 | "anise_canvas", 55 | web_options, 56 | Box::new(|cc| Ok(Box::new(UiApp::new(cc)))), 57 | ) 58 | .await 59 | .expect("failed to start eframe"); 60 | }); 61 | } 62 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["anise", "anise-cli", "anise-gui", "anise-py", "anise/fuzz"] 4 | 5 | [workspace.package] 6 | version = "0.9.0" 7 | edition = "2021" 8 | authors = ["Christopher Rabotin "] 9 | description = "ANISE provides a toolkit and files for Attitude, Navigation, Instrument, Spacecraft, and Ephemeris data. It's a modern replacement of NAIF SPICE file." 10 | homepage = "https://nyxspace.com/" 11 | documentation = "https://docs.rs/anise/" 12 | repository = "https://github.com/nyx-space/anise" 13 | keywords = ["attitude", "navigation", "instrument", "spacecraft", "ephemeris"] 14 | categories = ["science", "simulation"] 15 | readme = "README.md" 16 | license = "MPL-2.0" 17 | exclude = [ 18 | "cspice*", 19 | "data", 20 | "analysis", 21 | ".vscode", 22 | ".github", 23 | ".venv", 24 | ".vscode", 25 | "*.sh", 26 | "*.png" 27 | ] 28 | 29 | [workspace.dependencies] 30 | hifitime = "4.2.3" 31 | memmap2 = "0.9.4" 32 | crc32fast = "1.4.2" 33 | der = { version = "0.7.8", features = ["derive", "alloc", "real"] } 34 | log = "0.4" 35 | pretty_env_logger = "0.5" 36 | tabled = "=0.20" 37 | nalgebra = { version = "0.34", default-features = true, features = [ 38 | "serde-serialize", 39 | ] } 40 | zerocopy = { version = "0.8.0", features = ["derive"] } 41 | bytes = "1.6.0" 42 | snafu = { version = "0.8.0", features = ["backtrace"] } 43 | rstest = "0.26.1" 44 | pyo3 = { version = "0.27", features = ["multiple-pymethods"] } 45 | pyo3-log = "0.13" 46 | numpy = "0.27" 47 | ndarray = ">= 0.17, < 0.18" 48 | rayon = "1.10.0" 49 | 50 | anise = { path = "anise", default-features = false } 51 | 52 | [profile.bench] 53 | debug = true 54 | 55 | [profile.release] 56 | codegen-units = 1 57 | lto = "thin" 58 | -------------------------------------------------------------------------------- /anise/src/structure/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | /** 12 | * This module only contains the serialization and deserialization components of ANISE. 13 | * All other computations are at a higher level module. 14 | */ 15 | pub mod dataset; 16 | pub mod location; 17 | pub mod lookuptable; 18 | pub mod metadata; 19 | pub mod planetocentric; 20 | pub mod semver; 21 | pub mod spacecraft; 22 | 23 | use location::Location; 24 | 25 | use self::{ 26 | dataset::DataSet, planetocentric::PlanetaryData, semver::Semver, spacecraft::SpacecraftData, 27 | }; 28 | use crate::math::rotation::Quaternion; 29 | 30 | /// The current version of ANISE 31 | pub const ANISE_VERSION: Semver = Semver { 32 | major: 0, 33 | minor: 7, 34 | patch: 0, 35 | }; 36 | 37 | /// Spacecraft Data Set allow mapping an ID and/or name to spacecraft data, optionally including mass, drag, SRP, an inertia information 38 | pub type SpacecraftDataSet = DataSet; 39 | /// Planetary Data Set allow mapping an ID and/or name to planetary data, optionally including shape information and rotation information 40 | pub type PlanetaryDataSet = DataSet; 41 | /// Euler Parameter Data Set allow mapping an ID and/or name to a time invariant Quaternion 42 | pub type EulerParameterDataSet = DataSet; 43 | /// Location Data Set allow mapping an ID and/or name to a Location. 44 | pub type LocationDataSet = DataSet; 45 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/validation/type01_modified_diff.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use super::{compare::*, validate::Validation}; 12 | 13 | #[ignore = "Requires Rust SPICE -- must be executed serially"] 14 | #[test] 15 | fn validate_modified_diff_type01_mro() { 16 | let file_name = "spk-type01-validation-mod-diff".to_string(); 17 | let comparator = CompareEphem::new( 18 | vec!["../data/mro.bsp".to_string()], 19 | file_name.clone(), 20 | 10_000, 21 | None, 22 | ); 23 | 24 | let err_count = comparator.run(); 25 | 26 | assert_eq!(err_count, 0, "None of the queries should fail!"); 27 | 28 | // IMPORTANT 29 | // THE VALIDATION SHOWS ITS GREATEST ERROR at 810652114.2299933 ET. 30 | // HOWEVER, THIS ERROR IS DUE TO AN ACCUMULATION OF MINISCULE ERRORS THE TRANSLATIONS IN 31 | // MULTIPLE HOPS BETWEEN OBJECTS AS DEMONSTRATED IN THE TEST `spk1_highest_error` WHERE 32 | // THE TRANSLATION TO THE PARENT FRAME IS STRICTLY ZERO TO MACHINE PRECISION. 33 | // I'VE SPEND 10 DAYS DEBUGGING THIS UNTIL I ADDED DEBUG STATEMENTS IN CSPICE ITSELF 34 | // ONLY TO NOTICE THAT MY IMPLEMENTATION WAS INDEED CORRECT. 35 | 36 | let validator = Validation { 37 | file_name, 38 | max_q75_err: 3e-6, 39 | max_q99_err: 29.0, 40 | max_abs_err: 2.22e+3, 41 | ..Default::default() 42 | }; 43 | 44 | validator.validate(); 45 | } 46 | -------------------------------------------------------------------------------- /anise/benches/crit_bpc_rotation.rs: -------------------------------------------------------------------------------- 1 | use anise::{constants::orientations::ITRF93, prelude::*}; 2 | use criterion::{criterion_group, criterion_main, Criterion}; 3 | use std::hint::black_box; 4 | 5 | const NUM_QUERIES_PER_PAIR: f64 = 100.0; 6 | 7 | fn benchmark_spice_single_hop_type2_cheby(time_it: TimeSeries) { 8 | for epoch in time_it { 9 | black_box(spice::pxform( 10 | "ECLIPJ2000", 11 | "ITRF93", 12 | epoch.to_tdb_seconds(), 13 | )); 14 | } 15 | } 16 | 17 | fn benchmark_anise_single_hop_type2_cheby(ctx: &Almanac, time_it: TimeSeries) { 18 | for epoch in time_it { 19 | black_box( 20 | ctx.rotation_to_parent(Frame::from_orient_ssb(ITRF93), epoch) 21 | .unwrap(), 22 | ); 23 | } 24 | } 25 | 26 | pub fn criterion_benchmark(c: &mut Criterion) { 27 | let start_epoch = Epoch::from_gregorian_at_noon(2012, 1, 1, TimeScale::ET); 28 | let end_epoch = Epoch::from_gregorian_at_noon(2021, 1, 1, TimeScale::ET); 29 | let time_step = ((end_epoch - start_epoch).to_seconds() / NUM_QUERIES_PER_PAIR).seconds(); 30 | let time_it = TimeSeries::exclusive(start_epoch, end_epoch - time_step, time_step); 31 | 32 | let pck = "../data/earth_latest_high_prec.bpc"; 33 | spice::furnsh(pck); 34 | let bpc = BPC::load(pck).unwrap(); 35 | let almanac = Almanac::from_bpc(bpc); 36 | 37 | c.bench_function("ANISE DAF/BPC single hop to parent", |b| { 38 | b.iter(|| benchmark_anise_single_hop_type2_cheby(&almanac, time_it.clone())) 39 | }); 40 | 41 | c.bench_function("SPICE DAF/BPC single hop to parent", |b| { 42 | b.iter(|| benchmark_spice_single_hop_type2_cheby(time_it.clone())) 43 | }); 44 | } 45 | 46 | criterion_group!(bpc, criterion_benchmark); 47 | criterion_main!(bpc); 48 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/maintenance.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Maintenance 3 | about: This general maintenance template aims to capture all the details needed to understand, prioritize and complete various types of maintenance work that may come up. 4 | title: '' 5 | labels: 'Documentation' 6 | assignees: '' 7 | 8 | --- 9 | 10 | ## Maintenance task description 11 | 12 | Describe the general maintenance task needing to be addressed. For example: 13 | 14 | - Update third-party dependencies to latest versions 15 | - Review and update configs 16 | - Remove unused code/files 17 | - etc. 18 | 19 | ## Scope of work 20 | 21 | Provide details on the full scope of the maintenance task. For example: 22 | 23 | - List all dependencies needing updates (package names and current vs latest versions) 24 | - What config files need to be reviewed and what updates are required 25 | - What unused code/files have been identified to remove? 26 | 27 | Attach or link to any dependency reports, linting reports or other analyses that can help provide an overview of the work required. 28 | 29 | ## Priority 30 | 31 | Assign an overall priority level for completing this maintenance work. 32 | 33 | - High: Address immediately - impacts functionality 34 | - Medium: Address within 2 weeks - non-critical updates needed 35 | - Low: Address within 1 month - minor patches/fixes, remove tech debt 36 | 37 | ## Possible solutions (optional) 38 | 39 | If there are any tools, scripts or processes that could help automate or simplify this maintenance work, describe them here. We're open to any suggestions that could help improve our workflows! 40 | 41 | We appreciate you helping to keep our project clean and dependencies up to date! Please provide as much detail as possible about the work required so we can properly prioritize and plan completion of this task. -------------------------------------------------------------------------------- /anise/src/naif/daf/datatypes/posvel.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use core::fmt; 12 | use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; 13 | 14 | use crate::{ 15 | math::Vector3, 16 | naif::daf::{NAIFDataRecord, NAIFRecord}, 17 | }; 18 | 19 | #[derive(Copy, Clone, Default, IntoBytes, FromBytes, KnownLayout, Immutable, Debug)] 20 | #[repr(C)] 21 | pub struct PositionVelocityRecord { 22 | pub x_km: f64, 23 | pub y_km: f64, 24 | pub z_km: f64, 25 | pub vx_km_s: f64, 26 | pub vy_km_s: f64, 27 | pub vz_km_s: f64, 28 | } 29 | 30 | impl PositionVelocityRecord { 31 | pub fn to_pos_vel(&self) -> (Vector3, Vector3) { 32 | ( 33 | Vector3::new(self.x_km, self.y_km, self.z_km), 34 | Vector3::new(self.vx_km_s, self.vy_km_s, self.vz_km_s), 35 | ) 36 | } 37 | } 38 | 39 | impl fmt::Display for PositionVelocityRecord { 40 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 41 | write!(f, "{self:?}") 42 | } 43 | } 44 | 45 | impl NAIFRecord for PositionVelocityRecord {} 46 | 47 | impl<'a> NAIFDataRecord<'a> for PositionVelocityRecord { 48 | fn from_slice_f64(slice: &'a [f64]) -> Self { 49 | Self { 50 | x_km: slice[0], 51 | y_km: slice[1], 52 | z_km: slice[2], 53 | vx_km_s: slice[3], 54 | vy_km_s: slice[4], 55 | vz_km_s: slice[5], 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /anise-gui/README.md: -------------------------------------------------------------------------------- 1 | # ANISE GUI 2 | 3 | ANISE provides a graphical interface to inspect SPK, BPC, and PCA (Planetary Constant ANISE) files. Allows you to check the start/end times of the segments (shown in whichever time scale you want, including UNIX UTC seconds). 4 | 5 | **Latest binaries:** 6 | 7 | When updates are published, they'll be announced in the [Discussions](https://github.com/nyx-space/anise/discussions). 8 | 9 | ## Demos 10 | 11 | Inspect an SPK file ([video link](http://public-data.nyxspace.com/anise/demo/ANISE-SPK.webm)): 12 | 13 | ![Inspect an SPK file](http://public-data.nyxspace.com/anise/demo/ANISE-SPK.gif) 14 | 15 | Inspect an Binary PCK file (BPC) ([video link](http://public-data.nyxspace.com/anise/demo/ANISE-BPC.webm)): 16 | 17 | ![Inspect an SPK file](http://public-data.nyxspace.com/anise/demo/ANISE-BPC.gif) 18 | 19 | ## Building 20 | 21 | ### Native 22 | 23 | To build the GUI natively to your platform, from inside the `anise-gui` subdirectory, run `cargo build`. You'll need to have Rust installed. 24 | 25 | To run it, use `cargo run` instead of `cargo build`. Keep in mind that if building from a [distrobox](https://github.com/89luca89/distrobox), you will need to exit the distrobox to execute the program. Rust does not require dynamic loading of libraries, so it should work on any platform without issue. 26 | 27 | ### Web Assembly (wasm) 28 | 29 | To build the GUI for web assembly, you will need to install [Trunk](https://trunkrs.dev/). 30 | 31 | 1. Install the required target with `rustup target add wasm32-unknown-unknown`. 32 | 1. Install Trunk with `cargo install --locked trunk`. 33 | 1. Run `trunk serve anise-gui/index.html` to build and serve on http://127.0.0.1:8080. Trunk will rebuild automatically if you edit the project. 34 | 35 | Note that running `trunk serve` without any arguments will cause an error because the index page is not in the root of the project. 36 | -------------------------------------------------------------------------------- /anise/tests/frames/format.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use anise::constants::frames::*; 12 | 13 | /// Tests the ephemeris computations from the de438s which don't require any frame transformation. 14 | #[test] 15 | fn format_frame() { 16 | assert_eq!(format!("{SSB_J2000}"), "Solar System Barycenter J2000"); 17 | 18 | assert_eq!(format!("{SUN_J2000}"), "Sun J2000"); 19 | 20 | assert_eq!(format!("{MERCURY_J2000}"), "Mercury Barycenter J2000"); 21 | 22 | assert_eq!(format!("{VENUS_J2000}"), "Venus Barycenter J2000"); 23 | 24 | assert_eq!( 25 | format!("{EARTH_MOON_BARYCENTER_J2000}"), 26 | "Earth-Moon Barycenter J2000" 27 | ); 28 | 29 | assert_eq!(format!("{EARTH_J2000}"), "Earth J2000"); 30 | 31 | assert_eq!(format!("{MOON_J2000}"), "Moon J2000"); 32 | 33 | assert_eq!(format!("{MARS_J2000}"), "Mars J2000"); 34 | 35 | assert_eq!(format!("{MARS_BARYCENTER_J2000}"), "Mars Barycenter J2000"); 36 | 37 | assert_eq!( 38 | format!("{JUPITER_BARYCENTER_J2000}"), 39 | "Jupiter Barycenter J2000" 40 | ); 41 | 42 | assert_eq!( 43 | format!("{SATURN_BARYCENTER_J2000}"), 44 | "Saturn Barycenter J2000" 45 | ); 46 | 47 | assert_eq!( 48 | format!("{URANUS_BARYCENTER_J2000}"), 49 | "Uranus Barycenter J2000" 50 | ); 51 | 52 | assert_eq!( 53 | format!("{NEPTUNE_BARYCENTER_J2000}"), 54 | "Neptune Barycenter J2000" 55 | ); 56 | 57 | assert_eq!( 58 | format!("{PLUTO_BARYCENTER_J2000}"), 59 | "Pluto Barycenter J2000" 60 | ); 61 | } 62 | -------------------------------------------------------------------------------- /anise/benches/crit_jpl_ephemerides.rs: -------------------------------------------------------------------------------- 1 | use anise::{ 2 | constants::frames::{EARTH_J2000, MOON_J2000}, 3 | file2heap, 4 | prelude::*, 5 | }; 6 | use criterion::{criterion_group, criterion_main, Criterion}; 7 | use std::hint::black_box; 8 | 9 | const NUM_QUERIES_PER_PAIR: f64 = 100.0; 10 | 11 | fn benchmark_spice_single_hop_type2_cheby(time_it: TimeSeries) { 12 | for epoch in time_it { 13 | black_box(spice::spkezr( 14 | "EARTH", 15 | epoch.to_et_seconds(), 16 | "J2000", 17 | "NONE", 18 | "MOON", 19 | )); 20 | } 21 | } 22 | 23 | fn benchmark_anise_single_hop_type2_cheby(ctx: &Almanac, time_it: TimeSeries) { 24 | for epoch in time_it { 25 | black_box( 26 | ctx.translate_geometric(EARTH_J2000, MOON_J2000, epoch) 27 | .unwrap(), 28 | ); 29 | } 30 | } 31 | 32 | pub fn criterion_benchmark(c: &mut Criterion) { 33 | let start_epoch = Epoch::from_gregorian_at_noon(1900, 1, 1, TimeScale::ET); 34 | let end_epoch = Epoch::from_gregorian_at_noon(2099, 1, 1, TimeScale::ET); 35 | let time_step = ((end_epoch - start_epoch).to_seconds() / NUM_QUERIES_PER_PAIR).seconds(); 36 | let time_it = TimeSeries::exclusive(start_epoch, end_epoch - time_step, time_step); 37 | 38 | // Load ANISE data 39 | let path = "../data/de440s.bsp"; 40 | let buf = file2heap!(path).unwrap(); 41 | let spk = SPK::parse(buf).unwrap(); 42 | let ctx = Almanac::from_spk(spk); 43 | 44 | // Load SPICE data 45 | spice::furnsh("../data/de440s.bsp"); 46 | 47 | c.bench_function("ANISE ephemerides single hop", |b| { 48 | b.iter(|| benchmark_anise_single_hop_type2_cheby(&ctx, time_it.clone())) 49 | }); 50 | 51 | c.bench_function("SPICE ephemerides single hop", |b| { 52 | b.iter(|| benchmark_spice_single_hop_type2_cheby(time_it.clone())) 53 | }); 54 | } 55 | 56 | criterion_group!(de440s, criterion_benchmark); 57 | criterion_main!(de440s); 58 | -------------------------------------------------------------------------------- /anise/tests/test_analysis/spk_validation_plots.py: -------------------------------------------------------------------------------- 1 | from glob import glob 2 | from os import environ 3 | from os.path import abspath, basename, dirname, join 4 | 5 | import pandas as pd 6 | import plotly.express as px 7 | 8 | 9 | def is_on_github_actions(): 10 | return ("CI" in environ and environ["CI"] and "GITHUB_RUN_ID" in environ) 11 | 12 | 13 | if __name__ == '__main__': 14 | 15 | target_folder = join(abspath(dirname(__file__)), '..', '..', '..', 'target') 16 | 17 | plotted_anything = False 18 | 19 | for filename in glob(f"{target_folder}/spk-type*.parquet"): 20 | 21 | # Load the parquet file 22 | df = pd.read_parquet(filename) 23 | 24 | name = basename(filename) 25 | 26 | for kind, columns in [("Position", ["X", "Y", "Z"]), 27 | ("Velocity", ["VX", "VY", "VZ"])]: 28 | 29 | print(f"== {kind} {name} ==") 30 | 31 | subset = df.loc[df.component.isin(columns)] 32 | 33 | print(subset.describe()) 34 | 35 | plt = px.scatter(subset, 36 | x='ET Epoch (s)', 37 | y='Absolute difference', 38 | color='source frame', 39 | title=f"Validation of {name} for {kind}") 40 | 41 | plt.write_html( 42 | f"{target_folder}/validation-plot-{kind}-{name}.html") 43 | if not is_on_github_actions(): 44 | plt.show() 45 | plotted_anything = True 46 | 47 | # Plot all components together 48 | plt = px.scatter(df, 49 | x='ET Epoch (s)', 50 | y='Absolute difference', 51 | color='component', 52 | title=f"Validation of {name} (overall)") 53 | plt.write_html(f"{target_folder}/validation-plot-{name}.html") 54 | if not is_on_github_actions(): 55 | plt.show() 56 | plotted_anything = True 57 | assert plotted_anything, "did not plot anything" 58 | -------------------------------------------------------------------------------- /anise/build.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "embed_ephem")] 2 | fn main() { 3 | // Download the files to embed at build time. 4 | use std::{ 5 | fs::{self, File}, 6 | io::Write, 7 | path::Path, 8 | time::Duration, 9 | }; 10 | 11 | let client: ureq::Agent = ureq::Agent::config_builder() 12 | .timeout_global(Some(Duration::from_secs(30))) 13 | .build() 14 | .into(); 15 | 16 | let embedded_files = [ 17 | ( 18 | "http://public-data.nyxspace.com/anise/v0.5/pck11.pca", 19 | format!("{}/../data/pck11.pca", env!("CARGO_MANIFEST_DIR")), 20 | ), 21 | ( 22 | "http://public-data.nyxspace.com/anise/de440s.bsp", 23 | format!("{}/../data/de440s.bsp", env!("CARGO_MANIFEST_DIR")), 24 | ), 25 | ]; 26 | 27 | let data_path = Path::new(&env!("CARGO_MANIFEST_DIR")).join("../data"); 28 | 29 | // Create the directory if it doesn't exist 30 | if !data_path.exists() { 31 | if fs::create_dir_all(&data_path).is_err() { 32 | eprintln!("EMBEDDED EPHEM UNAVAILABLE: failed to create directory {data_path:?}"); 33 | // Try nothing else. 34 | return; 35 | } 36 | } 37 | 38 | for (url, dest_path) in embedded_files { 39 | let mut resp = client 40 | .get(url) 41 | .call() 42 | .expect(&format!("could not download {url}")); 43 | 44 | let bytes = resp 45 | .body_mut() 46 | .with_config() 47 | .limit(1024 * 1024 * 200) // 200 MB limit 48 | .read_to_vec() 49 | .expect(&format!("could not read bytes from {url}")); 50 | 51 | let mut file = 52 | File::create(&dest_path).expect(&format!("could not create the data path {dest_path}")); 53 | file.write_all(&bytes) 54 | .expect(&format!("could not write asset data to {dest_path}")); 55 | } 56 | } 57 | 58 | #[cfg(not(feature = "embed_ephem"))] 59 | fn main() { 60 | // Nothing to do if we aren't embedded files. 61 | } 62 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/validation/type13_hermite.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use super::{compare::*, validate::Validation}; 12 | 13 | #[ignore = "Requires Rust SPICE -- must be executed serially"] 14 | #[test] 15 | fn validate_hermite_type13_from_gmat() { 16 | let file_name = "spk-type13-validation-even-seg-size".to_string(); 17 | let comparator = CompareEphem::new( 18 | vec!["../data/gmat-hermite.bsp".to_string()], 19 | file_name.clone(), 20 | 10_000, 21 | None, 22 | ); 23 | 24 | let err_count = comparator.run(); 25 | 26 | assert_eq!(err_count, 0, "None of the queries should fail!"); 27 | 28 | let validator = Validation { 29 | file_name, 30 | ..Default::default() 31 | }; 32 | 33 | validator.validate(); 34 | } 35 | 36 | #[ignore = "Requires Rust SPICE -- must be executed serially"] 37 | #[test] 38 | fn validate_hermite_type13_with_varying_segment_sizes() { 39 | // ISSUE: This file is corrupt, cf. https://github.com/nyx-space/anise/issues/262 40 | let file_name = "spk-type13-validation-variable-seg-size".to_string(); 41 | let comparator = CompareEphem::new( 42 | vec!["../data/variable-seg-size-hermite.bsp".to_string()], 43 | file_name.clone(), 44 | 10_000, 45 | None, 46 | ); 47 | 48 | let err_count = comparator.run(); 49 | 50 | assert_eq!(err_count, 0, "None of the queries should fail!"); 51 | 52 | // BUG: For variable sized Type 13, there is an error at the very end of the file. 53 | let validator = Validation { 54 | file_name, 55 | max_q75_err: 5e-9, 56 | max_q99_err: 2e-7, 57 | max_abs_err: 0.05, 58 | }; 59 | 60 | validator.validate(); 61 | } 62 | -------------------------------------------------------------------------------- /anise/src/naif/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | pub mod daf; 12 | 13 | pub mod kpl; 14 | pub mod pck; 15 | pub mod spk; 16 | 17 | pub mod pretty_print; 18 | 19 | use daf::DAF; 20 | use pck::BPCSummaryRecord; 21 | use spk::summary::SPKSummaryRecord; 22 | 23 | /// Spacecraft Planetary Kernel 24 | pub type SPK = DAF; 25 | /// Binary Planetary Constant 26 | pub type BPC = DAF; 27 | 28 | #[macro_export] 29 | macro_rules! parse_bytes_as { 30 | ($type:ident, $input:expr, $order:expr) => {{ 31 | let (int_bytes, _) = $input.split_at(std::mem::size_of::<$type>()); 32 | 33 | match $order { 34 | Endian::Little => $type::from_le_bytes(int_bytes.try_into().unwrap()), 35 | Endian::Big => $type::from_be_bytes(int_bytes.try_into().unwrap()), 36 | } 37 | }}; 38 | } 39 | 40 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 41 | pub enum Endian { 42 | Little, 43 | Big, 44 | } 45 | 46 | impl Endian { 47 | /// Returns the endianness of the platform we're running on for an f64. 48 | /// This isn't const because f64 comparisons cannot be const yet 49 | fn f64_native() -> Self { 50 | let truth: f64 = 0.12345678; 51 | if (f64::from_ne_bytes(truth.to_be_bytes()) - truth).abs() < f64::EPSILON { 52 | Self::Big 53 | } else { 54 | Self::Little 55 | } 56 | } 57 | 58 | /// Returns the endianness of the platform we're running on for an f64. 59 | const fn u64_native() -> Self { 60 | let truth: u32 = 0x12345678; 61 | if u32::from_ne_bytes(truth.to_be_bytes()) == truth { 62 | Self::Big 63 | } else { 64 | Self::Little 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /.github/workflows/gui.yaml: -------------------------------------------------------------------------------- 1 | name: ANISE GUI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - '*' 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | env: 13 | RUST_BACKTRACE: 1 14 | RUST_LOG: info 15 | 16 | # Source: https://github.com/emilk/egui/blob/23732be0e5b9a977afb08a2e7cb23c31955abe43/.github/workflows/rust.yml 17 | 18 | jobs: 19 | build-linux: 20 | name: Build Linux GUI 21 | runs-on: ubuntu-22.04 22 | steps: 23 | - uses: actions/checkout@v4 24 | 25 | - uses: dtolnay/rust-toolchain@stable 26 | 27 | - name: Install packages (Linux) 28 | if: runner.os == 'Linux' 29 | uses: awalsh128/cache-apt-pkgs-action@latest 30 | with: 31 | packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd 32 | version: 1.0 33 | execute_install_scripts: true 34 | 35 | - name: Build Linux executable 36 | run: cargo build --release --bin anise-gui --workspace --exclude anise-py 37 | 38 | - name: Save executable 39 | uses: actions/upload-artifact@v4 40 | with: 41 | name: anise-gui-linux 42 | path: target/release/anise-gui 43 | if-no-files-found: error 44 | 45 | windows: 46 | name: Build Windows GUI 47 | runs-on: windows-latest 48 | steps: 49 | - uses: actions/checkout@v4 50 | - uses: dtolnay/rust-toolchain@stable 51 | 52 | - name: Set up cargo cache 53 | uses: Swatinem/rust-cache@v2.7.7 54 | 55 | - name: Build Windows executable 56 | run: cargo build --release --bin anise-gui --workspace --exclude anise-py 57 | 58 | - name: Save executable 59 | uses: actions/upload-artifact@v4 60 | with: 61 | name: anise-gui-windows 62 | path: target\release\anise-gui.exe 63 | if-no-files-found: error -------------------------------------------------------------------------------- /anise/src/structure/dataset/datatype.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use der::{Decode, Encode, Reader, Writer}; 12 | 13 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 14 | #[repr(u8)] 15 | pub enum DataSetType { 16 | /// Used only if not encoding a dataset but some other structure 17 | NotApplicable, 18 | SpacecraftData, 19 | PlanetaryData, 20 | EulerParameterData, 21 | LocationData, 22 | } 23 | 24 | impl TryFrom for DataSetType { 25 | type Error = &'static str; 26 | 27 | fn try_from(val: u8) -> Result { 28 | match val { 29 | 0 => Ok(DataSetType::NotApplicable), 30 | 1 => Ok(DataSetType::SpacecraftData), 31 | 2 => Ok(DataSetType::PlanetaryData), 32 | 3 => Ok(DataSetType::EulerParameterData), 33 | 4 => Ok(DataSetType::LocationData), 34 | _ => Err("Invalid value for DataSetType"), 35 | } 36 | } 37 | } 38 | 39 | impl From for u8 { 40 | fn from(val: DataSetType) -> Self { 41 | val as u8 42 | } 43 | } 44 | 45 | impl Encode for DataSetType { 46 | fn encoded_len(&self) -> der::Result { 47 | (*self as u8).encoded_len() 48 | } 49 | 50 | fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> { 51 | (*self as u8).encode(encoder) 52 | } 53 | } 54 | 55 | impl<'a> Decode<'a> for DataSetType { 56 | fn decode>(decoder: &mut R) -> der::Result { 57 | let asu8: u8 = decoder.decode()?; 58 | DataSetType::try_from(asu8).map_err(|_| { 59 | der::Error::new( 60 | der::ErrorKind::Value { 61 | tag: der::Tag::Integer, 62 | }, 63 | der::Length::ONE, 64 | ) 65 | }) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /anise/src/math/angles.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use std::f64::consts::TAU; 12 | 13 | /// Returns the provided angle bounded between 0.0 and 360.0. 14 | /// 15 | /// This function takes an angle (in degrees) and normalizes it to the range [0, 360). 16 | /// If the angle is negative, it will be converted to a positive angle in the equivalent position. 17 | /// For example, an angle of -90 degrees will be converted to 270 degrees. 18 | /// 19 | /// # Arguments 20 | /// 21 | /// * `angle` - An angle in degrees. 22 | pub fn between_0_360(angle: f64) -> f64 { 23 | angle.rem_euclid(360.0) 24 | } 25 | 26 | /// Returns the provided angle bounded between 0.0 and 2 PI (TAU). 27 | /// 28 | /// This function takes an angle (in radians) and normalizes it to the range [0, TAU). 29 | /// 30 | /// # Arguments 31 | /// 32 | /// * `angle_rad` - An angle in radians. 33 | pub fn between_0_tau(angle_rad: f64) -> f64 { 34 | angle_rad.rem_euclid(TAU) 35 | } 36 | /// Returns the provided angle bounded between -180.0 and +180.0 37 | pub fn between_pm_180(angle: f64) -> f64 { 38 | between_pm_x(angle, 180.0) 39 | } 40 | 41 | /// Returns the provided angle bounded between -x and +x. 42 | /// 43 | /// This function takes an angle (in degrees or radians) and normalizes it to the range [-x, x). 44 | /// If the angle is outside this range, it will be converted to an equivalent angle within this range. 45 | /// For example, if x is 180, an angle of 270 degrees will be converted to -90 degrees. 46 | /// 47 | /// # Arguments 48 | /// 49 | /// * `angle` - An angle in degrees or radians. 50 | /// * `x` - The boundary for the angle normalization. 51 | pub fn between_pm_x(angle: f64, x: f64) -> f64 { 52 | let mut bounded = angle.rem_euclid(2.0 * x); 53 | if bounded >= x { 54 | bounded -= 2.0 * x; 55 | } 56 | bounded 57 | } 58 | -------------------------------------------------------------------------------- /anise/src/math/interpolation/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | mod chebyshev; 12 | mod hermite; 13 | mod lagrange; 14 | 15 | pub use chebyshev::{chebyshev_eval, chebyshev_eval_poly}; 16 | pub use hermite::hermite_eval; 17 | use hifitime::Epoch; 18 | pub use lagrange::lagrange_eval; 19 | use snafu::Snafu; 20 | 21 | use crate::errors::{DecodingError, MathError}; 22 | 23 | /// Defines the maximum degree for an interpolation. 24 | /// Until https://github.com/rust-lang/rust/issues/60551 , we cannot do operations on const generic, so we need some hack around it. 25 | pub(crate) const MAX_SAMPLES: usize = 32; 26 | 27 | #[derive(Copy, Clone, Debug, Snafu, PartialEq)] 28 | #[snafu(visibility(pub(crate)))] 29 | #[non_exhaustive] 30 | pub enum InterpolationError { 31 | #[snafu(display("decoding error during interpolation: {source}"))] 32 | InterpDecoding { 33 | #[snafu(backtrace)] 34 | source: DecodingError, 35 | }, 36 | #[snafu(display("math error during interpolation: {source}"))] 37 | InterpMath { 38 | #[snafu(backtrace)] 39 | source: MathError, 40 | }, 41 | #[snafu(display("spline valid from {start} to {end} but requested {req}"))] 42 | NoInterpolationData { 43 | req: Epoch, 44 | start: Epoch, 45 | end: Epoch, 46 | }, 47 | #[snafu(display("no interpolation data to {epoch}, but prior checks succeeded (check integrity of the data?)"))] 48 | MissingInterpolationData { epoch: Epoch }, 49 | #[snafu(display("interpolation data corrupted: {what}"))] 50 | CorruptedData { what: &'static str }, 51 | #[snafu(display("{op} is unsupported for {kind}"))] 52 | UnsupportedOperation { 53 | kind: &'static str, 54 | op: &'static str, 55 | }, 56 | #[snafu(display( 57 | "{dataset} is not yet supported -- https://github.com/nyx-space/anise/issues/{issue}" 58 | ))] 59 | UnimplementedType { issue: u32, dataset: &'static str }, 60 | } 61 | -------------------------------------------------------------------------------- /anise/src/math/units.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | use core::fmt::Display; 11 | 12 | /// Re-export hifitime's units as DurationUnit. 13 | pub use hifitime::Unit as TimeUnit; 14 | 15 | /// Defines the distance units supported by ANISE. This notably allows storing interpolation information from instruments to comets. 16 | #[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Default)] 17 | pub enum LengthUnit { 18 | Micrometer, 19 | Millimeter, 20 | Meter, 21 | #[default] 22 | Kilometer, 23 | Megameter, 24 | } 25 | 26 | impl LengthUnit { 27 | /// Returns the conversion factor of this distance unit to meters. 28 | /// E.g. To convert Self::Kilometers into Self::Meters, multiply by 1e-3. 29 | #[must_use] 30 | pub const fn to_meters(&self) -> f64 { 31 | match self { 32 | Self::Micrometer => 1e6, 33 | Self::Millimeter => 1e3, 34 | Self::Meter => 1.0, 35 | Self::Kilometer => 1e-3, 36 | Self::Megameter => 1e-6, 37 | } 38 | } 39 | 40 | /// Returns the conversion factor of this distance unit from meters. 41 | /// E.g. To convert Self::Kilometers into Self::Meters, multiply by 1e3. 42 | #[must_use] 43 | pub const fn from_meters(&self) -> f64 { 44 | match self { 45 | Self::Micrometer => 1e-6, 46 | Self::Millimeter => 1e-3, 47 | Self::Meter => 1.0, 48 | Self::Kilometer => 1e3, 49 | Self::Megameter => 1e6, 50 | } 51 | } 52 | } 53 | 54 | impl Display for LengthUnit { 55 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 56 | match self { 57 | Self::Micrometer => write!(f, "um"), 58 | Self::Millimeter => write!(f, "mm"), 59 | Self::Meter => write!(f, "m"), 60 | Self::Kilometer => write!(f, "km"), 61 | Self::Megameter => write!(f, "Mm"), 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /anise/src/orientations/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use hifitime::Epoch; 12 | use snafu::prelude::*; 13 | 14 | use crate::{ 15 | errors::PhysicsError, math::interpolation::InterpolationError, naif::daf::DAFError, 16 | prelude::FrameUid, structure::dataset::DataSetError, 17 | }; 18 | 19 | mod paths; 20 | mod rotate_to_parent; 21 | mod rotations; 22 | 23 | #[derive(Debug, Snafu, PartialEq)] 24 | #[snafu(visibility(pub(crate)))] 25 | #[non_exhaustive] 26 | pub enum OrientationError { 27 | #[snafu(display( 28 | "somehow you've entered code that should not be reachable, please file a bug." 29 | ))] 30 | Unreachable, 31 | #[snafu(display("could not {action} because {alias} is not loaded"))] 32 | AliasNotFound { alias: String, action: &'static str }, 33 | #[snafu(display( 34 | "Could not rotate from {from} to {to}: no common origin found at epoch {epoch}" 35 | ))] 36 | RotationOrigin { 37 | from: FrameUid, 38 | to: FrameUid, 39 | epoch: Epoch, 40 | }, 41 | #[snafu(display("no orientation data loaded (must call load_bpc or DataSet::from_bytes)"))] 42 | NoOrientationsLoaded, 43 | #[snafu(display("when {action} caused {source}"))] 44 | BPC { 45 | action: &'static str, 46 | #[snafu(backtrace)] 47 | source: DAFError, 48 | }, 49 | #[snafu(display("during an orientation operation: {source}"))] 50 | OrientationPhysics { 51 | #[snafu(backtrace)] 52 | source: PhysicsError, 53 | }, 54 | #[snafu(display("during an orientation interpolation {source}"))] 55 | OrientationInterpolation { 56 | #[snafu(backtrace)] 57 | source: InterpolationError, 58 | }, 59 | #[snafu(display("during an orientation query {source}"))] 60 | OrientationDataSet { 61 | #[snafu(backtrace)] 62 | source: DataSetError, 63 | }, 64 | #[snafu(display("unknown orientation ID associated with `{name}`"))] 65 | OrientationNameToId { name: String }, 66 | } 67 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to ANISE 2 | 3 | First off, thanks for considering contributing ANISE! :tada::+1: 4 | 5 | Your expertise in software development and astrodynamics is invaluable in fostering a vibrant community around this project. 6 | 7 | Please adhere to a fundamental rule throughout your contribution: since this is an open-source project accessible to the public, avoid mentioning any customer names in any commit message, unless explicit consent has been granted by the customer. 8 | 9 | ## Reporting issues 10 | 11 | **Encountered a bug in ANISE?** We appreciate your report! Clearly describe the expected outcome versus what ANISE actually produced. Ensuring ANISE is bug-free and thoroughly validated is crucial. 12 | 13 | To facilitate bug reproduction and resolution, kindly provide a straightforward example (like a main function with the necessary imports). 14 | 15 | ## Helping out with development 16 | 17 | We welcome all forms of assistance. Remember, ANISE primarily aims to offer key NAIF SPICE functionalities and additional features like trajectory analysis. For comprehensive astrodynamics computations, including mission design and orbit determination, refer to Nyx: . 18 | 19 | Explore our [issues list](https://github.com/nyx-space/anise/issues) and review the [project milestones](https://github.com/nyx-space/anise/milestones) for guidance on development priorities. 20 | 21 | **Intending to submit a pull request?** First, verify or create an issue corresponding to your proposed changes. Assign it to yourself or indicate your intention in the issue's description. For traceability, please name your branch including the issue number (following GitHub's suggested naming convention). 22 | 23 | ### Guidelines 24 | 25 | For comprehensive software development and testing guidelines, visit the Nyx QA: . This document is dynamic and intended for broad use—feel free to adapt it for your projects, whether personal or corporate. 26 | 27 | ### Self-review 28 | 29 | If you have writing privileges in the repository, consider performing a self-review. Push your changes, ensure tests pass, then take a break for a few hours. Return with a fresh perspective to objectively evaluate your work. Recognizing potential improvements post-submission is a normal part of the development process. 30 | 31 | **We're looking forward to your contribution!** -------------------------------------------------------------------------------- /anise/src/structure/semver.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | use core::fmt; 11 | use der::{asn1::OctetStringRef, Decode, Encode, Error, ErrorKind, Length, Reader, Writer}; 12 | 13 | /// Semantic versioning is used throughout ANISE 14 | /// It is encoded as a single octet string of 3 bytes of content (prependded by 1 one tag byte and 1 length byte) 15 | #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] 16 | pub struct Semver { 17 | pub major: u8, 18 | pub minor: u8, 19 | pub patch: u8, 20 | } 21 | 22 | impl Encode for Semver { 23 | fn encoded_len(&self) -> der::Result { 24 | let data: [u8; 3] = [self.major, self.minor, self.patch]; 25 | let as_octet_string = OctetStringRef::new(&data).unwrap(); 26 | as_octet_string.encoded_len() 27 | } 28 | 29 | fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> { 30 | let data: [u8; 3] = [self.major, self.minor, self.patch]; 31 | let as_octet_string = OctetStringRef::new(&data).unwrap(); 32 | as_octet_string.encode(encoder) 33 | } 34 | } 35 | 36 | impl<'a> Decode<'a> for Semver { 37 | fn decode>(decoder: &mut R) -> der::Result { 38 | let data: OctetStringRef = decoder.decode()?; 39 | if data.len() != Length::new(3) { 40 | return Err(Error::new( 41 | ErrorKind::Incomplete { 42 | expected_len: Length::new(3), 43 | actual_len: data.len(), 44 | }, 45 | Length::new(0), 46 | )); 47 | } 48 | 49 | Ok(Self { 50 | major: data.as_bytes()[0], 51 | minor: data.as_bytes()[1], 52 | patch: data.as_bytes()[2], 53 | }) 54 | } 55 | } 56 | 57 | impl fmt::Display for Semver { 58 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 59 | writeln!( 60 | f, 61 | "ANISE version {}.{}.{}", 62 | self.major, self.minor, self.patch 63 | ) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /anise/benches/crit_spacecraft_ephemeris.rs: -------------------------------------------------------------------------------- 1 | use anise::{constants::frames::EARTH_J2000, file2heap, prelude::*}; 2 | use criterion::{criterion_group, criterion_main, Criterion}; 3 | use rand::seq::SliceRandom; 4 | use rand::SeedableRng; 5 | use rand_pcg::Pcg64; 6 | use std::hint::black_box; 7 | 8 | const NUM_QUERIES: f64 = 1000.0; 9 | const RNG_SEED: u64 = 1234567890; 10 | 11 | fn benchmark_spice_single_hop_type13_hermite(time_vec: &[Epoch]) { 12 | // SPICE load 13 | spice::furnsh("../data/de440s.bsp"); 14 | spice::furnsh("../data/lro.bsp"); 15 | 16 | for epoch in time_vec { 17 | black_box(spice::spkezr( 18 | "-85", 19 | epoch.to_et_seconds(), 20 | "J2000", 21 | "NONE", 22 | "EARTH", 23 | )); 24 | } 25 | 26 | spice::unload("../data/lro.bsp"); 27 | spice::unload("../data/de440s.bsp"); 28 | } 29 | 30 | fn benchmark_anise_single_hop_type13_hermite(ctx: &Almanac, time_vec: &[Epoch]) { 31 | let my_sc_j2k = Frame::from_ephem_j2000(-85); 32 | for epoch in time_vec.iter().copied() { 33 | black_box( 34 | ctx.translate_geometric(my_sc_j2k, EARTH_J2000, epoch) 35 | .unwrap(), 36 | ); 37 | } 38 | } 39 | 40 | pub fn criterion_benchmark(c: &mut Criterion) { 41 | let start_epoch = Epoch::from_gregorian_at_noon(2023, 12, 15, TimeScale::UTC); 42 | let end_epoch = Epoch::from_gregorian_at_midnight(2024, 1, 9, TimeScale::UTC); 43 | let time_step = ((end_epoch - start_epoch).to_seconds() / NUM_QUERIES).seconds(); 44 | let time_it = TimeSeries::exclusive(start_epoch, end_epoch - time_step, time_step); 45 | // Shuffle the time iterator 46 | let mut rng = Pcg64::seed_from_u64(RNG_SEED); 47 | let mut time_vec: Vec = time_it.collect(); 48 | time_vec.shuffle(&mut rng); 49 | 50 | let path = "../data/de440s.bsp"; 51 | let buf = file2heap!(path).unwrap(); 52 | let spk = SPK::parse(buf).unwrap(); 53 | 54 | let buf = file2heap!("../data/lro.bsp").unwrap(); 55 | let spacecraft = SPK::parse(buf).unwrap(); 56 | 57 | let ctx = Almanac::from_spk(spk).with_spk(spacecraft); 58 | 59 | c.bench_function("ANISE hermite", |b| { 60 | b.iter(|| benchmark_anise_single_hop_type13_hermite(&ctx, &time_vec)) 61 | }); 62 | 63 | c.bench_function("SPICE hermite", |b| { 64 | b.iter(|| benchmark_spice_single_hop_type13_hermite(&time_vec)) 65 | }); 66 | } 67 | 68 | criterion_group!(hermite, criterion_benchmark); 69 | criterion_main!(hermite); 70 | -------------------------------------------------------------------------------- /anise/src/almanac/embed.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | almanac::Almanac, 3 | errors::{AlmanacError, AlmanacResult, TLDataSetSnafu}, 4 | structure::PlanetaryDataSet, 5 | }; 6 | use bytes::BytesMut; 7 | use rust_embed::Embed; 8 | use snafu::ResultExt; 9 | 10 | #[derive(Embed)] 11 | #[cfg_attr(not(docsrs), folder = "$CARGO_MANIFEST_DIR/../data/")] 12 | #[cfg_attr(not(docsrs), include = "de440s.bsp")] 13 | #[cfg_attr(not(docsrs), include = "pck11.pca")] 14 | #[cfg_attr(docsrs, folder = "$OUT_DIR")] 15 | struct AstroData; 16 | 17 | impl Almanac { 18 | /// Provides planetary ephemerides from 2024-01-01 until 2035-01-01. Also provides planetary constants data (from the PCK11 kernel). 19 | /// 20 | /// Until , this will provide 100 years of data 21 | pub fn until_2035() -> AlmanacResult { 22 | // Regularly refer to https://github.com/nyx-space/anise/blob/master/data/ci_config.dhall for the latest CRC, although it should not change between minor versions! 23 | let pck11 = AstroData::get("pck11.pca").ok_or(AlmanacError::GenericError { 24 | err: "could not find pck11.pca in embedded files".to_string(), 25 | })?; 26 | let almanac = Almanac::default().with_planetary_data( 27 | PlanetaryDataSet::try_from_bytes(pck11.data.as_ref()).context(TLDataSetSnafu { 28 | action: "loading PCK11 from embedded file", 29 | })?, 30 | ); 31 | 32 | let pl_ephem = AstroData::get("de440s.bsp").ok_or(AlmanacError::GenericError { 33 | err: "could not find de440s.bsp in embedded files".to_string(), 34 | })?; 35 | 36 | almanac.load_from_bytes(BytesMut::from(pl_ephem.data.as_ref())) 37 | } 38 | } 39 | 40 | #[cfg(test)] 41 | mod ut_embed { 42 | use super::{Almanac, AstroData}; 43 | 44 | #[test] 45 | fn test_embedded_load() { 46 | let almanac = Almanac::until_2035().unwrap(); 47 | assert_eq!(almanac.num_loaded_spk(), 1); 48 | assert_eq!(almanac.num_loaded_bpc(), 0); 49 | assert_ne!(almanac.planetary_data.values().next().unwrap().crc32(), 0); 50 | } 51 | 52 | #[test] 53 | fn test_limited_set() { 54 | // Check only PCK11 is present 55 | assert!(AstroData::get("pck11.pca").is_some()); 56 | assert!(AstroData::get("pck08.pca").is_none()); 57 | // Check only one planetary ephem is present 58 | assert!(AstroData::get("de440s.bsp").is_some()); 59 | assert!(AstroData::get("de440.bsp").is_none()); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /anise/src/structure/spacecraft/drag.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | use der::{Decode, Encode, Reader, Writer}; 11 | use serde_derive::{Deserialize, Serialize}; 12 | 13 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 14 | pub struct DragData { 15 | /// Atmospheric drag area in m^2 -- default 0.0 16 | pub area_m2: f64, 17 | /// Drag coefficient (C_d) -- default 2.2 18 | pub coeff_drag: f64, 19 | } 20 | 21 | impl DragData { 22 | pub fn from_area(area_m2: f64) -> Self { 23 | Self { 24 | area_m2, 25 | ..Default::default() 26 | } 27 | } 28 | } 29 | 30 | impl Default for DragData { 31 | fn default() -> Self { 32 | Self { 33 | area_m2: 0.0, 34 | coeff_drag: 2.2, 35 | } 36 | } 37 | } 38 | 39 | impl Encode for DragData { 40 | fn encoded_len(&self) -> der::Result { 41 | self.area_m2.encoded_len()? + self.coeff_drag.encoded_len()? 42 | } 43 | 44 | fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> { 45 | self.area_m2.encode(encoder)?; 46 | self.coeff_drag.encode(encoder) 47 | } 48 | } 49 | 50 | impl<'a> Decode<'a> for DragData { 51 | fn decode>(decoder: &mut R) -> der::Result { 52 | Ok(Self { 53 | area_m2: decoder.decode()?, 54 | coeff_drag: decoder.decode()?, 55 | }) 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod drag_ut { 61 | use super::{Decode, DragData, Encode}; 62 | #[test] 63 | fn zero_repr() { 64 | let repr = DragData { 65 | area_m2: Default::default(), 66 | coeff_drag: Default::default(), 67 | }; 68 | 69 | let mut buf = vec![]; 70 | repr.encode_to_vec(&mut buf).unwrap(); 71 | 72 | let repr_dec = DragData::from_der(&buf).unwrap(); 73 | 74 | assert_eq!(repr, repr_dec); 75 | } 76 | 77 | #[test] 78 | fn default_repr() { 79 | let repr = DragData::default(); 80 | 81 | let mut buf = vec![]; 82 | repr.encode_to_vec(&mut buf).unwrap(); 83 | 84 | let repr_dec = DragData::from_der(&buf).unwrap(); 85 | 86 | assert_eq!(repr, repr_dec); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /anise/src/ephemerides/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use hifitime::Epoch; 12 | use snafu::prelude::*; 13 | 14 | use crate::{ 15 | astro::Aberration, errors::PhysicsError, math::interpolation::InterpolationError, 16 | naif::daf::DAFError, prelude::FrameUid, NaifId, 17 | }; 18 | 19 | pub mod paths; 20 | pub mod translate_to_parent; 21 | pub mod translations; 22 | 23 | #[derive(Debug, Snafu, PartialEq)] 24 | #[snafu(visibility(pub))] 25 | #[non_exhaustive] 26 | pub enum EphemerisError { 27 | /// Somehow you've entered code that should not be reachable, please file a bug. 28 | Unreachable, 29 | #[snafu(display("could not {action} because {alias} is not loaded"))] 30 | AliasNotFound { alias: String, action: &'static str }, 31 | #[snafu(display( 32 | "Could not translate from {from} to {to}: no common origin found at epoch {epoch}" 33 | ))] 34 | TranslationOrigin { 35 | from: FrameUid, 36 | to: FrameUid, 37 | epoch: Epoch, 38 | }, 39 | #[snafu(display("no ephemeris data loaded (must call load_spk)"))] 40 | NoEphemerisLoaded, 41 | #[snafu(display("when {action} caused {source}"))] 42 | SPK { 43 | action: &'static str, 44 | #[snafu(backtrace)] 45 | source: DAFError, 46 | }, 47 | #[snafu(display("when {action} for ephemeris {source}"))] 48 | EphemerisPhysics { 49 | action: &'static str, 50 | #[snafu(backtrace)] 51 | source: PhysicsError, 52 | }, 53 | #[snafu(display("during an ephemeris interpolation {source}"))] 54 | EphemInterpolation { 55 | #[snafu(backtrace)] 56 | source: InterpolationError, 57 | }, 58 | #[snafu(display("{ab_corr} corrects epoch from {epoch} to {epoch_lt}, but {source}"))] 59 | LightTimeCorrection { 60 | epoch: Epoch, 61 | epoch_lt: Epoch, 62 | ab_corr: Aberration, 63 | #[snafu(source(from(EphemerisError, Box::new)))] // This ensures the source error is boxed 64 | source: Box, 65 | }, 66 | #[snafu(display("unknown name associated with NAIF ID {id}"))] 67 | IdToName { id: NaifId }, 68 | #[snafu(display("unknown NAIF ID associated with `{name}`"))] 69 | NameToId { name: String }, 70 | } 71 | -------------------------------------------------------------------------------- /anise/src/py_errors.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use crate::almanac::metaload::MetaAlmanacError; 12 | use crate::almanac::planetary::PlanetaryDataError; 13 | use crate::analysis::AnalysisError; 14 | use crate::ephemerides::EphemerisError; 15 | use crate::errors::{AlmanacError, DecodingError, InputOutputError, IntegrityError, PhysicsError}; 16 | use crate::orientations::OrientationError; 17 | use crate::structure::dataset::DataSetError; 18 | use core::convert::From; 19 | 20 | use pyo3::{exceptions::PyException, prelude::*}; 21 | 22 | impl From for PyErr { 23 | fn from(err: PhysicsError) -> PyErr { 24 | PyException::new_err(err.to_string()) 25 | } 26 | } 27 | 28 | impl From for PyErr { 29 | fn from(err: IntegrityError) -> PyErr { 30 | PyException::new_err(err.to_string()) 31 | } 32 | } 33 | impl From for PyErr { 34 | fn from(err: DecodingError) -> PyErr { 35 | PyException::new_err(err.to_string()) 36 | } 37 | } 38 | impl From for PyErr { 39 | fn from(err: InputOutputError) -> PyErr { 40 | PyException::new_err(err.to_string()) 41 | } 42 | } 43 | impl From for PyErr { 44 | fn from(err: AlmanacError) -> PyErr { 45 | PyException::new_err(err.to_string()) 46 | } 47 | } 48 | impl From for PyErr { 49 | fn from(err: AnalysisError) -> PyErr { 50 | PyException::new_err(err.to_string()) 51 | } 52 | } 53 | impl From for PyErr { 54 | fn from(err: EphemerisError) -> PyErr { 55 | PyException::new_err(err.to_string()) 56 | } 57 | } 58 | impl From for PyErr { 59 | fn from(err: OrientationError) -> PyErr { 60 | PyException::new_err(err.to_string()) 61 | } 62 | } 63 | 64 | impl From for PyErr { 65 | fn from(err: PlanetaryDataError) -> PyErr { 66 | PyException::new_err(err.to_string()) 67 | } 68 | } 69 | 70 | impl From for PyErr { 71 | fn from(err: MetaAlmanacError) -> PyErr { 72 | PyException::new_err(err.to_string()) 73 | } 74 | } 75 | impl From for PyErr { 76 | fn from(err: DataSetError) -> PyErr { 77 | PyException::new_err(err.to_string()) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/validation/type02_chebyshev_jpl_de.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use super::{compare::*, validate::Validation}; 12 | use anise::prelude::Aberration; 13 | 14 | #[ignore = "Requires Rust SPICE -- must be executed serially"] 15 | #[test] 16 | fn validate_jplde_de440_full() { 17 | let file_name = "spk-type2-validation-de440".to_string(); 18 | let comparator = CompareEphem::new( 19 | vec!["../data/de440.bsp".to_string()], 20 | file_name.clone(), 21 | 1_000, 22 | None, 23 | ); 24 | 25 | let err_count = comparator.run(); 26 | 27 | assert_eq!(err_count, 0, "None of the queries should fail!"); 28 | 29 | let validator = Validation { 30 | file_name, 31 | ..Default::default() 32 | }; 33 | 34 | validator.validate(); 35 | } 36 | 37 | #[ignore = "Requires Rust SPICE -- must be executed serially"] 38 | #[test] 39 | fn validate_jplde_de440s_no_aberration() { 40 | let output_file_name = "spk-type2-validation-de440s".to_string(); 41 | let comparator = CompareEphem::new( 42 | vec!["../data/de440s.bsp".to_string()], 43 | output_file_name.clone(), 44 | 1_000, 45 | None, 46 | ); 47 | 48 | let err_count = comparator.run(); 49 | 50 | assert_eq!(err_count, 0, "None of the queries should fail!"); 51 | 52 | let validator = Validation { 53 | file_name: output_file_name, 54 | ..Default::default() 55 | }; 56 | 57 | validator.validate(); 58 | } 59 | 60 | #[ignore = "Requires Rust SPICE -- must be executed serially"] 61 | #[test] 62 | fn validate_jplde_de440s_aberration_lt() { 63 | let output_file_name = "spk-type2-validation-de440s-lt-aberration".to_string(); 64 | let comparator = CompareEphem::new( 65 | vec!["../data/de440s.bsp".to_string()], 66 | output_file_name.clone(), 67 | 1_000, 68 | Aberration::LT, 69 | ); 70 | 71 | let err_count = comparator.run(); 72 | 73 | assert!(err_count == 0, "None of the queries should fail!"); 74 | 75 | let validator = Validation { 76 | file_name: output_file_name, 77 | max_q75_err: 0.0, 78 | max_q99_err: 2e-5, 79 | max_abs_err: 6e-5, 80 | ..Default::default() 81 | }; 82 | 83 | validator.validate(); 84 | } 85 | -------------------------------------------------------------------------------- /anise/src/structure/spacecraft/srp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | use der::{Decode, Encode, Reader, Writer}; 11 | use serde_derive::{Deserialize, Serialize}; 12 | 13 | #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] 14 | pub struct SRPData { 15 | /// Solar radiation pressure area in m^2 -- default 0.0 16 | pub area_m2: f64, 17 | /// Solar radiation pressure coefficient of reflectivity (C_r) -- default 1.8 18 | pub coeff_reflectivity: f64, 19 | } 20 | 21 | impl SRPData { 22 | pub fn from_area(area_m2: f64) -> Self { 23 | Self { 24 | area_m2, 25 | ..Default::default() 26 | } 27 | } 28 | } 29 | 30 | impl Default for SRPData { 31 | fn default() -> Self { 32 | Self { 33 | area_m2: 0.0, 34 | coeff_reflectivity: 1.8, 35 | } 36 | } 37 | } 38 | 39 | impl Encode for SRPData { 40 | fn encoded_len(&self) -> der::Result { 41 | self.area_m2.encoded_len()? + self.coeff_reflectivity.encoded_len()? 42 | } 43 | 44 | fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> { 45 | self.area_m2.encode(encoder)?; 46 | self.coeff_reflectivity.encode(encoder) 47 | } 48 | } 49 | 50 | impl<'a> Decode<'a> for SRPData { 51 | fn decode>(decoder: &mut R) -> der::Result { 52 | Ok(Self { 53 | area_m2: decoder.decode()?, 54 | coeff_reflectivity: decoder.decode()?, 55 | }) 56 | } 57 | } 58 | 59 | #[cfg(test)] 60 | mod srp_ut { 61 | use super::{Decode, Encode, SRPData}; 62 | #[test] 63 | fn zero_repr() { 64 | let repr = SRPData { 65 | area_m2: Default::default(), 66 | coeff_reflectivity: Default::default(), 67 | }; 68 | 69 | let mut buf = vec![]; 70 | repr.encode_to_vec(&mut buf).unwrap(); 71 | 72 | let repr_dec = SRPData::from_der(&buf).unwrap(); 73 | 74 | assert_eq!(repr, repr_dec); 75 | } 76 | 77 | #[test] 78 | fn default_repr() { 79 | let repr = SRPData::default(); 80 | 81 | let mut buf = vec![]; 82 | repr.encode_to_vec(&mut buf).unwrap(); 83 | 84 | let repr_dec = SRPData::from_der(&buf).unwrap(); 85 | 86 | assert_eq!(repr, repr_dec); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /anise-cli/src/args.rs: -------------------------------------------------------------------------------- 1 | use std::path::PathBuf; 2 | 3 | use clap::{Args, Parser, Subcommand}; 4 | use hifitime::Epoch; 5 | 6 | #[derive(Parser, Debug)] 7 | #[clap(name="ANISE", author="Rabotin and ANISE contributors", version, about, long_about = None)] 8 | pub struct CliArgs { 9 | #[clap(subcommand)] 10 | pub action: Actions, 11 | } 12 | 13 | #[derive(Debug, PartialEq, Eq, PartialOrd, Subcommand)] 14 | pub enum Actions { 15 | /// Checks the integrity of the file 16 | Check { 17 | /// Path to ANISE file 18 | file: PathBuf, 19 | /// CRC32 checksum 20 | crc32_checksum: u32, 21 | }, 22 | /// Inspects what's in an ANISE file (and also checks the integrity) 23 | Inspect { 24 | /// Path to ANISE or NAIF file 25 | file: PathBuf, 26 | }, 27 | /// Convert the provided KPL files into ANISE datasets 28 | ConvertTpc { 29 | /// Path to the KPL PCK/TPC file (e.g. pck00008.tpc) 30 | pckfile: PathBuf, 31 | /// Path to the KPL gravity data TPC file (e.g. gm_de431.tpc) 32 | gmfile: PathBuf, 33 | /// Output ANISE binary file 34 | outfile: PathBuf, 35 | }, 36 | /// Convert the provided Frame Kernel into an ANISE dataset 37 | ConvertFk { 38 | /// Path to the FK (e.g. moon_080317.fk) 39 | fkfile: PathBuf, 40 | /// Output ANISE binary file 41 | outfile: PathBuf, 42 | }, 43 | /// Truncate the segment of the provided ID of the input NAIF DAF file to the provided start and end epochs 44 | /// Limitation: this may not work correctly if there are several segments with the same ID. 45 | /// Only works with Chebyshev Type 2 data types (i.e. planetary ephemerides). 46 | TruncDAFById(TruncateById), 47 | /// Remove the segment of the provided ID of the input NAIF DAF file. 48 | /// Limitation: this may not work correctly if there are several segments with the same ID. 49 | RmDAFById(RmById), 50 | } 51 | 52 | #[derive(Debug, PartialEq, Eq, PartialOrd, Args)] 53 | pub(crate) struct RmById { 54 | /// Input DAF file, SPK or BPC 55 | pub input: PathBuf, 56 | /// Output DAF file path 57 | pub output: PathBuf, 58 | /// ID of the segment to truncate 59 | pub id: i32, 60 | } 61 | 62 | #[derive(Debug, PartialEq, Eq, PartialOrd, Args)] 63 | pub(crate) struct TruncateById { 64 | /// Input DAF file, SPK or BPC 65 | pub input: PathBuf, 66 | /// Output DAF file path 67 | pub output: PathBuf, 68 | /// ID of the segment to truncate 69 | pub id: i32, 70 | /// New start epoch of the segment 71 | pub start: Option, 72 | /// New end epoch of the segment 73 | pub end: Option, 74 | } 75 | -------------------------------------------------------------------------------- /anise-py/anise/constants.pyi: -------------------------------------------------------------------------------- 1 | import typing 2 | 3 | from anise.astro import Frame 4 | 5 | @typing.final 6 | class CelestialObjects: 7 | EARTH: int = ... 8 | EARTH_MOON_BARYCENTER: int = ... 9 | JUPITER: int = ... 10 | JUPITER_BARYCENTER: int = ... 11 | MARS: int = ... 12 | MARS_BARYCENTER: int = ... 13 | MERCURY: int = ... 14 | MOON: int = ... 15 | NEPTUNE: int = ... 16 | NEPTUNE_BARYCENTER: int = ... 17 | PLUTO_BARYCENTER: int = ... 18 | SATURN: int = ... 19 | SATURN_BARYCENTER: int = ... 20 | SOLAR_SYSTEM_BARYCENTER: int = ... 21 | SUN: int = ... 22 | URANUS: int = ... 23 | URANUS_BARYCENTER: int = ... 24 | VENUS: int = ... 25 | 26 | @typing.final 27 | class Frames: 28 | EARTH_ECLIPJ2000: Frame = ... 29 | EARTH_ITRF93: Frame = ... 30 | EARTH_J2000: Frame = ... 31 | EARTH_MOON_BARYCENTER_J2000: Frame = ... 32 | EME2000: Frame = ... 33 | IAU_EARTH_FRAME: Frame = ... 34 | IAU_JUPITER_FRAME: Frame = ... 35 | IAU_MARS_FRAME: Frame = ... 36 | IAU_MERCURY_FRAME: Frame = ... 37 | IAU_MOON_FRAME: Frame = ... 38 | IAU_NEPTUNE_FRAME: Frame = ... 39 | IAU_SATURN_FRAME: Frame = ... 40 | IAU_URANUS_FRAME: Frame = ... 41 | IAU_VENUS_FRAME: Frame = ... 42 | JUPITER_BARYCENTER_J2000: Frame = ... 43 | MARS_BARYCENTER_J2000: Frame = ... 44 | MERCURY_J2000: Frame = ... 45 | MOON_J2000: Frame = ... 46 | MOON_ME_DE421_FRAME: Frame = ... 47 | MOON_ME_DE440_ME421_FRAME: Frame = ... 48 | MOON_ME_FRAME: Frame = ... 49 | MOON_PA_DE421_FRAME: Frame = ... 50 | MOON_PA_DE440_FRAME: Frame = ... 51 | MOON_PA_FRAME: Frame = ... 52 | NEPTUNE_BARYCENTER_J2000: Frame = ... 53 | PLUTO_BARYCENTER_J2000: Frame = ... 54 | SATURN_BARYCENTER_J2000: Frame = ... 55 | SSB_J2000: Frame = ... 56 | SUN_J2000: Frame = ... 57 | URANUS_BARYCENTER_J2000: Frame = ... 58 | VENUS_J2000: Frame = ... 59 | 60 | @typing.final 61 | class Orientations: 62 | ECLIPJ2000: int = ... 63 | IAU_EARTH: int = ... 64 | IAU_JUPITER: int = ... 65 | IAU_MARS: int = ... 66 | IAU_MERCURY: int = ... 67 | IAU_MOON: int = ... 68 | IAU_NEPTUNE: int = ... 69 | IAU_SATURN: int = ... 70 | IAU_URANUS: int = ... 71 | IAU_VENUS: int = ... 72 | ITRF93: int = ... 73 | J2000: int = ... 74 | MOON_ME: int = ... 75 | MOON_ME_DE421: int = ... 76 | MOON_ME_DE440_ME421: int = ... 77 | MOON_PA: int = ... 78 | MOON_PA_DE421: int = ... 79 | MOON_PA_DE440: int = ... 80 | 81 | @typing.final 82 | class UsualConstants: 83 | MEAN_EARTH_ANGULAR_VELOCITY_DEG_S: float = ... 84 | MEAN_MOON_ANGULAR_VELOCITY_DEG_S: float = ... 85 | SPEED_OF_LIGHT_KM_S: float = ... 86 | -------------------------------------------------------------------------------- /anise/src/structure/dataset/error.rs: -------------------------------------------------------------------------------- 1 | use snafu::prelude::*; 2 | 3 | use crate::{ 4 | errors::{DecodingError, IntegrityError}, 5 | structure::lookuptable::LutError, 6 | }; 7 | use std::io::Error as IOError; 8 | 9 | #[derive(Debug, Snafu)] 10 | #[snafu(visibility(pub(crate)))] 11 | #[non_exhaustive] 12 | pub enum DataSetError { 13 | #[snafu(display("when {action}, {source}"))] 14 | DataSetLut { 15 | action: &'static str, 16 | source: LutError, 17 | }, 18 | #[snafu(display("when {action}, {source}"))] 19 | DataSetIntegrity { 20 | action: &'static str, 21 | source: IntegrityError, 22 | }, 23 | #[snafu(display("when {action}, {source}"))] 24 | DataDecoding { 25 | action: &'static str, 26 | source: DecodingError, 27 | }, 28 | #[snafu(display("input/output error while {action}, {source}"))] 29 | IO { 30 | action: &'static str, 31 | source: IOError, 32 | }, 33 | #[snafu(display("data set conversion error: {action}"))] 34 | Conversion { action: String }, 35 | } 36 | 37 | impl PartialEq for DataSetError { 38 | fn eq(&self, other: &Self) -> bool { 39 | match (self, other) { 40 | ( 41 | Self::DataSetLut { 42 | action: l_action, 43 | source: l_source, 44 | }, 45 | Self::DataSetLut { 46 | action: r_action, 47 | source: r_source, 48 | }, 49 | ) => l_action == r_action && l_source == r_source, 50 | ( 51 | Self::DataSetIntegrity { 52 | action: l_action, 53 | source: l_source, 54 | }, 55 | Self::DataSetIntegrity { 56 | action: r_action, 57 | source: r_source, 58 | }, 59 | ) => l_action == r_action && l_source == r_source, 60 | ( 61 | Self::DataDecoding { 62 | action: l_action, 63 | source: l_source, 64 | }, 65 | Self::DataDecoding { 66 | action: r_action, 67 | source: r_source, 68 | }, 69 | ) => l_action == r_action && l_source == r_source, 70 | ( 71 | Self::IO { 72 | action: l_action, 73 | source: _l_source, 74 | }, 75 | Self::IO { 76 | action: r_action, 77 | source: _r_source, 78 | }, 79 | ) => l_action == r_action, 80 | _ => false, 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /anise-py/src/bin.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use pyo3::prelude::*; 12 | 13 | use ::anise::almanac::metaload::{MetaAlmanacError, MetaFile}; 14 | 15 | use std::env::consts::OS; 16 | use std::fs; 17 | use std::process::Command; 18 | 19 | #[allow(clippy::zombie_processes)] 20 | #[pyfunction] 21 | pub(crate) fn exec_gui() -> Result<(), MetaAlmanacError> { 22 | if ["windows", "linux"].contains(&OS) { 23 | let crc32 = match OS { 24 | "linux" => Some(0x2046a9b7), 25 | "windows" => Some(0xac191672), 26 | _ => unreachable!(), 27 | }; 28 | // Attempt to download from the public site. 29 | let mut gui = MetaFile { 30 | uri: format!("http://public-data.nyxspace.com/anise/v0.6/anise-gui-{OS}.exe"), 31 | crc32, 32 | }; 33 | gui.process(true)?; 34 | make_executable(&gui.uri).expect("could not make ANISE GUI executable"); 35 | // Now, execute this file. 36 | Command::new(gui.uri) 37 | .spawn() 38 | .expect("could not execute ANISE GUI"); 39 | Ok(()) 40 | } else { 41 | Err(MetaAlmanacError::FetchError { 42 | error: format!("{OS} not supported by ANISE GUI"), 43 | uri: format!("http://public-data.nyxspace.com/anise/v0.6/anise-gui-{OS}.exe"), 44 | }) 45 | } 46 | } 47 | 48 | /// Sets the executable permission on the file if running on a Unix-like system. 49 | /// Does nothing on other systems like Windows. 50 | pub fn make_executable(path: &str) -> Result<(), Box> { 51 | // This function's signature is available on all platforms. 52 | // However, the code inside the block below is only included in the 53 | // binary when compiling for a Unix target. 54 | #[cfg(unix)] 55 | { 56 | // This platform-specific code requires the `PermissionsExt` trait. 57 | use std::os::unix::fs::PermissionsExt; 58 | 59 | // Get the existing permissions 60 | let metadata = fs::metadata(path)?; 61 | let mut permissions = metadata.permissions(); 62 | 63 | // Add the execute permission using the octal mode 64 | let current_mode = permissions.mode(); 65 | permissions.set_mode(current_mode | 0o111); // Add u+x, g+x, o+x 66 | 67 | // Apply the new permissions 68 | fs::set_permissions(path, permissions)?; 69 | } 70 | 71 | Ok(()) 72 | } 73 | -------------------------------------------------------------------------------- /anise-py/src/utils.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use std::path::PathBuf; 12 | 13 | use anise::naif::kpl::parser::{convert_fk as convert_fk_rs, convert_tpc as convert_tpc_rs}; 14 | use anise::structure::dataset::DataSetError; 15 | use pyo3::prelude::*; 16 | 17 | #[pymodule] 18 | pub(crate) fn utils(_py: Python, sm: &Bound) -> PyResult<()> { 19 | sm.add_function(wrap_pyfunction!(convert_fk, sm)?)?; 20 | sm.add_function(wrap_pyfunction!(convert_tpc, sm)?)?; 21 | 22 | Ok(()) 23 | } 24 | 25 | /// Converts a KPL/FK file, that defines frame constants like fixed rotations, and frame name to ID mappings into the EulerParameterDataSet equivalent ANISE file. 26 | /// KPL/FK files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE. 27 | /// 28 | /// :type fk_file_path: str 29 | /// :type anise_output_path: str 30 | /// :type show_comments: bool, optional 31 | /// :type overwrite: bool, optional 32 | /// :rtype: None 33 | #[pyfunction] 34 | #[pyo3(signature = (fk_file_path, anise_output_path, show_comments=None, overwrite=None))] 35 | fn convert_fk( 36 | fk_file_path: String, 37 | anise_output_path: String, 38 | show_comments: Option, 39 | overwrite: Option, 40 | ) -> Result<(), DataSetError> { 41 | let dataset = convert_fk_rs(fk_file_path, show_comments.unwrap_or(false))?; 42 | 43 | dataset.save_as( 44 | &PathBuf::from(anise_output_path), 45 | overwrite.unwrap_or(false), 46 | )?; 47 | 48 | Ok(()) 49 | } 50 | 51 | /// 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. 52 | /// KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files before being loaded into ANISE. 53 | /// 54 | /// :type pck_file_path: str 55 | /// :type gm_file_path: str 56 | /// :type anise_output_path: str 57 | /// :type overwrite: bool, optional 58 | /// :rtype: None 59 | #[pyfunction] 60 | #[pyo3(signature = (pck_file_path, gm_file_path, anise_output_path, overwrite=None))] 61 | fn convert_tpc( 62 | pck_file_path: String, 63 | gm_file_path: String, 64 | anise_output_path: String, 65 | overwrite: Option, 66 | ) -> Result<(), DataSetError> { 67 | let dataset = convert_tpc_rs(pck_file_path, gm_file_path)?; 68 | 69 | dataset.save_as( 70 | &PathBuf::from(anise_output_path), 71 | overwrite.unwrap_or(false), 72 | )?; 73 | 74 | Ok(()) 75 | } 76 | -------------------------------------------------------------------------------- /.github/workflows/benchmarks.yml: -------------------------------------------------------------------------------- 1 | name: Benchmark ANISE versus SPICE 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | tags: 8 | - "*" 9 | pull_request: 10 | workflow_dispatch: 11 | 12 | jobs: 13 | ephem_type2_chebyshev: 14 | name: SPICE versus ANISE Benchmark 15 | runs-on: ubuntu-latest 16 | steps: 17 | - name: Checkout sources 18 | uses: actions/checkout@v4 19 | 20 | - name: Download data 21 | run: | 22 | wget -O data/de421.bsp http://public-data.nyxspace.com/anise/de421.bsp 23 | wget -O data/de430.bsp http://public-data.nyxspace.com/anise/de430.bsp 24 | wget -O data/de440s.bsp http://public-data.nyxspace.com/anise/de440s.bsp 25 | wget -O data/de440.bsp http://public-data.nyxspace.com/anise/de440.bsp 26 | wget -O data/pck08.pca http://public-data.nyxspace.com/anise/v0.5/pck08.pca 27 | wget -O data/gmat-hermite.bsp http://public-data.nyxspace.com/anise/ci/gmat-hermite.bsp 28 | wget -O data/gmat-hermite-big-endian.bsp http://public-data.nyxspace.com/anise/ci/gmat-hermite-big-endian.bsp 29 | wget -O data/variable-seg-size-hermite.bsp http://public-data.nyxspace.com/anise/ci/variable-seg-size-hermite.bsp 30 | wget -O data/earth_latest_high_prec.bpc http://public-data.nyxspace.com/anise/ci/earth_latest_high_prec-2023-09-08.bpc 31 | wget -O data/lro.bsp http://public-data.nyxspace.com/nyx/examples/lrorg_2023349_2024075_v01_LE.bsp 32 | 33 | - name: Install stable toolchain 34 | uses: dtolnay/rust-toolchain@stable 35 | 36 | - name: Install CSPICE 37 | run: sh dev-env-setup.sh && cd .. # Return to root 38 | 39 | - uses: taiki-e/install-action@cargo-binstall 40 | - name: Install iai-callgrind-runner 41 | run: | 42 | version=$(cargo metadata --format-version=1 |\ 43 | jq '.packages[] | select(.name == "iai-callgrind").version' |\ 44 | tr -d '"' 45 | ) 46 | cargo binstall --no-confirm iai-callgrind-runner --version $version 47 | sudo apt-get update 48 | sudo apt-get install -y valgrind 49 | 50 | 51 | - name: Bench JPL Ephemerides 52 | run: cargo bench --bench "*_jpl_ephemerides" --workspace --exclude anise-py 53 | 54 | - name: Bench Spacecraft (Hermite type 13) 55 | run: cargo bench --bench "*_spacecraft_ephemeris" --workspace --exclude anise-py 56 | 57 | - name: Bench Binary planetary constants 58 | run: cargo bench --bench "crit_bpc_rotation" --workspace --exclude anise-py 59 | 60 | - name: Bench planetary constants ANISE file 61 | run: cargo bench --bench "crit_planetary_data" --workspace --exclude anise-py 62 | 63 | - name: Save benchmark artifacts 64 | uses: actions/upload-artifact@v4 65 | with: 66 | name: jpl-development-ephemerides-benchmark 67 | path: target/criterion/**/report/ 68 | -------------------------------------------------------------------------------- /anise/src/math/rotation/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | /// The smallest difference between two radians is set to one arcsecond. 12 | pub(crate) const EPSILON_RAD: f64 = 4.8e-6; 13 | /// Equality of f64 for rotations 14 | pub(crate) const EPSILON: f64 = 1e-12; 15 | 16 | mod dcm; 17 | mod mrp; 18 | mod quaternion; 19 | pub use dcm::DCM; 20 | pub use mrp::MRP; 21 | pub use quaternion::{EulerParameter, Quaternion}; 22 | 23 | #[cfg(feature = "python")] 24 | mod dcm_py; 25 | 26 | #[cfg(feature = "python")] 27 | mod quaternion_py; 28 | 29 | pub trait Rotation: TryInto {} 30 | 31 | /// Build a 3x3 rotation matrix around the X axis 32 | pub fn r1(angle_rad: f64) -> Matrix3 { 33 | let (s, c) = angle_rad.sin_cos(); 34 | Matrix3::new(1.0, 0.0, 0.0, 0.0, c, s, 0.0, -s, c) 35 | } 36 | 37 | /// Build the derivative of the 3x3 rotation matrix around the X axis 38 | pub fn r1_dot(angle_rad: f64) -> Matrix3 { 39 | let (s, c) = angle_rad.sin_cos(); 40 | Matrix3::new(0.0, 0.0, 0.0, 0.0, -s, c, 0.0, -c, -s) 41 | } 42 | 43 | /// Build a 3x3 rotation matrix around the Y axis 44 | pub fn r2(angle_rad: f64) -> Matrix3 { 45 | let (s, c) = angle_rad.sin_cos(); 46 | Matrix3::new(c, 0.0, -s, 0.0, 1.0, 0.0, s, 0.0, c) 47 | } 48 | 49 | /// Build the derivative of the 3x3 rotation matrix around the Y axis 50 | pub fn r2_dot(angle_rad: f64) -> Matrix3 { 51 | let (s, c) = angle_rad.sin_cos(); 52 | Matrix3::new(-s, 0.0, -c, 0.0, 0.0, 0.0, c, 0.0, -s) 53 | } 54 | 55 | /// Build a 3x3 rotation matrix around the Z axis 56 | pub fn r3(angle_rad: f64) -> Matrix3 { 57 | let (s, c) = angle_rad.sin_cos(); 58 | Matrix3::new(c, s, 0.0, -s, c, 0.0, 0.0, 0.0, 1.0) 59 | } 60 | 61 | /// Build the derivative of the 3x3 rotation matrix around the Z axis 62 | pub fn r3_dot(angle_rad: f64) -> Matrix3 { 63 | let (s, c) = angle_rad.sin_cos(); 64 | Matrix3::new(-s, c, 0.0, -c, -s, 0.0, 0.0, 0.0, 0.0) 65 | } 66 | 67 | /// Generates the angles for the test 68 | #[cfg(test)] 69 | pub(crate) fn generate_angles() -> Vec { 70 | use core::f64::consts::TAU; 71 | let mut angles = Vec::new(); 72 | let mut angle = -TAU; 73 | loop { 74 | angles.push(angle); 75 | angle += 0.01 * TAU; 76 | if angle > TAU { 77 | break; 78 | } 79 | } 80 | angles 81 | } 82 | 83 | use super::Matrix3; 84 | #[cfg(test)] 85 | use super::Vector3; 86 | /// Returns whether two vectors can be considered equal after a rotation 87 | #[cfg(test)] 88 | pub(crate) fn vec3_eq(a: Vector3, b: Vector3, msg: String) { 89 | let rslt = (a - b).norm(); 90 | assert!(rslt < 1e-3, "{msg}:{rslt:.e}\ta = {a}\tb = {b}") 91 | } 92 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/parent_translation_verif.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use anise::constants::frames::VENUS_J2000; 12 | use anise::file2heap; 13 | use anise::math::Vector3; 14 | use anise::prelude::*; 15 | 16 | const ZEROS: &[u8] = &[0; 256]; 17 | /// Test that we can load data from a static pointer to it, even if there is less than one record length 18 | #[test] 19 | fn invalid_load_from_static() { 20 | assert!(SPK::from_static(&ZEROS).is_err()); 21 | } 22 | 23 | #[test] 24 | fn de400_domain() { 25 | let almanac = Almanac::new("../data/de440s.bsp").unwrap(); 26 | 27 | assert!(almanac.spk_domain(-1012).is_err()); 28 | assert!(almanac.spk_domain(399).is_ok()); 29 | assert!(almanac.spk_domains().is_ok()); 30 | 31 | // No BPC loaded, so it should error. 32 | assert!(almanac.bpc_domain(-1).is_err()); 33 | assert!(almanac.bpc_domain(399).is_err()); 34 | 35 | assert!(almanac.bpc_domains().is_err()); 36 | } 37 | 38 | #[test] 39 | fn de440s_parent_translation_verif() { 40 | let _ = pretty_env_logger::try_init(); 41 | 42 | let bytes = file2heap!("../data/de440s.bsp").unwrap(); 43 | let de438s = SPK::parse(bytes).unwrap(); 44 | let ctx = Almanac::from_spk(de438s); 45 | 46 | let epoch = Epoch::from_gregorian_utc_at_midnight(2002, 2, 7); 47 | 48 | /* 49 | Python code: 50 | >>> import spiceypy as sp 51 | >>> sp.furnsh('data/de440s.bsp') 52 | >>> sp.furnsh('../../hifitime/naif0012.txt') 53 | >>> et = sp.utc2et('2002 FEB 07 00:00:00') 54 | >>> et 55 | 66312064.18493876 56 | >>> ['{:.16e}'.format(x) for x in sp.spkez(2, et, "J2000", "NONE", 0)[0]] 57 | ['9.5205530594596043e+07', '-4.6160758818180226e+07', '-2.6779476581501361e+07', '1.6612048969243794e+01', '2.8272067093941200e+01', '1.1668575714409423e+01'] 58 | */ 59 | 60 | let state = ctx.translate_to_parent(VENUS_J2000, epoch).unwrap(); 61 | 62 | let pos_km = state.radius_km; 63 | let vel_km_s = state.velocity_km_s; 64 | 65 | let pos_expct_km = Vector3::new( 66 | 9.520_553_059_459_604e7, 67 | -4.616_075_881_818_022_6e7, 68 | -2.677_947_658_150_136e7, 69 | ); 70 | 71 | let vel_expct_km_s = Vector3::new( 72 | 1.661_204_896_924_379_4e1, 73 | 2.827_206_709_394_12e1, 74 | 1.166_857_571_440_942_3e1, 75 | ); 76 | 77 | // We expect exactly the same output as SPICE to machine precision. 78 | assert!( 79 | (pos_km - pos_expct_km).norm() < f64::EPSILON, 80 | "got {pos_km} but want {pos_expct_km}", 81 | ); 82 | 83 | assert!( 84 | (vel_km_s - vel_expct_km_s).norm() < f64::EPSILON, 85 | "got {vel_km_s} but want {vel_expct_km_s}", 86 | ); 87 | } 88 | -------------------------------------------------------------------------------- /download_test_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | mkdir -p data 3 | 4 | # Define a function to download only if missing or if it's a Git LFS pointer 5 | download_if_missing() { 6 | url="$1" 7 | # Ensure we strip any existing paths from the second argument to enforce the data/ directory 8 | filename=$(basename "$2") 9 | file="data/$filename" 10 | 11 | should_download=false 12 | 13 | if [ ! -f "$file" ]; then 14 | should_download=true 15 | else 16 | # File exists, check if it is an LFS pointer. 17 | # 1. Check size: LFS pointers are text files ~130 bytes. 18 | # Real SPICE kernels are binary and much larger. 19 | # We use 300 bytes as a safe upper bound for a pointer. 20 | fsize=$(wc -c < "$file" | tr -d ' ') 21 | 22 | if [ "$fsize" -lt 300 ]; then 23 | # 2. Check content: Look for the LFS signature url 24 | if grep -q "version" "$file"; then 25 | echo "Detected Git LFS pointer for $filename ($fsize bytes). Deleting..." 26 | rm "$file" 27 | should_download=true 28 | fi 29 | fi 30 | fi 31 | 32 | if [ "$should_download" = true ]; then 33 | echo "Downloading $filename..." 34 | wget -q -O "$file" "$url" 35 | else 36 | echo "Found $file (valid), skipping download." 37 | fi 38 | } 39 | download_if_missing "http://public-data.nyxspace.com/anise/de421.bsp" "de421.bsp" 40 | download_if_missing "http://public-data.nyxspace.com/anise/de430.bsp" "de430.bsp" 41 | download_if_missing "http://public-data.nyxspace.com/anise/de440s.bsp" "de440s.bsp" 42 | download_if_missing "http://public-data.nyxspace.com/anise/de440.bsp" "de440.bsp" 43 | download_if_missing "http://public-data.nyxspace.com/anise/de440_type3.bsp" "de440_type3.bsp" 44 | download_if_missing "http://public-data.nyxspace.com/anise/v0.5/pck08.pca" "pck08.pca" 45 | download_if_missing "http://public-data.nyxspace.com/anise/v0.5/pck11.pca" "pck11.pca" 46 | download_if_missing "http://public-data.nyxspace.com/anise/v0.5/moon_fk.epa" "moon_fk.epa" 47 | download_if_missing "http://public-data.nyxspace.com/anise/v0.5/moon_fk_de440.epa" "moon_fk_de440.epa" 48 | download_if_missing "http://public-data.nyxspace.com/anise/moon_pa_de440_200625.bpc" "moon_pa_de440_200625.bpc" 49 | download_if_missing "http://public-data.nyxspace.com/anise/ci/gmat-hermite.bsp" "gmat-hermite.bsp" 50 | download_if_missing "http://public-data.nyxspace.com/anise/ci/gmat-hermite-big-endian.bsp" "gmat-hermite-big-endian.bsp" 51 | download_if_missing "http://public-data.nyxspace.com/anise/ci/variable-seg-size-hermite.bsp" "variable-seg-size-hermite.bsp" 52 | download_if_missing "http://public-data.nyxspace.com/anise/ci/earth_latest_high_prec-2023-09-08.bpc" "earth_latest_high_prec.bpc" 53 | download_if_missing "http://public-data.nyxspace.com/nyx/examples/lrorg_2023349_2024075_v01_LE.bsp" "lro.bsp" 54 | download_if_missing "http://public-data.nyxspace.com/anise/ci/mro.bsp" "mro.bsp" 55 | download_if_missing "http://public-data.nyxspace.com/anise/ci/earth_2025_250826_2125_predict.bpc" "earth_2025_250826_2125_predict.bpc" 56 | -------------------------------------------------------------------------------- /anise/src/naif/daf/name_record.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; 12 | 13 | use crate::DBL_SIZE; 14 | use log::warn; 15 | 16 | use super::{DAFError, NAIFRecord, NAIFSummaryRecord, RCRD_LEN}; 17 | 18 | #[derive(IntoBytes, FromBytes, KnownLayout, Immutable, Clone, Debug)] 19 | #[repr(C)] 20 | pub struct NameRecord { 21 | raw_names: [u8; RCRD_LEN], 22 | } 23 | 24 | impl Default for NameRecord { 25 | fn default() -> Self { 26 | Self { 27 | raw_names: [0_u8; RCRD_LEN], 28 | } 29 | } 30 | } 31 | 32 | impl NAIFRecord for NameRecord {} 33 | 34 | impl NameRecord { 35 | /// Returns the maximum number of names in this record given the provided summary size. 36 | /// 37 | /// Note that we don't actually use `&self` here, but it's just easier to call. 38 | pub const fn num_entries(&self, summary_size: usize) -> usize { 39 | RCRD_LEN / (summary_size * DBL_SIZE) 40 | } 41 | 42 | pub fn nth_name(&self, n: usize, summary_size: usize) -> &str { 43 | let this_name = 44 | &self.raw_names[n * summary_size * DBL_SIZE..(n + 1) * summary_size * DBL_SIZE]; 45 | match core::str::from_utf8(this_name) { 46 | Ok(name) => name.trim(), 47 | Err(e) => { 48 | warn!("malformed name record: `{e}` from {this_name:?}!",); 49 | "UNNAMED OBJECT" 50 | } 51 | } 52 | } 53 | 54 | /// Changes the name of the n-th record 55 | pub fn set_nth_name(&mut self, n: usize, summary_size: usize, new_name: &str) { 56 | let this_name = 57 | &mut self.raw_names[n * summary_size * DBL_SIZE..(n + 1) * summary_size * DBL_SIZE]; 58 | 59 | // Copy the name (thanks Clippy) 60 | let cur_len = this_name.len(); 61 | this_name[..new_name.len().min(cur_len)] 62 | .copy_from_slice(&new_name.as_bytes()[..new_name.len().min(cur_len)]); 63 | 64 | // Set the rest of the data to spaces. 65 | for mut_char in this_name.iter_mut().skip(new_name.len()) { 66 | *mut_char = " ".as_bytes()[0]; 67 | } 68 | } 69 | 70 | /// Searches the name record for the provided name. 71 | /// 72 | /// **Warning:** this performs an O(N) search! 73 | pub fn index_from_name( 74 | &self, 75 | name: &str, 76 | summary_size: usize, 77 | ) -> Result { 78 | for i in 0..self.num_entries(summary_size) { 79 | if self.nth_name(i, summary_size) == name { 80 | return Ok(i); 81 | } 82 | } 83 | Err(DAFError::NameError { 84 | kind: R::NAME, 85 | name: name.to_string(), 86 | }) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /anise/src/naif/pck/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use crate::{ 12 | naif::daf::{NAIFRecord, NAIFSummaryRecord}, 13 | orientations::OrientationError, 14 | }; 15 | use hifitime::Epoch; 16 | use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; 17 | 18 | #[cfg(feature = "python")] 19 | use pyo3::prelude::*; 20 | 21 | use super::daf::DafDataType; 22 | 23 | #[cfg_attr(feature = "python", pyclass)] 24 | #[cfg_attr(feature = "python", pyo3(module = "anise.internals"))] 25 | #[derive(Clone, Copy, Debug, Default, IntoBytes, FromBytes, KnownLayout, Immutable, PartialEq)] 26 | #[repr(C)] 27 | pub struct BPCSummaryRecord { 28 | pub start_epoch_et_s: f64, 29 | pub end_epoch_et_s: f64, 30 | pub frame_id: i32, 31 | pub inertial_frame_id: i32, 32 | pub data_type_i: i32, 33 | pub start_idx: i32, 34 | pub end_idx: i32, 35 | pub unused: i32, 36 | } 37 | 38 | impl BPCSummaryRecord {} 39 | 40 | #[cfg(feature = "python")] 41 | #[pymethods] 42 | impl BPCSummaryRecord { 43 | /// Returns the start epoch of this BPC Summary 44 | pub fn start_epoch(&self) -> Epoch { 45 | ::start_epoch(self) 46 | } 47 | 48 | /// Returns the start epoch of this BPC Summary 49 | pub fn end_epoch(&self) -> Epoch { 50 | ::end_epoch(self) 51 | } 52 | } 53 | 54 | impl NAIFRecord for BPCSummaryRecord {} 55 | 56 | impl NAIFSummaryRecord for BPCSummaryRecord { 57 | const NAME: &'static str = "BPCSummaryRecord"; 58 | 59 | type Error = OrientationError; 60 | 61 | fn data_type(&self) -> Result { 62 | DafDataType::try_from(self.data_type_i).map_err(|source| OrientationError::BPC { 63 | action: "converting data type from i32", 64 | source, 65 | }) 66 | } 67 | 68 | fn start_index(&self) -> usize { 69 | self.start_idx as usize 70 | } 71 | 72 | fn end_index(&self) -> usize { 73 | self.end_idx as usize 74 | } 75 | 76 | fn start_epoch(&self) -> Epoch { 77 | Epoch::from_et_seconds(self.start_epoch_et_s) 78 | } 79 | 80 | fn end_epoch(&self) -> Epoch { 81 | Epoch::from_et_seconds(self.end_epoch_et_s) 82 | } 83 | 84 | fn id(&self) -> i32 { 85 | self.frame_id 86 | } 87 | 88 | fn start_epoch_et_s(&self) -> f64 { 89 | self.start_epoch_et_s 90 | } 91 | 92 | fn end_epoch_et_s(&self) -> f64 { 93 | self.end_epoch_et_s 94 | } 95 | 96 | fn update_indexes(&mut self, start: usize, end: usize) { 97 | self.start_idx = start as i32; 98 | self.end_idx = end as i32; 99 | } 100 | 101 | fn update_epochs(&mut self, start_epoch: Epoch, end_epoch: Epoch) { 102 | self.start_epoch_et_s = start_epoch.to_et_seconds(); 103 | self.end_epoch_et_s = end_epoch.to_et_seconds(); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /anise/src/math/interpolation/chebyshev.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use crate::errors::MathError; 12 | 13 | use hifitime::Epoch; 14 | 15 | use super::InterpolationError; 16 | 17 | /// Attempts to evaluate a Chebyshev polynomial given the coefficients, returning the value and its derivative 18 | /// 19 | /// # Notes 20 | /// 1. At this point, the splines are expected to be in Chebyshev format and no verification is done. 21 | pub fn chebyshev_eval( 22 | normalized_time: f64, 23 | spline_coeffs: &[f64], 24 | spline_radius_s: f64, 25 | eval_epoch: Epoch, 26 | degree: usize, 27 | ) -> Result<(f64, f64), InterpolationError> { 28 | if spline_radius_s.abs() < f64::EPSILON { 29 | return Err(InterpolationError::InterpMath { 30 | source: MathError::DivisionByZero { 31 | action: "spline radius in Chebyshev eval is zero", 32 | }, 33 | }); 34 | } 35 | // Workspace arrays 36 | let mut w = [0.0_f64; 3]; 37 | let mut dw = [0.0_f64; 3]; 38 | 39 | for j in (2..=degree + 1).rev() { 40 | w[2] = w[1]; 41 | w[1] = w[0]; 42 | w[0] = (spline_coeffs 43 | .get(j - 1) 44 | .ok_or(InterpolationError::MissingInterpolationData { epoch: eval_epoch })?) 45 | + (2.0 * normalized_time * w[1] - w[2]); 46 | 47 | dw[2] = dw[1]; 48 | dw[1] = dw[0]; 49 | dw[0] = w[1] * 2. + dw[1] * 2.0 * normalized_time - dw[2]; 50 | } 51 | 52 | let val = (spline_coeffs 53 | .first() 54 | .ok_or(InterpolationError::MissingInterpolationData { epoch: eval_epoch })?) 55 | + (normalized_time * w[0] - w[1]); 56 | 57 | let deriv = (w[0] + normalized_time * dw[0] - dw[1]) / spline_radius_s; 58 | Ok((val, deriv)) 59 | } 60 | 61 | /// Attempts to evaluate a Chebyshev polynomial given the coefficients, returning only the value 62 | /// 63 | /// # Notes 64 | /// 1. At this point, the splines are expected to be in Chebyshev format and no verification is done. 65 | pub fn chebyshev_eval_poly( 66 | normalized_time: f64, 67 | spline_coeffs: &[f64], 68 | eval_epoch: Epoch, 69 | degree: usize, 70 | ) -> Result { 71 | // Workspace array 72 | let mut w = [0.0_f64; 3]; 73 | 74 | for j in (2..=degree + 1).rev() { 75 | w[2] = w[1]; 76 | w[1] = w[0]; 77 | w[0] = (spline_coeffs 78 | .get(j - 1) 79 | .ok_or(InterpolationError::MissingInterpolationData { epoch: eval_epoch })?) 80 | + (2.0 * normalized_time * w[1] - w[2]); 81 | } 82 | 83 | // Code from chbval.c: 84 | // *p = s * w[0] - w[1] + cp[0]; 85 | // For us, s is normalized_time, cp are the spline coeffs, and w is also the workspace. 86 | 87 | let val = (normalized_time * w[0]) - w[1] 88 | + (spline_coeffs 89 | .first() 90 | .ok_or(InterpolationError::MissingInterpolationData { epoch: eval_epoch })?); 91 | 92 | Ok(val) 93 | } 94 | -------------------------------------------------------------------------------- /anise/src/lib.rs: -------------------------------------------------------------------------------- 1 | #![doc = include_str!("../README.md")] 2 | #![cfg_attr(docsrs, feature(doc_cfg))] 3 | /* 4 | * ANISE Toolkit 5 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 6 | * This Source Code Form is subject to the terms of the Mozilla Public 7 | * License, v. 2.0. If a copy of the MPL was not distributed with this 8 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 9 | * 10 | * Documentation: https://nyxspace.com/ 11 | */ 12 | 13 | extern crate const_format; 14 | extern crate hifitime; 15 | extern crate log; 16 | 17 | pub mod almanac; 18 | #[cfg(feature = "analysis")] 19 | pub mod analysis; 20 | pub mod astro; 21 | pub mod constants; 22 | pub mod ephemerides; 23 | pub mod errors; 24 | pub mod frames; 25 | pub mod math; 26 | pub mod naif; 27 | pub mod orientations; 28 | pub mod structure; 29 | 30 | /// Re-export of hifitime 31 | pub mod time { 32 | pub use core::str::FromStr; 33 | pub use hifitime::*; 34 | 35 | // Stupid but safe algo to find a new frame ID that only collides on the same microsecond 36 | pub(crate) fn uuid_from_epoch(id: i32, epoch: Epoch) -> i32 { 37 | let wrapped_days = epoch 38 | .to_tdb_duration() 39 | .to_unit(hifitime::Unit::Microsecond) 40 | .floor() 41 | .rem_euclid(f64::from(i32::MAX)) as i32; 42 | 43 | (id * 10_000).wrapping_add(wrapped_days) 44 | } 45 | } 46 | 47 | pub mod prelude { 48 | #[cfg(feature = "metaload")] 49 | pub use crate::almanac::metaload::MetaAlmanac; 50 | 51 | pub use crate::almanac::Almanac; 52 | pub use crate::astro::{orbit::Orbit, Aberration}; 53 | pub use crate::errors::InputOutputError; 54 | pub use crate::frames::*; 55 | pub use crate::math::units::*; 56 | pub use crate::naif::daf::NAIFSummaryRecord; 57 | pub use crate::naif::{BPC, SPK}; 58 | pub use crate::time::*; 59 | pub use std::fs::File; 60 | } 61 | 62 | #[cfg(feature = "python")] 63 | mod py_errors; 64 | 65 | /// Defines the number of bytes in a double (prevents magic numbers) 66 | pub(crate) const DBL_SIZE: usize = 8; 67 | 68 | /// Defines the hash used to identify parents. 69 | pub(crate) type NaifId = i32; 70 | 71 | /// Loads a file directly onto the heap, returning a BytesMut 72 | #[macro_export] 73 | macro_rules! file2heap { 74 | ($filename:tt) => { 75 | match std::fs::read($filename) { 76 | Err(e) => Err($crate::errors::InputOutputError::IOError { kind: e.kind() }), 77 | Ok(bytes) => { 78 | use bytes::BytesMut; 79 | Ok(BytesMut::from(&bytes[..])) 80 | } 81 | } 82 | }; 83 | } 84 | 85 | /// Memory maps a file and **copies** the data on the heap prior to returning a pointer to this heap data. 86 | #[macro_export] 87 | macro_rules! file_mmap { 88 | ($filename:tt) => { 89 | match File::open($filename) { 90 | Err(e) => Err(InputOutputError::IOError { kind: e.kind() }), 91 | Ok(file) => unsafe { 92 | use memmap2::MmapOptions; 93 | match MmapOptions::new().map(&file) { 94 | Err(_) => Err(InputOutputError::IOUnknownError), 95 | Ok(mmap) => Ok(mmap), 96 | } 97 | }, 98 | } 99 | }; 100 | } 101 | -------------------------------------------------------------------------------- /anise/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anise" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | edition = { workspace = true } 6 | homepage = { workspace = true } 7 | license = { workspace = true } 8 | repository = { workspace = true } 9 | description = "Core of the ANISE library" 10 | 11 | [package.metadata.docs.rs] 12 | all-features = true 13 | rustdoc-ars = ["--cfg", "docrs", "--generate-link-to-definition"] 14 | 15 | [dependencies] 16 | hifitime = { workspace = true } 17 | memmap2 = { workspace = true } 18 | crc32fast = { workspace = true } 19 | der = { workspace = true } 20 | log = { workspace = true } 21 | nalgebra = { workspace = true } 22 | zerocopy = { workspace = true } 23 | bytes = { workspace = true } 24 | snafu = { workspace = true } 25 | const_format = "0.2" 26 | serde = "1" 27 | serde_derive = "1" 28 | tabled = { workspace = true } 29 | # Optional dependencies follow 30 | pyo3 = { workspace = true, optional = true } 31 | pyo3-log = { workspace = true, optional = true } 32 | numpy = { workspace = true, optional = true } 33 | ndarray = { workspace = true, optional = true } 34 | url = { version = "2.5.0", optional = true } 35 | serde_dhall = { version = "0.13", optional = true, default-features = false } 36 | # serde_dhall = { git = "https://github.com/Nadrieril/dhall-rust/", optional = true, default-features = false } 37 | ureq = { version = "3.0.10", default-features = false, optional = true, features = [ 38 | "rustls", 39 | ] } 40 | platform-dirs = { version = "0.3.0", optional = true } 41 | rust-embed = { version = "8.4.0", features = [ 42 | "interpolate-folder-path", 43 | "include-exclude", 44 | ], optional = true } 45 | regex = { version = "1.10.5", optional = true } 46 | rayon = { workspace = true, optional = true } # Only used when building with Python or with Analysis 47 | serde-lexpr = {version = "0.1.3", optional = true} 48 | csv = {version = "1", optional = true} 49 | indexmap = "2.11.4" 50 | 51 | [dev-dependencies] 52 | rust-spice = "0.7.6" 53 | parquet = "57.1.0" 54 | arrow = "57.1.0" 55 | criterion = "0.8" 56 | iai-callgrind = "0.16" 57 | pretty_env_logger = { workspace = true } 58 | rstest = { workspace = true } 59 | approx = "0.5.1" 60 | polars = { version = "0.51.0", features = ["lazy", "parquet"] } 61 | rayon = "1.7" 62 | serde_yml = "0.0.12" 63 | rand_pcg = "0.9.0" 64 | rand = "0.9.1" 65 | 66 | [build-dependencies] 67 | ureq = { version = "3.0.10", default-features = false, optional = true, features = [ 68 | "rustls", 69 | ] } 70 | 71 | [features] 72 | default = ["metaload", "analysis"] 73 | python = ["pyo3", "pyo3-log", "numpy", "ndarray", "rayon"] 74 | metaload = ["url", "ureq", "platform-dirs", "regex", "serde_dhall"] 75 | embed_ephem = ["rust-embed", "ureq"] 76 | analysis = ["rayon", "serde-lexpr", "csv"] 77 | # Enabling this flag significantly increases compilation times due to Arrow and Polars. 78 | validation = [] 79 | 80 | [[bench]] 81 | name = "iai_jpl_ephemeris" 82 | harness = false 83 | 84 | [[bench]] 85 | name = "iai_spacecraft_ephemeris" 86 | harness = false 87 | 88 | [[bench]] 89 | name = "crit_jpl_ephemerides" 90 | harness = false 91 | 92 | [[bench]] 93 | name = "crit_spacecraft_ephemeris" 94 | harness = false 95 | 96 | [[bench]] 97 | name = "crit_bpc_rotation" 98 | harness = false 99 | 100 | [[bench]] 101 | name = "crit_planetary_data" 102 | harness = false 103 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/paths.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use std::str::FromStr; 12 | 13 | use anise::constants::celestial_objects::{EARTH_MOON_BARYCENTER, SOLAR_SYSTEM_BARYCENTER}; 14 | use anise::constants::frames::*; 15 | use anise::file2heap; 16 | use anise::prelude::*; 17 | 18 | /// Tests that direct path computations match what SPICE returned to within good precision. 19 | #[test] 20 | fn common_root_verif() { 21 | let _ = pretty_env_logger::try_init(); 22 | 23 | // SLS Launch epoch!!! IT'S LIIIIVEE!! 24 | let epoch = Epoch::from_str("2022-11-15T23:47:36+06:00").unwrap(); 25 | 26 | // Load the context 27 | // Check that this test works for DE430, DE438s (short), and DE440 28 | for path in [ 29 | "../data/de430.bsp", 30 | "../data/de440s.bsp", 31 | "../data/de440.bsp", 32 | ] { 33 | let buf = file2heap!(path).unwrap(); 34 | let spk = SPK::parse(buf).unwrap(); 35 | let ctx = Almanac::from_spk(spk); 36 | 37 | // The root of all these files should be the SSB 38 | assert_eq!( 39 | ctx.try_find_ephemeris_root().unwrap(), 40 | SOLAR_SYSTEM_BARYCENTER 41 | ); 42 | 43 | // Common root between all planets (apart from Earth) and the Moon should be the solar system barycenter 44 | for planet_ctr in &[ 45 | MERCURY_J2000, 46 | VENUS_J2000, 47 | MARS_BARYCENTER_J2000, 48 | JUPITER_BARYCENTER_J2000, 49 | SATURN_BARYCENTER_J2000, 50 | NEPTUNE_BARYCENTER_J2000, 51 | URANUS_BARYCENTER_J2000, 52 | PLUTO_BARYCENTER_J2000, 53 | ] { 54 | assert_eq!( 55 | ctx.common_ephemeris_path(*planet_ctr, MOON_J2000, epoch) 56 | .unwrap() 57 | .2, 58 | SOLAR_SYSTEM_BARYCENTER 59 | ); 60 | 61 | assert_eq!( 62 | ctx.common_ephemeris_path(MOON_J2000, *planet_ctr, epoch) 63 | .unwrap() 64 | .2, 65 | SOLAR_SYSTEM_BARYCENTER 66 | ); 67 | } 68 | 69 | // Common root between Earth and Moon should be EMB 70 | assert_eq!( 71 | ctx.common_ephemeris_path(MOON_J2000, EARTH_J2000, epoch) 72 | .unwrap() 73 | .2, 74 | EARTH_MOON_BARYCENTER 75 | ); 76 | assert_eq!( 77 | ctx.common_ephemeris_path(EARTH_J2000, MOON_J2000, epoch) 78 | .unwrap() 79 | .2, 80 | EARTH_MOON_BARYCENTER 81 | ); 82 | 83 | // Common root between EMB and Moon should be EMB 84 | assert_eq!( 85 | ctx.common_ephemeris_path(MOON_J2000, EARTH_MOON_BARYCENTER_J2000, epoch) 86 | .unwrap() 87 | .2, 88 | EARTH_MOON_BARYCENTER 89 | ); 90 | assert_eq!( 91 | ctx.common_ephemeris_path(EARTH_MOON_BARYCENTER_J2000, MOON_J2000, epoch) 92 | .unwrap() 93 | .2, 94 | EARTH_MOON_BARYCENTER 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /anise/src/math/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | // Vector3 is nalgebra's Vector3 with a 64-bit floating point representation. 12 | pub type Vector3 = nalgebra::Vector3; 13 | pub type Vector4 = nalgebra::Vector4; 14 | pub type Vector6 = nalgebra::Vector6; 15 | pub type Matrix3 = nalgebra::Matrix3; 16 | pub type Matrix6 = nalgebra::Matrix6; 17 | 18 | pub mod angles; 19 | pub mod cartesian; 20 | #[cfg(feature = "python")] 21 | mod cartesian_py; 22 | pub mod interpolation; 23 | pub mod rotation; 24 | pub mod units; 25 | 26 | use nalgebra::allocator::Allocator; 27 | use nalgebra::{DefaultAllocator, DimName, OVector}; 28 | 29 | /// Returns the root sum squared (RSS) between two vectors of any dimension N. 30 | pub fn root_sum_squared(vec_a: &OVector, vec_b: &OVector) -> f64 31 | where 32 | DefaultAllocator: Allocator, 33 | { 34 | vec_a 35 | .iter() 36 | .zip(vec_b.iter()) 37 | .map(|(&x, &y)| (x - y).powi(2)) 38 | .sum::() 39 | .sqrt() 40 | } 41 | 42 | /// Returns the root mean squared (RSS) between two vectors of any dimension N. 43 | pub fn root_mean_squared(vec_a: &OVector, vec_b: &OVector) -> f64 44 | where 45 | DefaultAllocator: Allocator, 46 | { 47 | let sum_of_squares = vec_a 48 | .iter() 49 | .zip(vec_b.iter()) 50 | .map(|(&x, &y)| (x - y).powi(2)) 51 | .sum::(); 52 | 53 | let mean_of_squares = sum_of_squares / vec_a.len() as f64; 54 | mean_of_squares.sqrt() 55 | } 56 | 57 | /// Returns the projection of a onto b 58 | /// Converted from NAIF SPICE's `projv` 59 | pub fn project_vector(a: &Vector3, b: &Vector3) -> Vector3 { 60 | b * a.dot(b) / b.dot(b) 61 | } 62 | 63 | /// Returns the components of vector a orthogonal to b 64 | /// Converted from NAIF SPICE's `prepv` 65 | pub fn perp_vector(a: &Vector3, b: &Vector3) -> Vector3 { 66 | let big_a = a[0].abs().max(a[1].abs().max(a[2].abs())); 67 | let big_b = b[0].abs().max(b[1].abs().max(b[2].abs())); 68 | if big_a < f64::EPSILON { 69 | Vector3::zeros() 70 | } else if big_b < f64::EPSILON { 71 | *a 72 | } else { 73 | let a_scl = a / big_a; 74 | let b_scl = b / big_b; 75 | let v = project_vector(&a_scl, &b_scl); 76 | big_a * (a_scl - v) 77 | } 78 | } 79 | 80 | /// Rotate the vector a around the provided axis by angle theta. 81 | pub fn rotate_vector(a: &Vector3, axis: &Vector3, theta_rad: f64) -> Vector3 { 82 | let k_hat = axis.normalize(); 83 | a.scale(theta_rad.cos()) 84 | + k_hat.cross(a).scale(theta_rad.sin()) 85 | + k_hat.scale(k_hat.dot(a) * (1.0 - theta_rad.cos())) 86 | } 87 | 88 | #[cfg(test)] 89 | mod math_ut { 90 | use super::{rotate_vector, Vector3}; 91 | #[test] 92 | fn test_rotate_vector() { 93 | use approx::assert_abs_diff_eq; 94 | let a = Vector3::new(1.0, 0.0, 0.0); 95 | let axis = Vector3::new(0.0, 0.0, 1.0); 96 | let theta_rad = std::f64::consts::PI / 2.0; 97 | let result = rotate_vector(&a, &axis, theta_rad); 98 | assert_abs_diff_eq!(result, Vector3::new(0.0, 1.0, 0.0), epsilon = 1e-7); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /anise/tests/ephemerides/validation/validate.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use polars::{lazy::dsl::Expr, prelude::*}; 12 | 13 | #[derive(Debug, Default)] 14 | pub struct Validation { 15 | pub file_name: String, 16 | pub max_q75_err: f64, 17 | pub max_q99_err: f64, 18 | pub max_abs_err: f64, 19 | } 20 | 21 | impl Validation { 22 | /// Computes the quantiles of the absolute errors in the Parquet file and asserts these are within the bounds of the validation. 23 | pub fn validate(&self) { 24 | let path = format!("../target/{}.parquet", self.file_name); 25 | // Open the parquet file with all the data 26 | let df = LazyFrame::scan_parquet(PlPath::new(&path), Default::default()).unwrap(); 27 | 28 | let abs_errors = df 29 | .clone() 30 | .select([ 31 | // Absolute difference 32 | min("Absolute difference").alias("min abs err"), 33 | col("Absolute difference") 34 | .quantile( 35 | Expr::Literal(polars::prelude::LiteralValue::Scalar(0.25.into())), 36 | QuantileMethod::Higher, 37 | ) 38 | .alias("q25 abs err"), 39 | col("Absolute difference").mean().alias("mean abs err"), 40 | col("Absolute difference").median().alias("median abs err"), 41 | col("Absolute difference") 42 | .quantile( 43 | Expr::Literal(polars::prelude::LiteralValue::Scalar(0.75.into())), 44 | QuantileMethod::Higher, 45 | ) 46 | .alias("q75 abs err"), 47 | col("Absolute difference") 48 | .quantile( 49 | Expr::Literal(polars::prelude::LiteralValue::Scalar(0.99.into())), 50 | QuantileMethod::Higher, 51 | ) 52 | .alias("q99 abs err"), 53 | max("Absolute difference").alias("max abs err"), 54 | ]) 55 | .collect() 56 | .unwrap(); 57 | println!("{abs_errors}"); 58 | 59 | // Validate results 60 | 61 | // q75 62 | let err = match abs_errors.get_row(0).unwrap().0[4] { 63 | AnyValue::Float64(val) => val, 64 | _ => unreachable!(), 65 | }; 66 | 67 | assert!( 68 | err <= self.max_q75_err, 69 | "q75 of absolute error is {err} > {}", 70 | self.max_q75_err 71 | ); 72 | 73 | // q99 74 | let err = match abs_errors.get_row(0).unwrap().0[5] { 75 | AnyValue::Float64(val) => val, 76 | _ => unreachable!(), 77 | }; 78 | 79 | assert!( 80 | err <= self.max_q99_err, 81 | "q99 of absolute error is {err} > {}", 82 | self.max_q99_err 83 | ); 84 | 85 | // max abs err 86 | let err = match abs_errors.get_row(0).unwrap().0[6] { 87 | AnyValue::Float64(val) => val, 88 | _ => unreachable!(), 89 | }; 90 | 91 | assert!( 92 | err <= self.max_abs_err, 93 | "maximum absolute error is {err} > {}", 94 | self.max_abs_err 95 | ); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /anise/src/math/interpolation/lagrange.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use crate::errors::MathError; 12 | 13 | use super::{InterpolationError, MAX_SAMPLES}; 14 | 15 | pub fn lagrange_eval( 16 | xs: &[f64], 17 | ys: &[f64], 18 | x_eval: f64, 19 | ) -> Result<(f64, f64), InterpolationError> { 20 | if xs.len() != ys.len() { 21 | return Err(InterpolationError::CorruptedData { 22 | what: "lengths of abscissas (xs), ordinates (ys), and first derivatives (ydots) differ", 23 | }); 24 | } else if xs.is_empty() { 25 | return Err(InterpolationError::CorruptedData { 26 | what: "list of abscissas (xs) is empty", 27 | }); 28 | } 29 | 30 | // At this point, we know that the lengths of items is correct, so we can directly address them without worry for overflowing the array. 31 | 32 | let work: &mut [f64] = &mut [0.0; MAX_SAMPLES]; 33 | let dwork: &mut [f64] = &mut [0.0; MAX_SAMPLES]; 34 | for (ii, y) in ys.iter().enumerate() { 35 | work[ii] = *y; 36 | } 37 | 38 | let n = xs.len(); 39 | 40 | for j in 1..n { 41 | for i in 0..(n - j) { 42 | let xi = xs[i]; 43 | let xij = xs[i + j]; 44 | 45 | let denom = xi - xij; 46 | if denom.abs() < f64::EPSILON { 47 | return Err(InterpolationError::InterpMath { 48 | source: MathError::DivisionByZero { 49 | action: "lagrange data contains duplicate states", 50 | }, 51 | }); 52 | } 53 | 54 | let work_i = work[i]; 55 | let work_ip1 = work[i + 1]; 56 | 57 | work[i] = ((x_eval - xij) * work_i + (xi - x_eval) * work_ip1) / denom; 58 | 59 | let deriv = (work_i - work_ip1) / denom; 60 | dwork[i] = ((x_eval - xij) * dwork[i] + (xi - x_eval) * dwork[i + 1]) / denom + deriv; 61 | } 62 | } 63 | 64 | let f = work[0]; 65 | let df = dwork[0]; 66 | Ok((f, df)) 67 | } 68 | 69 | #[test] 70 | fn lagrange_spice_docs_example() { 71 | let ts = [-1.0, 0.0, 3.0, 5.0]; 72 | let yvals = [-2.0, -7.0, -8.0, 26.0]; 73 | let dyvals = [ 74 | -4.633_333_333_333_334, 75 | -4.983_333_333_333_333, 76 | 7.766_666_666_666_667, 77 | 27.766_666_666_666_666, 78 | ]; 79 | 80 | // Check that we can interpolate the values exactly. 81 | for (i, t) in ts.iter().enumerate() { 82 | let (eval, deval) = lagrange_eval(&ts, &yvals, *t).unwrap(); 83 | let eval_err = (eval - yvals[i]).abs(); 84 | assert!(eval_err < f64::EPSILON, "f(x) error is {eval_err:e}"); 85 | 86 | let deval_err = (deval - dyvals[i]).abs(); 87 | assert!( 88 | deval_err < f64::EPSILON, 89 | "#{i}: f'(x) error is {deval_err:e}" 90 | ); 91 | } 92 | 93 | // Check the interpolation from the SPICE documentation 94 | let (x, dx) = lagrange_eval(&ts, &yvals, 2.0).unwrap(); 95 | 96 | // WARNING: The documentation data is wrong! Evaluation at 2.0 returns -12.2999... in spiceypy. 97 | let expected_x = -12.299999999999999; 98 | let expected_dx = 1.2166666666666666; 99 | dbg!(x, dx); 100 | assert!((x - expected_x).abs() < f64::EPSILON, "X error"); 101 | assert!((dx - expected_dx).abs() < f64::EPSILON, "dX error"); 102 | } 103 | -------------------------------------------------------------------------------- /anise-gui/src/epa.rs: -------------------------------------------------------------------------------- 1 | use anise::prelude::Almanac; 2 | use egui_extras::{Column, TableBuilder}; 3 | 4 | pub fn epa_ui(ui: &mut egui::Ui, almanac: &Almanac) { 5 | TableBuilder::new(ui) 6 | .column(Column::auto().at_least(100.0).resizable(true)) 7 | .column(Column::auto().at_least(75.0).resizable(true)) 8 | .column(Column::auto().at_least(75.0).resizable(true)) 9 | .column(Column::auto().at_least(75.0).resizable(true)) 10 | .column(Column::auto().at_least(75.0).resizable(true)) 11 | .column(Column::auto().at_least(75.0).resizable(true)) 12 | .column(Column::auto().at_least(75.0).resizable(true)) 13 | .column(Column::remainder()) 14 | .header(20.0, |mut header| { 15 | header.col(|ui| { 16 | ui.heading("Name"); 17 | }); 18 | header.col(|ui| { 19 | ui.heading("ID"); 20 | }); 21 | header.col(|ui| { 22 | ui.heading("Quat w"); 23 | }); 24 | 25 | header.col(|ui| { 26 | ui.heading("Quat x"); 27 | }); 28 | header.col(|ui| { 29 | ui.heading("Quat y"); 30 | }); 31 | header.col(|ui| { 32 | ui.heading("Quat z"); 33 | }); 34 | 35 | header.col(|ui| { 36 | ui.heading("From ID"); 37 | }); 38 | header.col(|ui| { 39 | ui.heading("To ID"); 40 | }); 41 | }) 42 | .body(|mut body| { 43 | let epa = &almanac.euler_param_data.values().next().unwrap(); 44 | 45 | let binding = epa.lut.entries(); 46 | let mut values = binding.values().collect::>().to_vec(); 47 | values.sort_by_key(|(opt_id, _)| match opt_id { 48 | Some(id) => *id, 49 | None => 0, 50 | }); 51 | 52 | for (opt_id, opt_name) in values { 53 | let data = if let Some(id) = opt_id { 54 | epa.get_by_id(*id).unwrap() 55 | } else { 56 | epa.get_by_name(&opt_name.clone().unwrap()).unwrap() 57 | }; 58 | 59 | body.row(30.0, |mut row| { 60 | row.col(|ui| { 61 | ui.label(match opt_name { 62 | Some(name) => name.to_string(), 63 | None => "Unset".to_string(), 64 | }); 65 | }); 66 | 67 | row.col(|ui| { 68 | ui.label(match opt_id { 69 | Some(id) => format!("{id}"), 70 | None => "Unset".to_string(), 71 | }); 72 | }); 73 | 74 | row.col(|ui| { 75 | ui.text_edit_singleline(&mut format!("{}", data.w)); 76 | }); 77 | 78 | row.col(|ui| { 79 | ui.text_edit_singleline(&mut format!("{}", data.x)); 80 | }); 81 | 82 | row.col(|ui| { 83 | ui.text_edit_singleline(&mut format!("{}", data.y)); 84 | }); 85 | 86 | row.col(|ui| { 87 | ui.text_edit_singleline(&mut format!("{}", data.z)); 88 | }); 89 | 90 | row.col(|ui| { 91 | ui.text_edit_singleline(&mut format!("{}", data.from)); 92 | }); 93 | 94 | row.col(|ui| { 95 | ui.text_edit_singleline(&mut format!("{}", data.to)); 96 | }); 97 | }) 98 | } 99 | }); 100 | } 101 | -------------------------------------------------------------------------------- /anise/src/frames/frameuid.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use crate::{ 12 | constants::{ 13 | celestial_objects::celestial_name_from_id, orientations::orientation_name_from_id, 14 | }, 15 | NaifId, 16 | }; 17 | use core::fmt; 18 | use der::{Decode, Encode, Reader, Writer}; 19 | use serde::{Deserialize, Serialize}; 20 | 21 | pub use super::Frame; 22 | 23 | #[cfg(feature = "analysis")] 24 | use serde_dhall::StaticType; 25 | 26 | #[cfg(feature = "python")] 27 | use pyo3::prelude::*; 28 | 29 | /// A unique frame reference that only contains enough information to build the actual Frame object. 30 | /// It cannot be used for any computations, is it be used in any structure apart from error structures. 31 | /// 32 | /// :type ephemeris_id: int 33 | /// :type orientation_id: int 34 | #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Serialize, Deserialize)] 35 | #[cfg_attr(feature = "analysis", derive(StaticType))] 36 | #[cfg_attr(feature = "python", pyclass)] 37 | #[cfg_attr(feature = "python", pyo3(module = "anise.astro"))] 38 | pub struct FrameUid { 39 | pub ephemeris_id: NaifId, 40 | pub orientation_id: NaifId, 41 | } 42 | 43 | impl From for FrameUid { 44 | fn from(frame: Frame) -> Self { 45 | Self { 46 | ephemeris_id: frame.ephemeris_id, 47 | orientation_id: frame.orientation_id, 48 | } 49 | } 50 | } 51 | 52 | impl From<&Frame> for FrameUid { 53 | fn from(frame: &Frame) -> Self { 54 | Self { 55 | ephemeris_id: frame.ephemeris_id, 56 | orientation_id: frame.orientation_id, 57 | } 58 | } 59 | } 60 | 61 | impl From for Frame { 62 | fn from(uid: FrameUid) -> Self { 63 | Self::new(uid.ephemeris_id, uid.orientation_id) 64 | } 65 | } 66 | 67 | impl From<&FrameUid> for Frame { 68 | fn from(uid: &FrameUid) -> Self { 69 | Self::new(uid.ephemeris_id, uid.orientation_id) 70 | } 71 | } 72 | 73 | impl fmt::Display for FrameUid { 74 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { 75 | let body_name = match celestial_name_from_id(self.ephemeris_id) { 76 | Some(name) => name.to_string(), 77 | None => format!("body {}", self.ephemeris_id), 78 | }; 79 | 80 | let orientation_name = match orientation_name_from_id(self.orientation_id) { 81 | Some(name) => name.to_string(), 82 | None => format!("orientation {}", self.orientation_id), 83 | }; 84 | 85 | write!(f, "{body_name} {orientation_name}") 86 | } 87 | } 88 | 89 | impl Encode for FrameUid { 90 | fn encoded_len(&self) -> der::Result { 91 | self.ephemeris_id.encoded_len()? + self.orientation_id.encoded_len()? 92 | } 93 | 94 | fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> { 95 | self.ephemeris_id.encode(encoder)?; 96 | self.orientation_id.encode(encoder) 97 | } 98 | } 99 | 100 | impl<'a> Decode<'a> for FrameUid { 101 | fn decode>(decoder: &mut R) -> der::Result { 102 | Ok(Self { 103 | ephemeris_id: decoder.decode()?, 104 | orientation_id: decoder.decode()?, 105 | }) 106 | } 107 | } 108 | 109 | #[cfg(feature = "python")] 110 | #[cfg_attr(feature = "python", pymethods)] 111 | impl FrameUid { 112 | #[new] 113 | fn py_new(ephemeris_id: NaifId, orientation_id: NaifId) -> Self { 114 | Self { 115 | ephemeris_id, 116 | orientation_id, 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /anise-py/src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use ::anise::almanac::metaload::{MetaAlmanac, MetaFile}; 12 | use ::anise::almanac::Almanac; 13 | use ::anise::analysis::prelude::{ 14 | find_arc_intersections, Condition, Event, EventArc, EventDetails, EventEdge, OrbitalElement, 15 | Plane, VisibilityArc, 16 | }; 17 | use ::anise::analysis::python::{ 18 | PyFrameSpec, PyOrthogonalFrame, PyScalarExpr, PyStateSpec, PyVectorExpr, 19 | }; 20 | use ::anise::analysis::report::PyReportScalars; 21 | use ::anise::astro::Aberration; 22 | use ::anise::structure::dataset::location_dhall::PyLocationDataSet; 23 | use ::anise::structure::dataset::location_dhall::{LocationDhallSet, LocationDhallSetEntry}; 24 | use hifitime::leap_seconds::{LatestLeapSeconds, LeapSecondsFile}; 25 | use hifitime::python::{PyDurationError, PyHifitimeError, PyParsingError}; 26 | use hifitime::ut1::Ut1Provider; 27 | use hifitime::{prelude::*, MonthName, Polynomial}; 28 | 29 | use pyo3::{prelude::*, wrap_pyfunction, wrap_pymodule}; 30 | 31 | mod astro; 32 | mod bin; 33 | mod constants; 34 | mod rotation; 35 | mod utils; 36 | 37 | /// A Python module implemented in Rust. 38 | #[pymodule] 39 | #[pyo3(name = "_anise")] 40 | fn anise(m: &Bound<'_, PyModule>) -> PyResult<()> { 41 | pyo3_log::init(); 42 | m.add_wrapped(wrap_pymodule!(time))?; 43 | m.add_wrapped(wrap_pymodule!(analysis))?; 44 | m.add_wrapped(wrap_pymodule!(astro::astro))?; 45 | m.add_wrapped(wrap_pymodule!(constants::constants))?; 46 | m.add_wrapped(wrap_pymodule!(utils::utils))?; 47 | m.add_wrapped(wrap_pymodule!(rotation::rotation))?; 48 | m.add_wrapped(wrap_pyfunction!(bin::exec_gui))?; 49 | 50 | m.add_class::()?; 51 | m.add_class::()?; 52 | m.add_class::()?; 53 | m.add_class::()?; 54 | m.add_class::()?; 55 | m.add_class::()?; 56 | m.add_class::()?; 57 | 58 | m.add("__version__", env!("CARGO_PKG_VERSION"))?; 59 | m.add("__doc__", env!("CARGO_PKG_DESCRIPTION"))?; 60 | m.add("__author__", env!("CARGO_PKG_AUTHORS"))?; 61 | 62 | Ok(()) 63 | } 64 | 65 | #[pymodule] 66 | fn analysis(_py: Python, sm: &Bound) -> PyResult<()> { 67 | sm.add_class::()?; 68 | sm.add_class::()?; 69 | sm.add_class::()?; 70 | sm.add_class::()?; 71 | sm.add_class::()?; 72 | sm.add_class::()?; 73 | sm.add_class::()?; 74 | sm.add_class::()?; 75 | sm.add_class::()?; 76 | sm.add_class::()?; 77 | sm.add_class::()?; 78 | sm.add_class::()?; 79 | sm.add_class::()?; 80 | sm.add_class::()?; 81 | sm.add_wrapped(wrap_pyfunction!(find_arc_intersections))?; 82 | Ok(()) 83 | } 84 | 85 | /// Reexport hifitime as anise.time 86 | #[pymodule] 87 | fn time(_py: Python, sm: &Bound) -> PyResult<()> { 88 | sm.add_class::()?; 89 | sm.add_class::()?; 90 | sm.add_class::()?; 91 | sm.add_class::()?; 92 | sm.add_class::()?; 93 | sm.add_class::()?; 94 | sm.add_class::()?; 95 | sm.add_class::()?; 96 | sm.add_class::()?; 97 | sm.add_class::()?; 98 | sm.add_class::()?; 99 | sm.add_class::()?; 100 | sm.add_class::()?; 101 | sm.add_class::()?; 102 | Ok(()) 103 | } 104 | -------------------------------------------------------------------------------- /anise/src/astro/orbit_equinoctial.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use super::{orbit::Orbit, PhysicsResult}; 12 | 13 | use crate::prelude::Frame; 14 | 15 | use hifitime::Epoch; 16 | 17 | #[cfg(feature = "python")] 18 | use pyo3::prelude::*; 19 | 20 | pub(crate) fn equinoctial_to_keplerian( 21 | sma_km: f64, 22 | h: f64, 23 | k: f64, 24 | p: f64, 25 | q: f64, 26 | lambda_deg: f64, 27 | ) -> (f64, f64, f64, f64, f64, f64) { 28 | let ecc = (h * h + k * k).sqrt(); 29 | 30 | let s_sq: f64 = p * p + q * q; // sin^2(i/2) 31 | 32 | // Handle potential rounding errors > 1.0 33 | let inc_rad = if s_sq <= 1.0 { 34 | (1.0 - 2.0 * s_sq).acos() 35 | } else { 36 | (1.0 - 2.0_f64).acos() // Match C++ logic 37 | }; 38 | let inc_deg = inc_rad.to_degrees(); 39 | 40 | let raan_deg = p.atan2(q).to_degrees(); 41 | let aop_plus_raan = h.atan2(k).to_degrees(); 42 | 43 | let aop_deg = aop_plus_raan - raan_deg; 44 | let ma_deg = lambda_deg - aop_plus_raan; 45 | 46 | (sma_km, ecc, inc_deg, raan_deg, aop_deg, ma_deg) 47 | } 48 | 49 | impl Orbit { 50 | /// Attempts to create a new Orbit from the Equinoctial orbital elements. 51 | /// 52 | /// Note that this function computes the Keplerian elements from the equinoctial and then 53 | /// calls the try_keplerian_mean_anomaly initializer. 54 | #[allow(clippy::too_many_arguments)] 55 | #[allow(clippy::neg_multiply)] 56 | pub fn try_equinoctial( 57 | sma_km: f64, 58 | h: f64, 59 | k: f64, 60 | p: f64, 61 | q: f64, 62 | lambda_deg: f64, 63 | epoch: Epoch, 64 | frame: Frame, 65 | ) -> PhysicsResult { 66 | let (sma_km, ecc, inc_deg, raan_deg, aop_deg, ma_deg) = 67 | equinoctial_to_keplerian(sma_km, h, k, p, q, lambda_deg); 68 | 69 | Self::try_keplerian_mean_anomaly( 70 | sma_km, ecc, inc_deg, raan_deg, aop_deg, ma_deg, epoch, frame, 71 | ) 72 | } 73 | } 74 | 75 | #[cfg_attr(feature = "python", pymethods)] 76 | impl Orbit { 77 | /// Returns the equinoctial semi-major axis (a) in km. 78 | /// 79 | /// :rtype: float 80 | pub fn equinoctial_a_km(&self) -> PhysicsResult { 81 | self.sma_km() 82 | } 83 | 84 | /// Returns the equinoctial element h (ecc * sin(aop + raan)). 85 | /// 86 | /// :rtype: float 87 | pub fn equinoctial_h(&self) -> PhysicsResult { 88 | Ok(self.ecc()? * (self.aop_deg()?.to_radians() + self.raan_deg()?.to_radians()).sin()) 89 | } 90 | 91 | /// Returns the equinoctial element k (ecc * cos(aop + raan)). 92 | /// 93 | /// :rtype: float 94 | pub fn equinoctial_k(&self) -> PhysicsResult { 95 | Ok(self.ecc()? * (self.aop_deg()?.to_radians() + self.raan_deg()?.to_radians()).cos()) 96 | } 97 | 98 | /// Returns the equinoctial element p (sin(inc/2) * sin(raan)). 99 | /// 100 | /// :rtype: float 101 | pub fn equinoctial_p(&self) -> PhysicsResult { 102 | Ok((self.inc_deg()?.to_radians() / 2.0).sin() * self.raan_deg()?.to_radians().sin()) 103 | } 104 | 105 | /// Returns the equinoctial element q (sin(inc/2) * cos(raan)). 106 | /// 107 | /// :rtype: float 108 | pub fn equinoctial_q(&self) -> PhysicsResult { 109 | Ok((self.inc_deg()?.to_radians() / 2.0).sin() * self.raan_deg()?.to_radians().cos()) 110 | } 111 | 112 | /// Returns the equinoctial mean longitude (lambda = raan + aop + ma) in degrees. 113 | /// 114 | /// :rtype: float 115 | pub fn equinoctial_lambda_mean_deg(&self) -> PhysicsResult { 116 | // C++ version `aeq[5]=kep[3]+kep[4]+kep[5]` sums degrees. 117 | Ok(self.raan_deg()? + self.aop_deg()? + self.ma_deg()?) 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /anise/tests/astro/eclipsing.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | 11 | use core::f64; 12 | 13 | use anise::constants::frames::EARTH_J2000; 14 | use anise::prelude::*; 15 | 16 | use rstest::*; 17 | 18 | #[fixture] 19 | pub fn almanac() -> Almanac { 20 | use std::path::PathBuf; 21 | 22 | let manifest_dir = 23 | PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap_or(".".to_string())); 24 | 25 | Almanac::new( 26 | &manifest_dir 27 | .clone() 28 | .join("../data/de440s.bsp") 29 | .to_string_lossy(), 30 | ) 31 | .unwrap() 32 | .load( 33 | &manifest_dir 34 | .clone() 35 | .join("../data/pck08.pca") 36 | .to_string_lossy(), 37 | ) 38 | .unwrap() 39 | } 40 | 41 | /// Computes the beta angle, checks that it remains stable throughout a two body propagation (as it should since it's based on the angular momentum). 42 | /// Importantly, this does not verify the implementation with known values, simply verifies the computation in a few regimes. 43 | /// The beta angle code is identical to that from GMAT 44 | #[rstest] 45 | fn verif_beta_angle_eclipse_time(almanac: Almanac) { 46 | let epoch = Epoch::from_gregorian_utc_at_midnight(2024, 1, 1); 47 | let eme2k = almanac.frame_info(EARTH_J2000).unwrap(); 48 | let raan_deg = 72.0; 49 | let aop_deg = 45.0; 50 | let ta_deg = 270.0; 51 | 52 | for alt_km in [500.0, 1500.0, 22000.0, 36000.0, 50000.0] { 53 | for inc_deg in [1.0, 18.0, 36.0, 54.0, 72.0, 90.0] { 54 | // Initialize an orbit at the provided inclination and altitude 55 | let ecc = inc_deg * 1e-2; 56 | let orbit = Orbit::try_keplerian_altitude( 57 | alt_km, ecc, inc_deg, raan_deg, aop_deg, ta_deg, epoch, eme2k, 58 | ) 59 | .unwrap_or_else(|_| { 60 | panic!("init failed with alt_km = {alt_km}; inc_deg = {inc_deg}; ecc = {ecc}") 61 | }); 62 | 63 | let mut eclipse_duration = 0.0.seconds(); 64 | let mut sum_beta_angles = 0.0; 65 | let mut count = 0; 66 | let step = 1.minutes(); 67 | // Two body propagation of a single orbit, computing whether we're in eclipse or not. 68 | for new_epoch in TimeSeries::exclusive(epoch, epoch + orbit.period().unwrap(), step) { 69 | count += 1; 70 | // Compute the solar eclipsing 71 | let occult = almanac 72 | .solar_eclipsing( 73 | EARTH_J2000, 74 | orbit.at_epoch(new_epoch).expect("two body prop failed"), 75 | None, 76 | ) 77 | .unwrap(); 78 | if occult.is_obstructed() { 79 | eclipse_duration += step; 80 | } 81 | sum_beta_angles += almanac 82 | .beta_angle_deg(orbit, None) 83 | .expect("beta angle failed"); 84 | } 85 | let beta_angle = almanac 86 | .beta_angle_deg(orbit, None) 87 | .expect("beta angle failed"); 88 | let avr_beta_angle = sum_beta_angles / (count as f64); 89 | 90 | println!( 91 | "beta angle = {beta_angle:.6} deg (avr. of {avr_beta_angle:.6} deg)\teclipse duration = {eclipse_duration} (+/- 2 min)" 92 | ); 93 | 94 | assert!( 95 | (avr_beta_angle - beta_angle).abs() < 1e-12, 96 | "beta angle should not vary over an orbit: avr = {avr_beta_angle} deg\tinst.: {beta_angle}" 97 | ); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /anise/fuzz/README.md: -------------------------------------------------------------------------------- 1 | # Fuzz Testing for Anise 2 | 3 | This directory contains fuzz tests for the Anise project, using [`cargo-fuzz`](https://github.com/rust-fuzz/cargo-fuzz) and [`libFuzzer`](https://llvm.org/docs/LibFuzzer.html). Fuzzing helps uncover bugs and security issues by running randomized inputs through your code. 4 | 5 | --- 6 | 7 | ## Prerequisites 8 | 9 | - **Rust nightly toolchain** (required by `cargo-fuzz`) 10 | - **cargo-fuzz** (install with `cargo install cargo-fuzz`) 11 | - (Optional) **LLVM tools** for advanced debugging 12 | 13 | --- 14 | 15 | ## Setup 16 | 17 | 1. **Install Rust Nightly:** 18 | ```sh 19 | rustup toolchain install nightly 20 | rustup override set nightly 21 | ``` 22 | 23 | 2. **Install cargo-fuzz:** 24 | ```sh 25 | cargo install cargo-fuzz 26 | ``` 27 | 28 | --- 29 | 30 | ## Building Fuzz Targets 31 | 32 | Fuzz targets must be built from within the `anise/anise` folder consisting of the Rust code. To build all fuzz targets: 33 | ```sh 34 | cargo fuzz build 35 | ``` 36 | Or build a specific target: 37 | ```sh 38 | cargo fuzz build 39 | ``` 40 | 41 | --- 42 | 43 | ## Running Fuzz Tests 44 | 45 | Fuzz targets must be run from within the `anise/anise` folder consisting of the Rust code. To run a fuzz target: 46 | ```sh 47 | cargo fuzz run 48 | ``` 49 | You can limit runtime (in seconds) with: 50 | ```sh 51 | cargo fuzz run -- -max_total_time=60 52 | ``` 53 | 54 | --- 55 | 56 | ## Debugging Fuzz Failures 57 | 58 | When a crash or bug is found, a minimized test case will be saved in the `artifacts/` directory. To debug: 59 | 1. Run the target with the crashing input: 60 | ```sh 61 | cargo fuzz run artifacts// 62 | ``` 63 | 2. Use `RUST_BACKTRACE=1` for stack traces: 64 | ```sh 65 | RUST_BACKTRACE=1 cargo fuzz run artifacts// 66 | ``` 67 | 68 | --- 69 | 70 | ## Adding New Fuzz Targets 71 | 72 | 1. Create a new file in [`fuzz_targets/`](fuzz_targets/) (e.g., `my_target.rs`). 73 | 2. Implement a `fuzz_target!` macro as in other targets. 74 | 3. Register the new target in [`Cargo.toml`](Cargo.toml) under `[[bin]]`: 75 | ```toml 76 | [[bin]] 77 | name = "my_target" 78 | path = "fuzz_targets/my_target.rs" 79 | test = false 80 | doc = false 81 | bench = false 82 | ``` 83 | 4. (Optional) If using a custom structure, update [`src/lib.rs`](src/lib.rs) to include arbitrary structure. 84 | 5. (Optional) Add a seed corpus in the [`corpus/`](corpus/) directory. 85 | 86 | --- 87 | 88 | ## OSS-Fuzz Integration 89 | 90 | This fuzz suite is integrated with [OSS-Fuzz](https://github.com/google/oss-fuzz) for continuous fuzzing on Google's infrastructure. 91 | 92 | - **Build system:** OSS-Fuzz uses the same `cargo-fuzz` targets defined here. 93 | - **Adding/Removing Targets:** Update both this repo and the [OSS-Fuzz project YAML](https://github.com/google/oss-fuzz/tree/master/projects/anise) as needed. 94 | - **Corpus/Artifacts:** OSS-Fuzz manages its own corpus and crash artifacts, but you can sync with local corpora for better coverage. 95 | - **Updating Dependencies:** Keep dependencies in sync with upstream to avoid build failures in OSS-Fuzz. 96 | - **Contact:** If you update the fuzz targets or dependencies, notify the OSS-Fuzz maintainers via a pull request or issue. 97 | 98 | For more details, see the [OSS-Fuzz documentation](https://google.github.io/oss-fuzz/). 99 | 100 | --- 101 | 102 | ## Directory Structure 103 | 104 | - [`fuzz_targets/`](fuzz_targets/): Individual fuzz target entrypoints. 105 | - [`src/lib.rs`](src/lib.rs): Shared fuzzing utilities and types. 106 | - [`corpus/`](corpus/): Optional seed corpora for each target. 107 | - [`artifacts/`](artifacts/): Crash/minimized test cases. 108 | - [`Cargo.toml`](Cargo.toml): Fuzz target registration. 109 | 110 | --- 111 | 112 | ## References 113 | 114 | - [cargo-fuzz documentation](https://rust-fuzz.github.io/book/cargo-fuzz.html) 115 | - [libFuzzer documentation](https://llvm.org/docs/LibFuzzer.html) 116 | - [Structure-Aware fuzzing documentation](https://rust-fuzz.github.io/book/cargo-fuzz/structure-aware-fuzzing.html) 117 | - [OSS-Fuzz documentation](https://google.github.io/oss-fuzz/) 118 | 119 | --- 120 | 121 | Feel free to open issues or pull requests to improve the fuzzing setup! 122 | -------------------------------------------------------------------------------- /anise/src/structure/spacecraft/inertia.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * ANISE Toolkit 3 | * Copyright (C) 2021-onward Christopher Rabotin et al. (cf. AUTHORS.md) 4 | * This Source Code Form is subject to the terms of the Mozilla Public 5 | * License, v. 2.0. If a copy of the MPL was not distributed with this 6 | * file, You can obtain one at https://mozilla.org/MPL/2.0/. 7 | * 8 | * Documentation: https://nyxspace.com/ 9 | */ 10 | use der::{Decode, Encode, Reader, Writer}; 11 | use nalgebra::Matrix3; 12 | use serde_derive::{Deserialize, Serialize}; 13 | 14 | use crate::NaifId; 15 | 16 | /// Inertial tensor definition 17 | #[derive(Copy, Clone, Debug, Default, PartialEq, Serialize, Deserialize)] 18 | pub struct Inertia { 19 | /// Inertia tensor reference frame hash 20 | pub orientation_id: NaifId, 21 | /// Moment of inertia about the X axis, in kg \cdot m^2 22 | pub i_xx_kgm2: f64, 23 | /// Moment of inertia about the Y axis, in kg \cdot m^2 24 | pub i_yy_kgm2: f64, 25 | /// Moment of inertia about the Z axis, in kg \cdot m^2 26 | pub i_zz_kgm2: f64, 27 | /// Inertia cross product of the X and Y axes, in kg \cdot m^2 28 | pub i_xy_kgm2: f64, 29 | /// Inertia cross product of the X and Y axes, in kg \cdot m^2 30 | pub i_xz_kgm2: f64, 31 | /// Inertia cross product of the Y and Z axes, in kg \cdot m^2 32 | pub i_yz_kgm2: f64, 33 | } 34 | 35 | impl Inertia { 36 | pub fn tensor_kgm2(&self) -> Matrix3 { 37 | Matrix3::new( 38 | self.i_xx_kgm2, 39 | self.i_xy_kgm2, 40 | self.i_xz_kgm2, 41 | self.i_xy_kgm2, 42 | self.i_yy_kgm2, 43 | self.i_yz_kgm2, 44 | self.i_xz_kgm2, 45 | self.i_yz_kgm2, 46 | self.i_zz_kgm2, 47 | ) 48 | } 49 | } 50 | 51 | impl Encode for Inertia { 52 | fn encoded_len(&self) -> der::Result { 53 | self.orientation_id.encoded_len()? 54 | + self.i_xx_kgm2.encoded_len()? 55 | + self.i_yy_kgm2.encoded_len()? 56 | + self.i_zz_kgm2.encoded_len()? 57 | + self.i_xy_kgm2.encoded_len()? 58 | + self.i_xz_kgm2.encoded_len()? 59 | + self.i_yz_kgm2.encoded_len()? 60 | } 61 | 62 | fn encode(&self, encoder: &mut impl Writer) -> der::Result<()> { 63 | self.orientation_id.encode(encoder)?; 64 | self.i_xx_kgm2.encode(encoder)?; 65 | self.i_yy_kgm2.encode(encoder)?; 66 | self.i_zz_kgm2.encode(encoder)?; 67 | self.i_xy_kgm2.encode(encoder)?; 68 | self.i_xz_kgm2.encode(encoder)?; 69 | self.i_yz_kgm2.encode(encoder) 70 | } 71 | } 72 | 73 | impl<'a> Decode<'a> for Inertia { 74 | fn decode>(decoder: &mut R) -> der::Result { 75 | Ok(Self { 76 | orientation_id: decoder.decode()?, 77 | i_xx_kgm2: decoder.decode()?, 78 | i_yy_kgm2: decoder.decode()?, 79 | i_zz_kgm2: decoder.decode()?, 80 | i_xy_kgm2: decoder.decode()?, 81 | i_xz_kgm2: decoder.decode()?, 82 | i_yz_kgm2: decoder.decode()?, 83 | }) 84 | } 85 | } 86 | 87 | #[cfg(test)] 88 | mod inertia_ut { 89 | use super::{Decode, Encode, Inertia, Matrix3}; 90 | #[test] 91 | fn example_repr() { 92 | let repr = Inertia { 93 | // Spacecraft IDs are typically negative 94 | orientation_id: -20, 95 | i_xx_kgm2: 120.0, 96 | i_yy_kgm2: 180.0, 97 | i_zz_kgm2: 220.0, 98 | i_xy_kgm2: 20.0, 99 | i_xz_kgm2: -15.0, 100 | i_yz_kgm2: 30.0, 101 | }; 102 | 103 | let mut buf = vec![]; 104 | repr.encode_to_vec(&mut buf).unwrap(); 105 | 106 | let repr_dec = Inertia::from_der(&buf).unwrap(); 107 | 108 | assert_eq!(repr, repr_dec); 109 | 110 | let tensor = Matrix3::new(120.0, 20.0, -15.0, 20.0, 180.0, 30.0, -15.0, 30.0, 220.0); 111 | assert_eq!(tensor, repr.tensor_kgm2()); 112 | } 113 | 114 | #[test] 115 | fn default_repr() { 116 | let repr = Inertia::default(); 117 | 118 | let mut buf = vec![]; 119 | repr.encode_to_vec(&mut buf).unwrap(); 120 | 121 | let repr_dec = Inertia::from_der(&buf).unwrap(); 122 | 123 | assert_eq!(repr, repr_dec); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /anise/src/structure/dataset/pretty_print.rs: -------------------------------------------------------------------------------- 1 | use tabled::{settings::Style, Table, Tabled}; 2 | 3 | use crate::structure::{EulerParameterDataSet, LocationDataSet}; 4 | 5 | use super::NaifId; 6 | 7 | #[derive(Tabled, Default)] 8 | struct EulerParamRow { 9 | #[tabled(rename = "Name")] 10 | name: String, 11 | #[tabled(rename = "ID")] 12 | id: String, 13 | #[tabled(rename = "Quat w")] 14 | qw: f64, 15 | #[tabled(rename = "Quat x")] 16 | qx: f64, 17 | #[tabled(rename = "Quat y")] 18 | qy: f64, 19 | #[tabled(rename = "Quat z")] 20 | qz: f64, 21 | #[tabled(rename = "To ID")] 22 | to: NaifId, 23 | #[tabled(rename = "From ID")] 24 | from: NaifId, 25 | } 26 | 27 | impl EulerParameterDataSet { 28 | /// Returns a table describing this planetary data set 29 | pub fn describe(&self) -> String { 30 | let binding = self.lut.entries(); 31 | let mut values = binding.values().collect::>().to_vec(); 32 | values.sort_by_key(|(opt_id, _)| match opt_id { 33 | Some(id) => *id, 34 | None => 0, 35 | }); 36 | 37 | let mut rows = Vec::new(); 38 | 39 | for (opt_id, opt_name) in values { 40 | let data = if let Some(id) = opt_id { 41 | self.get_by_id(*id).unwrap() 42 | } else { 43 | self.get_by_name(&opt_name.clone().unwrap()).unwrap() 44 | }; 45 | 46 | let row = EulerParamRow { 47 | name: match opt_name { 48 | Some(name) => name.clone(), 49 | None => "Unset".to_string(), 50 | }, 51 | id: match opt_id { 52 | Some(id) => format!("{id}"), 53 | None => "Unset".to_string(), 54 | }, 55 | qw: data.w, 56 | qx: data.x, 57 | qy: data.y, 58 | qz: data.z, 59 | to: data.to, 60 | from: data.from, 61 | }; 62 | 63 | rows.push(row); 64 | } 65 | 66 | let mut tbl = Table::new(rows); 67 | tbl.with(Style::modern()); 68 | format!("{tbl}") 69 | } 70 | } 71 | 72 | #[derive(Tabled, Default)] 73 | struct LocationRow { 74 | #[tabled(rename = "Name")] 75 | name: String, 76 | #[tabled(rename = "ID")] 77 | id: String, 78 | #[tabled(rename = "Latitude (deg)")] 79 | latitude_deg: f64, 80 | #[tabled(rename = "Longitude (deg)")] 81 | longitude_deg: f64, 82 | #[tabled(rename = "Height (km)")] 83 | height_km: f64, 84 | #[tabled(rename = "Terrain Mask ?")] 85 | has_terrain_mask: bool, 86 | #[tabled(rename = "Terrain Mask Ignored")] 87 | terrain_mask_ignored: bool, 88 | } 89 | 90 | impl LocationDataSet { 91 | /// Returns a table describing this planetary data set 92 | pub fn describe(&self) -> String { 93 | let binding = self.lut.entries(); 94 | let mut values = binding.values().collect::>().to_vec(); 95 | values.sort_by_key(|(opt_id, _)| match opt_id { 96 | Some(id) => *id, 97 | None => 0, 98 | }); 99 | 100 | let mut rows = Vec::new(); 101 | 102 | for (opt_id, opt_name) in values { 103 | let data = if let Some(id) = opt_id { 104 | self.get_by_id(*id).unwrap() 105 | } else { 106 | self.get_by_name(&opt_name.clone().unwrap()).unwrap() 107 | }; 108 | 109 | let row = LocationRow { 110 | name: match opt_name { 111 | Some(name) => name.clone(), 112 | None => "Unset".to_string(), 113 | }, 114 | id: match opt_id { 115 | Some(id) => format!("{id}"), 116 | None => "Unset".to_string(), 117 | }, 118 | latitude_deg: data.latitude_deg, 119 | longitude_deg: data.longitude_deg, 120 | height_km: data.height_km, 121 | has_terrain_mask: !data.terrain_mask.is_empty(), 122 | terrain_mask_ignored: data.terrain_mask_ignored, 123 | }; 124 | 125 | rows.push(row); 126 | } 127 | 128 | let mut tbl = Table::new(rows); 129 | tbl.with(Style::modern()); 130 | format!("{tbl}") 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /anise/fuzz/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "anise-fuzz" 3 | version = { workspace = true } 4 | authors = { workspace = true } 5 | edition = { workspace = true } 6 | publish = false 7 | 8 | [package.metadata] 9 | cargo-fuzz = true 10 | 11 | [dependencies] 12 | libfuzzer-sys = { version = "0.4", features = ["arbitrary-derive"] } 13 | hifitime = { workspace = true } 14 | bytes = "1.0" 15 | 16 | [dependencies.anise] 17 | path = ".." 18 | 19 | [lib] 20 | name = "anise_fuzz" 21 | path = "src/lib.rs" 22 | 23 | [[bin]] 24 | name = "fuzz_metadata" 25 | path = "fuzz_targets/fuzz_metadata.rs" 26 | test = false 27 | doc = false 28 | bench = false 29 | 30 | [[bin]] 31 | name = "spacecraft_dataset" 32 | path = "fuzz_targets/spacecraft_dataset.rs" 33 | test = false 34 | doc = false 35 | bench = false 36 | 37 | [[bin]] 38 | name = "planetary_dataset" 39 | path = "fuzz_targets/planetary_dataset.rs" 40 | test = false 41 | doc = false 42 | bench = false 43 | 44 | [[bin]] 45 | name = "euler_parameter_dataset" 46 | path = "fuzz_targets/euler_parameter_dataset.rs" 47 | test = false 48 | doc = false 49 | bench = false 50 | 51 | [[bin]] 52 | name = "parse_spk" 53 | path = "fuzz_targets/parse_spk.rs" 54 | test = false 55 | doc = false 56 | bench = false 57 | 58 | [[bin]] 59 | name = "parse_bpc" 60 | path = "fuzz_targets/parse_bpc.rs" 61 | test = false 62 | doc = false 63 | bench = false 64 | 65 | [[bin]] 66 | name = "fkitem_extract_key" 67 | path = "fuzz_targets/fkitem_extract_key.rs" 68 | test = false 69 | doc = false 70 | bench = false 71 | 72 | [[bin]] 73 | name = "tpcitem_extract_key" 74 | path = "fuzz_targets/tpcitem_extract_key.rs" 75 | test = false 76 | doc = false 77 | bench = false 78 | 79 | [[bin]] 80 | name = "fkitem_parse" 81 | path = "fuzz_targets/fkitem_parse.rs" 82 | test = false 83 | doc = false 84 | bench = false 85 | 86 | [[bin]] 87 | name = "kpl_parse_bytes_fkitem" 88 | path = "fuzz_targets/kpl_parse_bytes_fkitem.rs" 89 | test = false 90 | doc = false 91 | bench = false 92 | 93 | [[bin]] 94 | name = "kpl_parse_bytes_tpcitem" 95 | path = "fuzz_targets/kpl_parse_bytes_tpcitem.rs" 96 | test = false 97 | doc = false 98 | bench = false 99 | 100 | [[bin]] 101 | name = "convert_fk_items" 102 | path = "fuzz_targets/convert_fk_items.rs" 103 | test = false 104 | doc = false 105 | bench = false 106 | 107 | [[bin]] 108 | name = "convert_tpc_items" 109 | path = "fuzz_targets/convert_tpc_items.rs" 110 | test = false 111 | doc = false 112 | bench = false 113 | 114 | [[bin]] 115 | name = "load_from_bytes" 116 | path = "fuzz_targets/load_from_bytes.rs" 117 | test = false 118 | doc = false 119 | bench = false 120 | 121 | [[bin]] 122 | name = "rotation_dcm_to_quaternion" 123 | path = "fuzz_targets/rotation_dcm_to_quaternion.rs" 124 | test = false 125 | doc = false 126 | bench = false 127 | 128 | [[bin]] 129 | name = "orientations_paths_path" 130 | path = "fuzz_targets/orientations_paths_path.rs" 131 | test = false 132 | doc = false 133 | bench = false 134 | 135 | [[bin]] 136 | name = "orientations_paths_root" 137 | path = "fuzz_targets/orientations_paths_root.rs" 138 | test = false 139 | doc = false 140 | bench = false 141 | 142 | [[bin]] 143 | name = "orientations_rotations_rotate" 144 | path = "fuzz_targets/orientations_rotations_rotate.rs" 145 | test = false 146 | doc = false 147 | bench = false 148 | 149 | [[bin]] 150 | name = "rotation_dcm_partialeq" 151 | path = "fuzz_targets/rotation_dcm_partialeq.rs" 152 | test = false 153 | doc = false 154 | bench = false 155 | 156 | [[bin]] 157 | name = "rotation_quaternion_mul" 158 | path = "fuzz_targets/rotation_quaternion_mul.rs" 159 | test = false 160 | doc = false 161 | bench = false 162 | 163 | [[bin]] 164 | name = "rotation_mrp_normalize" 165 | path = "fuzz_targets/rotation_mrp_normalize.rs" 166 | test = false 167 | doc = false 168 | bench = false 169 | 170 | [[bin]] 171 | name = "rotation_mrp_partialeq" 172 | path = "fuzz_targets/rotation_mrp_partialeq.rs" 173 | test = false 174 | doc = false 175 | bench = false 176 | 177 | [[bin]] 178 | name = "almanac_describe" 179 | path = "fuzz_targets/almanac_describe.rs" 180 | test = false 181 | doc = false 182 | bench = false 183 | 184 | [[bin]] 185 | name = "try_find_ephemeris_root" 186 | path = "fuzz_targets/try_find_ephemeris_root.rs" 187 | test = false 188 | doc = false 189 | bench = false 190 | 191 | [[bin]] 192 | name = "common_ephemeris_path" 193 | path = "fuzz_targets/common_ephemeris_path.rs" 194 | test = false 195 | doc = false 196 | bench = false 197 | --------------------------------------------------------------------------------