Geometry refinement: bound offline reprocessing by iterations, not the clock

The per-image refinement stopped on a wall-clock budget (40 ms, and 20 ms for
the rotation-only extra pass). Online that is exactly right - the budget is real
and an image that overruns it costs the acquisition. Offline it means the same
file refines to a different lattice depending on what else the machine was doing
at the time, which is not a property reprocessing should have.

Bound it by iteration count instead when the caller is offline. IndexAndRefine
takes the workflow as a constructor argument: the receiver asks for the
wall-clock bound, rugnux and the viewer get the reproducible one. 50 iterations
is Ceres' own default; the per-image problem converges well inside it, so it
bounds the pathological case rather than the normal one - measured on five
battery crystals, every number is unchanged from the timed version.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-07-30 20:42:04 +02:00
co-authored by Claude Opus 5
parent a739b156f3
commit b161da05c1
5 changed files with 38 additions and 7 deletions
+17 -3
View File
@@ -14,9 +14,18 @@
#include "scale_merge/ReindexAmbiguity.h"
#include "scale_merge/ScaleOnTheFly.h"
IndexAndRefine::IndexAndRefine(const DiffractionExperiment &x, IndexerThreadPool *indexer, bool retain_outcomes)
namespace {
// Iterations the offline geometry refinement is allowed, standing in for the 40 ms the online path
// spends. Ceres' own default is 50; the per-image problem is small and converges well inside that,
// so this bounds the pathological case rather than the normal one.
constexpr int OFFLINE_REFINE_ITERATIONS = 50;
}
IndexAndRefine::IndexAndRefine(const DiffractionExperiment &x, IndexerThreadPool *indexer,
bool retain_outcomes, bool real_time)
: index_ice_rings(x.GetIndexingSettings().GetIndexIceRings()),
retain_outcomes_(retain_outcomes),
real_time(real_time),
experiment(x),
geom_(x.GetDiffractionGeometry()),
indexer_(indexer),
@@ -258,7 +267,11 @@ void IndexAndRefine::RefineGeometryIfNeeded(DataMessage &msg, IndexAndRefine::In
// enters the fit at the loose first tolerance is arbitrarily indexed noise. Weight every spot by
// how strong it is for its resolution so those contribute without dragging the orientation.
.weight_spots_by_confidence = true,
.max_time = 0.04 // 40 ms is max allowed time for the operation
// Online: 40 ms is the real budget per image, so the wall clock is the right bound even though
// it makes the answer depend on machine load. Offline: bound the same refinement by iterations
// instead, so reprocessing the same file twice gives the same lattice.
.max_time = 0.04,
.max_iterations = real_time ? 0 : OFFLINE_REFINE_ITERATIONS
};
@@ -346,7 +359,8 @@ void IndexAndRefine::RefineGeometryIfNeeded(DataMessage &msg, IndexAndRefine::In
.refine_unit_cell = false,
.refine_rotation_axis = false,
.index_ice_rings = experiment.GetIndexingSettings().GetIndexIceRings(),
.max_time = 0.02
.max_time = 0.02,
.max_iterations = real_time ? 0 : OFFLINE_REFINE_ITERATIONS / 2
};
XtalOptimizerRotationOnly(data_extra, msg.spots, 0.1);
el = data_extra.latt;