Skip to content

Commit 37015f4

Browse files
authored
Cap ASSIST dependencies for REBOUND 5 (#32)
Require ASSIST and REBOUND versions that remain compatible with the REBOUND 4 API used by ASSIST. Also adds dependency metadata tests, satisfies mypy for pyarrow compute calls, and uses a serialized SBDB orbit fixture for the back-to-back propagation test while retaining broader Horizons/SBDB integration coverage.
1 parent db188e4 commit 37015f4

5 files changed

Lines changed: 62 additions & 27 deletions

File tree

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ dependencies = [
2222
"adam-core>=0.5.4",
2323
"naif-de440",
2424
"jpl-small-bodies-de441-n16",
25-
"assist==1.2.0",
25+
"assist>=1.2.3,<1.3",
2626
"numpy",
2727
"ray",
2828
"spiceypy>=6.0.0",
29-
"rebound>=4.4.10",
29+
"rebound>=4.4.11,<5",
3030
"timezonefinder==8.0.0",
3131
]
3232

src/adam_assist/propagator.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,13 @@
4040
# adam_core defines it in au but we need it in km
4141
EARTH_RADIUS_KM = c.R_EARTH_EQUATORIAL * KM_P_AU
4242

43+
# pyarrow.compute type stubs are incomplete for the functions used here.
44+
pc_cast: Any = pc.cast
45+
pc_invert: Any = pc.invert # type: ignore[attr-defined]
46+
pc_is_in: Any = pc.is_in # type: ignore[attr-defined]
47+
pc_subtract: Any = pc.subtract # type: ignore[attr-defined]
48+
pc_unique: Any = pc.unique # type: ignore[attr-defined]
49+
4350

4451
def uint32_hash(s: str) -> c_uint32:
4552
sha256_result = hashlib.sha256(s.encode()).digest()
@@ -290,8 +297,8 @@ def _propagate_single_orbit_inner_optimized(
290297
)
291298

292299
if is_variant:
293-
orbit_ids_out = pa.repeat(pc.cast(orbit_id, pa.large_string()), N)
294-
variant_ids_out = pa.repeat(pc.cast(variant_id, pa.large_string()), N)
300+
orbit_ids_out = pa.repeat(pc_cast(orbit_id, pa.large_string()), N)
301+
variant_ids_out = pa.repeat(pc_cast(variant_id, pa.large_string()), N)
295302
object_id_out = pa.repeat(orbit.object_id[0], N)
296303
weights_out = pa.repeat(orbit.weights[0], N)
297304
weights_cov_out = pa.repeat(orbit.weights_cov[0], N)
@@ -319,7 +326,7 @@ def _propagate_single_orbit_inner_optimized(
319326
),
320327
)
321328
else:
322-
orbit_ids_out = pa.repeat(pc.cast(orbit_id, pa.large_string()), N)
329+
orbit_ids_out = pa.repeat(pc_cast(orbit_id, pa.large_string()), N)
323330
object_id_out = pa.repeat(orbit.object_id[0], N)
324331
physical_parameters_out = orbit.physical_parameters.take(
325332
np.zeros(N, dtype=np.int64)
@@ -408,7 +415,7 @@ def _propagate_orbits_inner(
408415

409416
# Prepare the times as jd - jd_ref
410417
integrator_times = times.rescale("tdb").jd()
411-
integrator_times = pc.subtract(integrator_times, ephem.jd_ref)
418+
integrator_times = pc_subtract(integrator_times, ephem.jd_ref)
412419
integrator_times = integrator_times.to_numpy()
413420

414421
# Unified accumulation for both Orbits and VariantOrbits
@@ -532,7 +539,7 @@ def _detect_collisions(
532539
conditions: CollisionConditions,
533540
) -> Tuple[VariantOrbits, CollisionEvent]:
534541
# Assert that the time for each orbit definition is the same for the simulator to work
535-
assert len(pc.unique(orbits.coordinates.time.mjd())) == 1
542+
assert len(pc_unique(orbits.coordinates.time.mjd())) == 1
536543

537544
# The coordinate frame is the equatorial International Celestial Reference Frame (ICRF).
538545
# This is also the native coordinate system for the JPL binary files.
@@ -807,12 +814,12 @@ def _detect_collisions(
807814
# For some reason, it fails if we let rebound convert the hash to c_uint32
808815

809816
if isinstance(orbits, VariantOrbits):
810-
keep_mask = pc.invert(
811-
pc.is_in(orbits.variant_id, colliding_orbits.variant_id)
817+
keep_mask = pc_invert(
818+
pc_is_in(orbits.variant_id, colliding_orbits.variant_id)
812819
)
813820
else:
814-
keep_mask = pc.invert(
815-
pc.is_in(orbits.orbit_id, colliding_orbits.orbit_id)
821+
keep_mask = pc_invert(
822+
pc_is_in(orbits.orbit_id, colliding_orbits.orbit_id)
816823
)
817824

818825
orbits = orbits.apply_mask(keep_mask)
@@ -836,12 +843,12 @@ def _detect_collisions(
836843
results_list.append(time_step_results)
837844
else:
838845
if isinstance(orbits, Orbits):
839-
still_in_simulation = pc.invert(
840-
pc.is_in(time_step_results.orbit_id, colliders.orbit_id)
846+
still_in_simulation = pc_invert(
847+
pc_is_in(time_step_results.orbit_id, colliders.orbit_id)
841848
)
842849
elif isinstance(orbits, VariantOrbits):
843-
still_in_simulation = pc.invert(
844-
pc.is_in(time_step_results.variant_id, colliders.variant_id)
850+
still_in_simulation = pc_invert(
851+
pc_is_in(time_step_results.variant_id, colliders.variant_id)
845852
)
846853
addl = time_step_results.apply_mask(still_in_simulation)
847854
results_list.append(addl)
5.82 KB
Binary file not shown.

tests/test_dependency_metadata.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import tomllib
2+
from pathlib import Path
3+
4+
5+
def _project_dependencies() -> list[str]:
6+
pyproject_path = Path(__file__).resolve().parents[1] / "pyproject.toml"
7+
with pyproject_path.open("rb") as pyproject_file:
8+
pyproject = tomllib.load(pyproject_file)
9+
return list(pyproject["project"]["dependencies"])
10+
11+
12+
def test_assist_dependency_uses_rebound_header_compatible_release() -> None:
13+
assert "assist>=1.2.3,<1.3" in _project_dependencies()
14+
15+
16+
def test_rebound_dependency_stays_on_assist_supported_major_version() -> None:
17+
assert "rebound>=4.4.11,<5" in _project_dependencies()

tests/test_propagate.py

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from adam_core.coordinates import CartesianCoordinates, Origin
66
from adam_core.coordinates.residuals import Residuals
77
from adam_core.orbits import Orbits
8-
from adam_core.orbits.query import query_sbdb
98
from adam_core.orbits.query.horizons import query_horizons
109
from adam_core.time import Timestamp
1110
from astropy import units as u
@@ -14,6 +13,7 @@
1413

1514
DEFAULT_POSITION_TOLERANCE = (50 * u.m).to(u.au).value
1615
DEFAULT_VELOCITY_TOLERANCE = (1 * u.mm / u.s).to(u.au / u.day).value
16+
BACK_TO_BACK_ORBIT_FILE_PATH = "tests/data/2013_RR165_orbit.parquet"
1717

1818

1919
OBJECTS = {
@@ -35,7 +35,7 @@
3535
},
3636
"2000 PH5": {
3737
# Accomodate 2 km uncertainty
38-
"position": (2 * u.km).to(u.au).value,
38+
"position": (2 * u.km).to(u.au).value,
3939
"velocity": DEFAULT_VELOCITY_TOLERANCE,
4040
},
4141
"1977 HB": {
@@ -131,6 +131,7 @@
131131
# "A802 FA",
132132
}
133133

134+
134135
@pytest.mark.parametrize("object_id", list(OBJECTS.keys()))
135136
def test_propagate(object_id):
136137
"""
@@ -153,8 +154,8 @@ def test_propagate(object_id):
153154
)
154155

155156
ephem_times_difference = pc.subtract(
156-
assist_propagated_orbits.coordinates.time.mjd(),
157-
horizons_propagated_orbits.coordinates.time.mjd()
157+
assist_propagated_orbits.coordinates.time.mjd(),
158+
horizons_propagated_orbits.coordinates.time.mjd(),
158159
)
159160
np.testing.assert_array_less(
160161
np.abs(ephem_times_difference.to_numpy(zero_copy_only=False)),
@@ -177,15 +178,19 @@ def test_propagate(object_id):
177178
pos_tol = OBJECTS.get(object_id).get("position")
178179
vel_tol = OBJECTS.get(object_id).get("velocity")
179180

180-
np.testing.assert_array_less(absolute_position, pos_tol, f"Failed position for {object_id}")
181-
np.testing.assert_array_less(absolute_velocity, vel_tol, f"Failed velocity for {object_id}")
181+
np.testing.assert_array_less(
182+
absolute_position, pos_tol, f"Failed position for {object_id}"
183+
)
184+
np.testing.assert_array_less(
185+
absolute_velocity, vel_tol, f"Failed velocity for {object_id}"
186+
)
182187

183188

184189
def test_propagate_different_input_times(mocker):
185190
"""
186191
Ensure that we can pass in vectors with different epochs
187192
"""
188-
193+
189194
prop = ASSISTPropagator()
190195
watched_propagate_orbits_inner = mocker.spy(prop, "_propagate_orbits_inner")
191196
orbits = Orbits.from_kwargs(
@@ -204,23 +209,29 @@ def test_propagate_different_input_times(mocker):
204209
),
205210
)
206211

207-
propagated_orbits = prop.propagate_orbits(orbits, Timestamp.from_mjd([60005, 60006], scale="tdb"))
212+
propagated_orbits = prop.propagate_orbits(
213+
orbits, Timestamp.from_mjd([60005, 60006], scale="tdb")
214+
)
208215

209-
assert watched_propagate_orbits_inner.call_count == 2, "Inner function should be called once for each unique input epoch"
216+
assert (
217+
watched_propagate_orbits_inner.call_count == 2
218+
), "Inner function should be called once for each unique input epoch"
210219

211220
assert len(propagated_orbits.coordinates.time.unique()) == 2
212-
assert len(propagated_orbits) == 8, "Should have input orbits x epochs number of results"
221+
assert (
222+
len(propagated_orbits) == 8
223+
), "Should have input orbits x epochs number of results"
213224

214225

215226
def test_back_to_back_propagations():
216227
"""
217-
Ensure that back-to-back multiprocessed propagations work. This test should
228+
Ensure that back-to-back multiprocessed propagations work. This test should
218229
fail at the moment since the ray remote cannot initialize a propagator object with an already
219230
defined simulation.
220231
221232
"""
222233
prop = ASSISTPropagator()
223-
orbits = query_sbdb(["2013 RR165"])
234+
orbits = Orbits.from_parquet(BACK_TO_BACK_ORBIT_FILE_PATH)
224235

225236
time = Timestamp.from_mjd([60000], scale="tdb")
226237
first_prop = prop.propagate_orbits(orbits, time, max_processes=1)

0 commit comments

Comments
 (0)