integration: add --reciprocal-profile dial (global reciprocal-space width)
Build Packages / Unit tests (push) Successful in 59m53s
Build Packages / build:windows:nocuda (push) Successful in 18m24s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 14m30s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 14m33s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 14m54s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 14m20s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 15m2s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 15m29s
Build Packages / build:rpm (rocky8) (push) Successful in 14m37s
Build Packages / build:rpm (rocky9) (push) Successful in 12m44s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 12m10s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 11m21s
Build Packages / DIALS test (push) Successful in 12m22s
Build Packages / XDS test (durin plugin) (push) Successful in 7m34s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 7m41s
Build Packages / XDS test (neggia plugin) (push) Successful in 7m52s
Build Packages / Generate python client (push) Successful in 24s
Build Packages / Build documentation (push) Successful in 1m0s
Build Packages / Create release (push) Skipped
Build Packages / build:windows:cuda (push) Successful in 16m53s

The per-shell profile width is learned in pixels, so it varies ~4x with resolution
(mostly the geometric projection of a near-constant reciprocal-space relrod) and must
be binned per shell -> it starves at high resolution / on sparse data. The new
--reciprocal-profile flag instead learns ONE global width in reciprocal space,
sigma2_q,tan = A + B|q| + C|q|^2: the Jacobian g_tan=cos(2theta) removes the geometric
projection, and C|q|^2 is the crystal mosaicity relrod (variance ~(eta|q|)^2). Applied
per reflection as sigma2_tan,px = (A + B|q| + C|q|^2)/g_tan^2 (B,C clamped >=0;
quadratic->linear->constant fallback).

Off by default. On the sharp HEWL test crystal (mosaicity 0.091deg, so C fits to ~0 and
it reduces to the validated linear form) it is metric-neutral: ISa 16.2->16.3, anomalous
0.92x unchanged, CCref band 90.0->89.9, CC1/2 a touch lower (per-shell isn't starved at
23k spots/shell, and a global fit is less flexible). So: simpler + more transferable at a
small CC1/2 cost, ISa/anomalous held. Its payoff is on MOSAIC crystals (large C|q|^2),
where per-shell starves on the wide weak high-res spots and 6 shells are too coarse; both
lyso test crystals are sharp, so it ships as a dial to try on mosaic data elsewhere.
A separate radial relrod fit was tried and dropped (no gain). See NEXTGEN_INTEGRATOR.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 14:26:08 +02:00
co-authored by Claude Opus 4.8
parent 5ba5fe8ad1
commit 3aec235f67
5 changed files with 123 additions and 5 deletions
+15 -1
View File
@@ -72,6 +72,7 @@ void print_usage() {
std::cout << " --bandwidth <num> Relative X-ray bandwidth FWHM (e.g. 0.01 for 1% DMM); default from file or 0" << std::endl;
std::cout << " --integration-radius <r> Signal-box radius r1, or r1,r2,r3 (px). One value => r2=r1+2, r3=r1+4" << std::endl;
std::cout << " --integrator <txt> Spot integrator boxsum|gaussian|empirical (default: gaussian profile-fit; boxsum is the classical fallback)" << std::endl;
std::cout << " --reciprocal-profile Learn one global reciprocal-space profile width (A+B|q|+C|q|^2) instead of per-shell; helps mosaic/sparse data" << std::endl;
}
enum {
@@ -97,7 +98,8 @@ enum {
OPT_SCALE_FULLS,
OPT_CAPTURE_UNCERTAINTY,
OPT_MOSAICITY,
OPT_SMOOTH_G
OPT_SMOOTH_G,
OPT_RECIPROCAL_PROFILE
};
static option long_options[] = {
@@ -142,6 +144,7 @@ static option long_options[] = {
{"bandwidth", required_argument, nullptr, OPT_BANDWIDTH},
{"integration-radius", required_argument, nullptr, OPT_INTEGRATION_RADIUS},
{"integrator", required_argument, nullptr, OPT_INTEGRATOR},
{"reciprocal-profile", no_argument, nullptr, OPT_RECIPROCAL_PROFILE},
{"reject-outliers", required_argument, nullptr, OPT_REJECT_OUTLIERS},
{"reject-delta-cchalf", required_argument, nullptr, OPT_REJECT_DELTA_CCHALF},
{nullptr, 0, nullptr, 0}
@@ -314,6 +317,7 @@ int main(int argc, char **argv) {
std::optional<float> d_min_scale_merge;
std::optional<std::string> integration_radius_arg; // "r1" or "r1,r2,r3"
std::optional<IntegratorMode> integrator_mode; // --integrator boxsum|gaussian|empirical
bool reciprocal_profile = false; // --reciprocal-profile
std::optional<double> outlier_reject_nsigma; // merge per-observation outlier rejection
std::optional<double> delta_cchalf_nsigma; // per-crystal CC1/2-delta rejection
@@ -535,6 +539,9 @@ int main(int argc, char **argv) {
else if (strcmp(optarg, "empirical") == 0) integrator_mode = IntegratorMode::ProfileEmpirical;
else { logger.Error("--integrator expects boxsum|gaussian|empirical"); return 1; }
break;
case OPT_RECIPROCAL_PROFILE:
reciprocal_profile = true;
break;
case OPT_REJECT_OUTLIERS:
outlier_reject_nsigma = std::stod(optarg);
break;
@@ -773,6 +780,13 @@ int main(int argc, char **argv) {
: "profile (empirical)");
}
if (reciprocal_profile) {
BraggIntegrationSettings bis = experiment.GetBraggIntegrationSettings();
bis.ReciprocalProfile(true);
experiment.ImportBraggIntegrationSettings(bis);
logger.Info("Reciprocal-space global profile width enabled (per-shell width replaced)");
}
SpotFindingSettings spot_settings;
spot_settings.enable = true;
spot_settings.indexing = true;