RefineModel (Refinement.h) refines a placed model against the merge, reading and writing nothing: - placement stage (R1): every chain of 50+ atoms as a rigid body at once (L-BFGS on six parameters per chain, their gradient from the per-atom one through Ceres jets), then one B shift per residue; - restrained stage (R2): positions and isotropic B of every atom of a residue the embedded dictionary describes, the rest held fixed but kept in Fcalc and the mask; riding hydrogens added by gemmi's topology, moved with their parents, re-placed every macro-cycle. Macro-cycles refit the mask, the scale and bulk solvent (FitModelScale / ModelScaleGPUPool) and the likelihood's D and beta (ML on the free set in resolution bins), rebuild the repulsion pairs (symmetry mates included), then L-BFGS on positions and, where chosen, on B. - weights: a ladder of full refinements at multiples of the gradient-balanced X-ray weight, chosen by R-free within bond/angle bounds (ties to the tighter geometry), then the B refinement tried at its own weights and kept only where it beats B held by more than the tie. The target and its gradient (RefinementXray, RefinementGPU): the Rice/centric amplitude likelihood (RefinementMath.h, shared host/device code), the gradient by Agarwal's FFT method - the adjoint of the reciprocal-space symmetry composition into a half-l (CPU) or half-u (cuFFT) spectrum, one inverse real transform, then each atom's density derivatives (xyz and B) summed over its box. On the GPU everything per evaluation stays on the device: ModelDensityGPU's gather, r2c, a fused compose + likelihood kernel, a CSR gather for the adjoint (no atomics), c2r, one warp per atom for the gradient; the restraints are one kernel per restraint plus a per-atom gather of gradient slots; cub reductions. Deterministic. Restraints (RefinementRestraints): bonds, angles, torsions (with esd, no phi/psi, no alternative sugar puckers), chiral volumes, least-squares planes from gemmi's Topo; repulsions beyond 1-4 (vdW radii from the dictionary's energy library, 2.6 A for H-bond pairs, 1.9 A metal-N/O/S); B similarity of bonded and angle-related atoms. DensityBlobs (D1): gemmi's flood-fill blobs of mFo-DFc above 3 sigma peaking above 4 sigma, placed beside the model and labelled WATER / ION / LIGAND / MODEL_ERROR by distance, contacts, volume and the anomalous map. tools/rugnux_refine_test refines a model against a finished run's p.mtz (developer utility, not installed). Tests: likelihood derivative, the embedded library, restraint values on ideal residues and every restraint kind's gradient against finite differences (CPU, and the GPU against the CPU), the X-ray gradient against finite differences in P1, P212121, C2 and P6122 (CPU and GPU), and a synthetic refinement that recovers a shaken model. Credits in docs/ACKNOWLEDGEMENT.md and at the algorithms. Measured on the open arm's battery outputs against REFMAC 5.8, 10 cycles, same p.mtz, free set and start model (R-free): 5reo 0.2228 vs 0.2236, 8dyz 0.1733 vs 0.1651, 9chw 0.1995 vs 0.1969 (CPU engine). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu (cherry picked from commit 8efe6f2322b3bfdae580bb34f256e67730dfc3c2)
42 lines
2.3 KiB
C++
42 lines
2.3 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#pragma once
|
|
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "gemmi/grid.hpp"
|
|
#include "gemmi/model.hpp"
|
|
|
|
// What a model leaves unexplained: the connected regions of positive mFo-DFc density above a cutoff ("blobs"),
|
|
// each placed beside the model, measured and given a likely reading - a water, an ion, something ligand-sized,
|
|
// or an error of the model itself (a misplaced or missing atom, an alternative conformation).
|
|
//
|
|
// The readings are rules on what is measured, meant to point a person at the density, not to identify a
|
|
// compound: a blob called LIGAND is density of ligand size and shape where the model has nothing, which a
|
|
// buffer molecule or an unmodelled side chain can be as well as the compound that was soaked.
|
|
struct DensityBlob {
|
|
enum class Kind { Water, Ion, Ligand, ModelError, Unassigned };
|
|
Kind kind = Kind::Unassigned;
|
|
gemmi::Position centroid; // the copy nearest the model
|
|
double peak_sigma = 0; // the highest point, in the map's rms
|
|
double volume = 0; // A^3 above the cutoff
|
|
double score = 0; // the density above the cutoff integrated, sigma A^3
|
|
double nearest_atom_distance = 0;
|
|
std::string nearest_atom; // e.g. "OG1 THR A42"
|
|
int polar_contacts = 0; // N or O of the model at 2.4-3.4 A
|
|
int metal_contacts = 0; // N, O or S of the model at 1.8-2.6 A
|
|
double anomalous_sigma = 0; // the anomalous difference map at the peak, 0 where there is none
|
|
};
|
|
|
|
const char *DensityBlobKindName(DensityBlob::Kind kind);
|
|
|
|
// The blobs of `difference` (an mFo-DFc map of the whole cell, rms `rms`) above 3 sigma that peak above 4 sigma,
|
|
// strongest (by score) first. `model`: what the map was made from, in the map's cell. `anomalous`: the anomalous
|
|
// difference map on any grid of the same cell, or null. Following the flood fill of Coot's unmodelled blobs as
|
|
// gemmi implements it (find_blobs_by_flood_fill).
|
|
std::vector<DensityBlob> FindDensityBlobs(const gemmi::Grid<float> &difference, double rms, const gemmi::Structure &model,
|
|
double d_min, const gemmi::Grid<float> *anomalous = nullptr,
|
|
double anomalous_rms = 0);
|