1.0.0-rc.174 (#84)
Build Packages / Create release (push) Successful in 17s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m22s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m37s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m33s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 10m39s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 11m4s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 13m19s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 17m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 18m49s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 19m10s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m26s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m31s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 18m54s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m45s
Build Packages / Generate python client (push) Successful in 37s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 20m20s
Build Packages / Build documentation (push) Successful in 1m32s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m37s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m6s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 19m49s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 20m29s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 17m2s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 14m27s
Build Packages / Unit tests (push) Successful in 1h18m12s
Build Packages / Create release (push) Successful in 17s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m22s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m37s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 9m33s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 10m39s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 11m4s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 13m19s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 17m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 18m49s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 19m10s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m26s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m31s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 18m54s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m45s
Build Packages / Generate python client (push) Successful in 37s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 20m20s
Build Packages / Build documentation (push) Successful in 1m32s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m37s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m6s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 19m49s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 20m29s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 17m2s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 14m27s
Build Packages / Unit tests (push) Successful in 1h18m12s
* Rugnux: Performance improvements on GPU and CPU (more of the pre-scan and of scaling on the GPU, faster CPU spot finding and crystal refinement), with unchanged results. * Rugnux: More robust processing - patches of persistently hot pixels are masked, an inconsistent merge triggers a retry at the measured beam centre, and builds targeting different CPU levels give the same results. * Rugnux: Improved scaling and merging - reflections with an overloaded pixel are dropped, as in XDS, sparse rotation sweeps are scaled more reliably, and French-Wilson amplitudes use an anisotropic Wilson prior. * Rugnux: Improved space-group determination - glide planes in groups without a centre of symmetry, screw axes from short or weak axial rows kept when a higher group is adopted, and more reliable decisions on twinned and pseudo-symmetric crystals. * Rugnux: Improved small-molecule processing - spots that grow wider than the integration disk and split spots are integrated over their measured footprint, sparse lattices are integrated on every frame, and the `.hkl` file holds unmerged scaled reflections (SHELX HKLF 4). * Rugnux: Reads Rigaku d*TREK SMV images (Saturn CCD), including detector 2theta and encoded pixel overflows; home-source (rotating-anode) datasets were added to the validation battery. * jfjoch_viewer: Fixed processing failing at the end with "Wrong JPEG library version" on Linux; the merge window shows the space group with proper subscripts and a checklist of crystal pathologies. Reviewed-on: #84 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
This commit was merged in pull request #84.
This commit is contained in:
1 parent
84228bf8be
commit
a395f358ef
292 files changed
+11586
-2438
No files matched your search
@@ -1,11 +1,13 @@
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include "../image_analysis/scale_merge/SearchSpaceGroup.h"
|
||||
#include "../image_analysis/scale_merge/CentreOfSymmetry.h"
|
||||
#include "gemmi/symmetry.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <random>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <unordered_set>
|
||||
@@ -279,6 +281,31 @@ TEST_CASE("SearchSpaceGroup weighs a screw's absences by evidence, not by how ma
|
||||
CHECK(result.best_space_group->short_name() == "P21");
|
||||
}
|
||||
|
||||
// A short monoclinic axis on weak data: four 0k0-odd reflections in range, each at 2% of its row
|
||||
// rather than at the floor. That zone reads ~11 nats - refused under the old bound of 20 on five
|
||||
// crystals that are P2_1 - and must be claimed; the same four at a fifth of their row must not.
|
||||
SECTION("four absences at a few percent of a short row: the screw is found, at a fifth it is not") {
|
||||
for (const auto& [fraction, expected] : {std::pair{0.02, "P21"}, std::pair{0.2, "P2"}}) {
|
||||
auto merged = GenerateMergedReflectionsForSpaceGroup(sg, 18);
|
||||
int kept = 0;
|
||||
std::erase_if(merged, [&](MergedReflection& r) {
|
||||
if (!gops.is_systematically_absent(gemmi::Op::Miller{{r.h, r.k, r.l}}))
|
||||
return false;
|
||||
if (kept >= 4)
|
||||
return true;
|
||||
++kept;
|
||||
r.I = static_cast<float>(fraction * 350.0);
|
||||
return false;
|
||||
});
|
||||
REQUIRE(kept == 4);
|
||||
|
||||
const auto result = SearchSpaceGroup(merged, opt);
|
||||
INFO(SearchSpaceGroupResultToText(result));
|
||||
REQUIRE(result.best_space_group.has_value());
|
||||
CHECK(result.best_space_group->short_name() == expected);
|
||||
}
|
||||
}
|
||||
|
||||
SECTION("a uniformly weak axial row decides nothing, however many absences it holds") {
|
||||
// The whole 0k0 row badly measured: the predicted-absent reflections are weak, but so is the
|
||||
// rest of their row, so there is no contrast and no screw to claim. A violation count cannot
|
||||
@@ -444,6 +471,17 @@ TEST_CASE("SearchSpaceGroup names an orthorhombic screw pair on the axes it lies
|
||||
REQUIRE(result.best_space_group.has_value());
|
||||
CHECK(result.best_space_group->xhm() == "P 2 21 21");
|
||||
}
|
||||
|
||||
// A cell refined free after integration is a few tenths of a degree off 90. The reference
|
||||
// candidates hold the same rotations and are offered on it, so the other settings must be too.
|
||||
SECTION("a free cell a few tenths off 90 still offers it") {
|
||||
opt.cell = gemmi::UnitCell(40.0, 50.0, 60.0, 90.32, 90.19, 90.19);
|
||||
opt.enumerate_all_settings = true;
|
||||
const auto result = SearchSpaceGroup(merged, opt);
|
||||
INFO(SearchSpaceGroupResultToText(result));
|
||||
REQUIRE(result.best_space_group.has_value());
|
||||
CHECK(result.best_space_group->xhm() == "P 2 21 21");
|
||||
}
|
||||
}
|
||||
|
||||
// A screw on a row the sweep never recorded is not a group the data refused, it is a question
|
||||
@@ -779,3 +817,127 @@ TEST_CASE("SearchSpaceGroup never claims an inversion centre") {
|
||||
CHECK(result.best_space_group->is_sohncke());
|
||||
CHECK_FALSE(result.glide_space_group.has_value());
|
||||
}
|
||||
|
||||
// A NON-centrosymmetric glide group holds only half the rotations of its Laue class, so it has to be
|
||||
// enumerated by Laue class to be a candidate at all. Where its absences have no centrosymmetric
|
||||
// carrier (I-42d, Fdd2, P-42_1c, I-43d) it is the only group naming the glide; I-42d and I4_1md
|
||||
// predict the same absences, so the one not written is reported beside the one that is.
|
||||
TEST_CASE("SearchSpaceGroup names a glide with no centrosymmetric carrier") {
|
||||
struct Case {
|
||||
std::string input_name;
|
||||
std::vector<std::string> accepted; // the written group and its same-absence twins
|
||||
gemmi::UnitCell cell;
|
||||
};
|
||||
const std::vector<Case> cases = {
|
||||
{"I -4 2 d", {"I 41 m d", "I -4 2 d"}, gemmi::UnitCell(7.4, 7.4, 6.9, 90, 90, 90)},
|
||||
{"P -4 21 c", {"P -4 21 c"}, gemmi::UnitCell(7.4, 7.4, 6.9, 90, 90, 90)},
|
||||
{"F d d 2", {"F d d 2"}, gemmi::UnitCell(11.0, 13.0, 17.0, 90, 90, 90)},
|
||||
{"I -4 3 d", {"I -4 3 d"}, gemmi::UnitCell(9.0, 9.0, 9.0, 90, 90, 90)},
|
||||
};
|
||||
for (const auto& tc : cases) {
|
||||
DYNAMIC_SECTION(tc.input_name) {
|
||||
const gemmi::SpaceGroup& sg = gemmi::get_spacegroup_by_name(tc.input_name);
|
||||
const auto merged = GenerateMergedReflectionsForSpaceGroup(sg, 12);
|
||||
|
||||
SearchSpaceGroupOptions opt;
|
||||
opt.merge_friedel = true;
|
||||
opt.cell = tc.cell;
|
||||
opt.enumerate_all_settings = true;
|
||||
|
||||
const auto result = SearchSpaceGroup(merged, opt);
|
||||
INFO(SearchSpaceGroupResultToText(result));
|
||||
REQUIRE(result.best_space_group.has_value());
|
||||
std::vector<std::string> named{result.best_space_group->xhm()};
|
||||
for (const auto& alt : result.alternatives)
|
||||
named.push_back(alt.xhm());
|
||||
CHECK_THAT(named, Catch::Matchers::UnorderedEquals(tc.accepted));
|
||||
CHECK(result.glide_space_group.has_value());
|
||||
REQUIRE(result.sohncke_space_group.has_value());
|
||||
CHECK(result.sohncke_space_group->is_sohncke());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Pc and P2/c (Pna2_1 and Pnma) predict the same absences and differ by an inversion centre, which
|
||||
// Friedel's law hides. By convention the CENTROSYMMETRIC one is written, whichever the data came
|
||||
// from, and the other is reported as an alternative - never dropped, so the run does not claim a
|
||||
// centre nothing measured.
|
||||
TEST_CASE("SearchSpaceGroup writes the centrosymmetric group and reports its non-centrosymmetric twin") {
|
||||
struct Case {
|
||||
std::string input_name;
|
||||
std::string written;
|
||||
std::string alternative;
|
||||
gemmi::UnitCell cell;
|
||||
};
|
||||
const gemmi::UnitCell monoclinic(11.0, 13.0, 17.0, 90.0, 101.0, 90.0);
|
||||
const gemmi::UnitCell orthorhombic(11.0, 13.0, 17.0, 90.0, 90.0, 90.0);
|
||||
const std::vector<Case> cases = {
|
||||
{"P 1 c 1", "P 1 2/c 1", "P 1 c 1", monoclinic},
|
||||
{"P 1 2/c 1", "P 1 2/c 1", "P 1 c 1", monoclinic},
|
||||
{"C 1 c 1", "C 1 2/c 1", "C 1 c 1", monoclinic},
|
||||
{"P n a 21", "P n a m", "P n a 21", orthorhombic},
|
||||
};
|
||||
for (const auto& tc : cases) {
|
||||
DYNAMIC_SECTION(tc.input_name) {
|
||||
const gemmi::SpaceGroup& sg = gemmi::get_spacegroup_by_name(tc.input_name);
|
||||
const auto merged = GenerateMergedReflectionsForSpaceGroup(sg, 10);
|
||||
|
||||
SearchSpaceGroupOptions opt;
|
||||
opt.merge_friedel = true;
|
||||
opt.cell = tc.cell;
|
||||
opt.enumerate_all_settings = true;
|
||||
|
||||
const auto result = SearchSpaceGroup(merged, opt);
|
||||
INFO(SearchSpaceGroupResultToText(result));
|
||||
REQUIRE(result.best_space_group.has_value());
|
||||
CHECK(result.best_space_group->xhm() == tc.written);
|
||||
CHECK(std::any_of(result.alternatives.begin(), result.alternatives.end(),
|
||||
[&](const gemmi::SpaceGroup& a) { return a.xhm() == tc.alternative; }));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The centre-of-symmetry statistics on synthetic Wilson data: acentric and centric intensities on the
|
||||
// general reflections of a 2/m Laue class, with the reflections a 2-fold maps to their Friedel mate
|
||||
// (h0l) centric in both. The calibrated readings must land at their ends, and only the acentric set
|
||||
// may read ACENTRIC.
|
||||
TEST_CASE("AnalyzeCentreOfSymmetry separates acentric from centric Wilson data") {
|
||||
const gemmi::UnitCell cell(11.0, 13.0, 17.0, 90.0, 101.0, 90.0);
|
||||
const gemmi::SpaceGroup& sg = gemmi::get_spacegroup_by_name("P 1 2/c 1");
|
||||
for (const bool centric : {false, true}) {
|
||||
DYNAMIC_SECTION((centric ? "centric" : "acentric")) {
|
||||
std::mt19937 rng(7);
|
||||
std::exponential_distribution<double> expo(1.0);
|
||||
std::normal_distribution<double> gauss(0.0, 1.0);
|
||||
std::vector<MergedReflection> merged;
|
||||
const gemmi::GroupOps gops = sg.operations();
|
||||
const gemmi::ReciprocalAsu asu(&sg);
|
||||
for (int h = -10; h <= 10; ++h)
|
||||
for (int k = 0; k <= 10; ++k)
|
||||
for (int l = -12; l <= 12; ++l) {
|
||||
const gemmi::Op::Miller hkl{{h, k, l}};
|
||||
if ((h == 0 && k == 0 && l == 0) || !asu.is_in(hkl) || gops.is_systematically_absent(hkl))
|
||||
continue;
|
||||
const double d = cell.calculate_d(hkl);
|
||||
if (d < 0.8)
|
||||
continue;
|
||||
// h0l is centric under the 2-fold whatever the crystal is.
|
||||
const bool c = centric || k == 0;
|
||||
const double z = gauss(rng);
|
||||
const double I = 1000.0 * std::exp(-2.0 / (d * d)) * (c ? z * z : expo(rng));
|
||||
merged.push_back(MergedReflection{.h = h, .k = k, .l = l,
|
||||
.I = static_cast<float>(I), .sigma = 1.0f,
|
||||
.d = static_cast<float>(d)});
|
||||
}
|
||||
const auto r = AnalyzeCentreOfSymmetry(merged, sg);
|
||||
INFO("n " << r.n_general << " pairs " << r.l_pairs << " L f " << r.mean_abs_l.f << " E f "
|
||||
<< r.mean_abs_e2_minus_1.f << " N f " << r.n_z_01.f << " control f "
|
||||
<< r.control_mean_abs_e2_minus_1.f);
|
||||
REQUIRE(r.n_general > 1000);
|
||||
CHECK(r.applicable);
|
||||
CHECK(r.mean_abs_l.f == Catch::Approx(centric ? 1.0 : 0.0).margin(0.2));
|
||||
CHECK(r.mean_abs_e2_minus_1.f == Catch::Approx(centric ? 1.0 : 0.0).margin(0.2));
|
||||
CHECK(r.reads_acentric == !centric);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user