Known-cell (FFBIDX) indexing was given the conventional cell of a centred lattice. Its own candidate scoring and the indexed-spot count of Refine() then worked on the primitive lattice of that cell, which is 2x (A, B, C, I), 3x (R) or 4x (F) denser than the crystal's, so a wrong orientation that fits only those extra nodes indexed as many spots as the true one: - R in the hexagonal setting: the obverse orientation and the reverse one (60 deg about c) share every node of the primitive hexagonal lattice. About half of the frames came back reverse; only the l = 3n layers agreed between them, and the merge showed a false translational pseudo-symmetry. - C/I/F: false orientations that break the centring were accepted. The choice is made inside the indexer's top-candidate scoring, so a centring condition on the count in Refine() alone does not always settle it: on synthetic R3 stills the obverse orientation was sometimes not among the candidates at all. FFBIDX (GPU and CPU) is now given the Niggli-reduced primitive cell of the given lattice (Indexer::IndexingCell, with the centring of the given space group), and every lattice it finds is put back into the given conventional cell by the integer matrix between the two (Indexer::ToConventional), so everything downstream sees the conventional basis as before. Every node of the primitive cell is a possible reflection, so no centring count is needed in Refine(). The reduction matters: the plain primitive cell of an elongated C cell has an angle far outside the window Refine() accepts, and such a set then indexed nothing. The seed-escalation fraction in IndexAndRefine counts only spots the centring allows on the FFBIDX path. A primitive space group, a cell without a space group, and the de-novo (FFT) indexers are unchanged. All known-cell stills flows reach this through IndexAndRefine and the indexer thread pool: the broker's FPGA and SIMPLON receivers, live rugnux stills and offline rugnux. Rotation sweeps of R3/R32 crystals processed as stills with the known cell: reverse frames 47-50% before, 0% after; indexed spots obeying -h+k+l = 3n 0.67-0.68 before, >= 0.997 after; merged <I>(l=3n)/<I>(l!=3n) 1.2-11.9 before, 0.95-1.01 after. Still battery: primitive control unchanged; on an I222 set fewer frames index (67% -> 58%) because the extra ones were on a wrong lattice (indexed spots obeying the centring 0.54 -> 0.99), with R_split 0.088 -> 0.078, CC1/2 0.962 -> 0.981, R-free 0.162 -> 0.156; I23 indexes 3% more frames; C2221 unchanged. Tests: synthetic R3 obverse vs reverse and C-centred false orientation in Refine() on primitive candidates; FFBIDX CPU end-to-end on R, C (including an elongated one), I and F cells; IndexAndRefine on predicted R3 stills with FFBIDX. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqXYdJKPUFLoeAchjzv3x4
60 lines
2.1 KiB
C++
60 lines
2.1 KiB
C++
// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "FFBIDXIndexer.h"
|
|
#include "PostIndexingRefinement.h"
|
|
|
|
// The ffbidx library implements the TORO algorithm:
|
|
// Gasparotto et al. (2024) J. Appl. Cryst. 57, 931-944
|
|
|
|
void FFBIDXIndexer::SetupUnitCell(const std::optional<UnitCell> &cell) {
|
|
if (!cell.has_value())
|
|
throw JFJochException(JFJochExceptionCategory::InputParameterInvalid,
|
|
"FFBIDX requires unit cell");
|
|
// Given the primitive cell of a centred lattice (IndexingCell); the lattices found are put back
|
|
// into the conventional cell in RunInternal.
|
|
const CrystalLattice l1 = IndexingCell(cell.value());
|
|
reference_unit_cell = l1.GetUnitCell();
|
|
|
|
Eigen::Matrix3f m;
|
|
indexer.iCellM() << l1.Vec0().x, l1.Vec0().y, l1.Vec0().z,
|
|
l1.Vec1().x, l1.Vec1().y, l1.Vec1().z,
|
|
l1.Vec2().x, l1.Vec2().y, l1.Vec2().z;
|
|
}
|
|
|
|
std::vector<CrystalLattice> FFBIDXIndexer::RunInternal(const std::vector<Coord> &coord, size_t nspots) {
|
|
std::vector<CrystalLattice> ret;
|
|
|
|
if (nspots > coord.size())
|
|
nspots = coord.size();
|
|
|
|
if (nspots < viable_cell_min_spots)
|
|
return ret;
|
|
|
|
assert(nspots <= MAX_SPOT_COUNT);
|
|
assert(coord.size() <= MAX_SPOT_COUNT);
|
|
|
|
for (int i = 0; i < coord.size(); i++) {
|
|
indexer.spotX(i) = coord[i].x;
|
|
indexer.spotY(i) = coord[i].y;
|
|
indexer.spotZ(i) = coord[i].z;
|
|
}
|
|
|
|
// Index
|
|
indexer.index(1, nspots);
|
|
|
|
RefineParameters parameters{
|
|
.viable_cell_min_spots = viable_cell_min_spots,
|
|
.dist_tolerance_vs_reference = dist_tolerance_vs_reference,
|
|
.reference_unit_cell = reference_unit_cell,
|
|
.min_length_A = 1, // doesn't matter
|
|
.max_length_A = 1000, // doesn't matter
|
|
.min_angle_deg = 30,
|
|
.max_angle_deg = 150,
|
|
.indexing_tolerance = indexing_tolerance
|
|
};
|
|
|
|
auto lattices = Refine(coord, nspots, indexer.oCellM(), indexer.oScoreV(), parameters);
|
|
return ToConventional(lattices);
|
|
}
|