Skip to content

Commit 771edff

Browse files
Add transform_many to Python, for multithreaded querying of the Almanac
1 parent 61831d6 commit 771edff

File tree

4 files changed

+75
-12
lines changed

4 files changed

+75
-12
lines changed

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ pyo3 = { version = "0.25", features = ["multiple-pymethods"] }
4444
pyo3-log = "0.12"
4545
numpy = "0.25"
4646
ndarray = ">= 0.15, < 0.17"
47+
rayon = "1.10.0"
4748

4849
anise = { version = "0.6.1", path = "anise", default-features = false }
4950

anise-py/tests/test_almanac.py

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from anise.astro import *
77
from anise.astro.constants import Frames
88
from anise.rotation import DCM
9-
from anise.time import Epoch
9+
from anise.time import Duration, Epoch, TimeSeries, Unit
1010
from anise.utils import convert_tpc
1111

1212
from os import environ
@@ -27,7 +27,7 @@ def test_state_transformation():
2727
print(meta)
2828
# Process the files to be loaded
2929
try:
30-
ctx = meta.process()
30+
almanac = meta.process()
3131
except Exception as e:
3232
if "lfs" in str(e):
3333
# Must be some LFS error in the CI again
@@ -36,14 +36,14 @@ def test_state_transformation():
3636
else:
3737
data_path = Path(__file__).parent.joinpath("..", "..", "data")
3838
# Must ensure that the path is a string
39-
ctx = Almanac(str(data_path.joinpath("de440s.bsp")))
39+
almanac = Almanac(str(data_path.joinpath("de440s.bsp")))
4040
# Let's add another file here -- note that the Almanac will load into a NEW variable, so we must overwrite it!
4141
# This prevents memory leaks (yes, I promise)
42-
ctx = ctx.load(str(data_path.joinpath("pck08.pca"))).load(
42+
almanac = almanac.load(str(data_path.joinpath("pck08.pca"))).load(
4343
str(data_path.joinpath("earth_latest_high_prec.bpc"))
4444
)
4545

46-
eme2k = ctx.frame_info(Frames.EME2000)
46+
eme2k = almanac.frame_info(Frames.EME2000)
4747
assert eme2k.mu_km3_s2() == 398600.435436096
4848
assert eme2k.shape.polar_radius_km == 6356.75
4949
assert abs(eme2k.shape.flattening() - 0.0033536422844278) < 2e-16
@@ -94,7 +94,7 @@ def test_state_transformation():
9494
# In Python, we can set the aberration to None
9595
aberration = None
9696

97-
state_itrf93 = ctx.transform_to(orig_state, Frames.EARTH_ITRF93, aberration)
97+
state_itrf93 = almanac.transform_to(orig_state, Frames.EARTH_ITRF93, aberration)
9898

9999
print(orig_state)
100100
print(state_itrf93)
@@ -104,7 +104,7 @@ def test_state_transformation():
104104
assert abs(state_itrf93.height_km() - 1814.503598063825) < 1e-10
105105

106106
# Convert back
107-
from_state_itrf93_to_eme2k = ctx.transform_to(state_itrf93, Frames.EARTH_J2000)
107+
from_state_itrf93_to_eme2k = almanac.transform_to(state_itrf93, Frames.EARTH_J2000)
108108

109109
print(from_state_itrf93_to_eme2k)
110110

@@ -113,7 +113,7 @@ def test_state_transformation():
113113
# Demo creation of a ground station
114114
mean_earth_angular_velocity_deg_s = 0.004178079012116429
115115
# Grab the loaded frame info
116-
itrf93 = ctx.frame_info(Frames.EARTH_ITRF93)
116+
itrf93 = almanac.frame_info(Frames.EARTH_ITRF93)
117117
paris = Orbit.from_latlongalt(
118118
48.8566,
119119
2.3522,
@@ -147,7 +147,30 @@ def test_state_transformation():
147147
"line_of_sight_obstructed",
148148
"azimuth_elevation_range_sez",
149149
]:
150-
assert hasattr(ctx, fname)
150+
assert hasattr(almanac, fname)
151+
152+
# Test the parallel function calls
153+
start = Epoch("2021-10-29 12:34:56 TDB")
154+
stop = Epoch("2022-10-29 12:34:56 TDB")
155+
time_series = TimeSeries(
156+
start,
157+
stop,
158+
Duration("1 min"),
159+
False,
160+
)
161+
162+
tick = Epoch.system_now()
163+
164+
states = almanac.transform_many(
165+
Frames.EARTH_J2000,
166+
Frames.SUN_J2000,
167+
time_series,
168+
None,
169+
)
170+
171+
clock_time = Epoch.system_now().timedelta(tick)
172+
print(f"Queried {len(states)} states in {clock_time}")
173+
assert len(states) == int(stop.timedelta(start).to_unit(Unit.Minute))
151174

152175

153176
def test_convert_tpc():
@@ -213,5 +236,5 @@ def test_frame_defs():
213236
# test_meta_load()
214237
# test_exports()
215238
# test_frame_defs()
216-
test_convert_tpc()
239+
# test_convert_tpc()
217240
test_state_transformation()

anise/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ rust-embed = { version = "8.4.0", features = [
4343
"include-exclude",
4444
], optional = true }
4545
regex = { version = "1.10.5", optional = true }
46+
rayon = { workspace = true, optional = true } # Only used when building with Python
4647

4748
[dev-dependencies]
4849
rust-spice = "0.7.6"
@@ -66,7 +67,7 @@ ureq = { version = "3.0.10", default-features = false, optional = true, features
6667

6768
[features]
6869
default = ["metaload"]
69-
python = ["pyo3", "pyo3-log", "numpy", "ndarray"]
70+
python = ["pyo3", "pyo3-log", "numpy", "ndarray", "rayon"]
7071
metaload = ["url", "ureq", "platform-dirs", "regex", "serde_dhall"]
7172
embed_ephem = ["rust-embed", "ureq"]
7273
# Enabling this flag significantly increases compilation times due to Arrow and Polars.

anise/src/almanac/python.rs

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,9 @@ use crate::{
2121
prelude::{Frame, Orbit},
2222
NaifId,
2323
};
24-
use hifitime::{Epoch, TimeScale};
24+
use hifitime::{Epoch, TimeScale, TimeSeries};
2525
use pyo3::prelude::*;
26+
use rayon::prelude::*;
2627
use snafu::prelude::*;
2728

2829
#[pymethods]
@@ -280,6 +281,43 @@ impl Almanac {
280281
self.transform(target_frame, observer_frame, epoch, ab_corr)
281282
}
282283

284+
/// Returns a dictionary of the Epoch to the Cartesian states that transform the `from_frame` to the `to_frame` for each epoch of the time series, computed in parallel under the hood.
285+
///
286+
/// Refer to [transform] for details.
287+
///
288+
/// :type target_frame: Orbit
289+
/// :type observer_frame: Frame
290+
/// :type epoch: Epoch
291+
/// :type ab_corr: Aberration, optional
292+
/// :rtype: Orbit
293+
#[pyo3(name = "transform_many", signature=(
294+
target_frame,
295+
observer_frame,
296+
time_series,
297+
ab_corr=None,
298+
))]
299+
fn py_transform_many<'py>(
300+
&self,
301+
target_frame: Frame,
302+
observer_frame: Frame,
303+
time_series: TimeSeries,
304+
ab_corr: Option<Aberration>,
305+
) -> Vec<Option<CartesianState>> {
306+
time_series
307+
.par_bridge()
308+
.map(|epoch| {
309+
self.transform(target_frame, observer_frame, epoch, ab_corr)
310+
.map_or_else(
311+
|e| {
312+
println!("{e}");
313+
None
314+
},
315+
|state| Some(state),
316+
)
317+
})
318+
.collect::<Vec<Option<CartesianState>>>()
319+
}
320+
283321
/// Translates a state with its origin (`to_frame`) and given its units (distance_unit, time_unit), returns that state with respect to the requested frame
284322
///
285323
/// **WARNING:** This function only performs the translation and no rotation _whatsoever_. Use the `transform_state_to` function instead to include rotations.

0 commit comments

Comments
 (0)