Skip to content

Commit e28438f

Browse files
committed
Add unit tests for ITRF93 transformations
1 parent 986b81a commit e28438f

File tree

3 files changed

+163
-16
lines changed

3 files changed

+163
-16
lines changed

src/adam_core/coordinates/tests/test_transforms_rotation.py

Lines changed: 133 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
1+
import quivr as qv
2+
import spiceypy as sp
3+
4+
from ...constants import KM_P_AU, S_P_DAY
15
from ...time import Timestamp
6+
from ...utils.spice import get_perturber_state, setup_SPICE
27
from ..cartesian import CartesianCoordinates
3-
from ..origin import Origin
8+
from ..origin import Origin, OriginCodes
49
from ..transform import cartesian_to_frame, transform_coordinates
510
from .test_transforms_translation import assert_coords_equal
611

@@ -121,3 +126,130 @@ def test_transform_coordinates_frame(orbital_elements, orbital_elements_equatori
121126
assert_coords_equal(
122127
cartesian_coordinates_equatorial_actual, cartesian_coordinates_equatorial
123128
)
129+
130+
131+
def test_transform_coordinates_to_itrf93():
132+
"""
133+
Test that transform_coordinates correctly converts between ecliptic and
134+
ITRF93 cartesian coordinates
135+
"""
136+
setup_SPICE()
137+
138+
# Get perturber states for a few of the planets
139+
times = Timestamp.from_mjd([59000, 59500, 60000], scale="tdb")
140+
states = CartesianCoordinates.empty()
141+
target_ids = []
142+
for perturber in [
143+
OriginCodes.MOON,
144+
OriginCodes.VENUS,
145+
OriginCodes.MARS_BARYCENTER,
146+
OriginCodes.JUPITER_BARYCENTER,
147+
OriginCodes.SATURN_BARYCENTER,
148+
OriginCodes.URANUS_BARYCENTER,
149+
OriginCodes.NEPTUNE_BARYCENTER,
150+
]:
151+
states = qv.concatenate(
152+
[
153+
states,
154+
get_perturber_state(
155+
perturber, times, origin=OriginCodes.SUN, frame="ecliptic"
156+
),
157+
]
158+
)
159+
target_ids.extend(perturber.name for _ in range(len(times)))
160+
161+
states_itrf93 = transform_coordinates(states, frame_out="itrf93")
162+
163+
# Repeat with SPICE
164+
states_spice_itrf93 = CartesianCoordinates.empty()
165+
for coord, target_id in zip(states_itrf93, target_ids):
166+
# Rotate the coordinates to the ITRF93 frame using SPICE
167+
state, lt = sp.spkezr(
168+
target_id, coord.time.et()[0].as_py(), "ITRF93", "NONE", "SUN"
169+
)
170+
states_spice_itrf93 = qv.concatenate(
171+
[
172+
states_spice_itrf93,
173+
CartesianCoordinates.from_kwargs(
174+
x=[state[0] / KM_P_AU],
175+
y=[state[1] / KM_P_AU],
176+
z=[state[2] / KM_P_AU],
177+
vx=[state[3] / KM_P_AU * S_P_DAY],
178+
vy=[state[4] / KM_P_AU * S_P_DAY],
179+
vz=[state[5] / KM_P_AU * S_P_DAY],
180+
time=coord.time, # Add time from original coordinate
181+
frame="itrf93", # Specify the frame
182+
origin=Origin.from_kwargs(code=["SUN"]), # Specify the origin
183+
),
184+
]
185+
)
186+
187+
# Test that the two coordinate sets are equal within tolerance
188+
assert_coords_equal(
189+
states_itrf93, states_spice_itrf93, position_tol_mm=10, velocity_tol_nm_s=500
190+
)
191+
192+
193+
def test_transform_coordinates_from_itrf93():
194+
"""
195+
Test that transform_coordinates correctly converts between ITRF93 and
196+
ecliptic cartesian coordinates
197+
"""
198+
setup_SPICE()
199+
200+
# Get perturber states for a few of the planets
201+
times = Timestamp.from_mjd([59000, 59500, 60000], scale="tdb")
202+
states = CartesianCoordinates.empty()
203+
target_ids = []
204+
for perturber in [
205+
OriginCodes.MOON,
206+
OriginCodes.VENUS,
207+
OriginCodes.MARS_BARYCENTER,
208+
OriginCodes.JUPITER_BARYCENTER,
209+
OriginCodes.SATURN_BARYCENTER,
210+
OriginCodes.URANUS_BARYCENTER,
211+
OriginCodes.NEPTUNE_BARYCENTER,
212+
]:
213+
states = qv.concatenate(
214+
[
215+
states,
216+
get_perturber_state(
217+
perturber, times, origin=OriginCodes.SUN, frame="itrf93"
218+
),
219+
]
220+
)
221+
target_ids.extend(perturber.name for _ in range(len(times)))
222+
223+
states_ecliptic = transform_coordinates(states, frame_out="ecliptic")
224+
225+
# Repeat with SPICE
226+
states_spice_ecliptic = CartesianCoordinates.empty()
227+
for coord, target_id in zip(states_ecliptic, target_ids):
228+
# Rotate the coordinates to the ecliptic frame using SPICE
229+
state, lt = sp.spkezr(
230+
target_id, coord.time.et()[0].as_py(), "ECLIPJ2000", "NONE", "SUN"
231+
)
232+
states_spice_ecliptic = qv.concatenate(
233+
[
234+
states_spice_ecliptic,
235+
CartesianCoordinates.from_kwargs(
236+
x=[state[0] / KM_P_AU],
237+
y=[state[1] / KM_P_AU],
238+
z=[state[2] / KM_P_AU],
239+
vx=[state[3] / KM_P_AU * S_P_DAY],
240+
vy=[state[4] / KM_P_AU * S_P_DAY],
241+
vz=[state[5] / KM_P_AU * S_P_DAY],
242+
time=coord.time, # Add time from original coordinate
243+
frame="ecliptic", # Specify the frame
244+
origin=Origin.from_kwargs(code=["SUN"]), # Specify the origin
245+
),
246+
]
247+
)
248+
249+
# Test that the two coordinate sets are equal within tolerance
250+
assert_coords_equal(
251+
states_ecliptic,
252+
states_spice_ecliptic,
253+
position_tol_mm=10,
254+
velocity_tol_nm_s=500,
255+
)

src/adam_core/coordinates/tests/test_transforms_translation.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111
def assert_coords_equal(
1212
have: CartesianCoordinates,
1313
want: CartesianCoordinates,
14+
position_tol_mm: float = 10,
15+
velocity_tol_nm_s: float = 10,
1416
):
1517
diff = want.values - have.values
1618

@@ -19,10 +21,10 @@ def assert_coords_equal(
1921
# Calculate offset in velocity in nm/s
2022
v_diff = np.linalg.norm(diff[:, 3:], axis=1) * (u.au / u.d).to(u.nm / u.s)
2123

22-
# Assert positions are to within 10 mm
23-
np.testing.assert_array_less(r_diff, 10)
24-
# Assert velocities are to within 10 nm/s
25-
np.testing.assert_array_less(v_diff, 10)
24+
# Assert positions are to within desired mm
25+
np.testing.assert_array_less(r_diff, position_tol_mm)
26+
# Assert velocities are to within desired nm/s
27+
np.testing.assert_array_less(v_diff, velocity_tol_nm_s)
2628

2729

2830
def test_cartesian_to_origin(orbital_elements, orbital_elements_barycentric):

src/adam_core/coordinates/transform.py

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1388,6 +1388,8 @@ def apply_time_varying_rotation(
13881388
else:
13891389
raise ValueError("Unsupported frame: {}".format(frame_in))
13901390

1391+
from ..constants import KM_P_AU, S_P_DAY
1392+
13911393
# Loop over unique times and then rotate each coordinate
13921394
coords_rotated = CartesianCoordinates.empty()
13931395
indices = []
@@ -1405,24 +1407,35 @@ def apply_time_varying_rotation(
14051407
# Store the indices so we can use to sort the coordinates later
14061408
indices.extend(indices_time.to_pylist())
14071409

1408-
rotation_matrix_3x3 = sp.pxform(
1410+
# The units of the transformation matrix are km and km/s and while
1411+
# our states are in au and au/d. So we need to convert the transformation
1412+
# matrix to the correct units.
1413+
rotation_matrix_6x6_kms = sp.sxform(
14091414
frame_spice_in, frame_spice_out, time.et().to_numpy(zero_copy_only=False)[0]
14101415
)
1411-
# Create 6x6 rotation matrix for position and velocity
1412-
rotation_matrix_6x6 = np.zeros((6, 6))
1413-
rotation_matrix_6x6[:3, :3] = rotation_matrix_3x3
1414-
rotation_matrix_6x6[3:, 3:] = rotation_matrix_3x3
14151416

1416-
# Rotate the coordinates
1417-
coords_rotated_time = coords.apply_mask(time_mask).rotate(
1418-
rotation_matrix_6x6, frame_out
1417+
# Compute unit conversion matrix to convert from km to au and km/s to au/d
1418+
rotation_unit_conversion = np.zeros((6, 6))
1419+
rotation_unit_conversion[:3, :3] = np.identity(3) * KM_P_AU
1420+
rotation_unit_conversion[3:, 3:] = np.identity(3) * KM_P_AU / S_P_DAY
1421+
1422+
rotation_matrix_6x6_aud = (
1423+
np.linalg.inv(rotation_unit_conversion)
1424+
@ rotation_matrix_6x6_kms
1425+
@ rotation_unit_conversion
14191426
)
14201427

1428+
# Rotate the coordinates
1429+
coords_time = coords.apply_mask(time_mask)
1430+
coords_rotated_time = coords_time.rotate(rotation_matrix_6x6_aud, frame_out)
1431+
14211432
# Add the rotated coordinates to the total coordinates
14221433
coords_rotated = qv.concatenate([coords_rotated, coords_rotated_time])
14231434

1424-
coords_rotated = coords_rotated.take(indices)
1425-
1435+
# Compute the indices that would sort the new coordinates
1436+
# to match the original coordinates
1437+
sorted_indices = np.argsort(indices)
1438+
coords_rotated = coords_rotated.take(sorted_indices)
14261439
return coords_rotated
14271440

14281441

@@ -1506,7 +1519,7 @@ def transform_coordinates(
15061519
if not isinstance(coords, CoordinatesClasses):
15071520
raise TypeError("Unsupported coordinate type: {}".format(type(coords)))
15081521

1509-
if frame_out not in {None, "equatorial", "ecliptic"}:
1522+
if frame_out not in {None, "equatorial", "ecliptic", "itrf93"}:
15101523
raise ValueError("Unsupported frame_out: {}".format(frame_out))
15111524

15121525
if origin_out is not None and not isinstance(origin_out, OriginCodes):

0 commit comments

Comments
 (0)