diff --git a/rugnux/ModelValidation.cpp b/rugnux/ModelValidation.cpp index ca67c89dd..a44bda1c1 100644 --- a/rugnux/ModelValidation.cpp +++ b/rugnux/ModelValidation.cpp @@ -38,7 +38,6 @@ #include "RigidBodyRefine.h" #include "SigmaA.h" #ifdef JFJOCH_USE_CUDA -#include #include "RigidBodyGPU.h" #include "../common/CUDAWrapper.h" #endif @@ -555,7 +554,7 @@ ModelValidationResult Validate(const std::vector &merged, RigidBodyGPUPool *rigid_body_pool = nullptr; #ifdef JFJOCH_USE_CUDA std::unique_ptr rigid_body_engines; - if (rigid_body_gpu && std::getenv("JFJOCH_RIGID_BODY_CPU") == nullptr) { + if (rigid_body_gpu) { const double finest_zone = RigidBodyLadder(d_min).back(); size_t zone_observations = 0; for (const MergedReflection &r : obs) diff --git a/tests/ModelMaskGPUTest.cpp b/tests/ModelMaskGPUTest.cpp index e5717c555..d3a313b42 100644 --- a/tests/ModelMaskGPUTest.cpp +++ b/tests/ModelMaskGPUTest.cpp @@ -16,6 +16,7 @@ #include #include +#include "../rugnux/ModelGrid.h" #include "../rugnux/ModelMaskGPU.h" namespace { @@ -127,13 +128,13 @@ double DistanceToMaskEdge(const gemmi::Grid &grid, const std::vector cpu = MaskGrid(st, d_min); + PutMaskOnGrid(cpu, st.models[0], OrbitLeaders(cpu, 4), 4); + REQUIRE(std::memcmp(cpu.data.data(), ref.data.data(), n * sizeof(float)) == 0); any_islands |= islands > 0; any_odd_size |= ref.nu % 8 != 0 || ref.nv % 8 != 0 || ref.nw % 8 != 0; @@ -198,9 +204,8 @@ TEST_CASE("ModelMaskGPU_MatchesGemmi", "[ModelValidation][gpu]") { // The shrink is not implemented: a grid fine enough for it to change anything is refused. TEST_CASE("ModelMaskGPU_RefusesAGridTheShrinkWouldChange", "[ModelValidation][gpu]") { - if (get_gpu_count() == 0) { + if (get_gpu_count() == 0) SKIP("No GPU"); - } CudaStream stream; const gemmi::Structure st = ClusterModel(kCrysts[0]); const std::vector ops = MaskOps(*st.find_spacegroup()); diff --git a/tests/ModelScaleGPUTest.cpp b/tests/ModelScaleGPUTest.cpp index 0b0bd5f33..a42bccb4b 100644 --- a/tests/ModelScaleGPUTest.cpp +++ b/tests/ModelScaleGPUTest.cpp @@ -230,7 +230,7 @@ const Case CASES[] = { TEST_CASE("ModelScaleGPU_FitMatchesGemmi", "[ModelValidation][gpu]") { if (get_gpu_count() == 0) - return; + SKIP("No GPU"); for (const Case &c : CASES) { INFO(c.name); gemmi::UnitCell cell(c.a, c.b, c.c, c.alpha, c.beta, c.gamma); @@ -261,7 +261,7 @@ TEST_CASE("ModelScaleGPU_FitMatchesGemmi", "[ModelValidation][gpu]") { TEST_CASE("ModelScaleGPU_SolventGridMatchesFitModelScale", "[ModelValidation][gpu]") { if (get_gpu_count() == 0) - return; + SKIP("No GPU"); for (const Case &c : CASES) { INFO(c.name); gemmi::UnitCell cell(c.a, c.b, c.c, c.alpha, c.beta, c.gamma); @@ -283,7 +283,7 @@ TEST_CASE("ModelScaleGPU_SolventGridMatchesFitModelScale", "[ModelValidation][gp // per-evaluation fit at that solvent pair. TEST_CASE("ModelScaleGPU_MatchesGemmiOnAModelsPoints", "[ModelValidation][gpu]") { if (get_gpu_count() == 0) - return; + SKIP("No GPU"); const char *crysts[] = { "CRYST1 40.000 50.000 60.000 90.00 90.00 90.00 P 1 1\n", "CRYST1 40.000 50.000 60.000 90.00 100.00 90.00 C 1 2 1 4\n", @@ -319,7 +319,7 @@ TEST_CASE("ModelScaleGPU_MatchesGemmiOnAModelsPoints", "[ModelValidation][gpu]") TEST_CASE("ModelScaleGPU_Deterministic", "[ModelValidation][gpu]") { if (get_gpu_count() == 0) - return; + SKIP("No GPU"); gemmi::UnitCell cell(72, 44, 51, 90, 112, 90); gemmi::Scaling cpu = MakeScaling(cell, *gemmi::find_spacegroup_by_name("C 1 2 1"), 2.5); GpuPoints gpu(cpu); diff --git a/tests/ModelValidationTest.cpp b/tests/ModelValidationTest.cpp index ed1267fa5..656a85552 100644 --- a/tests/ModelValidationTest.cpp +++ b/tests/ModelValidationTest.cpp @@ -1239,10 +1239,8 @@ TEST_CASE("ModelValidation_RigidBodyReportsTheRotationItApplied", "[ModelValidat // give the same residuals and the same Jacobian to rounding (float distances on the device, cuFFT for // FFTW), on every group of the composition tests, both zones, anisotropic atoms included. TEST_CASE("RigidBodyGPU_MatchesCPU", "[ModelValidation][gpu]") { - if (get_gpu_count() == 0) { - SUCCEED("no GPU"); - return; - } + if (get_gpu_count() == 0) + SKIP("No GPU"); Logger logger("RigidBodyGPU_MatchesCPU"); const double q0[6] = {0.12, -0.09, 0.07, 0.15, -0.10, 0.05}; for (const char *cryst : kRigidBodyCrysts) { @@ -1288,7 +1286,11 @@ TEST_CASE("RigidBodyGPU_MatchesCPU", "[ModelValidation][gpu]") { } INFO(cryst << " at " << zone << " A, column " << j << ": relative difference " << std::sqrt(diff / norm)); - CHECK(std::sqrt(diff) <= 2e-3 * std::sqrt(norm) + 1e-9); + // Measured at up to 5e-5 (the rotation columns, a forward difference of float-gridded + // density; the translation columns agree to about 1e-6). Every column is proportional to + // the overall scale, which the near-tie above can move by 1e-4, so that is the floor of + // an honest bound; 5e-4 leaves room for the difference's own rounding on another card. + CHECK(std::sqrt(diff) <= 5e-4 * std::sqrt(norm) + 1e-9); } } } @@ -1297,10 +1299,8 @@ TEST_CASE("RigidBodyGPU_MatchesCPU", "[ModelValidation][gpu]") { // The whole Jacobian on the GPU against the central difference of the GPU's own residuals, with the // bounds of ModelValidation_RigidBodyJacobianMatchesNumericScaleRefit. TEST_CASE("RigidBodyGPU_JacobianMatchesNumericScaleRefit", "[ModelValidation][gpu]") { - if (get_gpu_count() == 0) { - SUCCEED("no GPU"); - return; - } + if (get_gpu_count() == 0) + SKIP("No GPU"); const double close[6] = {0.0, 0.0, 0.0, 0.05, -0.04, 0.03}; const double rotated[6] = {0.12, -0.09, 0.07, 0.15, -0.10, 0.05}; for (const char *cryst : kRigidBodyCrysts) { @@ -1330,13 +1330,10 @@ namespace { } } -// The GPU fit walks where the CPU fit walks: from the same displaced start, to the same placement -// within a few thousandths of an angstrom. +// The GPU fit walks where the CPU fit walks: from the same displaced start, to the same placement. TEST_CASE("RigidBodyGPU_FitAgreesWithCPU", "[ModelValidation][gpu]") { - if (get_gpu_count() == 0) { - SUCCEED("no GPU"); - return; - } + if (get_gpu_count() == 0) + SKIP("No GPU"); Logger logger("RigidBodyGPU_FitAgreesWithCPU"); for (const char *cryst : {kCryst, kPolarCryst, kRigidBodyCrysts[3]}) { gemmi::Structure cpu_st = AnisoCluster(cryst), gpu_st = AnisoCluster(cryst); @@ -1352,17 +1349,18 @@ TEST_CASE("RigidBodyGPU_FitAgreesWithCPU", "[ModelValidation][gpu]") { rmsd += pc[i].dist_sq(pg[i]) / static_cast(pc.size()); INFO(cryst << ": CPU " << cpu.angle_deg << " deg " << cpu.shift_A << " A, GPU " << gpu.angle_deg << " deg " << gpu.shift_A << " A, " << std::sqrt(rmsd) << " A apart"); - CHECK(std::sqrt(rmsd) < 2e-3); + // Measured at up to 5e-7 A: the endpoint moves only by what the residuals differ by (a few + // 1e-6 of ) over the target's curvature. The bound leaves a factor 20 for another card's + // cuFFT and float rounding, and is still four orders under anything a placement is judged at. + CHECK(std::sqrt(rmsd) < 1e-5); } } // Deterministic: the same fit twice, and on a pool of one engine and of four, gives the same placement // bit for bit. TEST_CASE("RigidBodyGPU_Deterministic", "[ModelValidation][gpu]") { - if (get_gpu_count() == 0) { - SUCCEED("no GPU"); - return; - } + if (get_gpu_count() == 0) + SKIP("No GPU"); Logger logger("RigidBodyGPU_Deterministic"); const char *cryst = kRigidBodyCrysts[4]; std::vector> placed;