v1.0.0-rc.173 (#83)
Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
Build Packages / Create release (push) Successful in 24s
Build Packages / build:viewer:macos-arm64:nocuda (push) Successful in 3m29s
Build Packages / build:rugnux:macos-arm64:nocuda (push) Successful in 2m43s
Build Packages / build:rugnux:linux-aarch64:cuda (push) Successful in 8m27s
Build Packages / build:rugnux:linux-x86_64:cuda (push) Successful in 9m53s
Build Packages / build:viewer:linux-x86_64:nocuda (push) Successful in 9m58s
Build Packages / build:viewer:linux-x86_64:cuda (push) Successful in 11m22s
Build Packages / build:jfjoch:rocky8:nocuda (push) Successful in 13m39s
Build Packages / build:viewer:windows-x86_64:nocuda (push) Successful in 18m37s
Build Packages / build:jfjoch:rocky9:nocuda (push) Successful in 16m32s
Build Packages / build:viewer:windows-x86_64:cuda (push) Successful in 24m11s
Build Packages / HDF5 consumer tests (DIALS, XDS) (push) Successful in 25m30s
Build Packages / build:jfjoch:ubuntu2404:nocuda (push) Successful in 19m3s
Build Packages / build:jfjoch:ubuntu2204:nocuda (push) Successful in 20m23s
Build Packages / build:jfjoch:rocky8:cuda-sls9 (push) Successful in 19m41s
Build Packages / Generate python client (push) Successful in 50s
Build Packages / Build documentation (push) Successful in 1m16s
Build Packages / build:jfjoch:rocky9:cuda-sls9 (push) Successful in 21m0s
Build Packages / build:jfjoch:rocky8:cuda (push) Successful in 18m38s
Build Packages / build:rugnux:windows-x86_64:cuda (push) Successful in 14m33s
Build Packages / build:jfjoch:rocky9:cuda (push) Successful in 17m55s
Build Packages / build:jfjoch:ubuntu2204:cuda (push) Successful in 20m50s
Build Packages / build:jfjoch:ubuntu2404:cuda (push) Successful in 18m38s
Build Packages / Unit tests (push) Successful in 1h46m14s
* jfjoch_broker: Optional per-dataset authentication - statistics, images and plots can require a bearer token, which jfjoch_viewer supports. * jfjoch_viewer: Dark mode and a theme-matched colour scheme, a magnifier panel, and simpler contrast and background controls. * Rugnux: Multiple performance improvements on GPU and CPU (CPU-only processing up to 40% faster, faster image decoding on ARM), with unchanged results. * Rugnux: `--model` rigid-body refinement runs on the GPU, and the model-validation check is faster and more reliable. * Rugnux: Improved scaling and merging - error model, outlier rejection, absorption correction and French-Wilson amplitudes now agree more closely with XDS and ctruncate. * Rugnux: Improved integration - radial background on powder and ice rings, crowded rotation data keep their reflections, and CPU-only builds integrate large unit cells as GPU builds do. * Rugnux: More robust detector geometry - measured beam centre, X-ray bandwidth and goniometer rate, and geometry refinement accepted only on significant evidence. * Rugnux: Merged files are written in the standard setting, or in the setting of a reference MTZ, structure-factor mmCIF or model, with its free-R flags. * Rugnux: Richer report - ice and powder rings, further lattices, superstructure candidates and mosaicity, with warnings worded as prompts to check. * Rugnux: Clear error messages when a data set needs more GPU or host memory than is available. Reviewed-on: #83 Co-authored-by: Filip Leonarski <filip.leonarski@psi.ch>
This commit was merged in pull request #83.
This commit is contained in:
@@ -0,0 +1,346 @@
|
||||
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
#include "../common/CUDAWrapper.h"
|
||||
|
||||
#ifdef JFJOCH_USE_CUDA
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <complex>
|
||||
#include <cstring>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
#include "gemmi/asumask.hpp"
|
||||
#include "gemmi/mmread_gz.hpp"
|
||||
#include "gemmi/scaling.hpp"
|
||||
#include "gemmi/symmetry.hpp"
|
||||
#include "gemmi/unitcell.hpp"
|
||||
|
||||
#include "../common/Logger.h"
|
||||
#include "../rugnux/ModelFFT.h"
|
||||
#include "../rugnux/ModelGrid.h"
|
||||
#include "../rugnux/ModelScaling.h"
|
||||
#include "../rugnux/ModelScaleGPU.h"
|
||||
#include "../rugnux/ModelValidation.h"
|
||||
#include "../rugnux/RigidBodyRefine.h"
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double PI_ = 3.14159265358979323846;
|
||||
|
||||
// Scaling points in the reciprocal asymmetric unit of `sg` to d_min, sorted as prepare_points() leaves
|
||||
// them. Fcalc has random phases and a Wilson fall-off, the mask term is strong at low resolution and
|
||||
// roughly opposite in phase, and |Fobs| comes from a known overall scale, anisotropic B and solvent pair
|
||||
// with 5% noise - so the fit has a right answer and a realistic shape of residual.
|
||||
gemmi::Scaling<float> MakeScaling(const gemmi::UnitCell &cell, const gemmi::SpaceGroup &sg, double d_min) {
|
||||
gemmi::Scaling<float> scaling(cell, &sg);
|
||||
scaling.use_solvent = true;
|
||||
const gemmi::GroupOps gops = sg.operations();
|
||||
const gemmi::ReciprocalAsu asu(&sg);
|
||||
const gemmi::Miller lim = cell.get_hkl_limits(d_min);
|
||||
std::vector<gemmi::Miller> hkl;
|
||||
for (int h = -lim[0]; h <= lim[0]; h++)
|
||||
for (int k = -lim[1]; k <= lim[1]; k++)
|
||||
for (int l = -lim[2]; l <= lim[2]; l++) {
|
||||
const gemmi::Miller m{{h, k, l}};
|
||||
if ((h == 0 && k == 0 && l == 0) || cell.calculate_d(m) < d_min || !asu.is_in(m) ||
|
||||
gops.is_systematically_absent(m))
|
||||
continue;
|
||||
hkl.push_back(m);
|
||||
}
|
||||
std::sort(hkl.begin(), hkl.end());
|
||||
|
||||
// B_cart of 25/35/30 A^2 with an off-diagonal term, as B*
|
||||
gemmi::SMat33<double> b_cart{25, 35, 30, 4, -3, 2};
|
||||
const gemmi::SMat33<double> b_star = b_cart.transformed_by(cell.frac.mat);
|
||||
std::mt19937 rng(20260928);
|
||||
std::uniform_real_distribution<double> uniform(0.0, 1.0);
|
||||
std::normal_distribution<double> gauss(0.0, 1.0);
|
||||
const double k_overall = 3.0, k_sol = 0.38, b_sol = 52.0;
|
||||
for (const gemmi::Miller &m : hkl) {
|
||||
const double stol2 = cell.calculate_stol_sq(m);
|
||||
const double phase = 2 * PI_ * uniform(rng);
|
||||
const double amp = 200.0 * std::exp(-10.0 * stol2) * std::sqrt(-std::log(1.0 - 0.999 * uniform(rng)));
|
||||
const std::complex<double> fc = std::polar(amp, phase);
|
||||
const std::complex<double> fm = std::polar(1500.0 * std::exp(-20.0 * stol2) * (0.5 + uniform(rng)),
|
||||
phase + PI_ + 0.5 * gauss(rng));
|
||||
const std::complex<float> fcf(fc), fmf(fm);
|
||||
const std::complex<double> total = std::complex<double>(fcf) + k_sol * std::exp(-b_sol * stol2) * std::complex<double>(fmf);
|
||||
const double fobs = k_overall * std::exp(-0.25 * b_star.r_u_r(m)) * std::abs(total) * (1.0 + 0.05 * gauss(rng));
|
||||
gemmi::Scaling<float>::Point p{};
|
||||
p.hkl = m;
|
||||
p.stol2 = stol2;
|
||||
p.fcmol = fcf;
|
||||
p.fmask = fmf;
|
||||
p.fobs = static_cast<float>(std::fabs(fobs));
|
||||
p.sigma = static_cast<float>(0.05 * std::fabs(fobs) + 0.5);
|
||||
scaling.points.push_back(p);
|
||||
}
|
||||
return scaling;
|
||||
}
|
||||
|
||||
// A synthetic "protein" of 150 carbons, every third anisotropic, in `cryst` - ModelValidationTest.cpp's
|
||||
// rigid-body fixture - and the Scaling points RigidBodyTarget::Residuals fits at a displaced
|
||||
// placement q0: the model's own amplitudes as Fobs, and the Fcalc and bulk-solvent Fmask of the
|
||||
// displaced model, gridded and composed as the rigid body does.
|
||||
gemmi::Scaling<float> ModelPoints(const char *cryst, double zone) {
|
||||
std::string pdb = cryst;
|
||||
std::mt19937 rng(20260902);
|
||||
std::uniform_real_distribution<double> x(2, 14), y(2, 16), z(2, 18);
|
||||
char line[96];
|
||||
for (int i = 1; i <= 150; i++) {
|
||||
std::snprintf(line, sizeof line, "ATOM %5d C UNK A 1 %8.3f%8.3f%8.3f 1.00 20.00 C\n",
|
||||
i, x(rng), y(rng), z(rng));
|
||||
pdb += line;
|
||||
}
|
||||
pdb += "END\n";
|
||||
const std::string path = "model_scale_gpu_test.pdb";
|
||||
std::ofstream(path) << pdb;
|
||||
Logger logger("ModelScaleGPUTest");
|
||||
const auto reference = ModelReferenceIntensities(path, {}, {}, 3.0, logger);
|
||||
gemmi::Structure st = gemmi::read_structure_gz(path, gemmi::CoorFormat::Detect);
|
||||
std::filesystem::remove(path);
|
||||
st.setup_cell_images();
|
||||
int i = 0;
|
||||
for (gemmi::Chain &ch : st.models[0].chains)
|
||||
for (gemmi::Residue &r : ch.residues)
|
||||
for (gemmi::Atom &a : r.atoms)
|
||||
if (i++ % 3 == 0)
|
||||
a.aniso = {0.30f, 0.25f, 0.20f, 0.02f, -0.01f, 0.03f};
|
||||
const gemmi::SpaceGroup &sg = *st.find_spacegroup();
|
||||
|
||||
gemmi::AsuData<gemmi::ValueSigma<float>> fobs;
|
||||
fobs.unit_cell_ = st.cell;
|
||||
fobs.spacegroup_ = &sg;
|
||||
for (const auto &r : reference)
|
||||
if (st.cell.calculate_d({{r.h, r.k, r.l}}) >= zone)
|
||||
fobs.v.push_back({{{r.h, r.k, r.l}}, {std::sqrt(r.I), 1.0f}});
|
||||
fobs.ensure_sorted();
|
||||
|
||||
gemmi::Model model = st.models[0];
|
||||
RigidBodyTarget target(model, st.cell, sg, 4);
|
||||
const double q0[6] = {0.12, -0.09, 0.07, 0.15, -0.10, 0.05};
|
||||
target.Place(q0, model);
|
||||
|
||||
gemmi::Grid<float> grid;
|
||||
grid.unit_cell = st.cell;
|
||||
grid.spacegroup = &sg;
|
||||
grid.set_size_from_spacing(zone / 3.0, gemmi::GridSizeRounding::Up);
|
||||
std::vector<gemmi::Miller> hkl;
|
||||
for (const auto &hv : fobs.v)
|
||||
hkl.push_back(hv.hkl);
|
||||
const SymmetryComposition composition(grid, zone, hkl);
|
||||
|
||||
gemmi::DensityCalculator<gemmi::IT92<float>, float> dc;
|
||||
dc.d_min = zone;
|
||||
dc.rate = 1.5;
|
||||
dc.grid.unit_cell = st.cell;
|
||||
dc.grid.spacegroup = &sg;
|
||||
dc.set_refmac_compatible_blur(model);
|
||||
PutModelDensityOnGrid(dc, model, {}, 4);
|
||||
std::vector<std::complex<double>> fc;
|
||||
composition.Compose(MapToFPhi(dc.grid), dc.blur, fc, nullptr, 4);
|
||||
|
||||
gemmi::Grid<float> mask = grid;
|
||||
PutMaskOnGrid(mask, model, OrbitLeaders(mask, 4), 4);
|
||||
const gemmi::FPhiGrid<float> fm = MapToFPhi(mask);
|
||||
gemmi::AsuData<std::complex<float>> fcalc, fmask;
|
||||
for (size_t m = 0; m < composition.Hkl().size(); m++) {
|
||||
fcalc.v.push_back({composition.Hkl()[m], std::complex<float>(fc[m])});
|
||||
fmask.v.push_back({composition.Hkl()[m], fm.get_value_by_hkl(composition.Hkl()[m])});
|
||||
}
|
||||
gemmi::Scaling<float> scaling(st.cell, &sg);
|
||||
scaling.use_solvent = true;
|
||||
scaling.prepare_points(fcalc, fobs, &fmask);
|
||||
return scaling;
|
||||
}
|
||||
|
||||
struct GpuPoints {
|
||||
CudaStream stream;
|
||||
ModelScaleGPU scale;
|
||||
CudaDevicePtr<float2> fcmol, fmask;
|
||||
|
||||
explicit GpuPoints(const gemmi::Scaling<float> &s)
|
||||
: scale(stream, s.points.size()), fcmol(s.points.size()), fmask(s.points.size()) {
|
||||
std::vector<std::array<int, 3>> hkl;
|
||||
std::vector<double> stol2;
|
||||
std::vector<float> fobs, sigma;
|
||||
std::vector<float2> fc, fm;
|
||||
for (const auto &p : s.points) {
|
||||
hkl.push_back(p.hkl);
|
||||
stol2.push_back(p.stol2);
|
||||
fobs.push_back(p.fobs);
|
||||
sigma.push_back(p.sigma);
|
||||
fc.push_back(make_float2(p.fcmol.real(), p.fcmol.imag()));
|
||||
fm.push_back(make_float2(p.fmask.real(), p.fmask.imag()));
|
||||
}
|
||||
std::vector<std::array<double, 6>> constraints(s.constraint_matrix.begin(), s.constraint_matrix.end());
|
||||
double frac[9];
|
||||
for (int i = 0; i < 3; i++)
|
||||
for (int j = 0; j < 3; j++)
|
||||
frac[3 * i + j] = s.cell.frac.mat[i][j];
|
||||
scale.SetPoints(hkl, stol2, fobs, sigma, constraints, frac);
|
||||
// On the stream the fits run on: it is non-blocking, so nothing orders it after the legacy stream.
|
||||
REQUIRE(cudaMemcpyAsync(fcmol, fc.data(), fc.size() * sizeof(float2), cudaMemcpyHostToDevice, stream) ==
|
||||
cudaSuccess);
|
||||
REQUIRE(cudaMemcpyAsync(fmask, fm.data(), fm.size() * sizeof(float2), cudaMemcpyHostToDevice, stream) ==
|
||||
cudaSuccess);
|
||||
REQUIRE(cudaStreamSynchronize(stream) == cudaSuccess);
|
||||
}
|
||||
};
|
||||
|
||||
double MaxAbs(const double b[6]) {
|
||||
double m = 0;
|
||||
for (int i = 0; i < 6; i++)
|
||||
m = std::max(m, std::fabs(b[i]));
|
||||
return m;
|
||||
}
|
||||
|
||||
// The tolerances are those of gemmi's fit rather than of the arithmetic. Its Levenberg-Marquardt stops
|
||||
// when the WSSR has changed by less than 1e-5 twice, which leaves the scale short of the minimum by
|
||||
// about sqrt(1e-5) of the residual, so where a step is accepted or the fit stops on a near-tie the last
|
||||
// bit decides it: measured, gemmi moves by up to 1e-4 of <Fobs> when its own Fcalc changes by 1e-5.
|
||||
constexpr double R_TOLERANCE = 1e-5;
|
||||
|
||||
void CheckSameScale(const ModelScaleParams &gpu, const gemmi::Scaling<float> &cpu) {
|
||||
const double b_cpu[6] = {cpu.b_star.u11, cpu.b_star.u22, cpu.b_star.u33,
|
||||
cpu.b_star.u12, cpu.b_star.u13, cpu.b_star.u23};
|
||||
CHECK(std::fabs(gpu.k_overall - cpu.k_overall) <= 1e-4 * std::fabs(cpu.k_overall));
|
||||
for (int i = 0; i < 6; i++)
|
||||
CHECK(std::fabs(gpu.b_star[i] - b_cpu[i]) <= 1e-4 * MaxAbs(b_cpu) + 1e-12);
|
||||
}
|
||||
|
||||
struct Case {
|
||||
const char *name;
|
||||
double a, b, c, alpha, beta, gamma;
|
||||
const char *hm;
|
||||
};
|
||||
|
||||
const Case CASES[] = {
|
||||
{"triclinic", 41, 47, 53, 82, 97, 104, "P 1"},
|
||||
{"monoclinic", 72, 44, 51, 90, 112, 90, "C 1 2 1"},
|
||||
{"tetragonal", 64, 64, 81, 90, 90, 90, "P 4"},
|
||||
{"hexagonal", 58, 58, 96, 90, 90, 120, "P 6"},
|
||||
{"cubic", 92, 92, 92, 90, 90, 90, "P 2 3"},
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("ModelScaleGPU_FitMatchesGemmi", "[ModelValidation][gpu]") {
|
||||
if (get_gpu_count() == 0)
|
||||
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);
|
||||
const gemmi::SpaceGroup &sg = *gemmi::find_spacegroup_by_name(c.hm);
|
||||
gemmi::Scaling<float> cpu = MakeScaling(cell, sg, 2.8);
|
||||
REQUIRE(cpu.points.size() > 2000);
|
||||
GpuPoints gpu(cpu);
|
||||
|
||||
for (const double k_sol : {0.25, 0.4})
|
||||
for (const double b_sol : {30.0, 60.0}) {
|
||||
gemmi::Scaling<float> ref = cpu;
|
||||
ref.k_sol = k_sol;
|
||||
ref.b_sol = b_sol;
|
||||
ref.fix_k_sol = true;
|
||||
ref.fix_b_sol = true;
|
||||
ref.fit_isotropic_b_approximately();
|
||||
ref.fit_parameters();
|
||||
const ModelScaleParams p = gpu.scale.Fit(gpu.fcmol, gpu.fmask, k_sol, b_sol);
|
||||
CheckSameScale(p, ref);
|
||||
|
||||
gemmi::Scaling<float> with_gpu = ref;
|
||||
with_gpu.k_overall = p.k_overall;
|
||||
with_gpu.b_star = {p.b_star[0], p.b_star[1], p.b_star[2], p.b_star[3], p.b_star[4], p.b_star[5]};
|
||||
CHECK(std::fabs(with_gpu.calculate_r_factor() - ref.calculate_r_factor()) < R_TOLERANCE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ModelScaleGPU_SolventGridMatchesFitModelScale", "[ModelValidation][gpu]") {
|
||||
if (get_gpu_count() == 0)
|
||||
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);
|
||||
const gemmi::SpaceGroup &sg = *gemmi::find_spacegroup_by_name(c.hm);
|
||||
gemmi::Scaling<float> cpu = MakeScaling(cell, sg, 2.8);
|
||||
GpuPoints gpu(cpu);
|
||||
|
||||
const ModelScaleReport report = FitModelScale(cpu, {}, 8);
|
||||
const ModelSolventFit fit = gpu.scale.FitSolvent(gpu.fcmol, gpu.fmask);
|
||||
CHECK(fit.n_grid == report.n_grid);
|
||||
// The same grid point, so the same doubles - or, on a near-tie another card resolves the other
|
||||
// way, a neighbour whose R is the CPU winner's (checked either way).
|
||||
const bool same_point = fit.k_sol == cpu.k_sol && fit.b_sol == cpu.b_sol;
|
||||
CHECK(std::fabs(fit.r - report.r_work_fit) < R_TOLERANCE);
|
||||
if (same_point)
|
||||
CheckSameScale(fit.scale, cpu);
|
||||
}
|
||||
}
|
||||
|
||||
// The rigid body's own sequence on a model's points: FitModelScale once for the zone, then the
|
||||
// per-evaluation fit at that solvent pair.
|
||||
TEST_CASE("ModelScaleGPU_MatchesGemmiOnAModelsPoints", "[ModelValidation][gpu]") {
|
||||
if (get_gpu_count() == 0)
|
||||
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",
|
||||
"CRYST1 40.000 50.000 60.000 90.00 90.00 90.00 P 21 21 21 4\n",
|
||||
"CRYST1 60.000 60.000 60.000 90.00 90.00 90.00 I 2 3 24\n",
|
||||
"CRYST1 80.000 80.000 80.000 90.00 90.00 90.00 F 41 3 2 96\n",
|
||||
};
|
||||
for (const char *cryst : crysts)
|
||||
for (const double zone : {6.0, 3.5}) {
|
||||
INFO(cryst << " at " << zone << " A");
|
||||
gemmi::Scaling<float> cpu = ModelPoints(cryst, zone);
|
||||
REQUIRE(cpu.points.size() > 50);
|
||||
GpuPoints gpu(cpu);
|
||||
|
||||
const ModelScaleReport report = FitModelScale(cpu, {}, 4);
|
||||
const ModelSolventFit solvent = gpu.scale.FitSolvent(gpu.fcmol, gpu.fmask);
|
||||
// As above: the same grid point, or a near-tie's neighbour at the CPU winner's R.
|
||||
const bool same_point = solvent.k_sol == cpu.k_sol && solvent.b_sol == cpu.b_sol;
|
||||
CHECK(std::fabs(solvent.r - report.r_work_fit) < R_TOLERANCE);
|
||||
if (same_point)
|
||||
CheckSameScale(solvent.scale, cpu);
|
||||
|
||||
gemmi::Scaling<float> ref = cpu;
|
||||
ref.fix_k_sol = true;
|
||||
ref.fix_b_sol = true;
|
||||
ref.k_overall = 1;
|
||||
ref.b_star = {0, 0, 0, 0, 0, 0};
|
||||
ref.fit_isotropic_b_approximately();
|
||||
ref.fit_parameters();
|
||||
const ModelScaleParams p = gpu.scale.Fit(gpu.fcmol, gpu.fmask, cpu.k_sol, cpu.b_sol);
|
||||
CheckSameScale(p, ref);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ModelScaleGPU_Deterministic", "[ModelValidation][gpu]") {
|
||||
if (get_gpu_count() == 0)
|
||||
SKIP("No GPU");
|
||||
gemmi::UnitCell cell(72, 44, 51, 90, 112, 90);
|
||||
gemmi::Scaling<float> cpu = MakeScaling(cell, *gemmi::find_spacegroup_by_name("C 1 2 1"), 2.5);
|
||||
GpuPoints gpu(cpu);
|
||||
const ModelSolventFit first = gpu.scale.FitSolvent(gpu.fcmol, gpu.fmask);
|
||||
const ModelScaleParams first_fit = gpu.scale.Fit(gpu.fcmol, gpu.fmask, first.k_sol, first.b_sol);
|
||||
for (int repeat = 0; repeat < 3; repeat++) {
|
||||
const ModelSolventFit again = gpu.scale.FitSolvent(gpu.fcmol, gpu.fmask);
|
||||
CHECK(again.k_sol == first.k_sol);
|
||||
CHECK(again.b_sol == first.b_sol);
|
||||
CHECK(std::memcmp(&again.r, &first.r, sizeof(double)) == 0);
|
||||
CHECK(std::memcmp(&again.scale, &first.scale, sizeof(ModelScaleParams)) == 0);
|
||||
const ModelScaleParams fit = gpu.scale.Fit(gpu.fcmol, gpu.fmask, first.k_sol, first.b_sol);
|
||||
CHECK(std::memcmp(&fit, &first_fit, sizeof(ModelScaleParams)) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
Reference in New Issue
Block a user