Skip to content

Commit da7295c

Browse files
Merge pull request #443 from nyx-space/prep-for-0.6.0-py-fix
Duplicate python sig for newer pyo3
2 parents 009a437 + 208b72c commit da7295c

File tree

16 files changed

+749
-230
lines changed

16 files changed

+749
-230
lines changed

anise-py/anise.pyi

Lines changed: 147 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ This function performs a memory allocation."""
107107
# Warning
108108
This function performs a memory allocation."""
109109

110-
def describe(self, spk: bool=None, bpc: bool=None, planetary: bool=None, time_scale: TimeScale=None, round_time: bool=None) -> None:
110+
def describe(self, spk: bool=None, bpc: bool=None, planetary: bool=None, eulerparams: bool=None, time_scale: TimeScale=None, round_time: bool=None) -> None:
111111
"""Pretty prints the description of this Almanac, showing everything by default. Default time scale is TDB.
112112
If any parameter is set to true, then nothing other than that will be printed."""
113113

@@ -159,6 +159,20 @@ A 100% percent occultation means that the back object is fully hidden from the
159159
A value in between means that the back object is partially hidden from the observser (i.e. _penumbra_ if the back object is the Sun).
160160
Refer to the [MathSpec](https://nyxspace.com/nyxspace/MathSpec/celestial/eclipse/) for modeling details."""
161161

162+
def rotate(self, from_frame: Frame, to_frame: Frame, epoch: Epoch) -> DCM:
163+
"""Returns the 6x6 DCM needed to rotation the `from_frame` to the `to_frame`.
164+
165+
# Warning
166+
This function only performs the rotation and no translation whatsoever. Use the `transform_from_to` function instead to include rotations.
167+
168+
# Note
169+
This function performs a recursion of no more than twice the MAX_TREE_DEPTH."""
170+
171+
def rotate_to(self, state: CartesianState, observer_frame: Frame) -> CartesianState:
172+
"""Rotates the provided Cartesian state into the requested observer frame
173+
174+
**WARNING:** This function only performs the translation and no rotation _whatsoever_. Use the `transform_to` function instead to include rotations."""
175+
162176
def solar_eclipsing(self, eclipsing_frame: Frame, observer: Orbit, ab_corr: Aberration=None) -> Occultation:
163177
"""Computes the solar eclipsing of the observer due to the eclipsing_frame.
164178
@@ -183,7 +197,7 @@ This function performs a memory allocation."""
183197
# Warning
184198
This function performs a memory allocation."""
185199

186-
def state_of(self, object: int, observer: Frame, epoch: Epoch, ab_corr: Aberration=None) -> Orbit:
200+
def state_of(self, object_id: int, observer: Frame, epoch: Epoch, ab_corr: Aberration=None) -> Orbit:
187201
"""Returns the Cartesian state of the object as seen from the provided observer frame (essentially `spkezr`).
188202
189203
# Note
@@ -407,8 +421,6 @@ This function modified `self` and changes the URI to be the path to the download
407421

408422
@typing.final
409423
class astro:
410-
_all__: list = ["constants", "AzElRange", "Ellipsoid", "Occultation", "Orbit"]
411-
412424
@typing.final
413425
class AzElRange:
414426
"""A structure that stores the result of Azimuth, Elevation, Range, Range rate calculation."""
@@ -716,6 +728,127 @@ class astro:
716728
def c3_km2_s2(self) -> float:
717729
"""Returns the $C_3$ of this orbit in km^2/s^2"""
718730

731+
def cartesian_pos_vel(self) -> numpy.array:
732+
"""Returns this state as a Cartesian vector of size 6 in [km, km, km, km/s, km/s, km/s]
733+
734+
Note that the time is **not** returned in the vector."""
735+
736+
def dcm3x3_from_rcn_to_inertial(self) -> DCM:
737+
"""Builds the rotation matrix that rotates from this state's inertial frame to this state's RCN frame (radial, cross, normal)
738+
739+
# Frame warning
740+
If the stattion is NOT in an inertial frame, then this computation is INVALID.
741+
742+
# Algorithm
743+
1. Compute \\hat{r}, \\hat{h}, the unit vectors of the radius and orbital momentum.
744+
2. Compute the cross product of these
745+
3. Build the DCM with these unit vectors
746+
4. Return the DCM structure"""
747+
748+
def dcm3x3_from_ric_to_inertial(self) -> DCM:
749+
"""Builds the rotation matrix that rotates from this state's inertial frame to this state's RIC frame
750+
751+
# Frame warning
752+
If the state is NOT in an inertial frame, then this computation is INVALID.
753+
754+
# Algorithm
755+
1. Build the c vector as the normalized orbital momentum vector
756+
2. Build the i vector as the cross product of \\hat{r} and c
757+
3. Build the RIC DCM as a 3x3 of the columns [\\hat{r}, \\hat{i}, \\hat{c}]
758+
4. Return the DCM structure **without** accounting for the transport theorem."""
759+
760+
def dcm3x3_from_topocentric_to_body_fixed(self) -> DCM:
761+
"""Builds the rotation matrix that rotates from the topocentric frame (SEZ) into the body fixed frame of this state.
762+
763+
# Frame warning
764+
If the state is NOT in a body fixed frame (i.e. ITRF93), then this computation is INVALID.
765+
766+
# Source
767+
From the GMAT MathSpec, page 30 section 2.6.9 and from `Calculate_RFT` in `TopocentricAxes.cpp`, this returns the
768+
rotation matrix from the topocentric frame (SEZ) to body fixed frame.
769+
In the GMAT MathSpec notation, R_{IF} is the DCM from body fixed to inertial. Similarly, R{FT} is from topocentric
770+
to body fixed."""
771+
772+
def dcm3x3_from_vnc_to_inertial(self) -> DCM:
773+
"""Builds the rotation matrix that rotates from this state's inertial frame to this state's VNC frame (velocity, normal, cross)
774+
775+
# Frame warning
776+
If the stattion is NOT in an inertial frame, then this computation is INVALID.
777+
778+
# Algorithm
779+
1. Compute \\hat{v}, \\hat{h}, the unit vectors of the radius and orbital momentum.
780+
2. Compute the cross product of these
781+
3. Build the DCM with these unit vectors
782+
4. Return the DCM structure."""
783+
784+
def dcm_from_rcn_to_inertial(self) -> DCM:
785+
"""Builds the rotation matrix that rotates from this state's inertial frame to this state's RCN frame (radial, cross, normal)
786+
787+
# Frame warning
788+
If the stattion is NOT in an inertial frame, then this computation is INVALID.
789+
790+
# Algorithm
791+
1. Compute \\hat{r}, \\hat{h}, the unit vectors of the radius and orbital momentum.
792+
2. Compute the cross product of these
793+
3. Build the DCM with these unit vectors
794+
4. Return the DCM structure with a 6x6 DCM with the time derivative of the VNC frame set.
795+
796+
# Note on the time derivative
797+
If the pre or post states cannot be computed, then the time derivative of the DCM will _not_ be set.
798+
Further note that most astrodynamics tools do *not* account for the time derivative in the RIC frame."""
799+
800+
def dcm_from_ric_to_inertial(self) -> DCM:
801+
"""Builds the rotation matrix that rotates from this state's inertial frame to this state's RIC frame
802+
803+
# Frame warning
804+
If the state is NOT in an inertial frame, then this computation is INVALID.
805+
806+
# Algorithm
807+
1. Compute the state data one millisecond before and one millisecond assuming two body dynamics
808+
2. Compute the DCM for this state, and the pre and post states
809+
3. Build the c vector as the normalized orbital momentum vector
810+
4. Build the i vector as the cross product of \\hat{r} and c
811+
5. Build the RIC DCM as a 3x3 of the columns [\\hat{r}, \\hat{i}, \\hat{c}], for the post, post, and current states
812+
6. Compute the difference between the DCMs of the pre and post states, to build the DCM time derivative
813+
7. Return the DCM structure with a 6x6 state DCM.
814+
815+
# Note on the time derivative
816+
If the pre or post states cannot be computed, then the time derivative of the DCM will _not_ be set.
817+
Further note that most astrodynamics tools do *not* account for the time derivative in the RIC frame."""
818+
819+
def dcm_from_topocentric_to_body_fixed(self, _from: float) -> DCM:
820+
"""Builds the rotation matrix that rotates from the topocentric frame (SEZ) into the body fixed frame of this state.
821+
822+
# Frame warnings
823+
+ If the state is NOT in a body fixed frame (i.e. ITRF93), then this computation is INVALID.
824+
+ (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.
825+
826+
# UNUSED Arguments
827+
+ `from`: ID of this new frame. Only used to set the "from" frame of the DCM. -- No longer used since 0.5.3
828+
829+
# Source
830+
From the GMAT MathSpec, page 30 section 2.6.9 and from `Calculate_RFT` in `TopocentricAxes.cpp`, this returns the
831+
rotation matrix from the topocentric frame (SEZ) to body fixed frame.
832+
In the GMAT MathSpec notation, R_{IF} is the DCM from body fixed to inertial. Similarly, R{FT} is from topocentric
833+
to body fixed."""
834+
835+
def dcm_from_vnc_to_inertial(self) -> DCM:
836+
"""Builds the rotation matrix that rotates from this state's inertial frame to this state's VNC frame (velocity, normal, cross)
837+
838+
# Frame warning
839+
If the stattion is NOT in an inertial frame, then this computation is INVALID.
840+
841+
# Algorithm
842+
1. Compute \\hat{v}, \\hat{h}, the unit vectors of the radius and orbital momentum.
843+
2. Compute the cross product of these
844+
3. Build the DCM with these unit vectors
845+
4. Compute the difference between the DCMs of the pre and post states (+/- 1 ms), to build the DCM time derivative
846+
4. Return the DCM structure with a 6x6 DCM with the time derivative of the VNC frame set.
847+
848+
# Note on the time derivative
849+
If the pre or post states cannot be computed, then the time derivative of the DCM will _not_ be set.
850+
Further note that most astrodynamics tools do *not* account for the time derivative in the RIC frame."""
851+
719852
def declination_deg(self) -> float:
720853
"""Returns the declination of this orbit in degrees"""
721854

@@ -1023,6 +1156,7 @@ class astro:
10231156
def __str__(self) -> str:
10241157
"""Return str(self)."""
10251158

1159+
@typing.final
10261160
class constants:
10271161
@typing.final
10281162
class CelestialObjects:
@@ -1065,7 +1199,11 @@ class astro:
10651199
MARS_BARYCENTER_J2000: Frame = ...
10661200
MERCURY_J2000: Frame = ...
10671201
MOON_J2000: Frame = ...
1202+
MOON_ME_DE421_FRAME: Frame = ...
1203+
MOON_ME_DE440_ME421_FRAME: Frame = ...
10681204
MOON_ME_FRAME: Frame = ...
1205+
MOON_PA_DE421_FRAME: Frame = ...
1206+
MOON_PA_DE440_FRAME: Frame = ...
10691207
MOON_PA_FRAME: Frame = ...
10701208
NEPTUNE_BARYCENTER_J2000: Frame = ...
10711209
PLUTO_BARYCENTER_J2000: Frame = ...
@@ -1090,14 +1228,19 @@ class astro:
10901228
ITRF93: int = ...
10911229
J2000: int = ...
10921230
MOON_ME: int = ...
1231+
MOON_ME_DE421: int = ...
1232+
MOON_ME_DE440_ME421: int = ...
10931233
MOON_PA: int = ...
1234+
MOON_PA_DE421: int = ...
1235+
MOON_PA_DE440: int = ...
10941236

10951237
@typing.final
10961238
class UsualConstants:
10971239
MEAN_EARTH_ANGULAR_VELOCITY_DEG_S: float = ...
10981240
MEAN_MOON_ANGULAR_VELOCITY_DEG_S: float = ...
10991241
SPEED_OF_LIGHT_KM_S: float = ...
11001242

1243+
11011244
@typing.final
11021245
class time:
11031246
@typing.final
@@ -2201,10 +2344,8 @@ KPL/TPC files must be converted into "PCA" (Planetary Constant ANISE) files befo
22012344
__all__: list = ...
22022345
__name__: str = ...
22032346

2204-
22052347
@typing.final
22062348
class rotation:
2207-
22082349
@typing.final
22092350
class DCM:
22102351
"""Defines a direction cosine matrix from one frame ID to another frame ID, optionally with its time derivative.

anise-py/tests/test_almanac.py

Lines changed: 40 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ def test_state_transformation():
2020

2121
if environ.get("CI", False):
2222
# Load from meta kernel to not use Git LFS quota
23-
data_path = Path(__file__).parent.joinpath("..", "..", "data", "ci_config.dhall")
23+
data_path = Path(__file__).parent.joinpath(
24+
"..", "..", "data", "ci_config.dhall"
25+
)
2426
meta = MetaAlmanac(str(data_path))
2527
print(meta)
2628
# Process the files to be loaded
@@ -30,7 +32,7 @@ def test_state_transformation():
3032
if "lfs" in str(e):
3133
# Must be some LFS error in the CI again
3234
return
33-
raise # Otherwise, raise the error!
35+
raise # Otherwise, raise the error!
3436
else:
3537
data_path = Path(__file__).parent.joinpath("..", "..", "data")
3638
# Must ensure that the path is a string
@@ -68,7 +70,11 @@ def test_state_transformation():
6870
assert orig_state.cartesian_pos_vel().shape == (6,)
6971

7072
# Ensure we can call all of the DCM functions
71-
for func in ["dcm_from_ric_to_inertial", "dcm_from_rcn_to_inertial", "dcm_from_vnc_to_inertial"]:
73+
for func in [
74+
"dcm_from_ric_to_inertial",
75+
"dcm_from_rcn_to_inertial",
76+
"dcm_from_vnc_to_inertial",
77+
]:
7278
dcm = getattr(orig_state, func)()
7379
assert dcm.get_state_dcm().shape == (6, 6)
7480
assert dcm.rot_mat.shape == (3, 3)
@@ -77,11 +83,13 @@ def test_state_transformation():
7783
# Test rebuilding the DCM from its parts
7884
dcm_rebuilt = DCM(dcm.rot_mat, dcm.from_id, dcm.to_id, dcm.rot_mat_dt)
7985
assert dcm_rebuilt == dcm
80-
86+
8187
topo_dcm = orig_state.dcm_from_topocentric_to_body_fixed(123)
8288
assert topo_dcm.get_state_dcm().shape == (6, 6)
8389
assert topo_dcm.rot_mat.shape == (3, 3)
84-
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
90+
assert (
91+
topo_dcm.rot_mat_dt is not None and topo_dcm.rot_mat_dt.shape == (3, 3)
92+
) or topo_dcm.rot_mat_dt is None
8593

8694
# In Python, we can set the aberration to None
8795
aberration = None
@@ -96,9 +104,7 @@ def test_state_transformation():
96104
assert abs(state_itrf93.height_km() - 1814.503598063825) < 1e-10
97105

98106
# Convert back
99-
from_state_itrf93_to_eme2k = ctx.transform_to(
100-
state_itrf93, Frames.EARTH_J2000, aberration
101-
)
107+
from_state_itrf93_to_eme2k = ctx.transform_to(state_itrf93, Frames.EARTH_J2000)
102108

103109
print(from_state_itrf93_to_eme2k)
104110

@@ -124,8 +130,24 @@ def test_state_transformation():
124130
# Pickling test
125131
pickle.loads(pickle.dumps(eme2k)) == eme2k
126132
pickle.loads(pickle.dumps(eme2k.shape)) == eme2k.shape
127-
# Cannot yet pickle Epoch, so we can't pickle an Orbit yet
128-
# cf. https://github.com/nyx-space/hifitime/issues/270
133+
# Cannot pickle across module boundaries =(
134+
# pickle.loads(pickle.dumps(paris)) == paris
135+
136+
# Function export test
137+
for fname in [
138+
"transform",
139+
"transform_to",
140+
"translate",
141+
"translate_to",
142+
"translate_geometric",
143+
"spk_ezr",
144+
"state_of",
145+
"solar_eclipsing",
146+
"occultation",
147+
"line_of_sight_obstructed",
148+
"azimuth_elevation_range_sez",
149+
]:
150+
assert hasattr(ctx, fname)
129151

130152

131153
def test_convert_tpc():
@@ -139,18 +161,23 @@ def test_convert_tpc():
139161
convert_tpc("data/pck00011.tpc", "data/gm_de440.tpc", "test_constants.tpc")
140162

141163
# Second call, with overwrite enabled, also works
142-
convert_tpc("data/pck00011.tpc", "data/gm_de440.tpc", "test_constants.tpc", overwrite=True)
164+
convert_tpc(
165+
"data/pck00011.tpc", "data/gm_de440.tpc", "test_constants.tpc", overwrite=True
166+
)
143167

144168
# Try to load the constants file
145169
constants_file = MetaFile("test_constants.tpc")
146170
new_meta = MetaAlmanac()
147-
new_meta.files = [constants_file,]
171+
new_meta.files = [
172+
constants_file,
173+
]
148174
almanac = new_meta.process()
149175

150176
earth_j2k = almanac.frame_info(Frames.EARTH_J2000)
151177
assert earth_j2k.mu_km3_s2 != None
152178
almanac.describe()
153179

180+
154181
def test_meta_load():
155182
data_path = Path(__file__).parent.joinpath("..", "..", "data", "local.dhall")
156183
meta = MetaAlmanac(str(data_path))
@@ -186,4 +213,5 @@ def test_frame_defs():
186213
# test_meta_load()
187214
# test_exports()
188215
# test_frame_defs()
216+
test_convert_tpc()
189217
test_state_transformation()

anise/src/almanac/aer.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,6 @@ use log::warn;
2626

2727
use snafu::ResultExt;
2828

29-
#[cfg(feature = "python")]
30-
use pyo3::prelude::*;
31-
32-
#[cfg_attr(feature = "python", pymethods)]
3329
impl Almanac {
3430
/// Computes the azimuth (in degrees), elevation (in degrees), and range (in kilometers) of the
3531
/// receiver state (`rx`) seen from the transmitter state (`tx`), once converted into the SEZ frame of the transmitter.
@@ -45,12 +41,6 @@ impl Almanac {
4541
/// 5. Compute the range as the norm of the difference between these two position vectors.
4642
/// 6. Compute the elevation, and ensure it is between +/- 180 degrees.
4743
/// 7. Compute the azimuth with a quadrant check, and ensure it is between 0 and 360 degrees.
48-
///
49-
/// :type rx: Orbit
50-
/// :type tx: Orbit
51-
/// :type obstructing_body: Frame, optional
52-
/// :type ab_corr: Aberration, optional
53-
/// :rtype: AzElRange
5444
pub fn azimuth_elevation_range_sez(
5545
&self,
5646
rx: Orbit,

anise/src/almanac/eclipse.rs

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,6 @@ use crate::errors::AlmanacResult;
2424

2525
use snafu::ResultExt;
2626

27-
#[cfg(feature = "python")]
28-
use pyo3::prelude::*;
29-
30-
#[cfg_attr(feature = "python", pymethods)]
3127
impl Almanac {
3228
/// Computes whether the line of sight between an observer and an observed Cartesian state is obstructed by the obstructing body.
3329
/// Returns true if the obstructing body is in the way, false otherwise.

0 commit comments

Comments
 (0)