GeometryRefiner: residuals carry only the blocks a fit moves; beam and distance one block

The stills geometry refinement (module placement fits, the held-out fits that decide them, the tilt
decision) spent most of its time differentiating parameters it never moves and in Ceres' general
Schur elimination:

- Each spot's residual declared the rotation axis, the cell angles and the (usually held) tilt as
  blocks. Ceres sizes its dual numbers from the declared blocks, so a placement residual carried 20
  derivatives to use 12. They now enter as plain numbers (SpotResidual / TiltedSpotResidual).
- A fit that moves only the orientations (the held-out scoring) gets a residual over the
  orientation alone, with the observed and predicted vectors taken once per spot
  (OrientationResidual): 3 derivatives instead of 17.
- The beam and the distance are one block of three, so every refined block has three parameters
  and Ceres uses its fixed-size Schur eliminator (1.7x faster linear solve on one thread). A held
  distance is a SubsetManifold on that block.
- One robust loss shared by every spot instead of one allocated per spot.

Same arithmetic on the same values: replaying the dumped placement inputs of four multi-panel
stills runs (AGIPD 128 ASICs, MPCCD 8, JF4M 8, JF16M 32 modules) on one thread gives identical
held-out residuals, beam, distance, cell and every module shift at the printed precision. Single-
thread CPU of the nine placement fits: 397 -> 112 s (MPCCD), 179 -> 80 s (JF4M), 131 -> 68 s
(JF16M). On AGIPD the concurrent step goes 8.5 -> 6.4 s wall (quiet replay, 32 threads).

Test: RefineGlobalGeometry_ModulePlacement now also covers a held distance and an orientation-only
fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-10-23 11:51:24 +02:00
1 parent 077fad9379
commit f6c948732b
2 files changed
+176 -75

No files matched your search

+18
View File
@@ -103,6 +103,24 @@ TEST_CASE("RefineGlobalGeometry_ModulePlacement") {
CHECK(placed.beam_x_px == Catch::Approx(1000).margin(0.1));
CHECK(placed.beam_y_px == Catch::Approx(1000).margin(0.1));
CHECK(placed.distance_mm == Catch::Approx(150).margin(0.05));
// The distance held (it shares a block with the beam): it stays exactly where it was given.
GeometryRefinerSettings held_distance = st;
held_distance.refine_distance = false;
const auto fixed = RefineGlobalGeometry(geom, latt.GetUnitCell(), frames, held_distance);
REQUIRE(fixed.ok);
CHECK(fixed.distance_mm == geom.GetDetectorDistance_mm());
CHECK(fixed.modules[0].dx == Catch::Approx(truth[0].dx).margin(0.1));
// Only the orientations moved, on the modules as placed: the spots fit as well as in the placement.
GeometryRefinerSettings ev = st;
ev.refine_modules = false;
ev.refine_beam_distance = false;
ev.modules = placed.modules;
const auto scored = RefineGlobalGeometry(geom, latt.GetUnitCell(), frames, ev);
REQUIRE(scored.ok);
CHECK(scored.median_residual_px < 0.1);
CHECK(scored.distance_mm == geom.GetDetectorDistance_mm());
}
// The coarse search ahead of the placement: a module off by several pixels is found near where it is, and the