The fit has ONE parameter, and it was handed to Ceres as one residual block
per rocking event - 8 million of them on a large crystal. Each block is a
functor, an auto-diff cost function and a loss object on the heap, and the
solver then factorises an 8-million-by-one Jacobian on every iteration. It cost
13.7 s.
The residual is closed-form in k. A rotation preserves length, so |p_lab| is
|e_mid| whatever k is and only the z component moves; Rodrigues gives it
exactly:
r(k) = C + A cos(a k) - B sin(a k) = C + R cos(a k + psi)
C = lambda |e|^2 / 2 + u_z (u.e), A = e_z - u_z (u.e), B = (u x e)_z
with a the event's angle from the sweep centre. That is the same function the
functor computes - Ceres uses the exact Rodrigues form here, so there is no
small-angle branch to disagree with - and it reduces the fit to minimising a
smooth function of one variable over the interval the solver was bounded to.
It is scanned on a grid and then closed in by golden section; the objective's
curvature jumps wherever an event crosses the Huber knee, which is why this is
not a Newton iteration.
The coefficients are computed in double and stored narrowed. Their rounding
moves the minimiser by ~1e-10, and k is carried downstream as a float, so the
committed value is the same to far more digits than anything reads.
One pass over the events yields the five per-fifth partial sums, so the
all-data fit and the five leave-a-fifth-out folds share it. That matters
because the jackknife only runs when the fit is big enough to act on, and on a
crystal that trips it the old code paid for six full solves.
The partials gather ahead of it counted first and then filled instead of
growing one vector by push_back tens of millions of times, which copied the
whole thing on every doubling.
Measured: unchanged verdict and k to five decimals on the regression crystals.
Full 24-crystal battery: same space group on all 24, none failed, 15m32s ->
13m35s together with the scale/merge changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>