// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #pragma once #include #include #include "../../common/Coord.h" #include "../../common/CrystalLattice.h" // The exact, orbit-based counterpart of the per-image spindle severity. A still has to assume the // worst - that the nearest plausible lattice row is a lone 2-fold - but by the time a rotation run // is merged the point group and the indexed orientation are KNOWN, so nothing needs guessing: apply // the group's proper rotations to the sweep's blind double cone in the crystal's actual orientation // and count what no operator maps onto measured territory. A unique reflection is lost exactly when // its whole orbit stays inside the cone (Friedel never helps: the cone is double-sided, so the // inversion maps it onto itself). struct SpindleCuspLoss { // Fraction of all unique reflections to d_min that the mounting made unmeasurable. Weighted by // reciprocal volume: each shell has its own, narrower cone theta(d) = asin(lambda/2d), so a // direction at angle a from the spindle is only blind past q = 2 sin(a) / lambda. double lost_unique_fraction = 0; // The same loss as a fraction of the blind cone's own unique content: 1 = the group repaired // nothing (P1, or a lone diad on or perpendicular to the spindle), 0 = symmetry recovers the // whole cone. This is what severity means independent of how wide the cone happens to be. double cone_fraction = 0; double theta_max_deg = 0; }; std::optional SpindleUnrepairedFraction(const gemmi::SpaceGroup &sg, const CrystalLattice &lattice, const Coord &spindle, double wavelength_A, double d_min_A);