Files
Jungfraujoch/image_analysis/scale_merge/CrystalSetting.h
T
leonarski_f 84228bf8be
Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
v1.0.0-rc.173 (#83)
* jfjoch_broker: Optional per-dataset authentication - statistics, images and plots can require a bearer token, which jfjoch_viewer supports.
* jfjoch_viewer: Dark mode and a theme-matched colour scheme, a magnifier panel, and simpler contrast and background controls.
* Rugnux: Multiple performance improvements on GPU and CPU (CPU-only processing up to 40% faster, faster image decoding on ARM), with unchanged results.
* Rugnux: `--model` rigid-body refinement runs on the GPU, and the model-validation check is faster and more reliable.
* Rugnux: Improved scaling and merging - error model, outlier rejection, absorption correction and French-Wilson amplitudes now agree more closely with XDS and ctruncate.
* Rugnux: Improved integration - radial background on powder and ice rings, crowded rotation data keep their reflections, and CPU-only builds integrate large unit cells as GPU builds do.
* Rugnux: More robust detector geometry - measured beam centre, X-ray bandwidth and goniometer rate, and geometry refinement accepted only on significant evidence.
* Rugnux: Merged files are written in the standard setting, or in the setting of a reference MTZ, structure-factor mmCIF or model, with its free-R flags.
* Rugnux: Richer report - ice and powder rings, further lattices, superstructure candidates and mosaicity, with warnings worded as prompts to check.
* Rugnux: Clear error messages when a data set needs more GPU or host memory than is available.

Reviewed-on: #83
Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
2026-09-29 15:57:32 +02:00

101 lines
6.2 KiB
C++

// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
// SPDX-License-Identifier: GPL-3.0-only
#pragma once
#include <array>
#include <optional>
#include <string>
#include <vector>
#include "gemmi/symmetry.hpp"
#include "gemmi/unitcell.hpp"
#include "../../common/UnitCell.h"
// Settings of one lattice: which axes a cell and a space group are written on. Every change of basis
// here is stated the way GEMMI states it - `cob` acts on fractional coordinates (x' = cob x) - so the
// cell follows with CellInBasis, the group with SpaceGroupInBasis and a Miller index with
// HklOperator(cob). Only integer, determinant +1 changes of basis are used: they keep the volume and
// the hand, so a reflection file relabelled by one describes exactly the same measurements.
// The operator a Miller index moves by (for apply_to_hkl / ReindexMergedIntoAsu).
gemmi::Op HklOperator(const gemmi::Op &cob);
// An index operator written the way CCP4 REINDEX and POINTLESS write one - "k,l,h" is h'=k, k'=l,
// l'=h - rather than as GEMMI prints the transposed matrix apply_to_hkl uses.
std::string IndexTriplet(const gemmi::Op &hkl_op);
// The same change as a basis matrix P - new axis i = sum_j P[i][j] old axis j, and h' = P h - which is
// what CrystalLattice::Multiply and the _process.h5 reindex matrix take.
gemmi::Mat33 BasisMatrix(const gemmi::Op &cob);
UnitCell CellInBasis(const UnitCell &cell, const gemmi::Op &cob);
// The group `sg` becomes in the new basis, as a setting in GEMMI's table (an origin shift is searched
// for, since a permutation can move the origin of a screw-axis group); nullptr when none names it.
const gemmi::SpaceGroup *SpaceGroupInBasis(const gemmi::SpaceGroup &sg, const gemmi::Op &cob);
// How far a cell stands from the metric its space group requires. A group's own rotations must leave
// the metric tensor G invariant - R^T G R = G - so a cell that fails that cannot be described by that
// group whatever its centring is: -S P41212 on a=37.909 b=78.031 c=77.594 has the right centring and
// the wrong axis, the 4-fold running along a where the group puts it along c, and the group's
// operators do not act on the reflections' own indices. Returned relative to the largest diagonal
// element of G, so it is dimensionless and comparable across cells.
double MetricViolation(const UnitCell &uc, const gemmi::SpaceGroup &sg);
// The metric never fits exactly - the cell is refined against the data, not constrained to the group -
// so the test needs a tolerance, and it is used to REFUSE, so the tolerance has to sit above what a
// correct answer reaches rather than below what a wrong one does. Measured over 113 corpus runs, every
// group determined from its own cell scores under 0.032 (the worst is a C222 call on a cell whose
// alpha is 87.75); the known axis-permuted case scores 0.764. 0.1 leaves 3x headroom over the first
// and 7.6x under the second.
// SearchSpaceGroup's CellHostsRotations asks the same question at 2e-3, normalised per element pair:
// that is the right bound for ADMITTING an extra candidate setting, and it would refuse 6 of those
// 113 runs if it were used here.
constexpr double MAX_METRIC_VIOLATION = 0.1;
// Two cells that describe the same lattice on the same axes, within refinement noise (5% in each
// length, 3 deg in each angle) - two crystals of one form measured apart.
bool CellsCorrespond(const gemmi::UnitCell &a, const gemmi::UnitCell &b);
// Every integer change of basis with entries in {-1,0,1} and determinant +1, identity first. That set
// holds every axis permutation and sign flip and the off-diagonal that turns an I-centred monoclinic
// cell into a C-centred one; a halved or doubled axis is not in it, and must not be - that is an
// indexing error, not a choice of description.
const std::vector<gemmi::Op> &UnimodularOperators();
// The subset of UnimodularOperators that carries `from` onto `to` (CellsCorrespond).
std::vector<gemmi::Op> CellMappingOperators(const gemmi::UnitCell &from, const gemmi::UnitCell &to);
// The change of basis that writes `sg` on `cell` in the setting the files should carry. Every
// candidate is a relabelling of the same lattice, so nothing measured depends on the choice.
// - `target_cell` (the cell given with -C): the candidate whose cell is closest to it, in any setting
// of the group - or of `target`, where that is given too.
// - `target` (a group given with -S, in whatever setting it was given): that setting.
// - neither: the ITA reference setting, as XDS and POINTLESS write it. Orthorhombic axes the group
// does not tie are ordered shortest first (a<=b<=c; a<b with c fixed for P2221, P21212, C2221 and
// C222); monoclinic is b-unique, C-centred, least oblique with beta >= 90; triclinic keeps the
// reduced cell the run indexed.
// Among equal candidates the one closest to the identity wins, so a run already in the chosen setting
// is left exactly as it is. Identity where no candidate qualifies.
gemmi::Op ChooseOutputSetting(const UnitCell &cell, const gemmi::SpaceGroup &sg,
const gemmi::SpaceGroup *target = nullptr,
const std::optional<UnitCell> &target_cell = std::nullopt);
// One reflection's evidence for being present: its I/sigma, with the partials of a rotation sweep summed.
struct ReflectionZ {
std::array<int, 3> hkl{};
double z = 0.0;
};
// A space group given by the user names its symmetry elements by axis - P 21 21 2 puts the pure
// two-fold on c - while the indexed cell orders its axes by length, and for a group whose axes are not
// all alike the metric cannot say which axis is which. The absences can. Every change of basis that
// keeps a cell the group can describe and the centring the lattice has is a candidate, and the one
// whose predicted absences agree best with the measured ones wins (see the .cpp), the identity where
// they tie. Returns the change of basis to put the reflections through so that `sg`, exactly as given,
// describes them. `reflections` need only hold those on axial rows and zones - no other can be absent.
gemmi::Op SeatGroupByAbsences(const UnitCell &cell, const gemmi::SpaceGroup &sg,
const std::vector<ReflectionZ> &reflections);