diff --git a/common/BraggIntegrationSettings.cpp b/common/BraggIntegrationSettings.cpp index 95b0cd39..57d4b251 100644 --- a/common/BraggIntegrationSettings.cpp +++ b/common/BraggIntegrationSettings.cpp @@ -77,6 +77,20 @@ std::optional BraggIntegrationSettings::GetFixedProfileRadius_recipA() co return fixed_profile_radius; } +BraggIntegrationSettings & BraggIntegrationSettings::ForcedPredictionMosaicity_deg(std::optional input) { + if (input) { + check_finite("Prediction mosaicity", input.value()); + check_min("Prediction mosaicity [deg]", input.value(), 0.001); + check_max("Prediction mosaicity [deg]", input.value(), 10.0); + } + forced_prediction_mosaicity_deg = input; + return *this; +} + +std::optional BraggIntegrationSettings::GetForcedPredictionMosaicity_deg() const { + return forced_prediction_mosaicity_deg; +} + BraggIntegrationSettings &BraggIntegrationSettings::Integrator(IntegratorMode input) { integrator_mode = input; return *this; diff --git a/common/BraggIntegrationSettings.h b/common/BraggIntegrationSettings.h index 6b768748..1ac24669 100644 --- a/common/BraggIntegrationSettings.h +++ b/common/BraggIntegrationSettings.h @@ -52,6 +52,11 @@ class BraggIntegrationSettings { // past it. std::optional d_min_limit_A; std::optional fixed_profile_radius; + // Diagnostic: the rocking width the PREDICTION window is opened to, in place of the per-image + // sigma_M. Prediction and partiality use one number today, so a sigma_M that moves takes the + // integrated reflection population with it; pinning this holds the population still while the + // partiality keeps using the measured sigma_M, which separates the two effects. + std::optional forced_prediction_mosaicity_deg; float minimum_sigma_in_regards_to_i = 0.02; // The r2..r3 background ring is estimated with ONE of two robust means, never both: a high-side // sigma-clip (bkg_clip_nsigma, the default) or a symmetric trimmed mean (bkg_trim_fraction). Setting @@ -126,6 +131,7 @@ public: BraggIntegrationSettings& StencilKSigma(float input); BraggIntegrationSettings& DMinLimit_A(std::optional input); BraggIntegrationSettings& FixedProfileRadius_recipA(std::optional input); + BraggIntegrationSettings& ForcedPredictionMosaicity_deg(std::optional input); BraggIntegrationSettings& Integrator(IntegratorMode input); BraggIntegrationSettings& BackgroundTrimFraction(float input); BraggIntegrationSettings& BackgroundClipNSigma(float input); @@ -141,6 +147,7 @@ public: [[nodiscard]] float GetR3() const; [[nodiscard]] float GetStencilKSigma() const; [[nodiscard]] std::optional GetFixedProfileRadius_recipA() const; + [[nodiscard]] std::optional GetForcedPredictionMosaicity_deg() const; [[nodiscard]] std::optional GetDMinLimit_A() const; [[nodiscard]] float GetMinimumSigmaInRegardsToI() const; diff --git a/image_analysis/IndexAndRefine.cpp b/image_analysis/IndexAndRefine.cpp index 1e6f3b97..2ec7c4e0 100644 --- a/image_analysis/IndexAndRefine.cpp +++ b/image_analysis/IndexAndRefine.cpp @@ -479,7 +479,12 @@ void IndexAndRefine::QuickPredictAndIntegrate(DataMessage &msg, mosaicity[msg.number] = *mos_measured; } } - const float mos_deg = mos_measured.value_or(0.1f); + // The width the prediction window opens to. It is the measured sigma_M unless a fixed prediction + // mosaicity was asked for: the reported (and therefore integrated-against) sigma_M below is left + // alone either way, so the two uses of the number can be separated. + float mos_deg = mos_measured.value_or(0.1f); + if (const auto forced_pred = experiment.GetBraggIntegrationSettings().GetForcedPredictionMosaicity_deg()) + mos_deg = *forced_pred; IntegrationOutcome i_outcome{ .geom = outcome.experiment.GetDiffractionGeometry(), diff --git a/rugnux/rugnux_cli.cpp b/rugnux/rugnux_cli.cpp index 3e046191..ac2b012a 100644 --- a/rugnux/rugnux_cli.cpp +++ b/rugnux/rugnux_cli.cpp @@ -149,6 +149,7 @@ void print_usage() { std::cout << " --capture-uncertainty rot3d: systematic sigma ~num*(1-captured_fraction)*I on under-captured fulls (default: 1.0 for rot3d, 0 otherwise)" << std::endl; std::cout << " --min-captured-fraction rot3d: drop a combined full whose rocking curve was captured below this fraction (edge-of-sweep truncated fulls) (default: 0.7 for rotation, 0 otherwise; 0 = off)" << std::endl; std::cout << " --mosaicity Diagnostic: fix the scaling mosaicity (deg) instead of the per-image seed" << std::endl; + std::cout << " --prediction-mosaicity Diagnostic: fix the rocking width the PREDICTION window opens to (deg), leaving the partiality on the per-image sigma_M. Prediction and partiality are one number by default, so a sigma_M that moves takes the integrated reflection population with it" << std::endl; std::cout << " --reject-outliers Per-observation merge outlier rejection, N sigma from the per-reflection median (default: 6 for rot3d, XDS/DIALS-style; 0 = off)" << std::endl; std::cout << " --search-min-zeta De-novo space-group search only: also search a merge of just the observations whose Lorentz geometry |zeta| reaches this, and keep whichever search found MORE symmetry (default: 0.85 for rotation, 0 = single search). Reflections crossing the Ewald sphere near-tangentially are measured worst and can make a real symmetry operator look like a twin law" << std::endl; std::cout << " --min-image-cc Per-image CC limit in percent (default: no limit)" << std::endl; @@ -234,6 +235,7 @@ enum { OPT_CAPTURE_UNCERTAINTY, OPT_MIN_CAPTURED_FRACTION, OPT_MOSAICITY, + OPT_PREDICTION_MOSAICITY, OPT_SMOOTH_G, OPT_RELATIVE_B, OPT_NO_SCALING_CORRECTIONS, @@ -327,6 +329,7 @@ static option long_options[] = { {"capture-uncertainty", required_argument, nullptr, OPT_CAPTURE_UNCERTAINTY}, {"min-captured-fraction", required_argument, nullptr, OPT_MIN_CAPTURED_FRACTION}, {"mosaicity", required_argument, nullptr, OPT_MOSAICITY}, + {"prediction-mosaicity", required_argument, nullptr, OPT_PREDICTION_MOSAICITY}, {"min-image-cc", required_argument, nullptr, OPT_MIN_IMAGE_CC}, {"search-min-zeta", required_argument, nullptr, OPT_SEARCH_MIN_ZETA}, {"scaling-iterations", required_argument, nullptr, OPT_SCALING_ITERATIONS}, @@ -614,6 +617,7 @@ static int RunRugnux(int argc, char **argv) { std::optional min_captured_fraction_arg; // explicit --min-captured-fraction; default depends on rotation std::optional capture_uncertainty_arg; // explicit --capture-uncertainty; default depends on rot3d std::optional forced_mosaicity_arg; // diagnostic: fix the scaling mosaicity (deg) instead of the per-image seed + std::optional forced_prediction_mosaicity_arg; // diagnostic: fix the PREDICTION mosaicity (deg), leaving the partiality on the per-image value double min_image_cc = 0.0; std::optional search_min_zeta_arg; // --search-min-zeta; rotation default below int64_t scaling_iter = 3; @@ -989,6 +993,9 @@ static int RunRugnux(int argc, char **argv) { case OPT_MOSAICITY: forced_mosaicity_arg = parse_double_arg(optarg, "--mosaicity", logger); break; + case OPT_PREDICTION_MOSAICITY: + forced_prediction_mosaicity_arg = parse_double_arg(optarg, "--prediction-mosaicity", logger); + break; case OPT_INTEGRATION_RADIUS: integration_radius_arg = optarg; break; @@ -1895,6 +1902,14 @@ static int RunRugnux(int argc, char **argv) { logger.Info("Stills integration radii default to r1=6.0 r2=8.0 r3=12.0 (override with --integration-radius)"); } + if (forced_prediction_mosaicity_arg) { + BraggIntegrationSettings bis = experiment.GetBraggIntegrationSettings(); + bis.ForcedPredictionMosaicity_deg(static_cast(*forced_prediction_mosaicity_arg)); + experiment.ImportBraggIntegrationSettings(bis); + logger.Info("Prediction window fixed at a rocking width of {:.4f} deg; the partiality still uses " + "the per-image sigma_M", *forced_prediction_mosaicity_arg); + } + if (integration_stencil_arg) { const float k = parse_number_arg(integration_stencil_arg->c_str(), "--integration-stencil", logger, 0.0f, 10.0f);