Refinement score: the model's structure factors as REFMAC 5.8 scores them at 0 cycles

Three terms of the score of a model - the same F_model at every stage, the as-given start and the
refined end alike - and a breakdown of it:

- Riding hydrogens from the start: the dictionary's hydrogens are added before the model is first
  scored (REFMAC's 0-cycle restores them; they move R by 0.010-0.013 at 1.1 A). The engine is made
  once, with them.
- The bulk-solvent mask on a grid of its own, min(d_min/3, 0.4 A), at most 2^26 points. On the
  density grid (1.1 A at 3.3 A) gemmi's 0.8 A shrink found no grid offset and was skipped, which put
  R 0.015-0.03 high at >= 2.5 A. 0.3 and 0.4 A agree to 0.001.
- The overall scale and anisotropic B by robust least squares (pseudo-Huber, iteratively
  reweighted, c = median |residual|) in the fine pass of the k_sol/b_sol grid (ModelScaleBox::robust,
  refinement only; MR and validation keep the plain fit). Plain least squares on |F| is the exact LS
  optimum but lets a few badly-fitted strong reflections set the scale (1.1 A set: R 0.1706 against
  REFMAC's 0.1600). The scale fit of refinement now runs on the CPU (no ModelScaleGPUPool).

RefinementSettings::score_only scores the model as given and stops; RefinementResult::score_start
carries every term (k, B, anisotropic B, k_sol, b_sol, mask spacing, R in 20 shells, -lnL work/free,
the likelihood FOM, hydrogens; per-reflection terms with score_only). GeometryStatistics gains rmsZ.
rugnux_refine_test: --score-only, --dump.

Measured against REFMAC 5.8.0431 ncyc 0 (refi bref iso) on the same model, data and flags, 14 open-arm
sets 1.1-3.6 A: R-work/R-free within 0.002 on 12 (was +0.003..+0.03); 7n2s and 9yl4 score 0.003/0.005
below REFMAC, which a 0.6-0.8 A mask grid reproduces. REFMAC with ANISOU stripped scores identically,
so isotropic B is not a parity term. Open: the likelihood's D/FOM (ours 0.01-0.11 higher at high
resolution); geometry differs only by REFMAC's metal LINK restraints and residues outside the
embedded dictionary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 4f94b21611874ad9dcf89e6771fe311a0e10bb06)
(cherry picked from commit 00db6a6f539ac2da515c0a649474335f4c4b07b8)
This commit is contained in:
leonarski_f committed 2026-10-26 01:38:23 +01:00
1 parent 8933a72a89
commit c4d2d419ce
10 files changed
+310 -54

No files matched your search

@@ -551,17 +551,19 @@ void PlaceRidingHydrogens(RefinementTopology &topo) {
GeometryStatistics MeasureGeometry(const RestraintSet &geometry, const RestraintSet &repulsions,
const std::vector<std::array<double, 4>> &xyzb) {
GeometryStatistics s;
double sb = 0, sa = 0, sc = 0;
double sb = 0, sa = 0, sc = 0, zb = 0, za = 0;
int nc = 0;
for (const RestraintRecord &r : geometry.records) {
const RestraintKind kind = static_cast<RestraintKind>(r.kind);
if (kind == RestraintKind::Bond) {
const double d = RestraintValue(geometry, r, xyzb) - r.ideal;
sb += d * d;
zb += d * d / (double(r.sigma) * r.sigma);
s.bonds++;
} else if (kind == RestraintKind::Angle) {
const double d = RestraintValue(geometry, r, xyzb) - r.ideal;
sa += d * d;
za += d * d / (double(r.sigma) * r.sigma);
s.angles++;
} else if (kind == RestraintKind::Chiral) {
const double d = RestraintValue(geometry, r, xyzb) - r.ideal;
@@ -575,5 +577,7 @@ GeometryStatistics MeasureGeometry(const RestraintSet &geometry, const Restraint
s.rmsd_bonds = s.bonds > 0 ? std::sqrt(sb / s.bonds) : 0;
s.rmsd_angles = s.angles > 0 ? std::sqrt(sa / s.angles) : 0;
s.rmsd_chirals = nc > 0 ? std::sqrt(sc / nc) : 0;
s.rmsz_bonds = s.bonds > 0 ? std::sqrt(zb / s.bonds) : 0;
s.rmsz_angles = s.angles > 0 ? std::sqrt(za / s.angles) : 0;
return s;
}