Rigid body: no JFJOCH_RIGID_BODY_CPU; GPU/CPU parity tests tightened
The GPU rigid body is chosen by whether a card is there, nothing else. A run that must stay on the CPU says so at the process level (CUDA_VISIBLE_DEVICES=), which RigidBodyGPUPool::Create already honours through get_gpu_count(). Parity tests ([ModelValidation][gpu], compiled into jfjoch_test on a CUDA build): - every GPU case now SKIPs without a card, instead of passing silently; - ModelMaskGPU_MatchesGemmi also checks that the CPU path's PutMaskOnGrid is gemmi's mask bit for bit on the same grid, so the GPU mask is held against the CPU path itself; - RigidBodyGPU_MatchesCPU: Jacobian columns 2e-3 -> 5e-4 relative (measured up to 5e-5; the floor is the 1e-4 the scale's near-tie can move every column by). Residuals stay at 2e-4 of <Fobs> (measured up to 3.9e-5); - RigidBodyGPU_FitAgreesWithCPU: fit endpoint 2e-3 -> 1e-5 A rmsd (measured up to 5e-7 A). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D1G8gJVAy6gp1K5Dz3NE5C
This commit is contained in:
1 parent
b1f79ed27f
commit
23812b902c
4 files changed
+35
-33
No files matched your search
@@ -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<double>(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 <Fobs>) 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<std::vector<gemmi::Position>> placed;
|
||||
|
||||
Reference in new issue
Block a user