RigidBodyRefine: reuse the residuals of an accepted step when Ceres asks for its Jacobian

Ceres evaluates some points twice - the residuals alone to decide whether to take a step, then
residuals and Jacobian together at the same point - and every evaluation is a full Fcalc, a solvent
mask and a scale fit. The residuals are a function of the placement alone (the zone's bulk solvent
is fixed by its first evaluation), so the second computation is replaced by a copy of the first. On
the null of a 1.63 A P2 set the replicates' evaluations drop 46/64/72/89/136 -> 44/59/68/82/123, and
each saved evaluation is a serial one on the replicate's critical path. With the previous commit the
null there goes 152 s -> 87 s (27 busy cores); 8sa8 60 -> 53 s.

Exact: p.hkl, p.mtz, p.cif, p_maps.mtz, p_model.cif byte-identical to rc173 and the model-validation
report unchanged on 9hnc, 8sa8, 8xtg, 9fhc, 5epe, 6oel (battery commands, --model); rigid-body
rotations and translations in the log identical. [ModelValidation] tests pass.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C
This commit is contained in:
leonarski_fandClaude Opus 5.5 committed 2026-09-27 21:11:14 +02:00
1 parent ce87b98280
commit dff1543bd7
1 file changed
+13 -2
+13 -2
View File
@@ -214,8 +214,17 @@ public:
bool Evaluate(double const *const *parameters, double *residuals, double **jacobians) const override {
const int n = num_residuals();
if (!ev_.Residuals(parameters[0], residuals))
return false;
// Ceres evaluates every step it accepts twice: the residuals alone to decide whether to take
// it, then residuals and Jacobian together at the same point. The residuals are a function of
// the placement alone, so the second time they are the first time's, copied.
if (!last_residuals_.empty() && std::equal(parameters[0], parameters[0] + 6, last_q_.begin())) {
std::copy(last_residuals_.begin(), last_residuals_.end(), residuals);
} else {
if (!ev_.Residuals(parameters[0], residuals))
return false;
std::copy(parameters[0], parameters[0] + 6, last_q_.begin());
last_residuals_.assign(residuals, residuals + n);
}
if (jacobians != nullptr && jacobians[0] != nullptr) {
const double step = ev_.JacobianStep();
std::vector<std::vector<double>> shifted(6, std::vector<double>(n));
@@ -244,6 +253,8 @@ private:
Evaluator &ev_;
std::vector<Evaluator> &columns_;
size_t nthreads_;
mutable std::array<double, 6> last_q_{};
mutable std::vector<double> last_residuals_; // at last_q_; empty until an evaluation succeeds
};
// The directions in which this space group's origin is free. Translating the whole cell content along