Refactor optim optimizers into readable quasi-private helpers#220
Conversation
Split the four long methods in `artist/optim` into short orchestrators that call clearly-named quasi-private (`_`) helper methods. This is a pure extract-method refactor: the public API and numerical behaviour of all four methods are unchanged. Affected methods: - KinematicsReconstructor._reconstruct_kinematics_parameters_with_alignment - KinematicsReconstructor._reconstruct_kinematics_parameters_with_raytracing - SurfaceReconstructor.reconstruct_surfaces - AimPointOptimizer.optimize The two ~90%-identical kinematics methods now share their setup/parse/sync helpers. All helpers are per-class private methods (no new module functions, no base class, no new imports) to avoid coupling and circular-import risk. Verified: full test suite (407 passed) incl. real golden comparisons for the kinematics and surface reconstructors, coverage held at 95%, ruff check/format clean, and no new mypy errors versus main. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #220 +/- ##
==========================================
+ Coverage 96.38% 96.41% +0.02%
==========================================
Files 57 57
Lines 4208 4239 +31
==========================================
+ Hits 4056 4087 +31
Misses 152 152 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
I added a fix in |
mcw92
left a comment
There was a problem hiding this comment.
LGTM. I checked your comment regarding the :meth: directive @MarleneBusch. This turns out fine in the API references with a link to the respective method.
I did not check everything line by line for equivalence with the code before. However, this is ok for me as the tests pass, Claude was told to only do a refactor, and @MarleneBusch already checked everything carefully.
I think the optimize methods are now way more readable than before (even though there might still be room for improvement somewhere, as always). I will merge this now.
What & why
The optimizers/reconstructors in
artist/optimhad grown four very long methods (250–530 lines each) that packed setup, data loading, the epoch loop, constraints, and distributed synchronization into a single body — and the two kinematics methods were ~90% copy-paste of each other. This PR breaks each of them into a short orchestrator that reads as a sequence of clearly-named quasi-private (_) helper methods.This is a pure extract-method refactor. The public API and numerical behaviour of every method are unchanged. Nothing about how you call these classes changes.
Scope
KinematicsReconstructor._reconstruct_kinematics_parameters_with_raytracingKinematicsReconstructor._reconstruct_kinematics_parameters_with_alignmentSurfaceReconstructor.reconstruct_surfacesAimPointOptimizer.optimizeNothing outside
artist/optim/is touched.loss.py,regularizers.py, andtraining.pyare untouched.Design decisions
_initialize_reconstruction_bookkeepingand_parse_group_calibration_dataare duplicated betweenKinematicsReconstructorandSurfaceReconstructor— a deliberate, low tax for zero coupling._initialize_reconstruction_bookkeeping,_parse_group_calibration_data,_setup_optimizer_scheduler_early_stopping,_synchronize_*) since they live in the same class.whileloop,breakon early stopping,epoch += 1, per-epoch history.append(...), and the loss composition were intentionally not extracted — only cohesive blocks (setup, forward pass, constraints, distributed sync) moved into helpers.epoch == 0reference capture and the Augmented-Lagrangianlambda_*multipliers — is round-tripped in/out of the helper rather than hidden inside it. SeeAimPointOptimizer._compute_kl_constraintsandSurfaceReconstructor._compute_flux_integral_constraint._validate/_plot_fluxesconvention. This is why the line count grows despite the logic shrinking — the added lines are docstrings and explicit signatures, not new behaviour.What reviewers should focus on
Because this is behaviour-preserving by construction, the useful review question is "is each helper a faithful move of the original block?" rather than "is the logic correct?". Concretely:
AimPointOptimizer._compute_kl_constraints— the trickiest extraction. Check that the three constraints, theloss = flux_loss + ...composition, and the threelambda_*updates undertorch.no_grad()are byte-for-byte the original, and that theepoch == 0references + multipliers correctly round-trip (in as params, out in the return tuple) so state persists across epochs. Also confirm the pre-existing behaviour wherelossis only reassigned inside the KL branch is preserved.SurfaceReconstructor._compute_flux_integral_constraint/_compute_regularization_terms— confirm theepoch == 0flux_integrals_referencecapture stays in the orchestrator (guarding the call), and thatalpha/betadynamic balancing and theepsilonhandling are unchanged._synchronize_*helpers (all three classes) — these wrap theis_nested/is_distributedbranches. They're no-ops in the single-rank test path, so they're the least test-covered lines; a careful read of the broadcast/all-reduce/all-gather calls is worthwhile.nan_to_num_(alignment) vs_synchronize_gradients_nested_ddp(raytracing), and the_validatereduction (torch.meanvstorch.median)._predict_flux(surface) /_compute_raytracing_loss(kinematics) — confirm the ray-tracer construction args, ordering, and thesample_indices/local_indicesderivations match the originals exactly.A helpful way to review: read each orchestrator top-to-bottom first (it should now tell a clean story), then spot-check that each
self._helper(...)call receives/returns exactly what the inlined code used to.Verification
tests/data/expected_test_data.pt) with tolerances, so their passing genuinely validates behaviour-equivalence.main.ruff check+ruff formatclean; pre-commitmypypasses with no new errors versusmain.Non-goals / follow-ups
optim(heliostat_field.from_hdf5~355 lines,heliostat_ray_tracer.trace_rays~289) — intentionally out of scope here.tests/optim/test_aim_point_optimizer.pyoverwrites its own key inexpected_test_data.ptand then asserts against what it just wrote, so it always passes and dirties the working tree on every run. Not touched here, but worth a separate look.🤖 Generated with Claude Code