partiality: fix the rotation-prediction wedge, use the stored value in scaling

BraggPredictionRot halves settings.wedge_deg to the +/- half-wedge of the
partiality erf pair, but IndexAndRefine already passed GetWedge_deg()/2, so the
two /2 compounded to a half-wedge of increment/4 - half the correct Kabsch value
(increment/2, which ScaleOnTheFly's RotationPartiality already used). Pass the
full increment so prediction's partiality matches scaling.

With prediction correct, ScaleOnTheFly now uses the stored r.partiality directly
(the value the reflection was integrated with) rather than recomputing the erf
pair per reflection - recomputing only when scaling overrides the geometry
(-w wedge refinement, --mosaicity, or a scaling wedge override). Output-neutral
on the /data/rotation_test battery (SG/cell/completeness identical, ISa/CC1/2
within run noise on the stable crystals).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 14:57:16 +02:00
co-authored by Claude Fable 5
parent 8c084021e6
commit cdbd0bed5d
2 changed files with 15 additions and 3 deletions
+4 -1
View File
@@ -292,7 +292,10 @@ void IndexAndRefine::QuickPredictAndIntegrate(DataMessage &msg,
float mos_deg = 0.1f;
if (experiment.GetGoniometer().has_value()) {
wedge_deg = experiment.GetGoniometer()->GetWedge_deg() / 2.0;
// Full oscillation wedge of one frame; BraggPredictionRot halves it to the +/- half-wedge of the
// partiality erf pair (Kabsch). Passing the full increment gives a half-wedge of increment/2 -
// matching ScaleOnTheFly's RotationPartiality, so the predicted partiality is used directly there.
wedge_deg = experiment.GetGoniometer()->GetWedge_deg();
if (msg.mosaicity_deg) {
mos_deg = msg.mosaicity_deg.value();
+11 -2
View File
@@ -269,6 +269,12 @@ void ScaleOnTheFly::Scale(IntegrationOutcome &integration_outcome) const {
result.wedge = NAN;
}
// The partiality stored on each reflection was computed at prediction with this image's mosaicity and
// the (now correct) rotation wedge, so it is used directly. Recompute it only when scaling overrides
// that geometry: wedge refinement, a forced mosaicity, or a scaling wedge override.
const bool recompute_partiality = model == PartialityModel::Rotation
&& (refine_rot_wedge || s.GetForcedMosaicity().has_value() || s.GetRotationWedgeForScaling().has_value());
auto clear_scale = [&]() {
integration_outcome.image_scale_cc.reset();
integration_outcome.image_scale_cc_n.reset();
@@ -298,7 +304,9 @@ void ScaleOnTheFly::Scale(IntegrationOutcome &integration_outcome) const {
partiality = 1.0;
break;
case PartialityModel::Rotation:
partiality = RotationPartiality(r.delta_phi_deg, r.zeta, result.mos, result.wedge);
partiality = recompute_partiality
? RotationPartiality(r.delta_phi_deg, r.zeta, result.mos, result.wedge)
: r.partiality;
break;
default:
partiality = r.partiality;
@@ -399,7 +407,8 @@ void ScaleOnTheFly::Scale(IntegrationOutcome &integration_outcome) const {
r.partiality = 1.0;
break;
case PartialityModel::Rotation: {
if (std::isfinite(r.delta_phi_deg) && std::isfinite(r.zeta) && result.mos > 1e-6)
if (recompute_partiality && std::isfinite(r.delta_phi_deg) && std::isfinite(r.zeta)
&& result.mos > 1e-6)
r.partiality = RotationPartiality(r.delta_phi_deg, r.zeta, result.mos, result.wedge);
break;
}