Skip to content

Commit 10c8a30

Browse files
Merge pull request #382 from nyx-space/fix/gh-381
Fix range-rate calculation in azimuth_elevation_range_sez
2 parents e09a06d + ec87028 commit 10c8a30

File tree

14 files changed

+317
-64
lines changed

14 files changed

+317
-64
lines changed

.github/workflows/benchmarks.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ jobs:
4949
run: cargo bench --bench "crit_planetary_data" --workspace --exclude anise-py
5050

5151
- name: Save benchmark artifacts
52-
uses: actions/upload-artifact@v3
52+
uses: actions/upload-artifact@v4
5353
with:
5454
name: jpl-development-ephemerides-benchmark
55-
path: target/criterion/**/report/*
55+
path: target/criterion/**/report/

.github/workflows/gui.yaml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ jobs:
2626

2727
- name: Install packages (Linux)
2828
if: runner.os == 'Linux'
29-
uses: rerun-io/cache-apt-pkgs-action@59534850182063abf1b2c11bb3686722a12a8397
29+
uses: awalsh128/cache-apt-pkgs-action@latest
3030
with:
3131
packages: libxcb-render0-dev libxcb-shape0-dev libxcb-xfixes0-dev libxkbcommon-dev libssl-dev libgtk-3-dev # libgtk-3-dev is used by rfd
3232
version: 1.0
@@ -36,7 +36,7 @@ jobs:
3636
run: cargo build --release --bin anise-gui --workspace --exclude anise-py
3737

3838
- name: Save executable
39-
uses: actions/upload-artifact@v3
39+
uses: actions/upload-artifact@v4
4040
with:
4141
name: anise-gui-linux
4242
path: target/release/anise-gui
@@ -50,13 +50,13 @@ jobs:
5050
- uses: dtolnay/rust-toolchain@stable
5151

5252
- name: Set up cargo cache
53-
uses: Swatinem/rust-cache@v2
53+
uses: Swatinem/rust-cache@v2.7.7
5454

5555
- name: Build Windows executable
5656
run: cargo build --release --bin anise-gui --workspace --exclude anise-py
5757

5858
- name: Save executable
59-
uses: actions/upload-artifact@v3
59+
uses: actions/upload-artifact@v4
6060
with:
6161
name: anise-gui-windows
6262
path: target\release\anise-gui.exe

.github/workflows/python.yml

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -59,9 +59,9 @@ jobs:
5959
fi
6060
6161
- name: Upload wheels
62-
uses: actions/upload-artifact@v3
62+
uses: actions/upload-artifact@v4
6363
with:
64-
name: wheels
64+
name: wheels-linux-${{ matrix.target }}
6565
path: anise-py/dist
6666

6767
- name: pytest
@@ -126,9 +126,9 @@ jobs:
126126
working-directory: anise-py
127127

128128
- name: Upload wheels
129-
uses: actions/upload-artifact@v3
129+
uses: actions/upload-artifact@v4
130130
with:
131-
name: wheels
131+
name: wheels-windows-${{ matrix.target }}
132132
path: anise-py/dist
133133

134134
- name: pytest
@@ -161,9 +161,9 @@ jobs:
161161
working-directory: anise-py
162162

163163
- name: Upload wheels
164-
uses: actions/upload-artifact@v3
164+
uses: actions/upload-artifact@v4
165165
with:
166-
name: wheels
166+
name: wheels-macos-13
167167
path: anise-py/dist
168168

169169
- name: pytest
@@ -196,9 +196,9 @@ jobs:
196196
working-directory: anise-py
197197

198198
- name: Upload wheels
199-
uses: actions/upload-artifact@v3
199+
uses: actions/upload-artifact@v4
200200
with:
201-
name: wheels
201+
name: wheels-macos-14
202202
path: anise-py/dist
203203

204204
- name: pytest
@@ -233,9 +233,9 @@ jobs:
233233
working-directory: anise-py
234234

235235
- name: Upload sdist
236-
uses: actions/upload-artifact@v3
236+
uses: actions/upload-artifact@v4
237237
with:
238-
name: wheels
238+
name: wheels-sdist
239239
path: anise-py/dist
240240

241241
release:
@@ -244,9 +244,7 @@ jobs:
244244
if: github.ref_type == 'tag'
245245
needs: [linux, windows, macos-13, macos-14, sdist]
246246
steps:
247-
- uses: actions/download-artifact@v3
248-
with:
249-
name: wheels
247+
- uses: actions/download-artifact@v4 # No `name` to download all artifacts.
250248

251249
- name: Publish to PyPI
252250
uses: PyO3/maturin-action@v1

.github/workflows/rust.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ jobs:
166166
python spk_validation_plots.py
167167
168168
- name: Save validation artifacts
169-
uses: actions/upload-artifact@v3
169+
uses: actions/upload-artifact@v4
170170
with:
171171
name: validation-artifacts
172172
path: target/*.html

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ resolver = "2"
33
members = ["anise", "anise-cli", "anise-gui", "anise-py"]
44

55
[workspace.package]
6-
version = "0.5.2"
6+
version = "0.5.3"
77
edition = "2021"
88
authors = ["Christopher Rabotin <christopher.rabotin@gmail.com>"]
99
description = "ANISE provides a toolkit and files for Attitude, Navigation, Instrument, Spacecraft, and Ephemeris data. It's a modern replacement of NAIF SPICE file."
@@ -45,7 +45,7 @@ pyo3-log = "0.12"
4545
numpy = "0.23"
4646
ndarray = ">= 0.15, < 0.17"
4747

48-
anise = { version = "0.5.2", path = "anise", default-features = false }
48+
anise = { version = "0.5.3", path = "anise", default-features = false }
4949

5050
[profile.bench]
5151
debug = true

anise-py/tests/test_almanac.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ def test_state_transformation():
7979
topo_dcm = orig_state.dcm_from_topocentric_to_body_fixed(123)
8080
assert topo_dcm.get_state_dcm().shape == (6, 6)
8181
assert topo_dcm.rot_mat.shape == (3, 3)
82-
assert topo_dcm.rot_mat_dt is None
82+
assert (topo_dcm.rot_mat_dt is not None and topo_dcm.rot_mat_dt.shape == (3, 3)) or topo_dcm.rot_mat_dt is None
8383

8484
# In Python, we can set the aberration to None
8585
aberration = None

anise/src/almanac/aer.rs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ use crate::{
1616
frames::Frame,
1717
math::angles::{between_0_360, between_pm_180},
1818
prelude::Orbit,
19-
time::uuid_from_epoch,
2019
};
2120

2221
use super::Almanac;
@@ -81,10 +80,9 @@ impl Almanac {
8180
}
8281

8382
// Compute the SEZ DCM
84-
let from = uuid_from_epoch(tx.frame.orientation_id, rx.epoch);
8583
// SEZ DCM is topo to fixed
8684
let sez_dcm = tx
87-
.dcm_from_topocentric_to_body_fixed(from)
85+
.dcm_from_topocentric_to_body_fixed(-1)
8886
.context(EphemerisPhysicsSnafu { action: "" })
8987
.context(EphemerisSnafu {
9088
action: "computing SEZ DCM for AER",
@@ -96,7 +94,7 @@ impl Almanac {
9694
action: "transforming transmitter to SEZ",
9795
})?;
9896

99-
// Convert the receiver into the transmitter frame.
97+
// Convert the receiver into the body fixed transmitter frame.
10098
let rx_in_tx_frame = self.transform_to(rx, tx.frame, ab_corr)?;
10199
// Convert into SEZ frame
102100
let rx_sez = (sez_dcm.transpose() * rx_in_tx_frame)
@@ -105,12 +103,17 @@ impl Almanac {
105103
action: "transforming received to SEZ",
106104
})?;
107105

108-
// Compute the range ρ.
106+
// Convert receiver into the transmitter frame
107+
let rx_in_tx_frame = self.transform_to(rx, tx.frame, ab_corr)?;
108+
109+
// Compute the range ρ in the SEZ frame for az/el
109110
let rho_sez = rx_sez.radius_km - tx_sez.radius_km;
111+
// And in the body-fixed transmitter frame for range and range-rate.
112+
// While the norms of these vectors are identical, we need the exact vectors themselves for the range rate calculation.
113+
let rho_tx_frame = rx_in_tx_frame.radius_km - tx.radius_km;
110114

111-
// Compute the range-rate \dot ρ
112-
let range_rate_km_s =
113-
rho_sez.dot(&(rx_sez.velocity_km_s - tx_sez.velocity_km_s)) / rho_sez.norm();
115+
// Compute the range-rate \dot ρ. Note that rx_in_tx_frame is already the relative velocity of rx wrt tx!
116+
let range_rate_km_s = rho_tx_frame.dot(&rx_in_tx_frame.velocity_km_s) / rho_tx_frame.norm();
114117

115118
// Finally, compute the elevation (math is the same as declination)
116119
// Source: Vallado, section 4.4.3
@@ -270,7 +273,7 @@ mod ut_aer {
270273
assert_eq!(
271274
format!("{aer}"),
272275
format!(
273-
"{}: az.: 133.599990 deg el.: 7.237568 deg range: 91457.271742 km range-rate: -12.396849 km/s obstruction: none",
276+
"{}: az.: 133.599990 deg el.: 7.237568 deg range: 91457.271742 km range-rate: 2.198786 km/s obstruction: none",
274277
state.epoch
275278
)
276279
);
@@ -306,7 +309,7 @@ mod ut_aer {
306309
assert_eq!(
307310
format!("{aer}"),
308311
format!(
309-
"{}: az.: 133.599990 deg el.: 7.237568 deg range: 91457.271742 km range-rate: -12.396849 km/s obstruction: none",
312+
"{}: az.: 133.599990 deg el.: 7.237568 deg range: 91457.271742 km range-rate: 2.198786 km/s obstruction: none",
310313
state.epoch
311314
)
312315
);

anise/src/astro/orbit.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -320,20 +320,52 @@ impl Orbit {
320320
#[allow(clippy::too_many_arguments)]
321321
#[cfg_attr(feature = "python", pymethods)]
322322
impl Orbit {
323+
/// Builds the rotation matrix that rotates from the topocentric frame (SEZ) into the body fixed frame of this state.
324+
///
325+
/// # Frame warnings
326+
/// + If the state is NOT in a body fixed frame (i.e. ITRF93), then this computation is INVALID.
327+
/// + (Usually) no time derivative can be computed: the orbit is expected to be a body fixed frame where the `at_epoch` function will fail. Exceptions for Moon body fixed frames.
328+
///
329+
/// # UNUSED Arguments
330+
/// + `from`: ID of this new frame. Only used to set the "from" frame of the DCM. -- No longer used since 0.5.3
331+
///
332+
/// # Source
333+
/// From the GMAT MathSpec, page 30 section 2.6.9 and from `Calculate_RFT` in `TopocentricAxes.cpp`, this returns the
334+
/// rotation matrix from the topocentric frame (SEZ) to body fixed frame.
335+
/// In the GMAT MathSpec notation, R_{IF} is the DCM from body fixed to inertial. Similarly, R{FT} is from topocentric
336+
/// to body fixed.
337+
pub fn dcm_from_topocentric_to_body_fixed(&self, _from: NaifId) -> PhysicsResult<DCM> {
338+
let rot_mat_dt = if let Ok(pre) = self.at_epoch(self.epoch - Unit::Second * 1) {
339+
if let Ok(post) = self.at_epoch(self.epoch + Unit::Second * 1) {
340+
let dcm_pre = pre.dcm3x3_from_topocentric_to_body_fixed()?;
341+
let dcm_post = post.dcm3x3_from_topocentric_to_body_fixed()?;
342+
Some(0.5 * dcm_post.rot_mat - 0.5 * dcm_pre.rot_mat)
343+
} else {
344+
None
345+
}
346+
} else {
347+
None
348+
};
349+
350+
Ok(DCM {
351+
rot_mat: self.dcm3x3_from_topocentric_to_body_fixed()?.rot_mat,
352+
rot_mat_dt,
353+
from: uuid_from_epoch(self.frame.orientation_id, self.epoch),
354+
to: self.frame.orientation_id,
355+
})
356+
}
357+
323358
/// Builds the rotation matrix that rotates from the topocentric frame (SEZ) into the body fixed frame of this state.
324359
///
325360
/// # Frame warning
326361
/// If the state is NOT in a body fixed frame (i.e. ITRF93), then this computation is INVALID.
327362
///
328-
/// # Arguments
329-
/// + `from`: ID of this new frame, must be unique if it'll be added to the Almanac. Only used to set the "from" frame of the DCM.
330-
///
331363
/// # Source
332364
/// From the GMAT MathSpec, page 30 section 2.6.9 and from `Calculate_RFT` in `TopocentricAxes.cpp`, this returns the
333365
/// rotation matrix from the topocentric frame (SEZ) to body fixed frame.
334366
/// In the GMAT MathSpec notation, R_{IF} is the DCM from body fixed to inertial. Similarly, R{FT} is from topocentric
335367
/// to body fixed.
336-
pub fn dcm_from_topocentric_to_body_fixed(&self, from: NaifId) -> PhysicsResult<DCM> {
368+
pub fn dcm3x3_from_topocentric_to_body_fixed(&self) -> PhysicsResult<DCM> {
337369
if (self.radius_km.x.powi(2) + self.radius_km.y.powi(2)).sqrt() < 1e-3 {
338370
warn!("SEZ frame ill-defined when close to the poles");
339371
}
@@ -357,7 +389,7 @@ impl Orbit {
357389
Ok(DCM {
358390
rot_mat,
359391
rot_mat_dt: None,
360-
from,
392+
from: uuid_from_epoch(self.frame.orientation_id, self.epoch),
361393
to: self.frame.orientation_id,
362394
})
363395
}

anise/src/frames/frame.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,15 @@ impl Frame {
9191
self.shape = Some(shape);
9292
self
9393
}
94+
95+
/// Returns a copy of this frame with the graviational parameter and the shape information from this frame.
96+
/// Use this to prevent astrodynamical computations.
97+
///
98+
/// :rtype: None
99+
pub fn stripped(mut self) -> Self {
100+
self.strip();
101+
self
102+
}
94103
}
95104

96105
#[cfg(feature = "python")]

0 commit comments

Comments
 (0)