// SPDX-FileCopyrightText: 2024 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include "../image_analysis/bragg_integration/BraggPrediction.h" #include TEST_CASE("BraggPrediction_11keV") { DiffractionExperiment experiment(DetJF4M()); experiment.DetectorDistance_mm(100.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .IncidentEnergy_keV(11.0); DiffractionGeometry geom = experiment.GetDiffractionGeometry(); CrystalLattice lattice(Coord{20, 10, 0}, Coord{-20, 40, 0}, Coord{0, 0, 200}); BraggPredictionSettings settings{ .high_res_A = 2.0, .ewald_dist_cutoff = 0.001, .max_hkl = 40 }; BraggPrediction prediction; int count = prediction.Calc(experiment, lattice, settings); REQUIRE(count > 0); for (int i = 0; i < count; i++) { auto r = prediction.GetReflections().at(i); auto recip = r.h * lattice.Astar() + r.k * lattice.Bstar() + r.l * lattice.Cstar(); REQUIRE(std::abs(r.h) < settings.max_hkl ); REQUIRE(std::abs(r.k) < settings.max_hkl ); REQUIRE(std::abs(r.l) < settings.max_hkl ); REQUIRE(r.d >= settings.high_res_A); REQUIRE(r.d == Catch::Approx(1/std::sqrt(recip * recip)).margin(0.01f)); REQUIRE(r.dist_ewald == Catch::Approx(std::abs(geom.DistFromEwaldSphere(recip))).epsilon(1e-5)); auto [x,y] = geom.RecipToDector(recip); REQUIRE(r.predicted_x == Catch::Approx(x).margin(0.01)); REQUIRE(r.predicted_y == Catch::Approx(y).margin(0.01)); } } TEST_CASE("BraggPrediction_15keV") { DiffractionExperiment experiment(DetJF4M()); experiment.DetectorDistance_mm(100.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .IncidentEnergy_keV(15.0); DiffractionGeometry geom = experiment.GetDiffractionGeometry(); CrystalLattice lattice(Coord{20, 10, 0}, Coord{-20, 40, 0}, Coord{0, 0, 200}); BraggPredictionSettings settings{ .high_res_A = 2.0, .ewald_dist_cutoff = 0.001, .max_hkl = 40 }; BraggPrediction prediction; int count = prediction.Calc(experiment, lattice, settings); REQUIRE(count > 0); for (int i = 0; i < count; i++) { auto r = prediction.GetReflections().at(i); auto recip = r.h * lattice.Astar() + r.k * lattice.Bstar() + r.l * lattice.Cstar(); REQUIRE(std::abs(r.h) < settings.max_hkl ); REQUIRE(std::abs(r.k) < settings.max_hkl ); REQUIRE(std::abs(r.l) < settings.max_hkl ); REQUIRE(r.d >= settings.high_res_A); REQUIRE(r.d == Catch::Approx(1/std::sqrt(recip * recip)).margin(0.01f)); REQUIRE(r.dist_ewald == Catch::Approx(std::abs(geom.DistFromEwaldSphere(recip))).epsilon(1e-5)); auto [x,y] = geom.RecipToDector(recip); REQUIRE(r.predicted_x == Catch::Approx(x).margin(0.01)); REQUIRE(r.predicted_y == Catch::Approx(y).margin(0.01)); } } TEST_CASE("BraggPrediction_Rot1_Rot2") { DiffractionExperiment experiment(DetJF4M()); experiment.DetectorDistance_mm(100.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .PoniRot1_rad(2.0/180.0 * M_PI).PoniRot2_rad(3.0/180.0 * M_PI) .IncidentEnergy_keV(11.0); DiffractionGeometry geom = experiment.GetDiffractionGeometry(); CrystalLattice lattice(Coord{20, 10, 0}, Coord{-20, 40, 0}, Coord{0, 0, 200}); BraggPredictionSettings settings{ .high_res_A = 2.0, .ewald_dist_cutoff = 0.001, .max_hkl = 40 }; BraggPrediction prediction; int count = prediction.Calc(experiment, lattice, settings); REQUIRE(count > 0); for (int i = 0; i < count; i++) { auto r = prediction.GetReflections().at(i); auto recip = r.h * lattice.Astar() + r.k * lattice.Bstar() + r.l * lattice.Cstar(); REQUIRE(std::abs(r.h) < settings.max_hkl ); REQUIRE(std::abs(r.k) < settings.max_hkl ); REQUIRE(std::abs(r.l) < settings.max_hkl ); REQUIRE(r.d >= settings.high_res_A); REQUIRE(r.d == Catch::Approx(1/std::sqrt(recip * recip)).margin(0.01f)); REQUIRE(r.dist_ewald == Catch::Approx(std::abs(geom.DistFromEwaldSphere(recip))).epsilon(1e-5)); auto [x,y] = geom.RecipToDector(recip); REQUIRE(r.predicted_x == Catch::Approx(x).margin(0.01)); REQUIRE(r.predicted_y == Catch::Approx(y).margin(0.01)); } } TEST_CASE("BraggPrediction_backscattering") { DiffractionExperiment experiment(DetJF9M()); experiment.DetectorDistance_mm(120.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .IncidentEnergy_keV(3.0/WVL_1A_IN_KEV); // Orthogonal basis, sufficient to test parity rules CrystalLattice lattice( Coord{40, 0, 0}, Coord{0, 50, 0}, Coord{0, 0, 60} ); BraggPredictionSettings settings{ .high_res_A = 3.0f, .ewald_dist_cutoff = 0.1f, // Very large cutoff, to be able to see as many reflections as possible .max_hkl = 50 }; BraggPrediction pred; int count = pred.Calc(experiment, lattice, settings); REQUIRE(count > 0); for (const auto& r : pred.GetReflections()) { if (r.d == 0) break; REQUIRE(r.d > 3.0 / sqrt(2.0)); } } TEST_CASE("BraggPrediction_systematic_absences") { DiffractionExperiment experiment(DetJF4M()); experiment.DetectorDistance_mm(120.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .IncidentEnergy_keV(12.0); // Orthogonal basis, sufficient to test parity rules CrystalLattice lattice( Coord{40, 0, 0}, Coord{0, 50, 0}, Coord{0, 0, 60} ); BraggPredictionSettings settings{ .high_res_A = 3.0f, .ewald_dist_cutoff = 0.1f, // Very large cutoff, to be able to see as many reflections as possible .max_hkl = 50 }; BraggPrediction pred; SECTION("I centering") { // 1) Body-centered I: reflections with h+k+l odd must be absent settings.centering = 'I'; int count_I = pred.Calc(experiment, lattice, settings); REQUIRE(count_I > 0); for (const auto& r : pred.GetReflections()) { if (r.d == 0) break; // ignore unfilled tail if any REQUIRE(((r.h + r.k + r.l) % 2) == 0); } } SECTION ("F centering") { // 2) Face-centered F: h,k,l all even or all odd settings.centering = 'F'; int count_F = pred.Calc(experiment, lattice, settings); REQUIRE(count_F > 0); for (const auto& r : pred.GetReflections()) { if (r.d == 0) break; const bool he = (r.h & 1) == 0, ke = (r.k & 1) == 0, le = (r.l & 1) == 0; const bool all_even = he && ke && le; const bool all_odd = (!he) && (!ke) && (!le); REQUIRE((all_even || all_odd)); } } SECTION("R centering") { settings.centering = 'R'; int count_R = pred.Calc(experiment, lattice, settings); REQUIRE(count_R > 0); // R (hexagonal setting): -h + k + l = 3n for (const auto& r : pred.GetReflections()) { if (r.d == 0) break; int cond = (-r.h + r.k + r.l) % 3; if (cond < 0) cond += 3; REQUIRE(cond == 0); } } } #ifdef JFJOCH_USE_CUDA #include "../image_analysis/bragg_integration/BraggPredictionGPU.h" TEST_CASE("BraggPredictionGPU") { DiffractionExperiment experiment(DetJF4M()); experiment.DetectorDistance_mm(100.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .IncidentEnergy_keV(13.0); DiffractionGeometry geom = experiment.GetDiffractionGeometry(); CrystalLattice lattice(Coord{20, 10, 0}, Coord{-20, 40, 0}, Coord{0, 0, 200}); BraggPredictionSettings settings{ .high_res_A = 2.0, .ewald_dist_cutoff = 0.001, .max_hkl = 40 }; BraggPredictionGPU prediction; int count = prediction.Calc(experiment, lattice, settings); REQUIRE(count > 0); for (int i = 0; i < count; i++) { auto r = prediction.GetReflections().at(i); auto recip = r.h * lattice.Astar() + r.k * lattice.Bstar() + r.l * lattice.Cstar(); REQUIRE(std::abs(r.h) < settings.max_hkl ); REQUIRE(std::abs(r.k) < settings.max_hkl ); REQUIRE(std::abs(r.l) < settings.max_hkl ); REQUIRE(r.d >= settings.high_res_A); REQUIRE(r.d == Catch::Approx(1/std::sqrt(recip * recip)).margin(0.01f)); REQUIRE(r.dist_ewald == Catch::Approx(std::abs(geom.DistFromEwaldSphere(recip))).epsilon(2e-2)); auto [x,y] = geom.RecipToDector(recip); REQUIRE(r.predicted_x == Catch::Approx(x).margin(0.05)); REQUIRE(r.predicted_y == Catch::Approx(y).margin(0.05)); } } TEST_CASE("BraggPredictionGPU_Rot1_Rot2") { DiffractionExperiment experiment(DetJF4M()); experiment.DetectorDistance_mm(100.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .PoniRot1_rad(2.0/180.0 * M_PI).PoniRot2_rad(3.0/180.0 * M_PI) .IncidentEnergy_keV(11.0); DiffractionGeometry geom = experiment.GetDiffractionGeometry(); CrystalLattice lattice(Coord{20, 10, 0}, Coord{-20, 40, 0}, Coord{0, 0, 200}); BraggPredictionSettings settings{ .high_res_A = 2.0, .ewald_dist_cutoff = 0.001, .max_hkl = 40 }; BraggPredictionGPU prediction; int count = prediction.Calc(experiment, lattice, settings); REQUIRE(count > 0); for (int i = 0; i < count; i++) { auto r = prediction.GetReflections().at(i); auto recip = r.h * lattice.Astar() + r.k * lattice.Bstar() + r.l * lattice.Cstar(); REQUIRE(std::abs(r.h) < settings.max_hkl ); REQUIRE(std::abs(r.k) < settings.max_hkl ); REQUIRE(std::abs(r.l) < settings.max_hkl ); REQUIRE(r.d >= settings.high_res_A); REQUIRE(r.d == Catch::Approx(1/std::sqrt(recip * recip)).margin(0.01f)); REQUIRE(r.dist_ewald == Catch::Approx(std::abs(geom.DistFromEwaldSphere(recip))).epsilon(1e-3)); auto [x,y] = geom.RecipToDector(recip); REQUIRE(r.predicted_x == Catch::Approx(x).margin(0.05)); REQUIRE(r.predicted_y == Catch::Approx(y).margin(0.05)); } } TEST_CASE("BraggPredictionGPU_systematic_absences") { DiffractionExperiment experiment(DetJF4M()); experiment.DetectorDistance_mm(120.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .IncidentEnergy_keV(12.0); CrystalLattice lattice( Coord{40, 0, 0}, Coord{0, 50, 0}, Coord{0, 0, 60} ); BraggPredictionSettings settings{ .high_res_A = 3.0f, .ewald_dist_cutoff = 0.1f, .max_hkl = 50 }; BraggPredictionGPU pred; SECTION ("I centering") { // 1) Body-centered I settings.centering = 'I'; int count_I = pred.Calc(experiment, lattice, settings); REQUIRE(count_I > 0); for (const auto& r : pred.GetReflections()) { if (r.d == 0) break; REQUIRE(((r.h + r.k + r.l) % 2) == 0); } } SECTION ("F centering") { // 2) Face-centered F settings.centering = 'F'; int count_F = pred.Calc(experiment, lattice, settings); REQUIRE(count_F > 0); for (const auto& r : pred.GetReflections()) { if (r.d == 0) break; const bool he = (r.h & 1) == 0, ke = (r.k & 1) == 0, le = (r.l & 1) == 0; const bool all_even = he && ke && le; const bool all_odd = (!he) && (!ke) && (!le); REQUIRE((all_even || all_odd)); } } SECTION("R centering") { settings.centering = 'R'; int count_R = pred.Calc(experiment, lattice, settings); REQUIRE(count_R > 0); // R (hexagonal setting): -h + k + l = 3n for (const auto& r : pred.GetReflections()) { if (r.d == 0) break; int cond = (-r.h + r.k + r.l) % 3; if (cond < 0) cond += 3; REQUIRE(cond == 0); } } } TEST_CASE("BraggPredictionGPU_backscattering") { DiffractionExperiment experiment(DetJF9M()); experiment.DetectorDistance_mm(120.0).BeamX_pxl(1500.0).BeamY_pxl(1000.0) .IncidentEnergy_keV(3.0/WVL_1A_IN_KEV); // Orthogonal basis, sufficient to test parity rules CrystalLattice lattice( Coord{40, 0, 0}, Coord{0, 50, 0}, Coord{0, 0, 60} ); BraggPredictionSettings settings{ .high_res_A = 3.0f, .ewald_dist_cutoff = 0.1f, // Very large cutoff, to be able to see as many reflections as possible .max_hkl = 50 }; BraggPredictionGPU pred; int count = pred.Calc(experiment, lattice, settings); REQUIRE(count > 0); for (const auto& r : pred.GetReflections()) { if (r.d == 0) break; REQUIRE(r.d > 3.0 / sqrt(2.0)); } } #endif