Bragg prediction: derive the lattice walk from the cell, and expose it in the API
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m15s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m42s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m47s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m1s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m11s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m19s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m31s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 12m39s
Build Packages / build:rpm (rocky8) (push) Successful in 11m41s
Build Packages / XDS test (durin plugin) (push) Successful in 7m52s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Build documentation (push) Successful in 1m3s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 12m10s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m0s
Build Packages / build:rpm (rocky9) (push) Successful in 13m39s
Build Packages / DIALS test (push) Successful in 14m29s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m33s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m25s
Build Packages / Unit tests (push) Successful in 1h36m21s
Build Packages / build:windows:nocuda (push) Canceled after 0s
Build Packages / build:windows:cuda (push) Canceled after 0s
Build Packages / build:viewer-tgz:cpu (push) Successful in 7m15s
Build Packages / build:viewer-tgz:cuda (push) Successful in 8m42s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 13m47s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m1s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m11s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m19s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 14m31s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 12m39s
Build Packages / build:rpm (rocky8) (push) Successful in 11m41s
Build Packages / XDS test (durin plugin) (push) Successful in 7m52s
Build Packages / Generate python client (push) Successful in 34s
Build Packages / Build documentation (push) Successful in 1m3s
Build Packages / Create release (push) Skipped
Build Packages / build:rpm (ubuntu2404) (push) Successful in 12m10s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 13m0s
Build Packages / build:rpm (rocky9) (push) Successful in 13m39s
Build Packages / DIALS test (push) Successful in 14m29s
Build Packages / XDS test (neggia plugin) (push) Successful in 8m33s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 9m25s
Build Packages / Unit tests (push) Successful in 1h36m21s
Build Packages / build:windows:nocuda (push) Canceled after 0s
Build Packages / build:windows:cuda (push) Canceled after 0s
Follow-up to making max_hkl a setting: it is now an optional, and unset means "take it from this crystal". The predictor keeps only |q| <= 1/d_min and h = a.q for the real-space axis a, so |h| <= a/d_min exactly - and likewise |k| <= b/d_min and |l| <= c/d_min. max(a,b,c)/d_min therefore bounds all three at once: nothing that could be predicted lies outside it, and nothing inside it is reached by a shorter axis. It applies to rotation and stills alike, both going through the one place the prediction settings are built. Offline (rugnux, viewer) the default is unset, so every crystal gets its own range; --max-hkl overrides it. Online the broker holds a concrete number, because the cost is the cube of it per image and a live acquisition should not have its frame rate decided by whichever sample is mounted: max_hkl joins bragg_integration_settings in the OpenAPI with a default of 100, so an omitted field arrives as that default (the generated model carries it) rather than as "derive it", and the frontend exposes it next to the integration model. Measured against a fixed 100 on six rotation crystals: three are bit-identical, two were being truncated and recover 419k and 5.8k observations with the high-shell CC1/2 going 15.1 -> 25.8% and 52.1 -> 55.3%, and the space group is unchanged 6/6. It reproduces a fixed 200 exactly, which is the bound being tight rather than merely safe. The sixth is worth recording: a 149/83/226 A cell derives 227, and because a single scalar has to cover the longest axis the cube is ~16x what a per-axis box would be - 22% wall clock, for a net 22 observations out of 364k (the per-frame 65536-reflection cap re-selects at the margin when more candidates are offered) and identical CC1/2, ISa and space group. Per-axis limits would remove that; the predictors already map a thread index to h, k and l separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <cstdlib>
|
||||
#include "IndexAndRefine.h"
|
||||
@@ -19,6 +21,31 @@ namespace {
|
||||
// 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;
|
||||
|
||||
// How far the predictor has to walk the lattice for THIS crystal. The predictor keeps only
|
||||
// reflections with |q| <= 1/d_min, and h = a.q for the real-space axis a, so |h| <= a/d_min exactly
|
||||
// (Cauchy-Schwarz, equality when a lies along q); the same for k and l. Walking beyond
|
||||
// max(a,b,c)/d_min therefore generates only candidates the resolution cut throws away, and stopping
|
||||
// short of it silently truncates the outermost reflections of the longest axis.
|
||||
//
|
||||
// A fixed bound cannot be right for both: 100 covers a 150 A axis at 1.5 A and truncates the same
|
||||
// axis at 1.0 A, while being ~4x more cube than a small cell at 2 A ever needs. One index of margin
|
||||
// covers the rounding.
|
||||
int MaxHKLForCell(const CrystalLattice &latt, float d_min_A) {
|
||||
const UnitCell cell = latt.GetUnitCell();
|
||||
const float longest_axis_A = std::max({cell.a, cell.b, cell.c});
|
||||
return static_cast<int>(std::ceil(longest_axis_A / d_min_A)) + 1;
|
||||
}
|
||||
|
||||
// An explicit setting is enforced as given; otherwise the cell decides. The online path carries a
|
||||
// value (the broker bootstraps one and the API can change it), so live acquisition never has its
|
||||
// per-frame cost decided by the crystal that happened to be mounted.
|
||||
int PredictionMaxHKL(const DiffractionExperiment &experiment, const CrystalLattice &latt) {
|
||||
const auto &bragg = experiment.GetBraggIntegrationSettings();
|
||||
if (const auto fixed = bragg.GetMaxHKL())
|
||||
return *fixed;
|
||||
return MaxHKLForCell(latt, bragg.GetDMinLimit_A());
|
||||
}
|
||||
}
|
||||
|
||||
IndexAndRefine::IndexAndRefine(const DiffractionExperiment &x, IndexerThreadPool *indexer,
|
||||
@@ -438,7 +465,7 @@ void IndexAndRefine::QuickPredictAndIntegrate(DataMessage &msg,
|
||||
const BraggPredictionSettings settings_prediction{
|
||||
.high_res_A = experiment.GetBraggIntegrationSettings().GetDMinLimit_A(),
|
||||
.ewald_dist_cutoff = ewald_dist_cutoff,
|
||||
.max_hkl = experiment.GetBraggIntegrationSettings().GetMaxHKL(),
|
||||
.max_hkl = PredictionMaxHKL(experiment, latt),
|
||||
// Centering is a hypothesis to confirm, not assume: with no user-fixed space group, predict
|
||||
// in P so the centering-absent reflections are integrated and the space-group search can
|
||||
// confirm or disprove centering (and catch a missed superstructure). A user-fixed space
|
||||
|
||||
Reference in New Issue
Block a user