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

Implement spk parser #4

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e622050
Add nom dep
matzipan Mar 12, 2023
f75f04c
Parse DAF/SPK file record
matzipan Mar 12, 2023
ecd9a60
Implement comment area parsing
matzipan Mar 19, 2023
cfd1b92
Implement DAF summary parsing
matzipan Apr 5, 2023
62bde36
Use char instead of str
matzipan Apr 5, 2023
9d40cc1
Pass the count via the summary record
matzipan Oct 14, 2023
ae9abab
Make the offset parametric
matzipan Oct 14, 2023
c8bded4
Clarify which parts are DAF-only
matzipan Oct 15, 2023
e9143d8
Fix integer components type
matzipan Oct 15, 2023
627b49d
Parse DAF summary into SPK segment
matzipan Oct 15, 2023
2d509d3
Fix case for Spk type
matzipan Oct 15, 2023
359b81d
Remove unused imports
matzipan Oct 15, 2023
250e723
Move expected data after the test code
matzipan Oct 15, 2023
4df03ff
Implement endianness support
matzipan Oct 16, 2023
ea74c45
Improve error flow
matzipan Oct 16, 2023
b4318fb
Implement SPK type 2 segment parsing
matzipan Oct 31, 2023
35cfb06
Transform all the test segments to uppercase
matzipan Nov 3, 2023
0322cc0
Use as casting to keep the pattern
matzipan Nov 4, 2023
ad36110
Clean-up unused import
matzipan Nov 4, 2023
d94a365
Update tests for SPK segment parsing
matzipan Nov 4, 2023
bcdfe0d
Implement position retrieval
matzipan Nov 5, 2023
03a62b5
Transpose the coefficients
matzipan Nov 8, 2023
95d5fb2
Implement get_segments
matzipan Nov 8, 2023
1646c6b
Fix the order according to the spec
matzipan Nov 8, 2023
8d3b2aa
Implement position and state
matzipan Nov 9, 2023
3b160b9
Fix velocity calculation
helgee Nov 10, 2023
75b349c
Preallocate to win a few miliseconds
matzipan Nov 11, 2023
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
21 changes: 11 additions & 10 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions crates/lox_core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ float_eq = "1.0.1"
glam = "0.24.2"
num = "0.4.0"
thiserror.workspace = true
nom = "7.1.3"

[dev-dependencies]
proptest = "1.1.0"
Expand Down
1 change: 1 addition & 0 deletions crates/lox_core/src/ephemeris.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod daf_spk;
2 changes: 2 additions & 0 deletions crates/lox_core/src/ephemeris/daf_spk.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
pub mod api;
pub mod parser;
263 changes: 263 additions & 0 deletions crates/lox_core/src/ephemeris/daf_spk/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
use std::collections::HashMap;

use super::parser::{DafSpkError, Spk, SpkSegment, SpkType2Array, SpkType2Coefficients};

type Position = (f64, f64, f64);
type Velocity = (f64, f64, f64);
type Epoch = f64;
type Body = i32;

impl Spk {
fn find_segment(
&self,
origin: Body,
target: Body,
) -> Result<(&SpkSegment, isize), DafSpkError> {
let mut sign = 1;
matzipan marked this conversation as resolved.
Show resolved Hide resolved

let mut target = target;
let mut origin = origin;
if target < origin {
(origin, target) = (target, origin);
sign = -1;
}

let segment = self
.segments
.get(&origin)
.ok_or(DafSpkError::UnableToFindMatchingSegment)?
.get(&target)
.ok_or(DafSpkError::UnableToFindMatchingSegment)?;

Ok((&segment, sign))
}

fn find_record<'a>(
&'a self,
array: &'a SpkType2Array,
initial_epoch: Epoch,
epoch: Epoch,
) -> Result<(&Vec<SpkType2Coefficients>, f64), DafSpkError> {
let seconds_from_record_start = epoch - initial_epoch;

let intlen = array.intlen as f64;
let mut record_number = (seconds_from_record_start / intlen).floor() as usize;
let mut fraction = seconds_from_record_start % intlen;

// Chebyshev piecewise polynomials overlap at patchpoints. This means that one
// can safely take the end of the interval from the next record. But this implies
// special handling of the last record, where there's no next record that we can
// draw from.
if record_number == array.n as usize {
record_number -= 1;
fraction = array.intlen as f64;
}

let record = array
.records
.get(record_number)
.ok_or(DafSpkError::UnableToFindMatchingRecord)?;

Ok((record, fraction))
}

pub fn get_segments(&self) -> &HashMap<i32, HashMap<i32, SpkSegment>> {
&self.segments
}

fn get_chebyshev_polynomial<'a>(
&'a self,
epoch: Epoch,
segment: &'a SpkSegment,
) -> Result<(Vec<f64>, &Vec<SpkType2Coefficients>), DafSpkError> {
let (coefficients, record) = match &segment.data {
super::parser::SpkArray::Type2(array) => {
let (record, fraction) = self.find_record(array, segment.initial_epoch, epoch)?;

let degree_of_polynomial = array.degree_of_polynomial() as usize;
let mut coefficients = Vec::<f64>::with_capacity(degree_of_polynomial);

coefficients.push(1f64);
coefficients.push(2f64 * fraction / array.intlen as f64 - 1f64);

for i in 2..degree_of_polynomial {
coefficients
.push(2f64 * coefficients[1] * coefficients[i - 1] - coefficients[i - 2]);
}

(coefficients, record)
}
};

Ok((coefficients, record))
}

pub fn position(
&self,
epoch: Epoch,
origin: Body,
target: Body,
) -> Result<Position, DafSpkError> {
let (segment, sign) = self.find_segment(origin, target)?;

if epoch < segment.initial_epoch || epoch > segment.final_epoch {
return Err(DafSpkError::UnableToFindMatchingSegment);
}

let mut x = 0f64;
let mut y = 0f64;
let mut z = 0f64;

match &segment.data {
super::parser::SpkArray::Type2(array) => {
let (polynomial, record) = self.get_chebyshev_polynomial(epoch, segment)?;
let sign = sign as f64;

let degree_of_polynomial = array.degree_of_polynomial() as usize;

#[allow(clippy::needless_range_loop)]
for i in 0..degree_of_polynomial {
x += sign * record[i].x * polynomial[i];
y += sign * record[i].y * polynomial[i];
z += sign * record[i].z * polynomial[i];
}
}
}

Ok((x, y, z))
}

pub fn velocity(
&self,
epoch: Epoch,
origin: Body,
target: Body,
) -> Result<Velocity, DafSpkError> {
let (segment, sign) = self.find_segment(origin, target)?;

if epoch < segment.initial_epoch || epoch > segment.final_epoch {
return Err(DafSpkError::UnableToFindMatchingSegment);
}

let mut x = 0f64;
let mut y = 0f64;
let mut z = 0f64;

match &segment.data {
super::parser::SpkArray::Type2(array) => {
let (polynomial, record) = self.get_chebyshev_polynomial(epoch, segment)?;
let sign = sign as f64;

let degree_of_polynomial = array.degree_of_polynomial() as usize;

let mut derivative = Vec::<f64>::with_capacity(degree_of_polynomial);

derivative.push(0f64);
derivative.push(1f64);

if degree_of_polynomial > 2 {
derivative.push(4f64 * polynomial[1]);
for i in 3..degree_of_polynomial {
let x = 2f64 * polynomial[1] * derivative[i - 1] - derivative[i - 2]
+ polynomial[i - 1]
+ polynomial[i - 1];

derivative.push(x);
}
}

let derivative: Vec<f64> = derivative
.iter()
.map(|d| 2.0 * d / array.intlen as f64)
.collect();

#[allow(clippy::needless_range_loop)]
for i in 0..degree_of_polynomial {
x += sign * record[i].x * derivative[i];
y += sign * record[i].y * derivative[i];
z += sign * record[i].z * derivative[i];
}
}
}

Ok((x, y, z))
}

pub fn state(
&self,
epoch: Epoch,
origin: Body,
target: Body,
) -> Result<(Position, Velocity), DafSpkError> {
let position = self.position(epoch, origin, target)?;
let velocity = self.velocity(epoch, origin, target)?;

Ok((position, velocity))
}
}

#[cfg(test)]
mod test {
use crate::ephemeris::daf_spk::parser::parse_daf_spk;
use crate::ephemeris::daf_spk::parser::test::{get_expected_segments, FILE_CONTENTS};

use super::*;

#[test]
fn test_unable_to_find_segment() {
let spk = parse_daf_spk(&FILE_CONTENTS).expect("Unable to parse DAF/SPK");
matzipan marked this conversation as resolved.
Show resolved Hide resolved

assert_eq!(
Err(DafSpkError::UnableToFindMatchingSegment),
spk.position(2457388.5000000 as Epoch, 1, 2)
);
}

#[test]
fn test_position() {
let spk = parse_daf_spk(&FILE_CONTENTS).expect("Unable to parse DAF/SPK");

assert_eq!(
Ok((-32703259.291699532, 31370540.51993667, 20159681.594182793)),
spk.position(-14200747200.0 as Epoch, 0, 1)
);
}

#[test]
fn test_velocity() {
let spk = parse_daf_spk(&FILE_CONTENTS).expect("Unable to parse DAF/SPK");

assert_eq!(
Ok((
-46.723420416476635,
-28.050723083678367,
-10.055174230490163,
)),
spk.velocity(-14200747200.0 as Epoch, 0, 1)
);
}

#[test]
fn test_state() {
let spk = parse_daf_spk(&FILE_CONTENTS).expect("Unable to parse DAF/SPK");

assert_eq!(
Ok((
(-32703259.291699532, 31370540.51993667, 20159681.594182793),
(
-46.723420416476635,
-28.050723083678367,
-10.055174230490163,
),
)),
spk.state(-14200747200.0 as Epoch, 0, 1)
);
}

#[test]
fn test_get_segments() {
let spk = parse_daf_spk(&FILE_CONTENTS).expect("Unable to parse DAF/SPK");

assert_eq!(&get_expected_segments(), spk.get_segments());
}
}
Loading
Loading