Repository navigation
Mm rework lingfeng into Dev - #26
Merged
Merged
Conversation
…or plotly_stars; Added show_plot control for quiver residual plots
…cified in fixed_params_dict, will try to look for them in the table columns; For optional fixed params, will use the default value if not provided
…upport for 2d time arrays (if length==len(stars), assumes a corresponding relation instead of returning a 2D)
…ping regions; Added briteN control in MosaicSelfRef and MosaicToRef
…flystar into mm_rework_lingfeng
…lumns -> meta data -> default value; Added random seed control in bootstrap functions.
…mbine_lists; Added support for artificial star motion model fitting with x/y out (index 1).
Orbit has 6 free orbital elements (orb_P, orb_t0, orb_e, orb_i, orb_Omega, orb_omega), so n_params should be 6, not 3. With demote=True, a star labeled Orbit that has fewer than 6 unique epochs is automatically demoted to a simpler model (e.g. Linear). This handles the case where align adds a new star to the reference with motion_model_input='Orbit' but only a handful of detections. Frozen orbit stars are protected by the existing frozen_motion_mask subtable mechanism: they are excluded from fit_motion_models before the demotion block is reached, so demote=True has no effect on them. Test added: test_orbit_star_not_in_reference_demoted_to_linear Test present: test_frozen_orbit_is_bitwise_unchanged_with_few_detections Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
Add docs/examples/name_match_example.ipynb demonstrating the name-matching workflow. Update docs/index.rst to include the new notebook in the Examples toctree. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
…fill_value _element_jacobian accepted a `seed` argument that was immediately deleted inside the function with no use. Remove it from the signature, its docstring entry, and the single call site in _solve_one_orbit. Also remove the `del fill_value` statement in Orbit.run_fit, which was an unnecessary explicit deletion of a parameter that is simply not used in that code path. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
Replace the `**self.name_match_kwargs(...)` splat with explicit
`if self.name_match: name_kws = {...}` blocks at all three match.match
call sites. Readers can now see exactly which keyword arguments
match.match receives without following an indirection through a helper
method.
The lazy `name_key` column initialisation is moved to a single setup
block before the iteration loop (and at the top of match_lists), so
the call sites just read from an already-existing column.
The now-unused `name_match_kwargs` method is removed.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
name_prefer2 was a tiebreaker for when the reference list contained multiple stars with the same name. The reference list is now required to have unique names (enforced by a new ValueError check in MosaicToRef.__init__), and flystar assigns unique names to every new star added during an alignment run. With duplicates impossible, name_prefer2 is dead code and has been removed from all three match.match call sites in align.py. The duplicate-name check fires on the name column only when present and reports the offending names (first 5, with "..." if more). Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
The reference list is now guaranteed to have unique names (checked at MosaicToRef construction), so duplicate same-name candidates in catalog 2 cannot arise. name_prefer2 / prefer2 — the tiebreaker that picked the preferred row among duplicates — is therefore dead code. Removed from: - same_name_candidates(): prefer2 param, pref array, and the prefer2-weighted argmin logic - match_name_override(): name_prefer2 param and pass-through to same_name_candidates - match(): name_prefer2 param, pass to match_name_override, and name_opts dict entry; same in the tiebreak path - match_chi2(): name_prefer2 key in the names dict description and the prefer2= kwarg on the same_name_candidates call Also removed test_prefer_original_row_and_availability and the "duplicate ref rows" sub-test in test_override_exclusion_and_duplicates, which both tested the now-deleted feature. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
Replace the name_kws dict + **name_kws splat pattern with explicit keyword arguments on each of the three match.match call sites. All five name-match arguments (names1, names2, name_dr_tol, name_exclude, name_mode) are now visible inline; the conditional `if self.name_match else None/default` form makes the no-name-match case equally clear without a separate else branch. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
…one_orbit → Orbit.fit_one_star - Move all 10 orbit-only private helpers (_angdiff, _nearest_angle, _sky_xy, _row_value, _cov_for_star, _cov_state, _position_sigma, _elements_from_internal, _choose_branch, _element_jacobian) from motion_model.py to orbits.py, where the underlying Kepler mechanics already live. - Fix _sky_xy to call kep2xyz directly (same module) instead of orbits.kep2xyz. - Rename _solve_one_orbit → Orbit.fit_one_star (@staticmethod); update call site. - Import helpers from flystar.orbits in motion_model.py. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
…_broadcast_seeds Instead of calling _row_value (scalar-or-array dispatch) n_stars times per parameter inside the model/run_fit loops, pre-broadcast all optional fixed params and element seeds to (n_stars,) float arrays once before each loop. Defaults come from self.optional_fixed_params, so they live in one place. - Add Orbit._broadcast_fixed: normalizes optional fixed params to (n_stars,) arrays, falling back to optional_fixed_params for absent keys. - Add Orbit._broadcast_seeds: normalizes orbital-element seeds (orb_P … orb_omega) from fixed_params_dict to a (n_stars, 6) float array. - Replace all _row_value call sites in Orbit.model and Orbit.run_fit. - Remove _row_value from flystar/orbits.py and from the motion_model import. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
Replace the NaN/inf encoding in orb_cov with an explicit integer status column that records the outcome of each orbit fit: 0 = success (finite, positive-definite covariance) 1 = singular covariance (solver converged but dof <= 0 or gram singular) 2 = optimizer failed (least_squares did not converge) 3 = skipped / no data (idx == 0, bad seed, or fewer than 3 epochs) Key changes: - Orbit.prediction_columns: add 'orb_fit_status' alongside 'orb_cov'. - Orbit.model: pre-broadcast fit_status_arr before the per-star loop; use status codes to decide xe/ye rather than the 'missing'/'bad'/ 'finite' encoding from _cov_state. Legacy tables that predate the column fall back to the old _cov_state logic. - fit_one_star: rename cov_bad to cov_nan (np.nan fill); add status arg to _fail(); tag each early-return path with its status code; success path defaults to status=1 and promotes to status=0 when the full covariance is finite and all diagonal entries are positive. - run_fit: initialize cov to NaN (was inf); add status array (default 3); unpack the new 8-tuple from fit_one_star; include 'orb_fit_status' in the diagnostics dict returned to startables. - startables._write_diagnostics_to_table: update docstring to document the status column semantics; the -1 sentinel fill for new int columns already handles 'never fit' stars correctly. - Orbit.demote: set to False so stars classified as Orbit stay Orbit even when the current frame has too few epochs to solve; the seed is kept and the star uses orb_fit_status=3 instead of being demoted to Fixed. - test_orbits: add orb_fit_status assertions to the converged-fit, too-few-epochs, no-dof, and bad-seed test cases. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
Both functions were defined at module level in orbits.py but are orbit- specific and only called from Orbit.model / Orbit.fit_one_star in motion_model.py. Grouping them inside the class: * makes the dependency explicit — _sky_xy is now Orbit._sky_xy * allows subclasses to override the sky-projection or Jacobian (e.g. for a relativistic variant) * eliminates the circular-import hazard that would result from keeping _sky_xy in orbits.py while _position_sigma needs to call it Changes: - orbits.py: remove _sky_xy and _position_sigma definitions - motion_model.py: import list drops _sky_xy / _position_sigma - motion_model.py: Orbit._sky_xy(self, ...) and Orbit._position_sigma(self, ...) added after _broadcast_seeds - motion_model.py: Orbit.fit_one_star demoted from @staticmethod to a regular instance method so its _chi2 / residual closures can reach self._sky_xy - All call sites updated to self._sky_xy / self._position_sigma Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
Replace the _with_fit_seed hack that tunnelled orbital element starting
guesses through fixed_params_dict with an explicit params_init_guess_dict
parameter on run_fit() and fit(). This cleanly separates truly-fixed
physics constraints from optimizer initial guesses.
Changes:
- motion_model.py: add params_init_guess_dict= to all run_fit() stubs
and to Orbit.run_fit() / fit(); pass it through to _broadcast_seeds()
(which now reads from params_init_guess_dict instead of fixed_params_dict);
standardize every run_fit() to return a 5-tuple
(params, param_errs, chi2x, chi2y, diagnostics) — non-Orbit models
return {} as diagnostics; fit() always returns a 5-tuple when
return_chi2=True (diagnostics={} for non-Orbit, squeezed dict for Orbit)
- startables.py: remove _with_fit_seed entirely; build
params_init_guess_dict inline from table columns in all three call
paths (batch, multiprocessing, sequential); update
_fit_motion_models_worker signature; remove len(result)>4 guards now
that the interface is uniform
- tests/test_motion_model.py: update 4-tuple unpacks to *_ star-unpack
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
…an column Remove the two-mechanism freeze system (fixed_motion_models list + per-star fit_motion string column) and replace it with a single boolean column update_ref_motion on the reference StarTable. True (default) — refit this star's motion model each iteration False — hold this star's motion model frozen Changes: - motion_model.py: frozen_motion_mask() now reads only update_ref_motion; removed _FIT_MOTION_VALUES constant. Updated Orbit docstring to name the new column. - startables.py: fit_motion_models() loses fixed_motion_models parameter. StarTable.__init__() injects update_ref_motion=True for all stars when the column is absent. - align.py: MosaicToRef.__init__() loses fixed_motion_models parameter. All internal callers updated. Column list that keeps per-star columns 1-D updated from fit_motion to update_ref_motion. - tests/test_motion_model.py: rewrite freeze tests to set update_ref_motion directly instead of fixed_motion_models list or fit_motion string column. - tests/test_align.py: _tiny_mosaic helper, and the three frozen- orbit align tests updated to use update_ref_motion. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
Bug fix
-------
add_rows_for_new_stars now explicitly sets update_ref_motion=True for
every new star added to ref_table during alignment. Previously the
generic boolean-column fallback set it to False (frozen), meaning newly
discovered stars could never have their motion models refit.
Code documentation
------------------
* MosaicToRef class docstring: note that update_ref_motion provides
per-star freeze control independent of update_ref_orig.
* MosaicToRef.__init__ docstring: document the update_ref_motion column
near update_ref_orig, with a table of per-star semantics.
* update_ref_table_aggregates docstring: explain the interaction between
update_ref_orig (catalog-level policy) and update_ref_motion (per-star
unconditional freeze), including how the frozen mask is OR-ed into
keep_orig.
* add_rows_for_new_stars docstring: note the explicit True default.
ReadTheDocs (docs/alignment.rst)
---------------------------------
Expand the update_ref_orig table entry to reference update_ref_motion.
Add a new section "Per-star freeze control: update_ref_motion" with:
- a two-row meaning table (True / False),
- a code snippet showing how to freeze anchor stars,
- an admonition explaining the update_ref_orig/update_ref_motion
interaction with a four-row truth table.
Example notebooks
-----------------
* motion_model_example.ipynb: new section 2.5 "Freezing specific stars
with update_ref_motion" with runnable code showing StarTable injection
and a code-block showing MosaicToRef usage with frozen anchors.
* flystar_mosaic_to_gaia/gaia_flystar_example.ipynb: new markdown cell
after the pass-3 MosaicToRef call showing how to freeze Gaia anchor
stars using update_ref_motion.
Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
Commit 14fe153 set demote=True so that stars labelled Orbit but with fewer than n_params=6 unique epochs are automatically demoted to a simpler model (e.g. Linear) rather than kept as Orbit with an under-determined fit. A subsequent commit reverted demote back to False (while leaving n_params=n_fit_params intact), breaking test_orbit_star_not_in_reference_demoted_to_linear. The comment added alongside demote=False ("Keep the star as Orbit even when too few epochs …") described the opposite of the intended behaviour and is removed along with the revert. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> Claude-Session: https://claude.ai/code/session_01TKVLLQoFaG93mAU9u7q2P7
…el-1130 Add a Newtonian Orbit motion model
…raw values The bootstrap errors were computed from running sums of the raw transformed positions (and motion parameters) and their squares, so each variance was the small difference of two large, nearly equal sums. With positions far from the origin, or a spread close to zero, rounding made it 0 or negative, giving zero or NaN errors: with the test field shifted 1e4 units, 11 xe_boot values came out NaN and 10 zero. Sum each value's difference from the first iteration's instead, and clip the variance at 0. The errors are the same standard deviation as before, without the rounding. New test: test_bootstrap_errors_far_from_origin. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Fix NaN and zero bootstrap errors from rounding in calc_bootstrap_errors
- setup.py: local_scheme='no-local-version', so an untagged commit builds e.g. 0.1.dev948 rather than 0.1.dev948+g<hash>, which PyPI and TestPyPI reject; the publish workflow's manual TestPyPI dry run needs this. Drop the unused import of setuptools.config.read_configuration. - Delete flystar/_astropy_init.py: leftover from the astropy package template, imported by nothing, and failing to import under astropy 8. Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Packaging fixes for PyPI publishing
… truth The test showed that chi2 matching leaves fewer duplicate rows than legacy matching, but not that its matches are right: fewer rows could also come from combining different stars. Every list in the test keeps its stars in the same order, so a detection's idx_in_list is its true star. The test now also counts, per mode, the detections combined with another star's (wrong matches) and the stars kept whole (all 4 detections in one row), and asserts that chi2 makes no wrong matches and keeps more stars whole than legacy. On the test's field (400 stars, 4 epochs, seed 17): legacy: 429 rows, 390 of 400 stars whole, 0 wrong matches chi2: 400 rows, 400 of 400 stars whole, 0 wrong matches Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
…tionModel, StarTable, and align.py
- Index predicted positions by epoch column ([:, ee]) instead of star row in plot_quiver_residuals_with_orig_all_epochs, plot_mag_scatter_multi_trans_all_epochs and plot_quiver_residuals_magcolor_all_epochs. - plot_stars_nfilt: default epoch_array_list/color_list so it runs with defaults; drop duplicated prediction call. - plot_quiver_residuals_magcolor: use the colormap (not the matplotlib.cm module) and set up colors in both pixel and arcsec units. - plot_quiver_residuals_orig, plot_mag_scatter, plot_y_scatter: accept the show_plot/save_path arguments their caller passes (new _finish_figure helper); fix rgood_new.data2 typo. - Restore plot_mag_scatter_multi_trans (deleted helper) and fix plot_mag_scatter_multi_trans_all_epochs: per-table predictions, per-epoch grouping instead of wrong stride. - Remove commented-out validate_motion_model_dict lines and the unused make_fake_starlists_poly1_par test helper. Co-authored-by: Cursor <[email protected]>
Replace the boolean MotionModel.demote with one of DEMOTE_OPTIONS: 'always' (default), 'never', or 'on-missing-init'. Orbit uses 'on-missing-init': an under-detected star with a complete finite set of initial orbital elements stays Orbit (fit skipped, initial parameters kept); one without them (e.g. newly added to the reference) is demoted. - New MotionModel.keep_model_mask(tab, rows) decides per star; used by StarTable.fit_motion_models and by MosaicSelfRef's guard that keeps such stars out of combine_lists_xym averaging (dead since demote=True). - motion_model_map() rejects an unknown demote value. - Fixes test_too_few_epochs_and_a_failed_solve_keep_the_initial_params and test_mosaic_fit_and_fixed_orbit_with_update_ref_orig while keeping test_orbit_star_not_in_reference_demoted_to_linear passing. - New tests for the three modes, partial initial parameters, the validation error, and fit_motion_models end to end. - docs/motion_models.rst documents demote. Rename 'seed' (the fit's starting values) to 'initial parameters' in Orbit code, orbits.py helpers, tests and docstrings, so 'seed' only refers to random number generators. Orbit.run_fit's docstring now documents params_init_guess_dict, which is where the initial elements actually arrive. Co-authored-by: Cursor <[email protected]>
- Exclude docs/plans/** (internal design notes) from the build; it was not in any toctree. - name_match_example.ipynb: point the table of contents at the actual heading anchors for sections 4 and 5. Co-authored-by: Cursor <[email protected]>
jluastro
had a problem deploying
to
github-pages
October 29, 2026 01:38 — with
GitHub Actions
Failure
Closed
This branch had an error being deployed
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.
Summary
This merges
mm_rework_lingfengintodev. It supersedes #25 (which compared this branch againstmm_rework) — everything from that comparison is folded into the sections below, organized by what actually changed and why, alongside a substantial follow-on pass of correctness, performance, memory, and documentation work done since.API changes from
mm_reworkmm_reworkalready has amotion_model.pyand aFixed/Linear/Acceleration/Parallaxsystem of its own, so unlike the rest of this description, the API differences below are specifically against that branch's design (notdev, which predates motion models entirely and has neither side of any of this).default_motion_model(a single model name) →motion_models(a list):mm_rework'sMosaicSelfRef.__init__takesdefault_motion_model='Fixed', andfit_velocitiesseparately takes its owndefault_motion_model='Linear'— one model name, used as a single global fallback. Both are replaced by onemotion_modelsparameter (a list, e.g.motion_models=['Linear']): each star is now fit with the most complex model its own number of valid epochs actually supports, rather than every star sharing one default.motion_model_dict→fixed_params_dict, andt0stops being a special case:mm_rework'srun_fit(self, t, x, y, xe, ye, t0, ...)takest0as its own required positional argument everywhere, with a separate, loosely-typedmotion_model_dict={}alongside it for anything else. Both are unified into onefixed_params_dict(holdingt0forLinear/Acceleration;t0,ra,dec,pa,obsLocationforParallax), so every model's non-fit parameters flow through the same mechanism instead oft0being threaded separately from everything else.Emptymotion model:mm_reworkonly hasFixed/Linear/Acceleration/Parallax.Empty(a trivial placeholder for a star with no usable data — alwaysfill_value/inf) is new.StarTable.fit_velocities→StarTable.fit_motion_models, reflecting the move from "always fits a velocity" to "fits whichever model applies."fit_velocities_all_detected, a separate helper for the "every star has every epoch" case, has no equivalent — the general per-star model selection already covers it.fit_velocitiesparameters removed or replaced:mask_val→mask_value;fixed_t0=False(a special-cased boolean/array just fort0) is gone now thatt0is an ordinaryfixed_params_dictentry;show_progress/reassign_motion_modelare gone (reassignment now happens automatically every call from each star's current epoch count — seekeep_existing, new, to control whether stars not being refit keep their existing values instead of being overwritten withnan, described further down). New, with nomm_reworkequivalent at all:keep_existing,seed,method(scipy method name),fill_value,art_star, and the multiprocessing controlsprocesses/chunksize/mp_star_threshold.MotionModelsubclass internals renamed (relevant only if calling code subclasses or touchesMotionModeldirectly, rather than going throughStarTable/align):n_pts_req(hardcoded per class, e.g.Parallax.n_pts_req = 4) →n_params(derived asceil(n_fit_params / 2), givingParallax.n_params = 3— the same fix as then_pts_reqpoint already covered above);fitter_param_names→fit_param_names;optional_param_names(a bare list of names) →optional_fixed_params(a dict of name → actual default value);get_pos_at_time/get_batch_pos_at_time(separate per-star and per-batch methods) unified into onemodel()method that vectorizes automatically based on input shape;motion_model.get_weights→calc_sigma(see "Uncertainty model" above); newrun_fit_batchonEmpty/Fixed/Linear, with nomm_reworkequivalent (see "Vectorized, multiprocessing-aware motion-model fitting" below).mm_rework, covered in their own sections below:inherit_n_detect,match_workers,mp_star_threshold.Uncertainty model: error propagation, not weighted variance
The codebase's biggest conceptual change: uncertainty on an averaged/fit quantity is now computed by analytic error propagation (the classic "error on the mean,"
sigma = sqrt(1 / sum(1/sigma_i^2))) everywhere, instead of the sqrt of a weighted sample variance.scipy.curve_fit's covariance matrix already computes internally for models that use it — so this also makes every motion model's reported uncertainty consistent with every other one, instead ofFixedcomputing something structurally different fromLinear/Acceleration/Parallax.combine_listsswitched to error propagation (also removing its now-unnecessary >1-epoch requirement for weighted averaging), andFixed.run_fit's uncertainty was brought in line with the scipy-covariance convention every other model already used.motion_model.get_weights'sx_wt = 1/xe**2→sigma = 1/x_wt**0.5round-trip accumulated floating-point error across repeatedalign()iterations; replaced withcalc_sigma, which returnsxeorsqrt(xe)directly usable by scipy, computing an inverse-variance weight only where one's actually still needed.Parallax.run_fithad two real bugs from not using that same path:sigmawas never square-rooted before being passed tocurve_fit, andabsolute_sigmawas silently ignored. Both fixed by routing it throughcalc_sigma+absolute_sigmalike everything else. (Worked example provingcurve_fit(..., absolute_sigma=True)and analytic error propagation agree bit-for-bit is preserved from Update mm_rework to match mm_rework_lingfeng for comparison #25, at the bottom of this description.)MosaicSelfRef/MosaicToRef'sabsolute_sigmadefault changed fromFalsetoTrue— required for scipy's covariance matrix to actually mean "error propagation" rather than a reduced-chi2-rescaled quantity.Uncertainty columns:
inf, nevernan, for "no information"A second, related convention, enforced consistently for the first time: a star's uncertainty is
infwhen there's no real information to compute one from — nevernan.1/inf**2 == 0, so aninfuncertainty correctly contributes zero weight anywhere it's combined with other stars' measurements;naninstead poisons every downstream computation it touches (x + nan == nan) and vanishes silently from filters, sincenan == nanisFalseandnan < anythingisFalse. Several real bugs were all instances of this convention being violated in different places:xe/ye/medefaulted tonaninstead ofinffor invalid entries in the newly-vectorizedFixed/Emptybatch paths — corrected toinf.combine_lists' weighted "no usable uncertainty anywhere" fallback used to patch a fake weight into the raw error array, fit through that, and then had to remember to force the reported error back toinfafterward — fragile, and exactly the kind of place a fabricated finite value could silently leak through instead. Rewritten soinffalls out naturally: the weight sum is built only from real, known uncertainties, sostd = sqrt(1/wgt_sum)isinfvia ordinary1/0whenever none exist, with no override to remember.absolute_sigma=False's chi2 rescaling could turn a singular fit's correctinferror intonan—inf * sqrt(nan) == nan. Fixed by reapplying the singular/insufficient-data override unconditionally as the fit's final step, so nothing downstream can rescale it away.NaN-propagation bugs (not theinfconvention itself, but bugs the convention's violation exposed on real, imperfect data): a non-finitem0could never be excluded by a magnitude-range filter (NaNcomparisons are alwaysFalse), letting bad reference stars corrupt the magnitude-zeropoint transform; andfit_motion_models' defaultt0could silently come outNaNfor some multi-epoch stars from mixing a masked weights array with plain (not masked) averaging. Both fixed.Vectorized, multiprocessing-aware motion-model fitting
fit_motion_modelsnow fits a whole group of stars sharing a motion model in one vectorized pass instead of one star at a time, for every model that supports it:FixedandEmpty(trivial/closed-form) andLinear(closed-form 2×2 solve). On a real dataset,processes=3dropped from 7.07s to 2.68s (matching serial's 2.66s) once all three were batched — a multiprocessing pool no longer needs to spin up at all just to run an O(1) fill forEmptystars.numpy.maindexing bottleneck (tens of millions of slow per-elementnumpy.ma.core.__getitem__calls) madefit_motion_models~38% faster to fix; one multiprocessing pool is now reused across the whole call instead of respawned per motion-model group.match()gained aworkersparameter (default 1) threadingscipy.spatial.KDTree.query_ball_point;align.pyexposes it asmatch_workers(default 1 — production runs typically share a machine, so the speedup is opt-in). Verified this doesn't affect the delicatedm_min == dr_mintie-break, which depends on within-list neighbor order:workers=1vsworkers=-1give identical, order-preserved neighbor lists, checked against dense/duplicate-point edge cases and a full end-to-end run with all 37ref_tableoutput columns byte-identical.mp_star_threshold(default 100,000): a multiprocessing pool for motion-model fitting (Acceleration/Parallax, or any model withbootstrap > 0— the models with no vectorized path) is only spun up when the number of stars actually needing it meets this threshold, even ifprocesses > 1was requested. Measured break-even for that fixed pool-spawn/IPC overhead was between 20,000 and 100,000 stars on a 10-core machine.Memory footprint at mosaic scale
StarTableconstruction gained an opt-incopy=Falseand now builds all columns in one constructor call instead of manyadd_column()calls (~1.2s / +4.6GB → ~0.001s / ~0GB for ~29 columns at 1.4M rows).vstack-ed it onto the growingref_table, transiently holding old + new + concatenated data for every column at once — roughly doubling peak memory on every "add new stars" step. Now concatenates columns directly and drops old references immediately.fit_motion_modelsdid an O(N_stars) data-prep step regardless of how few stars actually needed refitting; now sliced down to just the selected rows first. A redundant double-copy incombine_lists/fit_motion_models's array prep (anarange-based fancy-index copy stacked on top of an already-copyingmasked_invalid/deepcopycall) was removed. A per-star fixed-params dict is now built lazily, only for stars whose motion model actually needs it — skipped for ~84% of stars (those already handled by the vectorized batch path, above) in one benchmark.Other correctness & behavior changes
fit_star_idxsno longer wipes unrelated stars: refitting only a subset of stars used to overwrite every other star's existing result withnan; now keeps existing values by default and only touches the requested subset.Parallax's minimum-epoch requirement wasn_pts_req=4; 3 (x, y) pairs are enough to solve its 5-parameter model, so lowered to 3 — a caller wanting to require 4+ can still do so via themotion_model_inputcolumn.inherit_n_detect: newMosaicSelfRef/MosaicToRefparameter (defaultTrue) so a star'sn_detectreflects the total number of raw detections it represents across nested alignment layers, not just 1 per input starlist.StarListwarning: every pickle round-trip (astropy passes columns positionally to__init__) incorrectly warned about missing required arguments.align()iterations) kept stale params (vx/vy) from its old model indefinitely; now reset tofill_value/infright after reclassification.parallax.parallax_in_direction'sTime(mjd + 2400000.5, format='jd', ...)simplified toTime(mjd, format='mjd', ...); a misleadingtqdmprogress bar over a handful-of-motion-model-types loop (never per-star) removed.Documentation
.readthedocs.yaml. Getting a working build also required fixing three real, pre-existing bugs unrelated to Read the Docs specifically — they'd have broken a freshpip install .for anyone on a current Python:pyproject.toml's[build-system]pinnedcython==0.29.14(leftover astropy-template boilerplate for a package with zero.pyx/C extensions), which imports the stdlibcgimodule removed in Python 3.13, breaking install outright; the declareddependencieswere missingscipy,matplotlib,tqdm,joblib, andpandas, all imported unconditionally at module level; andsetup.cfg'sgithub_projectwas still the template default (astropy/astropy), whichconf.pyuses unconditionally to build doc issue-links. Verified via two independent fresh-venv builds (editable and the exact non-editablepip install .[docs]Read the Docs runs) —sphinx-buildsucceeds, 92 warnings, all pre-existing docstring formatting nits unrelated to this change.wei-lingfeng/flystarrather than this repo directly: importingMovingUniverseLab/flystarinto Read the Docs needs repo-admin access (to add its webhook) not currently available on this account, and theflystarproject name is already taken there by an existing (currently stale) project for this same package — re-pointing that one at this branch, rather than creating a competing project, needs maintainer access to it instead. The fork tracks this branch 1:1 in the meantime, so content is identical either way.Validation
Each change was validated individually — synthetic fuzz tests against reference implementations (thousands to tens of thousands of cases per change), byte-for-bit output comparison on real multi-thousand and multi-million-star datasets, and dedicated new unit tests where behavior changed. Full test suite passes except two pre-existing, environment/data-dependent failures unrelated to any change here:
test_masked_cols(missing test fixture file) andtest_generic_match(test data contains no finite values). (Thetest_MosaicSelfRef_velfailure present at one point inmm_rework— see the magnitude-weighting fix above — is resolved on this branch.)Worked example:
scipy.curve_fitvs. analytic error propagation (referenced above)Output: