Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix read/write OEM in other frames #194

Merged
merged 2 commits into from
Jul 1, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/daily.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
LLVM_PROFILE_FILE: "target/coverage/nyx_space-%p-%m.profraw"
run: |
cargo test --doc
grcov . --binary-path ./target/debug/ -t lcov -s . --keep-only 'src/*' > lcov-lib.txt
grcov . --binary-path ./target/debug/ -t lcov -s . --keep-only 'src/*' > lcov-docs.txt

- name: Generate coverage report for cosmic integr. tests
env:
Expand Down
293 changes: 174 additions & 119 deletions src/md/trajectory/orbit_traj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -142,13 +142,10 @@ impl Traj<Orbit> {
let name = parts[1].trim().to_string();
debug!("[line: {}] Found object {name}", lno + 1);
traj.name = Some(name);
} else if line.starts_with("REF_FRAME") {
} else if line.starts_with("CENTER_NAME") {
let parts: Vec<&str> = line.split('=').collect();
let mut ref_frame = parts[1].trim();
if ref_frame == "ICRF" {
ref_frame = "EME2000";
}
frame = Some(cosm.try_frame(ref_frame)?);
let center_name = parts[1].trim();
frame = Some(cosm.try_frame(&format!("{center_name} J2000"))?);
} else if line.starts_with("TIME_SYSTEM") {
let parts: Vec<&str> = line.split('=').collect();
time_system = parts[1].trim().to_string();
Expand Down Expand Up @@ -220,9 +217,7 @@ impl Traj<Orbit> {
// Grab the path here before we move stuff.
let path_buf = cfg.actual_path(path);

let metadata = cfg.metadata.ok_or(NyxError::CCSDS(
"Missing metadata in ExportCfg structure".to_string(),
))?;
let metadata = cfg.metadata.unwrap_or(HashMap::new());

let file = File::create(&path_buf)
.map_err(|e| NyxError::CCSDS(format!("File creation error: {e}")))?;
Expand Down Expand Up @@ -255,9 +250,9 @@ impl Traj<Orbit> {
writeln!(
writer,
"ORIGINATOR = {}\n",
metadata.get("originator").ok_or(NyxError::CCSDS(
"Metadata is missing `originator` entry".to_string()
))?
metadata
.get("originator")
.unwrap_or(&"Nyx Space".to_string())
)
.map_err(err_hdlr)?;

Expand All @@ -269,7 +264,22 @@ impl Traj<Orbit> {
writeln!(writer, "OBJECT_NAME = {}", object_name).map_err(err_hdlr)?;
}

writeln!(writer, "REF_FRAME = {}", states[0].frame).map_err(err_hdlr)?;
let frame_str = states[0].frame.to_string();
let splt: Vec<&str> = frame_str.split(' ').collect();
let center = splt[0];
let ref_frame = frame_str.replace(center, " ");
writeln!(
writer,
"REF_FRAME = {}",
match ref_frame.trim() {
"J2000" => "ICRF",
_ => ref_frame.trim(),
}
)
.map_err(err_hdlr)?;

writeln!(writer, "CENTER_NAME = {center}",).map_err(err_hdlr)?;

writeln!(writer, "TIME_SYSTEM = {}", states[0].epoch.time_scale).map_err(err_hdlr)?;

writeln!(
Expand Down Expand Up @@ -301,7 +311,7 @@ impl Traj<Orbit> {

writeln!(
writer,
"COMMENT Generated by {} (License AGPLv3)\n",
"COMMENT Generated by {} provided in AGPLv3 license -- https://nyxspace.com/\n",
prj_name_ver()
)
.map_err(err_hdlr)?;
Expand Down Expand Up @@ -334,134 +344,179 @@ impl Traj<Orbit> {
}
}

#[test]
fn test_load_oem_leo() {
fn the_test() {
#[cfg(test)]
mod ut_ccsds_oem {

use crate::md::prelude::{Cosm, OrbitalDynamics, Propagator};
use crate::time::{Epoch, TimeUnits};
use crate::{io::ExportCfg, md::prelude::Traj, Orbit};
use pretty_env_logger;
use std::env;
use std::str::FromStr;
use std::{collections::HashMap, path::PathBuf};

#[test]
fn test_load_oem_leo() {
fn the_test() {
// All three samples were taken from https://github.com/bradsease/oem/blob/main/tests/samples/real/
let path: PathBuf = [
env!("CARGO_MANIFEST_DIR"),
"data",
"tests",
"ccsds",
"oem",
"LEO_10s.oem",
]
.iter()
.collect();

let _ = pretty_env_logger::try_init();

let traj: Traj<Orbit> = Traj::<Orbit>::from_oem_file(path).unwrap();

// This trajectory has two duplicate epochs, which should be removed by the call to finalize()
assert_eq!(traj.states.len(), 361);
assert_eq!(traj.name.unwrap(), "TEST_OBJ".to_string());
}

use easybench::bench;
use pretty_env_logger;
use std::env;

the_test();
// If the test is successful, print the benchmark
println!("benchmark CCSDS OEM Loading: {}", bench(|| the_test()));
}

#[test]
fn test_load_oem_meo() {
// All three samples were taken from https://github.com/bradsease/oem/blob/main/tests/samples/real/
let path: PathBuf = [
env!("CARGO_MANIFEST_DIR"),
"data",
"tests",
"ccsds",
"oem",
"LEO_10s.oem",
"MEO_60s.oem",
]
.iter()
.collect();

let _ = pretty_env_logger::try_init();

let traj: Traj<Orbit> = Traj::<Orbit>::from_oem_file(path).unwrap();
let traj = Traj::<Orbit>::from_oem_file(path).unwrap();

// This trajectory has two duplicate epochs, which should be removed by the call to finalize()
assert_eq!(traj.states.len(), 361);
assert_eq!(traj.states.len(), 61);
assert_eq!(traj.name.unwrap(), "TEST_OBJ".to_string());
}

use easybench::bench;
use pretty_env_logger;
use std::env;
#[test]
fn test_load_oem_geo() {
use pretty_env_logger;
use std::env;

the_test();
// If the test is successful, print the benchmark
println!("benchmark CCSDS OEM Loading: {}", bench(|| the_test()));
}
// All three samples were taken from https://github.com/bradsease/oem/blob/main/tests/samples/real/
let path: PathBuf = [
env!("CARGO_MANIFEST_DIR"),
"data",
"tests",
"ccsds",
"oem",
"GEO_20s.oem",
]
.iter()
.collect();

#[test]
fn test_load_oem_meo() {
use pretty_env_logger;
use std::env;
let _ = pretty_env_logger::try_init();

// All three samples were taken from https://github.com/bradsease/oem/blob/main/tests/samples/real/
let path: PathBuf = [
env!("CARGO_MANIFEST_DIR"),
"data",
"tests",
"ccsds",
"oem",
"MEO_60s.oem",
]
.iter()
.collect();
let traj: Traj<Orbit> = Traj::<Orbit>::from_oem_file(path).unwrap();

let _ = pretty_env_logger::try_init();
assert_eq!(traj.states.len(), 181);
assert_eq!(traj.name.as_ref().unwrap(), &"TEST_OBJ".to_string());

let traj = Traj::<Orbit>::from_oem_file(path).unwrap();
// Reexport this to CCSDS.
let cfg = ExportCfg::builder()
.timestamp(true)
.metadata(HashMap::from([
("originator".to_string(), "Test suite".to_string()),
("object_name".to_string(), "TEST_OBJ".to_string()),
]))
.build();

assert_eq!(traj.states.len(), 61);
assert_eq!(traj.name.unwrap(), "TEST_OBJ".to_string());
}
let path: PathBuf = [
env!("CARGO_MANIFEST_DIR"),
"output_data",
"GEO_20s_rebuilt.oem",
]
.iter()
.collect();

#[test]
fn test_load_oem_geo() {
use pretty_env_logger;
use std::env;
let out_path = traj.to_oem_file(path.clone(), cfg).unwrap();
// And reload, make sure we have the same data.
let traj_reloaded: Traj<Orbit> = Traj::<Orbit>::from_oem_file(out_path).unwrap();

assert_eq!(traj_reloaded, traj);

// Now export after trimming one state on either end
let cfg = ExportCfg::builder()
.timestamp(true)
.metadata(HashMap::from([
("originator".to_string(), "Test suite".to_string()),
("object_name".to_string(), "TEST_OBJ".to_string()),
]))
.step(20.seconds())
.start_epoch(traj.first().epoch + 1.seconds())
.end_epoch(traj.last().epoch - 1.seconds())
.build();
let out_path = traj.to_oem_file(path, cfg).unwrap();
// And reload, make sure we have the same data.
let traj_reloaded: Traj<Orbit> = Traj::<Orbit>::from_oem_file(out_path).unwrap();

// Note that the number of states has changed because we interpolated with a step similar to the original one but
// we started with a different time.
assert_eq!(traj_reloaded.states.len(), traj.states.len() - 1);
assert_eq!(
traj_reloaded.first().epoch,
traj.first().epoch + 1.seconds()
);
// Note: because we used a fixed step, the last epoch is actually an offset of step size - end offset
// from the original trajectory
assert_eq!(traj_reloaded.last().epoch, traj.last().epoch - 19.seconds());
}

// All three samples were taken from https://github.com/bradsease/oem/blob/main/tests/samples/real/
let path: PathBuf = [
env!("CARGO_MANIFEST_DIR"),
"data",
"tests",
"ccsds",
"oem",
"GEO_20s.oem",
]
.iter()
.collect();

let _ = pretty_env_logger::try_init();

let traj: Traj<Orbit> = Traj::<Orbit>::from_oem_file(path).unwrap();

assert_eq!(traj.states.len(), 181);
assert_eq!(traj.name.as_ref().unwrap(), &"TEST_OBJ".to_string());

// Reexport this to CCSDS.
let cfg = ExportCfg::builder()
.timestamp(true)
.metadata(HashMap::from([
("originator".to_string(), "Test suite".to_string()),
("object_name".to_string(), "TEST_OBJ".to_string()),
]))
.build();

let path: PathBuf = [
env!("CARGO_MANIFEST_DIR"),
"output_data",
"GEO_20s_rebuilt.oem",
]
.iter()
.collect();

let out_path = traj.to_oem_file(path.clone(), cfg).unwrap();
// And reload, make sure we have the same data.
let traj_reloaded: Traj<Orbit> = Traj::<Orbit>::from_oem_file(out_path).unwrap();

assert_eq!(traj_reloaded, traj);

// Now export after trimming one state on either end
let cfg = ExportCfg::builder()
.timestamp(true)
.metadata(HashMap::from([
("originator".to_string(), "Test suite".to_string()),
("object_name".to_string(), "TEST_OBJ".to_string()),
]))
.step(20.seconds())
.start_epoch(traj.first().epoch + 1.seconds())
.end_epoch(traj.last().epoch - 1.seconds())
.build();
let out_path = traj.to_oem_file(path, cfg).unwrap();
// And reload, make sure we have the same data.
let traj_reloaded: Traj<Orbit> = Traj::<Orbit>::from_oem_file(out_path).unwrap();

// Note that the number of states has changed because we interpolated with a step similar to the original one but
// we started with a different time.
assert_eq!(traj_reloaded.states.len(), traj.states.len() - 1);
assert_eq!(
traj_reloaded.first().epoch,
traj.first().epoch + 1.seconds()
);
// Note: because we used a fixed step, the last epoch is actually an offset of step size - end offset
// from the original trajectory
assert_eq!(traj_reloaded.last().epoch, traj.last().epoch - 19.seconds());
#[test]
fn test_moon_frame_long_prop() {
let cosm = Cosm::de438();
let epoch = Epoch::from_str("2022-06-13T12:00:00").unwrap();
let orbit = Orbit::keplerian_altitude(
350.0,
0.02,
30.0,
45.0,
85.0,
0.0,
epoch,
cosm.frame("Moon J2000"),
);

let mut traj = Propagator::default_dp78(OrbitalDynamics::two_body())
.with(orbit)
.for_duration_with_traj(45.days())
.unwrap()
.1;
// Set the name of this object
traj.name = Some("TEST_MOON_OBJ".to_string());

// Export CCSDS OEM file
let path: PathBuf = [env!("CARGO_MANIFEST_DIR"), "output_data", "moon_45days.oem"]
.iter()
.collect();

let out_path = traj.to_oem_file(path, ExportCfg::default()).unwrap();

// And reload
let traj_reloaded: Traj<Orbit> = Traj::<Orbit>::from_oem_file(out_path).unwrap();

assert_eq!(traj, traj_reloaded);
}
}
7 changes: 6 additions & 1 deletion src/md/trajectory/traj.rs
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,12 @@ where
let dur = self.last().epoch() - self.first().epoch();
write!(
f,
"Trajectory from {} to {} ({}, or {:.3} s) [{} states]",
"Trajectory {}in {} from {} to {} ({}, or {:.3} s) [{} states]",
match &self.name {
Some(name) => format!("of {name}"),
None => String::new(),
},
self.first().frame(),
self.first().epoch(),
self.last().epoch(),
dur,
Expand Down