// SPDX-FileCopyrightText: 2025 Filip Leonarski, Paul Scherrer Institute // SPDX-License-Identifier: GPL-3.0-only #include #include #include "../image_analysis/geom_refinement/XtalOptimizer.h" #include "../image_analysis/bragg_integration/BraggPrediction.h" TEST_CASE("XtalOptimizer") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(40,40,80,90,90,90); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001}; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(40.2,39.4,80.2, 90,91, 89); xtal_opt.geom.BeamX_pxl(1010).BeamY_pxl(995).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Triclinic; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_i = latt_i.GetUnitCell(); auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.05); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.05); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.2); CHECK(fabsf(uc_i.alpha - uc_o.alpha) < 0.1); CHECK(fabsf(uc_i.beta - uc_o.beta) < 0.1); CHECK(fabsf(uc_i.gamma - uc_o.gamma) < 0.1); } TEST_CASE("XtalOptimizer_NoBeamCenter") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(40,50,80,90,95,90); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001}; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(40.2,49.4,80.2, 90,94, 89); xtal_opt.geom.BeamX_pxl(999.8).BeamY_pxl(1000.2).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Monoclinic; xtal_opt.refine_beam_center = false; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_i = latt_i.GetUnitCell(); auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - 999.8) < 0.01); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - 1000.2) < 0.01); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.2); CHECK(fabsf(uc_i.alpha - uc_o.alpha) < 0.1); CHECK(fabsf(uc_i.beta - uc_o.beta) < 0.1); CHECK(fabsf(uc_i.gamma - uc_o.gamma) < 0.1); } TEST_CASE("XtalOptimizer_orthorombic") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(40,50,80,90,90,90); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001}; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(40.2,49.6,80.3, 90,91, 89); xtal_opt.geom.BeamX_pxl(1005).BeamY_pxl(997).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Orthorhombic; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_i = latt_i.GetUnitCell(); auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.1); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.1); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.2); CHECK(fabs(uc_o.alpha - 90) < 0.02); CHECK(fabs(uc_o.beta - 90) < 0.02); CHECK(fabs(uc_o.gamma - 90) < 0.02); } TEST_CASE("XtalOptimizer_triclinic") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(40,55,120,95,97,100); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001, }; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(40.1,54.9,121, 95,97, 99.5); xtal_opt.geom.BeamX_pxl(997).BeamY_pxl(1005).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Triclinic; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_i = latt_i.GetUnitCell(); auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.1); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.1); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.5); CHECK(fabsf(uc_i.alpha - uc_o.alpha) < 0.05); CHECK(fabsf(uc_i.beta - uc_o.beta) < 0.05); CHECK(fabsf(uc_i.gamma - uc_o.gamma) < 0.05); } TEST_CASE("XtalOptimizer_tetragonal") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(40,40,80,90,90,90); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001}; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(40.6,39.3,80.5, 90,91, 89); xtal_opt.geom.BeamX_pxl(1010).BeamY_pxl(995).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Tetragonal; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_i = latt_i.GetUnitCell(); auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.1); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.1); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.5); CHECK(fabs(uc_o.alpha - 90) < 0.02); CHECK(fabs(uc_o.beta - 90) < 0.02); CHECK(fabs(uc_o.gamma - 90) < 0.02); } TEST_CASE("XtalOptimizer_hexagonal") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(40,40,70,90,90,120); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001}; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(39.5,39.8,70.1, 90,90, 119.5); xtal_opt.geom.BeamX_pxl(1007).BeamY_pxl(990).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Hexagonal; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_i = latt_i.GetUnitCell(); auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << " " << uc_o.alpha << " " << uc_o.beta << " " << uc_o.gamma << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.1); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.1); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.2); CHECK(fabs(uc_o.alpha - 90) < 0.02); CHECK(fabs(uc_o.beta - 90) < 0.01); CHECK(fabs(uc_o.gamma - 120) < 0.01); } TEST_CASE("XtalOptimizer_hexagonal_unconstrained") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(40,40,70,90,90,120); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001}; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(39.9,39.8,70.1, 90,90, 120); xtal_opt.geom.BeamX_pxl(1002).BeamY_pxl(998).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Triclinic; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_i = latt_i.GetUnitCell(); auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << " " << uc_o.alpha << " " << uc_o.beta << " " << uc_o.gamma << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.1); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.1); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.2); CHECK(fabs(uc_o.alpha - 90) < 0.02); CHECK(fabs(uc_o.beta - 90) < 0.01); CHECK(fabs(uc_o.gamma - 120) < 0.01); } TEST_CASE("XtalOptimizer_cubic") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(Coord(40,0,0), Coord(0, 40 / sqrt(2), -40 / sqrt(2)), Coord(0, 40 / sqrt(2), 40 / sqrt(2))); auto uc_i = latt_i.GetUnitCell(); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001}; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(Coord(39,0,0), Coord(0, 39.5 / sqrt(2), -40.5 / sqrt(2)), Coord(0, 39.2 / sqrt(2), 39.7 / sqrt(2))); xtal_opt.geom.BeamX_pxl(1007).BeamY_pxl(990).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Cubic; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.1); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.1); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.2); CHECK(fabs(uc_o.alpha - 90) < 0.02); CHECK(fabs(uc_o.beta - 90) < 0.02); CHECK(fabs(uc_o.gamma - 90) < 0.02); } TEST_CASE("XtalOptimizer_monoclinic") { DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); CrystalLattice latt_i(50,60,70,90,96,90); auto uc_i = latt_i.GetUnitCell(); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.001 }; BraggPrediction prediction; auto count = prediction.Calc(exp_i, latt_i, prediction_settings); std::vector spots; for (int i = 0; i < count; ++i) { auto refl = prediction.GetReflections().at(i); spots.push_back(SpotToSave{refl.predicted_x, refl.predicted_y}); } XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(49.5, 60.5, 69.8, 90, 95.5, 90); xtal_opt.geom.BeamX_pxl(1007).BeamY_pxl(990).DetectorDistance_mm(200) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Monoclinic; auto start = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto end = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer took " << std::chrono::duration_cast(end - start).count() << " microseconds" << std::endl; auto uc_o = xtal_opt.latt.GetUnitCell(); std::cout << "Beam center: " << xtal_opt.geom.GetBeamX_pxl() << " " << xtal_opt.geom.GetBeamY_pxl() << std::endl; std::cout << "Unit cell: " << uc_o.a << " " << uc_o.b << " " << uc_o.c << " " << uc_o.alpha << " " << uc_o.beta << " " << uc_o.gamma << std::endl; CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.1); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.1); CHECK(fabsf(uc_i.a - uc_o.a) < 0.1); CHECK(fabsf(uc_i.b - uc_o.b) < 0.1); CHECK(fabsf(uc_i.c - uc_o.c) < 0.2); CHECK(fabs(uc_o.alpha - 90) < 0.02); CHECK(fabs(uc_o.beta - uc_i.beta) < 0.02); CHECK(fabs(uc_o.gamma - 90) < 0.02); } TEST_CASE("LatticeToRodrigues") { double rod[3]; double lengths[3]; CrystalLattice latt_i(40,50,80,90,90,90); LatticeToRodriguesAndLengths_GS(latt_i, rod, lengths); CHECK(lengths[0] == Catch::Approx(40.0)); CHECK(lengths[1] == Catch::Approx(50.0)); CHECK(lengths[2] == Catch::Approx(80.0)); CHECK(fabs(rod[0]) < 0.001); CHECK(fabs(rod[1]) < 0.001); CHECK(fabs(rod[2]) < 0.001); auto latt_o = AngleAxisAndLengthsToLattice(rod, lengths, false); CHECK(latt_o.Vec0().Length() == Catch::Approx(40.0)); CHECK(latt_o.Vec1().Length() == Catch::Approx(50.0)); CHECK(latt_o.Vec2().Length() == Catch::Approx(80.0)); } TEST_CASE("LatticeToRodrigues_irregular") { double rod[3]; double lengths[3]; CrystalLattice latt_i(Coord(40,0,0), Coord(0, 50 / sqrt(2), -50 / sqrt(2)), Coord(0, 80 / sqrt(2), 80 / sqrt(2))); LatticeToRodriguesAndLengths_GS(latt_i, rod, lengths); CHECK(lengths[0] == Catch::Approx(40.0)); CHECK(lengths[1] == Catch::Approx(50.0)); CHECK(lengths[2] == Catch::Approx(80.0)); auto latt_o = AngleAxisAndLengthsToLattice(rod, lengths, false); CHECK(latt_o.Vec0().Length() == Catch::Approx(40.0)); CHECK(latt_o.Vec1().Length() == Catch::Approx(50.0)); CHECK(latt_o.Vec2().Length() == Catch::Approx(80.0)); } TEST_CASE("LatticeToRodrigues_Hex") { double rod[3]; double lengths[3]; Coord a = Coord(40,0,0); Coord b = Coord(40 / 2, 40 * sqrt(3)/ 2.0, 0); Coord c = Coord(0, 0, 70); RotMatrix R(1.0, Coord(0,1,1)); CrystalLattice latt_i(R*a,R*b,R*c); LatticeToRodriguesAndLengths_Hex(latt_i, rod, lengths); CHECK(lengths[0] == Catch::Approx(40.0)); CHECK(lengths[1] == Catch::Approx(40.0)); CHECK(lengths[2] == Catch::Approx(70.0)); auto latt_o = AngleAxisAndLengthsToLattice(rod, lengths, true); auto uc_o = latt_o.GetUnitCell(); CHECK(uc_o.a == Catch::Approx(40.0)); CHECK(uc_o.b == Catch::Approx(40.0)); CHECK(uc_o.c == Catch::Approx(70.0)); CHECK(uc_o.alpha == Catch::Approx(90.0)); CHECK(uc_o.beta == Catch::Approx(90.0)); CHECK(uc_o.gamma == Catch::Approx(120.0)); } TEST_CASE("XtalOptimizer_rotation") { // Geometry DiffractionExperiment exp_i; exp_i.IncidentEnergy_keV(WVL_1A_IN_KEV) .BeamX_pxl(1000) .BeamY_pxl(1000) .PoniRot1_rad(0.01) .PoniRot2_rad(0.02) .DetectorDistance_mm(200); // Base lattice (non-pathological) CrystalLattice latt_base(40, 50, 80, 90, 95, 90); auto uc_ref = latt_base.GetUnitCell(); // Rotation axis: around X with 3 deg per image GoniometerAxis axis("omega", 0.0f, 3.0f, Coord(1,0,0), std::nullopt); BraggPredictionSettings prediction_settings{ .high_res_A = 1.5, .ewald_dist_cutoff = 0.002 }; std::vector spots; BraggPrediction prediction; // Predict reflections for images at 0-30 deg. for (int img = 0; img < 10; ++img) { // For a rotated image, per-image lattice is obtained as Multiply(rot.transpose()) const RotMatrix rot = axis.GetTransformation(img); const CrystalLattice latt_img = latt_base.Multiply(rot.transpose()); const auto n = prediction.Calc(exp_i, latt_img, prediction_settings); for (int i = 0; i < n; ++i) { const auto& r = prediction.GetReflections().at(i); SpotToSave s{}; s.x = r.predicted_x; s.y = r.predicted_y; s.image = img; // provide image index for rotation-aware refinement s.intensity = 1.0f; // minimal positive value s.ice_ring = false; s.indexed = true; spots.push_back(s); } } // Seed slightly perturbed geometry and lattice; provide rotation axis for refinement XtalOptimizerData xtal_opt; xtal_opt.latt = CrystalLattice(39.7f, 50.6f, 79.6f, 90.0f, 94.5f, 90.5f); xtal_opt.geom.BeamX_pxl(1003).BeamY_pxl(997).DetectorDistance_mm(200.0) .PoniRot1_rad(0.01).PoniRot2_rad(0.02); xtal_opt.crystal_system = gemmi::CrystalSystem::Monoclinic; xtal_opt.axis = axis; xtal_opt.min_spots = 200; xtal_opt.refine_beam_center = true; xtal_opt.refine_distance_mm = false; xtal_opt.refine_detector_angles = false; auto t0 = std::chrono::high_resolution_clock::now(); REQUIRE(XtalOptimizer(xtal_opt, spots)); auto t1 = std::chrono::high_resolution_clock::now(); std::cout << "XtalOptimizer (rotation 4 images) took " << std::chrono::duration_cast(t1 - t0).count() << " microseconds" << std::endl; const auto uc_out = xtal_opt.latt.GetUnitCell(); // Geometry checks CHECK(fabsf(xtal_opt.geom.GetBeamX_pxl() - exp_i.GetBeamX_pxl()) < 0.2f); CHECK(fabsf(xtal_opt.geom.GetBeamY_pxl() - exp_i.GetBeamY_pxl()) < 0.2f); // Lattice checks CHECK(fabsf(uc_ref.a - uc_out.a) < 0.2f); CHECK(fabsf(uc_ref.b - uc_out.b) < 0.2f); CHECK(fabsf(uc_ref.c - uc_out.c) < 0.3f); CHECK(fabsf(uc_ref.alpha - uc_out.alpha) < 0.2f); CHECK(fabsf(uc_ref.beta - uc_out.beta) < 0.2f); CHECK(fabsf(uc_ref.gamma - uc_out.gamma) < 0.2f); }