Refinement: B restraints in proportion to B (Kullback-Leibler form) with contacts, an own ADP weight, overall B into the atoms
- B similarity: E = (B_i - B_j)^2 / (kappa^2 B_i B_j), the symmetric Kullback-Leibler divergence of two isotropic Gaussians (Murshudov et al. 2011), so sigma = kappa sqrt(B_i B_j) instead of a fixed 1.5-4.5 A^2. kappa 0.1 / 0.133 / 0.2 / 0.3 for main-chain bonded / angle-related and side-chain pairs (REFMAC's logs list sigma ~0.1 B for main-chain bonded pairs at B 11-140; the ratios are those of the old sigmas, which these equal at B = 15). New class: atoms within 4 A that no bond or angle relates, waters and symmetry mates included, kappa 0.5 (BuildBContacts, sharing the neighbour search with BuildRepulsions). CPU and GPU. - The B factors' X-ray weight is balanced on its own (BalancedADPWeight: B-restraint over X-ray B-gradient on the model with B shaken by 20%) instead of being a multiple of the positional weight; the ladder tries B held and refined at that weight, the trajectory's B steps divide and multiply it by four, positional steps and the final ladder leave it. - Before each B-refining macro-cycle the overall isotropic B of the scale moves into the atoms and the solvent's B (F_model unchanged), so the two do not drift against each other. Tried and not kept: xyz and B as one L-BFGS vector (the B block scaled by its curvature, the positions by the median X-ray curvature): no better than alternation with a memory per block on 12 starts (mean R-free 0.265 vs 0.262), worse on 7arr/7d98; a refit before each B step: no gain for +20-40% time. Refinement battery MD starts, median R-free by the REFMAC 0-cycle scorer, previous commit -> this (REFMAC to convergence): 5f6m x8 0.167 -> 0.167 (0.168), 8dqb x8 0.291 -> 0.294 (0.284), 7arr x4 0.270 -> 0.278 (0.311), 11if x4 0.245 -> 0.244 (0.251), 5reo x4 0.312 -> 0.258 (0.261: the ladder no longer keeps B held where B is the model's largest error), 7d98 x4 0.342 -> 0.335 (0.361). Deposited models: unchanged (B stays held there). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu (cherry picked from commit ed292f35b482b696d81f71f1d97e0016ca05b144)
This commit is contained in:
1 parent
6369a263b1
commit
bda90dbdd5
7 files changed
+242
-130
No files matched your search
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_map>
|
||||
|
||||
@@ -25,9 +26,14 @@ constexpr float CHIRAL_SIGMA = 0.2f; // A^3
|
||||
constexpr float REPULSION_SIGMA = 0.2f; // A
|
||||
constexpr float HBOND_CONTACT = 2.6f; // A
|
||||
constexpr float METAL_CONTACT = 1.9f; // A, to N, O or S
|
||||
// B similarity: main chain and the rest, for bonded pairs and pairs bonded to a common atom.
|
||||
constexpr float B_SIGMA_MAIN_BOND = 1.5f, B_SIGMA_MAIN_ANGLE = 2.0f;
|
||||
constexpr float B_SIGMA_SIDE_BOND = 3.0f, B_SIGMA_SIDE_ANGLE = 4.5f;
|
||||
// B similarity (BSimilarityTerm's kappa: sigma = kappa sqrt(B_i B_j)): main chain and the rest, for bonded pairs and
|
||||
// pairs bonded to a common atom - REFMAC's logs list sigma near 0.1 B for main-chain bonded pairs from B 11 to 140
|
||||
// A^2, the ratios between the classes are those of the fixed sigmas this replaced (1.5, 2.0, 3.0, 4.5 A^2, i.e. these
|
||||
// at B = 15) - and atoms in contact, waters among them, that no bond or angle relates.
|
||||
constexpr float B_KAPPA_MAIN_BOND = 0.1f, B_KAPPA_MAIN_ANGLE = 0.133f;
|
||||
constexpr float B_KAPPA_SIDE_BOND = 0.2f, B_KAPPA_SIDE_ANGLE = 0.3f;
|
||||
constexpr float B_KAPPA_CONTACT = 0.5f;
|
||||
constexpr double B_CONTACT_DISTANCE = 4.0; // A
|
||||
|
||||
bool IsMainChain(const gemmi::Atom &a) {
|
||||
static const std::set<std::string> names = {"N", "CA", "C", "O", "OXT", "P", "OP1", "OP2", "O5'", "C5'",
|
||||
@@ -303,11 +309,11 @@ double EvaluateRestraints(const RestraintSet &set, const std::vector<std::array<
|
||||
break;
|
||||
}
|
||||
case RestraintKind::BSimilarity: {
|
||||
const double rr = (xyzb[r.atom[0]][3] - xyzb[r.atom[1]][3]) / sigma;
|
||||
e = rr * rr;
|
||||
double gi = 0, gj = 0;
|
||||
e = refine_math::BSimilarityTerm(xyzb[r.atom[0]][3], xyzb[r.atom[1]][3], sigma, gi, gj);
|
||||
if (grad != nullptr) {
|
||||
(*grad)[r.atom[0]][3] += 2 * rr / sigma;
|
||||
(*grad)[r.atom[1]][3] -= 2 * rr / sigma;
|
||||
(*grad)[r.atom[0]][3] += gi;
|
||||
(*grad)[r.atom[1]][3] += gj;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -328,8 +334,10 @@ std::vector<double> BRestraintCurvature(const RestraintSet &set, const std::vect
|
||||
std::vector<double> h(xyzb.size(), 0.0);
|
||||
for (const RestraintRecord &r : set.records)
|
||||
if (r.kind == static_cast<int>(RestraintKind::BSimilarity)) {
|
||||
h[r.atom[0]] += 2.0 / (r.sigma * r.sigma);
|
||||
h[r.atom[1]] += 2.0 / (r.sigma * r.sigma);
|
||||
// d2E/dB_i^2 of BSimilarityTerm = 2 B_j / (kappa^2 B_i^3)
|
||||
const double bi = xyzb[r.atom[0]][3], bj = xyzb[r.atom[1]][3], k2 = double(r.sigma) * r.sigma;
|
||||
h[r.atom[0]] += 2.0 * bj / (k2 * bi * bi * bi);
|
||||
h[r.atom[1]] += 2.0 * bi / (k2 * bj * bj * bj);
|
||||
}
|
||||
return h;
|
||||
}
|
||||
@@ -454,7 +462,7 @@ RefinementTopology BuildRefinementTopology(gemmi::Structure &st, bool hydrogens)
|
||||
rb.atom[0] = i;
|
||||
rb.atom[1] = j;
|
||||
const bool main = IsMainChain(*b.atoms[0]) && IsMainChain(*b.atoms[1]);
|
||||
rb.sigma = main ? B_SIGMA_MAIN_BOND : B_SIGMA_SIDE_BOND;
|
||||
rb.sigma = main ? B_KAPPA_MAIN_BOND : B_KAPPA_SIDE_BOND;
|
||||
out.adp.Add(rb, 2);
|
||||
}
|
||||
for (const gemmi::Topo::Angle &an : topo->angles) {
|
||||
@@ -479,7 +487,7 @@ RefinementTopology BuildRefinementTopology(gemmi::Structure &st, bool hydrogens)
|
||||
rb.atom[0] = i;
|
||||
rb.atom[1] = k;
|
||||
const bool main = IsMainChain(*an.atoms[0]) && IsMainChain(*an.atoms[2]);
|
||||
rb.sigma = main ? B_SIGMA_MAIN_ANGLE : B_SIGMA_SIDE_ANGLE;
|
||||
rb.sigma = main ? B_KAPPA_MAIN_ANGLE : B_KAPPA_SIDE_ANGLE;
|
||||
out.adp.Add(rb, 2);
|
||||
}
|
||||
for (const gemmi::Topo::Torsion &t : topo->torsions) {
|
||||
@@ -557,14 +565,17 @@ RefinementTopology BuildRefinementTopology(gemmi::Structure &st, bool hydrogens)
|
||||
return out;
|
||||
}
|
||||
|
||||
RestraintSet BuildRepulsions(const RefinementTopology &topo, gemmi::Model &model, const gemmi::UnitCell &cell_in,
|
||||
const gemmi::SpaceGroup &sg, double margin, size_t nthreads) {
|
||||
namespace {
|
||||
|
||||
// The pairs of atoms of `model`, symmetry mates included, closer than `reach` and not excluded, where at least one of
|
||||
// the two is refinable, neither is a hydrogen and the two are not alternative conformations of each other: `make`
|
||||
// turns each into a record (the image of j as x -> rot x + tran, Cartesian, and the distance), or says to leave it out.
|
||||
// The search runs on `nthreads` threads; the list is in atom order whatever their number.
|
||||
RestraintSet Contacts(const RefinementTopology &topo, gemmi::Model &model, const gemmi::UnitCell &cell_in,
|
||||
const gemmi::SpaceGroup &sg, double reach, size_t nthreads,
|
||||
const std::function<bool(int i, int j, double d, RestraintRecord &r)> &make) {
|
||||
gemmi::UnitCell cell = cell_in;
|
||||
cell.set_cell_images_from_spacegroup(&sg);
|
||||
float max_radius = 0;
|
||||
for (float r : topo.vdw_radius)
|
||||
max_radius = std::max(max_radius, r);
|
||||
const double reach = 2 * max_radius + margin;
|
||||
gemmi::NeighborSearch ns(model, cell, std::max(reach, 5.0));
|
||||
ns.populate(false);
|
||||
|
||||
@@ -609,29 +620,16 @@ RestraintSet BuildRepulsions(const RefinementTopology &topo, gemmi::Model &model
|
||||
const gemmi::Mat33 rot = cell.orth.mat.multiply(ft.mat).multiply(cell.frac.mat);
|
||||
const gemmi::Vec3 tran = cell.orth.mat.multiply(ft.vec + shift);
|
||||
const gemmi::Position image(rot.multiply(pos[j]) + tran);
|
||||
const double d = pos[i].dist(image);
|
||||
|
||||
float d0 = topo.vdw_radius[i] + topo.vdw_radius[j];
|
||||
if (topo.metal[i] || topo.metal[j]) {
|
||||
const gemmi::El other = topo.element[topo.metal[i] ? j : static_cast<int>(i)];
|
||||
if (other == gemmi::El::N || other == gemmi::El::O || other == gemmi::El::S)
|
||||
d0 = METAL_CONTACT;
|
||||
} else if (topo.hbond[i] && topo.hbond[j])
|
||||
d0 = HBOND_CONTACT;
|
||||
if (!(d < d0 + margin))
|
||||
return;
|
||||
RestraintRecord r;
|
||||
r.kind = static_cast<int>(RestraintKind::Repulsion);
|
||||
r.atom[0] = static_cast<int>(i);
|
||||
r.atom[1] = j;
|
||||
r.ideal = d0;
|
||||
r.sigma = REPULSION_SIGMA;
|
||||
for (int a = 0; a < 3; a++) {
|
||||
for (int b = 0; b < 3; b++)
|
||||
r.rot[3 * a + b] = static_cast<float>(rot[a][b]);
|
||||
r.tran[a] = static_cast<float>(tran.at(a));
|
||||
}
|
||||
found[block].push_back(r);
|
||||
if (make(static_cast<int>(i), j, pos[i].dist(image), r))
|
||||
found[block].push_back(r);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -643,6 +641,42 @@ RestraintSet BuildRepulsions(const RefinementTopology &topo, gemmi::Model &model
|
||||
return out;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
RestraintSet BuildRepulsions(const RefinementTopology &topo, gemmi::Model &model, const gemmi::UnitCell &cell,
|
||||
const gemmi::SpaceGroup &sg, double margin, size_t nthreads) {
|
||||
float max_radius = 0;
|
||||
for (float r : topo.vdw_radius)
|
||||
max_radius = std::max(max_radius, r);
|
||||
return Contacts(topo, model, cell, sg, 2 * max_radius + margin, nthreads,
|
||||
[&](int i, int j, double d, RestraintRecord &r) {
|
||||
float d0 = topo.vdw_radius[i] + topo.vdw_radius[j];
|
||||
if (topo.metal[i] || topo.metal[j]) {
|
||||
const gemmi::El other = topo.element[topo.metal[i] ? j : i];
|
||||
if (other == gemmi::El::N || other == gemmi::El::O || other == gemmi::El::S)
|
||||
d0 = METAL_CONTACT;
|
||||
} else if (topo.hbond[i] && topo.hbond[j])
|
||||
d0 = HBOND_CONTACT;
|
||||
if (!(d < d0 + margin))
|
||||
return false;
|
||||
r.kind = static_cast<int>(RestraintKind::Repulsion);
|
||||
r.ideal = d0;
|
||||
r.sigma = REPULSION_SIGMA;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
RestraintSet BuildBContacts(const RefinementTopology &topo, gemmi::Model &model, const gemmi::UnitCell &cell,
|
||||
const gemmi::SpaceGroup &sg, size_t nthreads) {
|
||||
return Contacts(topo, model, cell, sg, B_CONTACT_DISTANCE, nthreads, [&](int i, int j, double d, RestraintRecord &r) {
|
||||
if (i == j || !(d < B_CONTACT_DISTANCE))
|
||||
return false;
|
||||
r.kind = static_cast<int>(RestraintKind::BSimilarity);
|
||||
r.sigma = B_KAPPA_CONTACT;
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
void PlaceRidingHydrogens(RefinementTopology &topo) {
|
||||
gemmi::place_hydrogens_on_all_atoms(*topo.topo);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user