geom_refinement: stop committing refinements that did not converge

Four of the seven ceres::Solve calls in image_analysis obtained a
Solver::Summary and never looked at it, so a solve that failed numerically
had its parameters written back and was reported as success.
StillsPartialityRefine and both PostRefine solves already gated on
IsSolutionUsable(); this brings the rest to the same contract.

IsSolutionUsable() is the right test rather than checking for CONVERGENCE:
it accepts a solve that ran out of iterations or wall-clock time but still
descended, which is exactly what the real-time callers depend on when they
set max_solver_time instead of max_num_iterations. Only FAILURE and
USER_FAILURE are rejected.

XtalOptimizer checks before the write-back, so a failed refinement now
leaves the caller's geom and latt untouched instead of half-updated.
GeometryRefiner folds it into result.ok, which previously reported success
from spot and frame counts alone. RingOptimizer returns a geometry by
value that both callers assign straight back over their input, so it hands
back the unchanged reference rather than a diverged beam centre.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-31 15:02:11 +02:00
co-authored by Claude Opus 5
parent b1740935d8
commit 164f15c903
3 changed files with 22 additions and 1 deletions
@@ -174,6 +174,10 @@ GeometryRefinerResult RefineGlobalGeometry(const DiffractionGeometry &nominal_ge
return 0.3 - (0.3 - 0.1) * t;
};
// Whether the last solve that ran produced a usable solution; folded into result.ok below,
// which otherwise reports success on spot and frame counts alone.
bool solve_usable = false;
for (int round = 0; round < settings.rounds; ++round) {
EffectiveCell(system, cell_len, cell_ang, eff_len, eff_alpha, eff_beta, eff_gamma);
@@ -265,6 +269,7 @@ GeometryRefinerResult RefineGlobalGeometry(const DiffractionGeometry &nominal_ge
ceres::Solver::Summary summary;
ceres::Solve(options, &problem, &summary);
solve_usable = summary.IsSolutionUsable();
result.frames_used = frames_used;
result.spots_used = spots_used;
@@ -298,7 +303,7 @@ GeometryRefinerResult RefineGlobalGeometry(const DiffractionGeometry &nominal_ge
result.median_residual_px = residuals[residuals.size() / 2];
}
result.ok = result.spots_used >= settings.min_spots_total && result.frames_used > 0;
result.ok = solve_usable && result.spots_used >= settings.min_spots_total && result.frames_used > 0;
result.beam_x_px = beam[0];
result.beam_y_px = beam[1];
result.distance_mm = distance_mm;