The per-image reference/model choice of indexing ignored the crystals' refined cells. On a pseudo-merohedral cell, where at least 90% of the crystals' cells fit the run's cell better as indexed than after a twin law, the cells decide that law - the rule the data-only resolution already uses (AnchorOnCells, factored out of ResolveReindexReferenceFree and shared). The resolver now keeps each image's CC per indexing (IntegrationOutcome::reference_cc); before the stills merge, AnchorReferenceReindex puts every image back as indexed, applies the cell rule, and lets the reference choose only among the indexings left open. Exactly merohedral lattices (no metric signal) are unaffected. Still battery (REFMAC 0-cycle R-free, model before -> anchored; data-only in brackets): pal90 0.1883 -> 0.1834 (0.1832), alvra213 0.2073 -> 0.2011 (0.2013), alvra213_int16 0.2081 -> 0.2021 (0.2024); euxfel83 0.2037, euxfel152 0.2452, sacla237, lcls222 unchanged; lcls242_t 0.2847 -> 0.2952 (0.2942): there the model had moved about half the crystals against cells that decide all three laws (96/96/100%). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu (cherry picked from commit 85c804676ffd15ffd66e2ba05928000d6f4e765f)
78 lines
5.1 KiB
C++
78 lines
5.1 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <vector>
|
|
|
|
#include "gemmi/symmetry.hpp"
|
|
|
|
#include "../../common/DiffractionExperiment.h"
|
|
#include "../IntegrationOutcome.h"
|
|
|
|
// Serial stills with no reference: every crystal is indexed in one of the indexings its lattice allows
|
|
// (the identity and the twin-law operators of ReindexAmbiguityOperators), at random. Which one each
|
|
// crystal is in is decided from the data alone, by putting each crystal into the indexing in which it
|
|
// agrees best with all the others. The overall choice among the equivalent consistent answers is the
|
|
// one that moves the fewest crystals.
|
|
//
|
|
// An operator is only an ambiguity where the crystal's own lattice cannot tell the indexings apart. Where
|
|
// the cell is merely close to a more symmetric one (two axes a few per cent apart, say), the crystals'
|
|
// refined cells already say which axis is which, and the indexer has used that. For such an operator a
|
|
// crystal whose cell prefers the indexing it came in stays there, as an anchor, and only the others are
|
|
// decided by the intensities.
|
|
// An operator whose reindexed cell is the worse fit to the run's cell for at least this share of the
|
|
// crystals is one the cells decide. For a true (merohedral) ambiguity the two fits are equal and the
|
|
// share is a half.
|
|
constexpr double METRIC_DECIDED = 0.9;
|
|
|
|
// Which indexings each image's own refined cell leaves open. Indexing g is the image as indexed for g = 0
|
|
// and after ops[g - 1] otherwise; `cell` is the run's cell. An operator the cells decide is closed to
|
|
// every image whose cell fits `cell` better as indexed; everything else is open.
|
|
struct MetricAnchors {
|
|
std::vector<double> metric_preference; // per operator: share of the images whose refined cell is
|
|
// closer to `cell` as indexed than after the operator
|
|
std::vector<uint8_t> allowed; // allowed[o * (ops.size() + 1) + g]: indexing g open to image o
|
|
};
|
|
MetricAnchors AnchorOnCells(const std::vector<IntegrationOutcome> &outcomes, const UnitCell &cell,
|
|
const std::vector<gemmi::Op> &ops);
|
|
|
|
// With a reference - a reference MTZ or intensities from the model - each image has been put, image by
|
|
// image, into the indexing that correlates best with it (ReindexAmbiguityResolver, which leaves the scores in
|
|
// IntegrationOutcome::reference_cc, over the same `ops`). The reference knows nothing of the cells, so
|
|
// here the cells have their say first, as above: an image its own cell anchors keeps that indexing,
|
|
// and the reference decides among the indexings left open to the others.
|
|
struct ReferenceAnchorResult {
|
|
std::vector<double> metric_preference; // as in MetricAnchors
|
|
size_t overruled = 0; // images the reference had put into an indexing their cell
|
|
// excludes, now in the best open one instead
|
|
};
|
|
ReferenceAnchorResult AnchorReferenceReindex(std::vector<IntegrationOutcome> &outcomes, const UnitCell &cell,
|
|
const std::vector<gemmi::Op> &ops);
|
|
|
|
struct ReferenceFreeReindexResult {
|
|
size_t crystals = 0; // crystals that took part (enough reflections to say anything, and an
|
|
// indexing their own cell leaves open)
|
|
size_t reindexed = 0; // of them, put into an indexing other than the one they came in
|
|
double median_sigma = NAN; // per crystal, how far its chosen indexing beats the best other one
|
|
// in agreement with the rest of the merge, in sigma; the median
|
|
double decided_fraction = NAN; // share of the crystals for which that margin is over 3 sigma
|
|
double cc_chosen = NAN; // median CC of a crystal with the merge of all the others, as chosen
|
|
double cc_other = NAN; // ... and in the best other indexing
|
|
std::vector<double> metric_preference; // per operator: share of crystals whose refined cell is
|
|
// closer to `cell` as indexed than after the operator
|
|
int sweeps = 0; // passes over the crystals until (almost) none changed (the start kept)
|
|
std::vector<int> choice; // per outcome: -1 did not take part, 0 kept, k reindexed by ops[k - 1]
|
|
};
|
|
|
|
// `cell` is the run's cell, `ops` its twin-law operators (identity excluded). With apply = false nothing is changed - the
|
|
// result then says how many crystals the data alone would put into another indexing than they are in,
|
|
// which, after a reference has resolved them, is the disagreement with the reference.
|
|
ReferenceFreeReindexResult ResolveReindexReferenceFree(std::vector<IntegrationOutcome> &outcomes,
|
|
const DiffractionExperiment &x,
|
|
const UnitCell &cell,
|
|
const std::vector<gemmi::Op> &ops,
|
|
size_t nthreads, bool apply);
|