Skip to content

Commit 43c9791

Browse files
Merge pull request #458 from nyx-space/fix-gil-deadlock
Fix Python GIL deadlock in `many` functions
2 parents 1d8338e + 9a76427 commit 43c9791

File tree

4 files changed

+123
-70
lines changed

4 files changed

+123
-70
lines changed

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", "anise/fuzz"]
44

55
[workspace.package]
6-
version = "0.6.1"
6+
version = "0.6.2"
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."
@@ -46,7 +46,7 @@ numpy = "0.25"
4646
ndarray = ">= 0.15, < 0.17"
4747
rayon = "1.10.0"
4848

49-
anise = { version = "0.6.1", path = "anise", default-features = false }
49+
anise = { version = "0.6.2", path = "anise", default-features = false }
5050

5151
[profile.bench]
5252
debug = true

anise/src/almanac/python.rs

Lines changed: 72 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -143,25 +143,28 @@ impl Almanac {
143143
))]
144144
fn py_azimuth_elevation_range_sez_many(
145145
&self,
146+
py: Python,
146147
rx_tx_states: Vec<(CartesianState, CartesianState)>,
147148
obstructing_body: Option<Frame>,
148149
ab_corr: Option<Aberration>,
149150
) -> Vec<AzElRange> {
150-
let mut rslt = rx_tx_states
151-
.par_iter()
152-
.filter_map(|(rx, tx)| {
153-
self.azimuth_elevation_range_sez(*rx, *tx, obstructing_body, ab_corr)
154-
.map_or_else(
155-
|e| {
156-
println!("{e}");
157-
None
158-
},
159-
|aer| Some(aer),
160-
)
161-
})
162-
.collect::<Vec<AzElRange>>();
163-
rslt.sort_by(|aer_a, aer_b| aer_a.epoch.cmp(&aer_b.epoch));
164-
rslt
151+
py.allow_threads(|| {
152+
let mut rslt = rx_tx_states
153+
.par_iter()
154+
.filter_map(|(rx, tx)| {
155+
self.azimuth_elevation_range_sez(*rx, *tx, obstructing_body, ab_corr)
156+
.map_or_else(
157+
|e| {
158+
println!("{e}");
159+
None
160+
},
161+
|aer| Some(aer),
162+
)
163+
})
164+
.collect::<Vec<AzElRange>>();
165+
rslt.sort_by(|aer_a, aer_b| aer_a.epoch.cmp(&aer_b.epoch));
166+
rslt
167+
})
165168
}
166169

167170
/// Computes whether the line of sight between an observer and an observed Cartesian state is obstructed by the obstructing body.
@@ -283,25 +286,28 @@ impl Almanac {
283286
))]
284287
fn py_solar_eclipsing_many(
285288
&self,
289+
py: Python,
286290
eclipsing_frame: Frame,
287291
observers: Vec<Orbit>,
288292
ab_corr: Option<Aberration>,
289293
) -> Vec<Occultation> {
290-
let mut rslt = observers
291-
.par_iter()
292-
.filter_map(|observer| {
293-
self.solar_eclipsing(eclipsing_frame, *observer, ab_corr)
294-
.map_or_else(
295-
|e| {
296-
println!("{e}");
297-
None
298-
},
299-
|aer| Some(aer),
300-
)
301-
})
302-
.collect::<Vec<Occultation>>();
303-
rslt.sort_by(|aer_a, aer_b| aer_a.epoch.cmp(&aer_b.epoch));
304-
rslt
294+
py.allow_threads(|| {
295+
let mut rslt = observers
296+
.par_iter()
297+
.filter_map(|observer| {
298+
self.solar_eclipsing(eclipsing_frame, *observer, ab_corr)
299+
.map_or_else(
300+
|e| {
301+
println!("{e}");
302+
None
303+
},
304+
|aer| Some(aer),
305+
)
306+
})
307+
.collect::<Vec<Occultation>>();
308+
rslt.sort_by(|aer_a, aer_b| aer_a.epoch.cmp(&aer_b.epoch));
309+
rslt
310+
})
305311
}
306312

307313
/// Computes the Beta angle (β) for a given orbital state, in degrees. A Beta angle of 0° indicates that the orbit plane is edge-on to the Sun, leading to maximum eclipse time. Conversely, a Beta angle of +90° or -90° means the orbit plane is face-on to the Sun, resulting in continuous sunlight exposure and no eclipses.
@@ -377,26 +383,29 @@ impl Almanac {
377383
))]
378384
fn py_transform_many<'py>(
379385
&self,
386+
py: Python,
380387
target_frame: Frame,
381388
observer_frame: Frame,
382389
time_series: TimeSeries,
383390
ab_corr: Option<Aberration>,
384391
) -> Vec<CartesianState> {
385-
let mut states = time_series
386-
.par_bridge()
387-
.filter_map(|epoch| {
388-
self.transform(target_frame, observer_frame, epoch, ab_corr)
389-
.map_or_else(
390-
|e| {
391-
eprintln!("{e}");
392-
None
393-
},
394-
|state| Some(state),
395-
)
396-
})
397-
.collect::<Vec<CartesianState>>();
398-
states.sort_by(|state_a, state_b| state_a.epoch.cmp(&state_b.epoch));
399-
states
392+
py.allow_threads(|| {
393+
let mut states = time_series
394+
.par_bridge()
395+
.filter_map(|epoch| {
396+
self.transform(target_frame, observer_frame, epoch, ab_corr)
397+
.map_or_else(
398+
|e| {
399+
eprintln!("{e}");
400+
None
401+
},
402+
|state| Some(state),
403+
)
404+
})
405+
.collect::<Vec<CartesianState>>();
406+
states.sort_by(|state_a, state_b| state_a.epoch.cmp(&state_b.epoch));
407+
states
408+
})
400409
}
401410

402411
/// Returns the provided state as seen from the observer frame, given the aberration.
@@ -436,25 +445,28 @@ impl Almanac {
436445
))]
437446
fn py_transform_many_to(
438447
&self,
448+
py: Python,
439449
states: Vec<CartesianState>,
440450
observer_frame: Frame,
441451
ab_corr: Option<Aberration>,
442452
) -> Vec<CartesianState> {
443-
let mut rslt = states
444-
.par_iter()
445-
.filter_map(|state| {
446-
self.transform_to(*state, observer_frame, ab_corr)
447-
.map_or_else(
448-
|e| {
449-
println!("{e}");
450-
None
451-
},
452-
|state| Some(state),
453-
)
454-
})
455-
.collect::<Vec<CartesianState>>();
456-
rslt.sort_by(|state_a, state_b| state_a.epoch.cmp(&state_b.epoch));
457-
rslt
453+
py.allow_threads(|| {
454+
let mut rslt = states
455+
.par_iter()
456+
.filter_map(|state| {
457+
self.transform_to(*state, observer_frame, ab_corr)
458+
.map_or_else(
459+
|e| {
460+
println!("{e}");
461+
None
462+
},
463+
|state| Some(state),
464+
)
465+
})
466+
.collect::<Vec<CartesianState>>();
467+
rslt.sort_by(|state_a, state_b| state_a.epoch.cmp(&state_b.epoch));
468+
rslt
469+
})
458470
}
459471

460472
/// Returns the Cartesian state of the object as seen from the provided observer frame (essentially `spkezr`).

anise/src/astro/orbit.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ use super::PhysicsResult;
1313

1414
use crate::{
1515
errors::{
16-
HyperbolicTrueAnomalySnafu, InfiniteValueSnafu, ParabolicEccentricitySnafu,
16+
HyperbolicTrueAnomalySnafu, InfiniteValueSnafu, MathError, ParabolicEccentricitySnafu,
1717
ParabolicSemiParamSnafu, PhysicsError, RadiusSnafu, VelocitySnafu,
1818
},
1919
math::{
@@ -25,7 +25,7 @@ use crate::{
2525
prelude::{uuid_from_epoch, Frame},
2626
NaifId,
2727
};
28-
use core::f64::consts::PI;
28+
use core::f64::consts::{PI, TAU};
2929

3030
use core::fmt;
3131
use hifitime::{Duration, Epoch, TimeUnits, Unit};
@@ -37,7 +37,7 @@ use pyo3::prelude::*;
3737
#[cfg(feature = "python")]
3838
use pyo3::types::PyType;
3939

40-
/// If an orbit has an eccentricity below the following value, it is considered circular (only affects warning messages)
40+
/// If an orbit has an eccentricity below the following value, it is considered circular.
4141
pub const ECC_EPSILON: f64 = 1e-11;
4242

4343
/// A helper type alias, but no assumptions are made on the underlying validity of the frame.
@@ -787,8 +787,7 @@ impl Orbit {
787787
///
788788
/// :rtype: Duration
789789
pub fn period(&self) -> PhysicsResult<Duration> {
790-
Ok(2.0
791-
* PI
790+
Ok(TAU
792791
* (self.sma_km()?.powi(3) / self.frame.mu_km3_s2()?)
793792
.sqrt()
794793
.seconds())
@@ -904,7 +903,7 @@ impl Orbit {
904903
Ok(0.0)
905904
}
906905
} else if self.evec()?[2] < 0.0 {
907-
Ok((2.0 * PI - aop).to_degrees())
906+
Ok((TAU - aop).to_degrees())
908907
} else {
909908
Ok(aop.to_degrees())
910909
}
@@ -965,7 +964,7 @@ impl Orbit {
965964
Ok(0.0)
966965
}
967966
} else if n[1] < 0.0 {
968-
Ok((2.0 * PI - raan).to_degrees())
967+
Ok((TAU - raan).to_degrees())
969968
} else {
970969
Ok(raan.to_degrees())
971970
}
@@ -1039,7 +1038,7 @@ impl Orbit {
10391038
Ok(0.0)
10401039
}
10411040
} else if self.radius_km.dot(&self.velocity_km_s) < 0.0 {
1042-
Ok((2.0 * PI - ta).to_degrees())
1041+
Ok((TAU - ta).to_degrees())
10431042
} else {
10441043
Ok(ta.to_degrees())
10451044
}
@@ -1301,6 +1300,30 @@ impl Orbit {
13011300
Ok(-self.frame.mu_km3_s2()? / self.sma_km()?)
13021301
}
13031302

1303+
/// Returns the Longitude of the Ascending Node (LTAN), or an error of equatorial orbits
1304+
///
1305+
/// :rtype: float
1306+
pub fn ltan_deg(&self) -> PhysicsResult<f64> {
1307+
let n = Vector3::new(0.0, 0.0, 1.0).cross(&self.hvec()?);
1308+
1309+
if n.norm_squared() < f64::EPSILON {
1310+
return Err(PhysicsError::AppliedMath {
1311+
source: MathError::DomainError {
1312+
value: n.norm_squared(),
1313+
msg: "orbit is equatorial",
1314+
},
1315+
});
1316+
}
1317+
1318+
let mut ltan = n.y.atan2(n.x);
1319+
1320+
if ltan < 0.0 {
1321+
ltan += TAU;
1322+
}
1323+
1324+
Ok(ltan.to_degrees())
1325+
}
1326+
13041327
/// Returns the radius of periapse in kilometers for the provided turn angle of this hyperbolic orbit.
13051328
/// Returns an error if the orbit is not hyperbolic.
13061329
///

anise/tests/astro/orbit.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -855,3 +855,21 @@ fn gh_regression_340(almanac: Almanac) {
855855
assert!(orbit.at_epoch(epoch).is_ok(), "error on {epoch}");
856856
}
857857
}
858+
859+
#[rstest]
860+
fn misc_verif(almanac: Almanac) {
861+
let eme2k = almanac.frame_from_uid(EARTH_J2000).unwrap();
862+
863+
// Blue Ghost landing day!
864+
let epoch = Epoch::from_gregorian_utc_at_noon(2025, 3, 2);
865+
866+
// LTAN
867+
let prograde = Orbit::new(0.0, 7000.0, 0.0, -1.0, 0.0, 7.5, epoch, eme2k);
868+
f64_eq!(prograde.ltan_deg().unwrap(), 90.0, "prograde LTAN");
869+
870+
let retrograde = Orbit::new(0.0, -7000.0, 0.0, -1.0, 0.0, 7.5, epoch, eme2k);
871+
f64_eq!(retrograde.ltan_deg().unwrap(), 270.0, "retrograde LTAN");
872+
873+
let equatorial = Orbit::keplerian(7000.0, 1e-4, 1e-12, 270.0, 45.0, 76.0, epoch, eme2k);
874+
assert!(equatorial.ltan_deg().is_err())
875+
}

0 commit comments

Comments
 (0)