The automatic cutoff fits the CC1/2 fall-off, takes the crossing of the target (0.30 by default) and then deliberately keeps one more shell. Only the extended limit was reported, as INCLUDE_RESOLUTION_RANGE, and that is the number a reader takes for "the resolution of this dataset" - so the report quoted a limit that is generous on purpose as if it were the measurement. The generosity itself is right and stays: a shell that is included can still be downweighted or dropped by refinement, while one that was truncated cannot be put back. What was missing is the other number. ComputeCCHalfLogisticCutoff now also returns the crossing, ApplyResolutionCutoff passes it out, and the report prints it as FITTED_RESOLUTION beside the range the reflections were written to, with a sentence saying which is which. The log line names both as well. Nothing about the cut, the merge or the written reflections changes. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EFEJG6WBQv8th4UJFNe53N
52 lines
3.3 KiB
C++
52 lines
3.3 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "../../common/Logger.h"
|
|
#include "../../common/Reflection.h" // MergedReflection
|
|
#include "../../common/ScalingSettings.h" // ResolutionCutoffMethod
|
|
|
|
// Automatic high-resolution cutoff from the CC1/2 fall-off of the merged half-sets (DIALS-style).
|
|
// The merge itself, the error model and the per-image _process.h5 are left untouched - only the
|
|
// written reflections and the reported shell table should be trimmed to the returned d_cut.
|
|
//
|
|
// Method (see docs/rugnux_resolution_cutoff_design.md): bin CC1/2 against s = 1/d^2 in fine bins,
|
|
// fit a logistic CC1/2(s) = 1/(1+exp(k*(s-s0))) to the contiguous-from-low-res fall-off, take the s
|
|
// where the fit crosses cc_target, then extend by one mean (10-shell) shell width in s ("one shell
|
|
// too far", generous). d_cut is nullopt when the fit is degenerate (too few bins, flat/non-monotone)
|
|
// or CC1/2 never falls below cc_target inside the measured range - the caller then keeps the full
|
|
// range. cc_target in (0,1); merged must carry finite I_half[0]/I_half[1] to contribute.
|
|
struct ResolutionCutoffResult {
|
|
std::optional<double> d_cut; // high-resolution limit (A); nullopt => keep the full range
|
|
// Where the fitted CC1/2 crosses cc_target, BEFORE the deliberate one-shell extension - i.e. the
|
|
// resolution the data are judged to reach, as opposed to the (coarser in s, finer in d) limit the
|
|
// reflections are actually written to. This is the number to quote. Set whenever the fit produced
|
|
// a crossing inside the measured range, even when no cut was applied.
|
|
std::optional<double> d_fit;
|
|
std::string note; // human-readable description of the decision (for logging)
|
|
};
|
|
|
|
ResolutionCutoffResult ComputeCCHalfLogisticCutoff(const std::vector<MergedReflection> &merged,
|
|
double cc_target, Logger &logger);
|
|
|
|
// Resolve the effective high-resolution limit and trim `merged` to it, in one place shared by the
|
|
// stills merge, the rotation merge and the offline --scale path. A manual limit (manual_limit) wins;
|
|
// otherwise, unless this is a P1 space-group search merge (for_search), the CCHalfLogistic method
|
|
// takes the auto CC1/2 cutoff. Reflections beyond the resolved limit are erased from `merged`.
|
|
// Returns the applied limit (nullopt => the full range was kept). The decision is logged as before.
|
|
// fit_limit_out, when given, receives ResolutionCutoffResult::d_fit - the CC1/2 crossing without the
|
|
// one-shell extension, for reporting. Left untouched when no automatic fit ran (a manual limit, a
|
|
// search merge, or the method turned off), so a caller can tell "not fitted" from "fitted".
|
|
std::optional<double> ApplyResolutionCutoff(std::vector<MergedReflection> &merged,
|
|
std::optional<double> manual_limit,
|
|
ResolutionCutoffMethod method,
|
|
double cc_target,
|
|
bool for_search,
|
|
Logger &logger,
|
|
std::optional<double> *fit_limit_out = nullptr);
|