Build Packages / Unit tests (push) Successful in 1h22m15s
Build Packages / build:windows:nocuda (push) Successful in 18m0s
Build Packages / build:windows:cuda (push) Successful in 20m30s
Build Packages / build:viewer-tgz:cpu (push) Successful in 10m32s
Build Packages / build:viewer-tgz:cuda (push) Successful in 11m39s
Build Packages / build:rugnux-tgz (x86_64) (push) Successful in 8m55s
Build Packages / build:rugnux:windows (push) Successful in 11m25s
Build Packages / build:rpm (rocky8_nocuda) (push) Successful in 20m6s
Build Packages / build:rpm (rocky9_nocuda) (push) Successful in 16m27s
Build Packages / build:rpm (ubuntu2204_nocuda) (push) Successful in 20m19s
Build Packages / build:rpm (ubuntu2404_nocuda) (push) Successful in 15m34s
Build Packages / build:rpm (rocky8_sls9) (push) Successful in 20m25s
Build Packages / build:rpm (rocky9_sls9) (push) Successful in 19m36s
Build Packages / build:rpm (rocky8) (push) Successful in 17m43s
Build Packages / build:rpm (rocky9) (push) Successful in 13m34s
Build Packages / build:rpm (ubuntu2204) (push) Successful in 21m28s
Build Packages / build:rpm (ubuntu2404) (push) Successful in 18m19s
Build Packages / DIALS test (push) Successful in 12m36s
Build Packages / XDS test (durin plugin) (push) Successful in 6m56s
Build Packages / XDS test (JFJoch plugin) (push) Successful in 6m48s
Build Packages / XDS test (neggia plugin) (push) Successful in 6m7s
Build Packages / Generate python client (push) Successful in 11s
Build Packages / Build documentation (push) Successful in 36s
Build Packages / Create release (push) Skipped
Build Packages / build:rugnux:aarch64 (cross) (push) Successful in 5m11s
* `rugnux --mode calibration` writes `<prefix>.json` beside the `.poni`, whose `dataset_settings` member is a `jfjoch_broker` `dataset_settings` body as it stands. * `rugnux` and `jfjoch_viewer` read PILATUS miniCBF sweeps natively, without conversion. * Masters written by other facilities open, including Eiger 1.x and third-party NXmx variants. * `rugnux` measures the beam centre on every run, and indexes with it when the file's value indexes nothing. * A detector swung out on a 2theta arm is placed where the file says it stands, and the calibration can hold the tilt fixed. * `rugnux` writes the unmerged MTZ by default, and a P1 merge beside it, so a wrong space group can be re-merged without reprocessing. * Significant improvements to symmetry handling in `rugnux`: the lattice, the point group, the setting and the systematic absences. * The `rugnux` report gives the resolution the CC1/2 fit reached, beside the range the reflections were written to. * The `rugnux` report gives the twinning statistics measured before the space group was decided, beside the ones measured after. * The `rugnux` report gives the strong-direction diffraction limit, and warns when CC1/2 is not monotone with resolution. * `rugnux` ranks screw axes on the evidence their absences carry, rather than on how many control reflections a candidate happens to have. * Twinning is no longer reported when the L-test contradicts it. * The `rugnux` report gives the detector tilt, the measured tilt and the direct beam beside the beam centre, and a post-refined beam centre is judged against the run's own measurement rather than the file's. * `--no-refine-tilt` holds the detector tilt at the value in the file, instead of zeroing it, when the calibration starts from the spots. * The `jfjoch_viewer` grid scan view draws the cells in the proportion of the scan steps, so the map has the shape of the scanned area. Reviewed-on: #76 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
210 lines
8.1 KiB
C++
210 lines
8.1 KiB
C++
// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include <catch2/catch_all.hpp>
|
|
#include "../common/CrystalLattice.h"
|
|
|
|
TEST_CASE("CrystalLattice") {
|
|
CrystalLattice l(50,60,80, 90, 90, 90);
|
|
REQUIRE(l.Vec0().Length() == Catch::Approx(50));
|
|
REQUIRE(l.Vec1().Length() == Catch::Approx(60));
|
|
REQUIRE(l.Vec2().Length() == Catch::Approx(80));
|
|
REQUIRE(angle_deg(l.Vec0(), l.Vec2()) == 90);
|
|
REQUIRE(angle_deg(l.Vec0(), l.Vec1()) == 90);
|
|
REQUIRE(angle_deg(l.Vec1(), l.Vec2()) == 90);
|
|
|
|
auto uc0 = l.GetUnitCell();
|
|
REQUIRE(uc0.a == Catch::Approx(50));
|
|
REQUIRE(uc0.b == Catch::Approx(60));
|
|
REQUIRE(uc0.c == Catch::Approx(80));
|
|
REQUIRE(uc0.alpha == Catch::Approx(90));
|
|
REQUIRE(uc0.beta == Catch::Approx(90));
|
|
REQUIRE(uc0.gamma == Catch::Approx(90));
|
|
|
|
l = CrystalLattice(30, 40, 70, 90, 95, 90);
|
|
REQUIRE(l.Vec0().Length() == Catch::Approx(30));
|
|
REQUIRE(l.Vec1().Length() == Catch::Approx(40));
|
|
REQUIRE(l.Vec2().Length() == Catch::Approx(70));
|
|
REQUIRE(angle_deg(l.Vec0(), l.Vec2()) == 95);
|
|
REQUIRE(angle_deg(l.Vec0(), l.Vec1()) == 90);
|
|
REQUIRE(angle_deg(l.Vec1(), l.Vec2()) == 90);
|
|
|
|
auto uc1 = l.GetUnitCell();
|
|
REQUIRE(uc1.a == Catch::Approx(30));
|
|
REQUIRE(uc1.b == Catch::Approx(40));
|
|
REQUIRE(uc1.c == Catch::Approx(70));
|
|
REQUIRE(uc1.alpha == Catch::Approx(90));
|
|
REQUIRE(uc1.beta == Catch::Approx(95));
|
|
REQUIRE(uc1.gamma == Catch::Approx(90));
|
|
|
|
l = CrystalLattice(45, 45, 70, 90, 90, 120);
|
|
REQUIRE(l.Vec0().Length() == Catch::Approx(45));
|
|
REQUIRE(l.Vec1().Length() == Catch::Approx(45));
|
|
REQUIRE(l.Vec2().Length() == Catch::Approx(70));
|
|
REQUIRE(angle_deg(l.Vec0(), l.Vec2()) == Catch::Approx(90));
|
|
REQUIRE(angle_deg(l.Vec0(), l.Vec1()) == Catch::Approx(120));
|
|
REQUIRE(angle_deg(l.Vec1(), l.Vec2()) == Catch::Approx(90));
|
|
|
|
auto uc2 = l.GetUnitCell();
|
|
REQUIRE(uc2.a == Catch::Approx(45));
|
|
REQUIRE(uc2.b == Catch::Approx(45));
|
|
REQUIRE(uc2.c == Catch::Approx(70));
|
|
REQUIRE(uc2.alpha == Catch::Approx(90));
|
|
REQUIRE(uc2.beta == Catch::Approx(90));
|
|
REQUIRE(uc2.gamma == Catch::Approx(120));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_Sort") {
|
|
CrystalLattice l(80,60,50, 120, 90, 90);
|
|
l.Sort();
|
|
REQUIRE(l.Vec0().Length() == Catch::Approx(50));
|
|
REQUIRE(l.Vec1().Length() == Catch::Approx(60));
|
|
REQUIRE(l.Vec2().Length() == Catch::Approx(80));
|
|
REQUIRE(angle_deg(l.Vec0(), l.Vec2()) == Catch::Approx(90));
|
|
REQUIRE(angle_deg(l.Vec0(), l.Vec1()) == Catch::Approx(120));
|
|
REQUIRE(angle_deg(l.Vec1(), l.Vec2()) == Catch::Approx(90));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_ReorderMonoclinic") {
|
|
std::vector<CrystalLattice> latt = {
|
|
{60, 85, 70, 90, 70, 90},
|
|
{60, 85, 70, 90, 110, 90},
|
|
};
|
|
for (const auto &l_in :latt) {
|
|
CrystalLattice l = l_in;
|
|
l.ReorderMonoclinic();
|
|
|
|
CHECK(l.Vec0().Length() == Catch::Approx(60));
|
|
CHECK(l.Vec1().Length() == Catch::Approx(85));
|
|
CHECK(l.Vec2().Length() == Catch::Approx(70));
|
|
CHECK(l.GetUnitCell().beta == Catch::Approx(110.0));
|
|
}
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_ReorderTetragonal") {
|
|
CrystalLattice l(40,60,60, 90, 90, 90);
|
|
l.ReorderABEqual();
|
|
REQUIRE(l.Vec0().Length() == Catch::Approx(60));
|
|
REQUIRE(l.Vec1().Length() == Catch::Approx(60));
|
|
REQUIRE(l.Vec2().Length() == Catch::Approx(40));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_ReorderTetragonal_ForHexagonal") {
|
|
CrystalLattice l(40,60,60, 120, 90, 90);
|
|
l.ReorderABEqual();
|
|
REQUIRE(l.Vec0().Length() == Catch::Approx(60));
|
|
REQUIRE(l.Vec1().Length() == Catch::Approx(60));
|
|
REQUIRE(l.Vec2().Length() == Catch::Approx(40));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_Handedness") {
|
|
CrystalLattice l(Coord(1,0,0), Coord(0,1,0), Coord(0,0,-1));
|
|
REQUIRE(l.Vec0().x == Catch::Approx(1));
|
|
REQUIRE(l.Vec1().y == Catch::Approx(1));
|
|
REQUIRE(l.Vec2().z == Catch::Approx(1));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_Volume") {
|
|
CrystalLattice l(50, 60, 80, 90, 90, 90);
|
|
REQUIRE(l.CalcVolume() == 50 * 60 * 80);
|
|
|
|
CrystalLattice l2(50,60,80, 90, 120, 90);
|
|
float sin120 = std::sqrt(3) / 2;
|
|
REQUIRE(l2.CalcVolume() == Catch::Approx(50 * 60 * 80 * sin120));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_VolumeFraction") {
|
|
CrystalLattice ortho(50, 60, 80, 90, 90, 90);
|
|
REQUIRE(ortho.VolumeFraction() == Catch::Approx(1.0));
|
|
|
|
// All three angles at 60 deg is about as oblique as a reduced cell gets, and is still an order
|
|
// of magnitude clear of MIN_BASIS_VOLUME_FRACTION.
|
|
CrystalLattice oblique(50, 50, 50, 60, 60, 60);
|
|
REQUIRE(oblique.VolumeFraction() == Catch::Approx(std::sqrt(0.5)).margin(1e-4));
|
|
REQUIRE(oblique.VolumeFraction() > MIN_BASIS_VOLUME_FRACTION);
|
|
|
|
// Three coplanar rows: ordinary lengths, ordinary angles, no volume. This is what an indexing
|
|
// candidate built from directions that all lie in one dense reciprocal plane looks like, and
|
|
// what nothing downstream can refine - 1/V is not finite.
|
|
CrystalLattice flat(Coord(50, 0, 0), Coord(0, 60, 0), Coord(30, 40, 0));
|
|
REQUIRE(flat.VolumeFraction() < MIN_BASIS_VOLUME_FRACTION);
|
|
|
|
CrystalLattice degenerate(Coord(0, 0, 0), Coord(0, 60, 0), Coord(0, 0, 80));
|
|
REQUIRE(degenerate.VolumeFraction() == 0.0f);
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_Recip") {
|
|
CrystalLattice l(50,60,80, 90, 90, 90);
|
|
REQUIRE(l.Astar().Length() == Catch::Approx(1/50.0));
|
|
REQUIRE(l.Astar().x == Catch::Approx(1/50.0));
|
|
REQUIRE(l.Bstar().Length() == Catch::Approx(1/60.0));
|
|
REQUIRE(l.Bstar().y == Catch::Approx(1/60.0));
|
|
REQUIRE(l.Cstar().Length() == Catch::Approx(1/80.0));
|
|
REQUIRE(l.Cstar().z == Catch::Approx(1/80.0));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_ToPrimitive") {
|
|
// Conventional cubic (take as I-conventional basis): a=b=c=50, orthogonal
|
|
CrystalLattice conv(50, 50, 50, 90, 90, 90);
|
|
const float Vconv = conv.CalcVolume();
|
|
REQUIRE(Vconv == Catch::Approx(50.f * 50.f * 50.f).margin(1e-4f));
|
|
|
|
CrystalLattice prim = conv.ToPrimitive('I');
|
|
|
|
// Volume halves (index 2)
|
|
CHECK(prim.CalcVolume() == Catch::Approx(Vconv * 0.5f).margin(1e-4f));
|
|
|
|
// bcc primitive lengths and angles
|
|
auto uc_p = prim.GetUnitCell();
|
|
const float a0 = 50.f;
|
|
const float expected_len = a0 * std::sqrt(3.f) * 0.5f; // a*sqrt(3)/2
|
|
const float expected_angle = 109.4712206f; // arccos(-1/3) in degrees
|
|
|
|
CHECK(uc_p.a == Catch::Approx(expected_len).margin(1e-3f));
|
|
CHECK(uc_p.b == Catch::Approx(expected_len).margin(1e-3f));
|
|
CHECK(uc_p.c == Catch::Approx(expected_len).margin(1e-3f));
|
|
CHECK(uc_p.alpha == Catch::Approx(expected_angle).margin(1e-3f));
|
|
CHECK(uc_p.beta == Catch::Approx(expected_angle).margin(1e-3f));
|
|
CHECK(uc_p.gamma == Catch::Approx(expected_angle).margin(1e-3f));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_FromPrimitive") {
|
|
// Conventional cubic (take as I-conventional basis): a=b=c=50, orthogonal
|
|
const float a0 = 50.f;
|
|
const float expected_len = a0 * std::sqrt(3.f) * 0.5f; // a*sqrt(3)/2
|
|
const float expected_angle = 109.4712206f; // arccos(-1/3) in degrees
|
|
|
|
CrystalLattice conv(expected_len, expected_len, expected_len,
|
|
expected_angle, expected_angle, expected_angle);
|
|
|
|
auto prim = conv.FromPrimitive('I');
|
|
auto uc_c = prim.GetUnitCell();
|
|
|
|
// Back to conventional cube with original volume
|
|
CHECK(prim.CalcVolume() == Catch::Approx(a0 * a0 * a0).margin(1e-4f));
|
|
CHECK(uc_c.a == Catch::Approx(a0).margin(1e-3f));
|
|
CHECK(uc_c.b == Catch::Approx(a0).margin(1e-3f));
|
|
CHECK(uc_c.c == Catch::Approx(a0).margin(1e-3f));
|
|
CHECK(uc_c.alpha == Catch::Approx(90.f).margin(1e-3f));
|
|
CHECK(uc_c.beta == Catch::Approx(90.f).margin(1e-3f));
|
|
CHECK(uc_c.gamma == Catch::Approx(90.f).margin(1e-3f));
|
|
}
|
|
|
|
TEST_CASE("CrystalLattice_NiggliReduce") {
|
|
Coord a = {3,0,0};
|
|
Coord b = {0,4,0};
|
|
Coord c = {0,0,5};
|
|
|
|
CrystalLattice l(a + 2 * b, b, c - 5 * b);
|
|
|
|
auto red_uc = l.NiggliReduce().GetUnitCell();
|
|
|
|
CHECK(red_uc.a == Catch::Approx(3.0));
|
|
CHECK(red_uc.b == Catch::Approx(4.0));
|
|
CHECK(red_uc.c == Catch::Approx(5.0));
|
|
|
|
CHECK(red_uc.alpha == Catch::Approx(90.0));
|
|
CHECK(red_uc.beta == Catch::Approx(90.0));
|
|
CHECK(red_uc.gamma == Catch::Approx(90.0));
|
|
}
|