rugnux: the rotation-scale walk stops where the next step would move the sweep end by under half a frame
Each refit reads a share of the error left, so the walk's steps contract by about the ratio of the
last two. Where the step that ratio predicts moves the end of the sweep by less than half a frame,
the refit (an integration of the pre-pass frames and a post-refinement) and the indexing probe after
it are not made; the scale the validation frames last preferred is adopted, as before.
Measured on the walk sets of the battery against rc175 edde4439c: p.hkl and MTZ data identical; it
fired on one open set (2 refits instead of 3, same adopted scale), about -7 s there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EizBKhTqrAYago9KJG3dA3
This commit is contained in:
+11
-1
@@ -134,7 +134,7 @@ bool ValidationEvidenceBeatsChance(const ValidationSpotEvidence &e, double signi
|
||||
RotationScaleWalk WalkRotationScale(double first_fit,
|
||||
const std::function<ValidationSpotEvidence(float)> &index_at,
|
||||
const std::function<std::optional<double>(float)> &refit_at,
|
||||
int max_rounds) {
|
||||
int max_rounds, int64_t sweep_images) {
|
||||
RotationScaleWalk walk;
|
||||
auto next = static_cast<float>(first_fit);
|
||||
if (next == walk.scale)
|
||||
@@ -145,13 +145,23 @@ RotationScaleWalk WalkRotationScale(double first_fit,
|
||||
};
|
||||
walk.evidence = index_at(walk.scale);
|
||||
walk.trail = fmt::format("{:.5f}: {:.1f}%", walk.scale, score(walk.evidence));
|
||||
double last_step = 0.0;
|
||||
for (int round = 0; round < max_rounds && next != walk.scale; ++round) {
|
||||
const ValidationSpotEvidence e = index_at(next);
|
||||
walk.trail += fmt::format(", {:.5f}: {:.1f}%", next, score(e));
|
||||
if (!ValidationEvidencePrefers(walk.evidence, e))
|
||||
break;
|
||||
const double step = next - walk.scale;
|
||||
walk.scale = next;
|
||||
walk.evidence = e;
|
||||
// Each refit reads a share of the error left, so the steps contract by about the ratio of the
|
||||
// last two. Where the step that ratio predicts moves the end of the sweep by less than half a
|
||||
// frame, the scale it would reach indexes the frames as this one does, and the refit is not made.
|
||||
if (last_step != 0.0 && std::fabs(step * step / last_step) * static_cast<double>(sweep_images) < 0.5) {
|
||||
walk.trail += fmt::format(" (next step predicted {:.5f}, under half a frame)", step * step / last_step);
|
||||
break;
|
||||
}
|
||||
last_step = step;
|
||||
const auto fit = refit_at(walk.scale);
|
||||
if (!fit)
|
||||
break;
|
||||
|
||||
+2
-1
@@ -284,6 +284,7 @@ std::pair<double, double> ValidationEvidenceDifference(const ValidationSpotEvide
|
||||
// index_at(k): the validation evidence of the lattice indexed with the stored angles scaled by k
|
||||
// refit_at(k): the scale the post-refinement fits on reflections integrated at k, relative to k;
|
||||
// empty where it fitted none
|
||||
// sweep_images: the frames of the sweep, against which a step too small to refit for is measured
|
||||
struct RotationScaleWalk {
|
||||
float scale = 1.0f; // adopted; 1 = the stored angles stand
|
||||
ValidationSpotEvidence evidence; // at the adopted scale
|
||||
@@ -292,7 +293,7 @@ struct RotationScaleWalk {
|
||||
RotationScaleWalk WalkRotationScale(double first_fit,
|
||||
const std::function<ValidationSpotEvidence(float)> &index_at,
|
||||
const std::function<std::optional<double>(float)> &refit_at,
|
||||
int max_rounds);
|
||||
int max_rounds, int64_t sweep_images);
|
||||
|
||||
// The first pass found no lattice of the crystal in the frames - what a sample with no crystal in the
|
||||
// beam ends in. A statement about the data, not an error in the input: the CLI writes a report with
|
||||
|
||||
@@ -729,7 +729,8 @@ RotationScaleWalk Rugnux::WalkRotationScaleBeside(const ProcessResult &pass1, Ro
|
||||
};
|
||||
constexpr int MAX_ROTATION_SCALE_ROUNDS = 8;
|
||||
const RotationScaleWalk walk = WalkRotationScale(pass1.post_refine->rotation_scale, index_at,
|
||||
refit_at, MAX_ROTATION_SCALE_ROUNDS);
|
||||
refit_at, MAX_ROTATION_SCALE_ROUNDS,
|
||||
experiment_.GetImageNum());
|
||||
std::string text;
|
||||
if (!walk.trail.empty()) {
|
||||
text = fmt::format(
|
||||
|
||||
@@ -31,9 +31,9 @@ namespace {
|
||||
return 1.0 + 0.7 * (true_scale / k - 1.0);
|
||||
}
|
||||
|
||||
RotationScaleWalk Walk(double first_fit) {
|
||||
RotationScaleWalk Walk(double first_fit, int64_t sweep_images = 3600) {
|
||||
return WalkRotationScale(first_fit, [this](float k) { return IndexAt(k); },
|
||||
[this](float k) { return RefitAt(k); }, 8);
|
||||
[this](float k) { return RefitAt(k); }, 8, sweep_images);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -65,6 +65,22 @@ TEST_CASE("WalkRotationScale_ReachesTheFixedPoint", "[RotationScale]") {
|
||||
CHECK_FALSE(walk.trail.empty());
|
||||
}
|
||||
|
||||
TEST_CASE("WalkRotationScale_StopsWhereTheNextStepIsUnderHalfAFrame", "[RotationScale]") {
|
||||
// Spots enough for the validation frames to prefer every step: what ends the walk is the step the
|
||||
// contraction predicts, measured against the sweep. Each refit reads 70 % of the error left, so the
|
||||
// steps contract by 0.3.
|
||||
SyntheticSweep short_sweep{0.97};
|
||||
short_sweep.spots = 1000000000;
|
||||
const auto a = short_sweep.Walk(short_sweep.RefitAt(1.0f).value(), 360);
|
||||
SyntheticSweep long_sweep{0.97};
|
||||
long_sweep.spots = 1000000000;
|
||||
const auto b = long_sweep.Walk(long_sweep.RefitAt(1.0f).value(), 36000);
|
||||
CHECK(short_sweep.refit_calls < long_sweep.refit_calls);
|
||||
// Where it stops, what is left of the error moves the end of the sweep by under a frame.
|
||||
CHECK(std::fabs(a.scale - 0.97) * 360 < 1.0);
|
||||
CHECK(std::fabs(b.scale - 0.97) * 36000 < 1.0);
|
||||
}
|
||||
|
||||
TEST_CASE("WalkRotationScale_StoredAnglesStand", "[RotationScale]") {
|
||||
SECTION("A healthy stage: a fit off by noise scores no better than the stored angles") {
|
||||
SyntheticSweep sweep{1.0};
|
||||
|
||||
Reference in New Issue
Block a user