Fix propagate() dropping the orbit dimension for batch orbits at one shared time#1138
Open
apos wants to merge 1 commit into
Open
Fix propagate() dropping the orbit dimension for batch orbits at one shared time#1138apos wants to merge 1 commit into
apos wants to merge 1 commit into
Conversation
…shared time
_KeplerOrbit._at()/propagate() computed output_shape from t1.shape alone,
before position/velocity were normalized to their real per-orbit shape.
When N>1 orbits (e.g. a comets dataframe batch-built via
skyfield.data.mpc._comet_orbits()) are propagated to a single shared Time
(t1 is 0-d), the arrays are actually computed with shape (3, N, 1), but
were then force-reshaped into (3,) + t1.shape = (3,), raising:
ValueError: cannot reshape array of size 3*N into shape (3,)
Recover the true orbit count from position's shape (available before the
ndim==1 normalization runs) and only widen output_shape for this batch
case; the existing single-orbit squeeze to (3,) is unchanged.
Added a regression test alongside the existing
test_kepler_shape_with_time_of_length_one, building a multi-orbit
_KeplerOrbit directly via _from_periapsis() (no network/data files
needed) and propagating it to one shared scalar time.
apos
added a commit
to apos/PiFinder_Stellarmate
that referenced
this pull request
Jul 18, 2026
…main.py patch skyfield >=1.51 (installed: 1.54) drops the per-orbit dimension in propagate() when N>1 orbits share one observation time, breaking PiFinder's vectorized comet propagation (comets.py _calc_comets_vectorized(), ~965 comets -> 1 shared time). PiFinder's own try/except silently falls back to the slow per-comet loop on every call, which is what caused the CPU-hogging/keyboard-lag symptom. Reported and fixed upstream: skyfielders/python-skyfield#1138. - Pin requirements.txt to skyfield==1.50 instead of unpinning to "whatever's newest": >=1.48 drops numpy.float_ (removed in numpy 2.0), <1.51 predates the propagate() regression, so no patch is needed at all for the normal case. - keplerlib_batch_propagate_smos.diff kept as a defensive fallback (only applied if the buggy pattern is actually present), in case skyfield ever ends up unpinned/newer again. - Regenerated diffs/main_py.diff: PiFinder 2.6.0 changed IMU access from dict-style (_imu["moving"]) to attribute-style (_imu.moving) and added new GPS location-source guards (MANUAL/replay), which broke 2 of our hunks silently (patch continued past the failures). Rebuilt both against the current upstream code: - PowerManager pre-solve sleep state machine (was leaving a dead _solve_state field set but never read, old simple sleep-timeout logic still active) - GPS location-update condition simplification (error_in_m comparison removal), preserving the new MANUAL/replay guards Co-Authored-By: Claude Sonnet 5 <[email protected]>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Current Status
This is a regression introduced in v1.51 by fb12074 ("Fix #959: Kepler orbits with
time arrays of len==1"), which replaced
squeeze(position_prop)with a hand-computedoutput_shape. That commit correctly fixed the len-1 time-array case, but broke thecase of several orbits propagated to one shared time. Confirmed still present on
master(as of this PR) and in the latest released version, 1.54.Bug Description
_KeplerOrbit._at()/propagate()raiseswhenever N>1 orbits that were built together as one batch
_KeplerOrbit(e.g. viaskyfield.data.mpc._comet_orbits()) are propagated to a single sharedTime(a 0-dt1).We hit this in PiFinder, an open-source
telescope-pointing device, in
_calc_comets_vectorized()(
python/PiFinder/comets.py, added in commit194c9b5e/ PR #470, "Vectorize cometpropagation to fix locked-on-target CPU hog"). The relevant call is exactly:
comets_dfholds every active comet from the MPC comets file (currently ~965 rows);tis a singleTimefor "now". This batches all comets into one_KeplerOrbitandpropagates them all to that one shared moment in a single call, replacing a per-comet
Python loop that pegged a CPU core continuously whenever PiFinder was locked on a
target. This exact call pattern — many orbits, one shared observation time — triggers
the bug on every call.
Notably, PiFinder pins
skyfield==1.45in its ownrequirements.txt, so stockPiFinder never hits this (1.45 still used
squeeze()). It surfaces for anyone whoupgrades past 1.51 — e.g. our downstream fork, which unpins skyfield for unrelated
NumPy 2.0 compatibility reasons and ends up on 1.54. PiFinder's own code already
wraps the vectorized call in a
try/exceptthat falls back to the old per-comet loopon any exception, so this doesn't crash — but it silently reinstates the exact
CPU-hogging behavior PR #470 was written to eliminate, which is how we found it (a
"why is the UI laggy again" investigation).
Explanation of the Solution
output_shape = (3,) + t1.shapeis computed at the very top ofpropagate(), beforeposition/velocityare normalized to their working shape. Whent1is 0-d (oneshared time),
output_shapebecomes(3,)— but the position/velocity arrayscomputed later in the function actually have shape
(3, N, 1)for N orbits. The finalin-place
position_prop.shape = output_shapethen fails, since the sizes (3*Nvs.3) don't match.Solution
Recover the true orbit count
Nfromposition.shape[1]— already available at thatpoint, before the
ndim==1normalization runs — and only widenoutput_shapeto(3, N)for this batch/shared-time case. The existing single-orbit behavior (squeezeto
(3,)) is unchanged.Added a regression test,
test_kepler_shape_with_multiple_orbits_and_single_shared_time,next to the existing
test_kepler_shape_with_time_of_length_one. It builds amulti-orbit
_KeplerOrbitdirectly via_from_periapsis()(no network/data filesneeded) and propagates it to one shared scalar time, plus checks the single-orbit case
still squeezes to
(3,).skyfield/tests/test_keplerlib.pyandskyfield/tests/test_elementslib.py(the twotest modules exercising
propagate()/_KeplerOrbit) pass in full, 186 further testsacross the rest of the suite that don't require network-fetched ephemeris/star data
also pass unchanged.