Refinement restraints: metal ions bound to their donors by the model's LINK records
Metal sites were held only by the 1.9 A metal contact of the repulsions; REFMAC restrains the metal LINKs of the coordinate file (its documentation), which was the one geometry term the 0-cycle comparison did not share (one 2.8 A set: bond rmsd 0.018 against REFMAC's 0.042). - monomers/metals.cif: the Monomer Library's own statistics of metal sites (metals.json, LGPL data as the rest of monomers/), cut by update_monomers.py to the metal ions of its compound list and O, N, S donors: mean distance, spread and number of sites per metal, donor element and coordination number. - AddMetalLinks (before prepare_topology): every LINK / struct_conn record between a metal ion and an O, N or S that no link of the dictionary describes (its ZN-CYS, ZN-HIS, FE-CYS, FE-HIS are taken as they are) gets a link of one bond at EmbeddedMetalDistance for the two elements and the ion's coordination - the number of its donor records, symmetry mates included - from the rows resting on at least 20 sites (the coordination's own row, else the neighbours interpolated), sigma their spread (>= 0.02 A); a His or Cys donor loses the proton the bond replaces (DEL-HD1 / DEL-HE2 / CYSmod1, as the library's links do). Records only, no distance search; records to a symmetry mate count for the coordination but are not restrained (bonds to symmetry mates are not, anywhere). - The metal bonds are a restraint set of their own (RefinementTopology::metal_bonds), evaluated with the geometry and reported apart (GeometryStatistics::rmsd_metal, REFINE_RMSD_METAL_BONDS, REFINE_SCORE_RMSD_METAL_BONDS / _BONDS_ALL): their sigma is the spread of real sites, so they sit further from ideal than covalent bonds, and the weight ladder's 0.02 A bound stays a bound on the covalent ones. refine_parity.py compares REFMAC's bond rmsd (links included) with the combined one, and takes --gpu. Score-only geometry against REFMAC 5.8 0-cycle, all bonds (count): 2.8 A Zn/Fe set 0.0402 (4934) vs 0.042 (4935), was 0.0178; a Zn-Cys set 0.0186 (7102) vs 0.019 (7102); a Ca set 0.0297 vs 0.026 (REFMAC also refines the sugars and uses one Ca-O distance); a Mg set 0.0114 vs 0.017 (REFMAC restrains 8 symmetry links and puts Mg-O at 1.99 A, the library's statistics at 2.07). R of the scored model unchanged (+0.0001 on the first from the removed His/Cys protons). Refined R-free with the links vs without: +0.0016, -0.0002, +0.0005, +0.0004. Test: RefinementRestraints_metal_links (Zn-Cys4 by the dictionary's link, Mg(H2O)6 by the new ones, both back at their distances). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu (cherry picked from commit 0fcffc5c71e03e184cff18f07524473462a57b68)
This commit is contained in:
1 parent
9da3851844
commit
bcb71136ab
13 files changed
+547
-29
No files matched your search
@@ -3,6 +3,8 @@
|
||||
|
||||
#include <catch2/catch_all.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <map>
|
||||
#include <random>
|
||||
@@ -247,6 +249,88 @@ TEST_CASE("RefinementRestraints_ideal_residues", "[Refinement]") {
|
||||
CHECK(g.rmsd_chirals < 0.2);
|
||||
}
|
||||
|
||||
// A zinc bound by four cysteines (the dictionary's own ZN-CYS link) and a magnesium by six waters (links made from
|
||||
// the library's metal-site statistics), both from LINK records: the bonds come out at the expected distances,
|
||||
// and a metal set off its site returns there under them alone.
|
||||
TEST_CASE("RefinementRestraints_metal_links", "[Refinement]") {
|
||||
const gemmi::UnitCell cell(40, 40, 40, 90, 90, 90);
|
||||
gemmi::Structure st = residue_crystal({"CYS", "CYS", "CYS", "CYS", "ZN", "HOH", "HOH", "HOH", "HOH", "HOH", "HOH",
|
||||
"MG"}, cell, "P 1", 11);
|
||||
double mg_o = 0, mg_sigma = 0;
|
||||
REQUIRE(EmbeddedMetalDistance(gemmi::El::Mg, gemmi::El::O, 6, mg_o, mg_sigma));
|
||||
CHECK(mg_o == Catch::Approx(2.07).margin(0.02)); // Mg-O of an octahedral site
|
||||
constexpr double ZN_S = 2.34; // the ZN-CYS link
|
||||
std::vector<gemmi::Residue> &res = st.models[0].chains[0].residues;
|
||||
// the donors on a tetrahedron and an octahedron around their sites, each residue moved as a whole
|
||||
const gemmi::Position zn_site(10, 10, 10), mg_site(25, 25, 25);
|
||||
const double t = 1.0 / std::sqrt(3.0);
|
||||
const gemmi::Position tetra[4] = {{t, t, t}, {t, -t, -t}, {-t, t, -t}, {-t, -t, t}};
|
||||
const gemmi::Position octa[6] = {{1, 0, 0}, {-1, 0, 0}, {0, 1, 0}, {0, -1, 0}, {0, 0, 1}, {0, 0, -1}};
|
||||
auto donor_to = [](gemmi::Residue &r, const std::string &atom, const gemmi::Position &target) {
|
||||
const gemmi::Position shift = target - r.find_atom(atom, '*')->pos;
|
||||
for (gemmi::Atom &a : r.atoms)
|
||||
a.pos += shift;
|
||||
};
|
||||
for (int k = 0; k < 4; k++)
|
||||
donor_to(res[k], "SG", zn_site + tetra[k] * ZN_S);
|
||||
for (int k = 0; k < 6; k++)
|
||||
donor_to(res[5 + k], "O", mg_site + octa[k] * mg_o);
|
||||
// the metals off their sites by 0.3 A
|
||||
res[4].atoms[0].pos = zn_site + gemmi::Position(0.2, -0.15, 0.17);
|
||||
res[11].atoms[0].pos = mg_site + gemmi::Position(-0.1, 0.25, 0.12);
|
||||
auto link = [&](int metal, int donor, const char *atom) {
|
||||
gemmi::Connection c;
|
||||
c.type = gemmi::Connection::MetalC;
|
||||
c.asu = gemmi::Asu::Same;
|
||||
c.partner1 = gemmi::AtomAddress("A", res[donor].seqid, res[donor].name, atom);
|
||||
c.partner2 = gemmi::AtomAddress("A", res[metal].seqid, res[metal].name, res[metal].atoms[0].name);
|
||||
st.connections.push_back(c);
|
||||
};
|
||||
for (int k = 0; k < 4; k++)
|
||||
link(4, k, "SG");
|
||||
for (int k = 0; k < 6; k++)
|
||||
link(11, 5 + k, "O");
|
||||
|
||||
const RefinementTopology topo = BuildRefinementTopology(st);
|
||||
CHECK(topo.metal_links == 6); // the Mg-O links; Zn-S is the dictionary's
|
||||
std::vector<gemmi::Atom *> atoms = atoms_of(st.models[0]);
|
||||
const int zn = static_cast<int>(std::find(atoms.begin(), atoms.end(), &res[4].atoms[0]) - atoms.begin());
|
||||
const int mg = static_cast<int>(std::find(atoms.begin(), atoms.end(), &res[11].atoms[0]) - atoms.begin());
|
||||
// the metals' bonds kept apart from the covalent ones, none left among them
|
||||
for (const RestraintRecord &r : topo.geometry.records)
|
||||
CHECK_FALSE((r.kind == static_cast<int>(RestraintKind::Bond) &&
|
||||
(r.atom[0] == zn || r.atom[1] == zn || r.atom[0] == mg || r.atom[1] == mg)));
|
||||
const RestraintSet &metal_bonds = topo.metal_bonds;
|
||||
REQUIRE(metal_bonds.records.size() == 10);
|
||||
for (const RestraintRecord &r : metal_bonds.records) {
|
||||
const bool is_zn = r.atom[0] == zn || r.atom[1] == zn;
|
||||
CHECK((is_zn || r.atom[0] == mg || r.atom[1] == mg));
|
||||
CHECK(r.ideal == Catch::Approx(is_zn ? ZN_S : mg_o).margin(1e-4));
|
||||
}
|
||||
|
||||
// the two metals moved down the gradient of their bonds, everything else held
|
||||
std::vector<std::array<double, 4>> xyzb;
|
||||
for (gemmi::Atom *a : atoms)
|
||||
xyzb.push_back({a->pos.x, a->pos.y, a->pos.z, a->b_iso});
|
||||
double e = EvaluateRestraints(metal_bonds, xyzb, nullptr), step = 1e-4;
|
||||
for (int it = 0; it < 2000 && step > 1e-12; it++) {
|
||||
std::vector<std::array<double, 4>> grad(xyzb.size(), {0, 0, 0, 0}), trial = xyzb;
|
||||
EvaluateRestraints(metal_bonds, xyzb, &grad);
|
||||
for (int a : {zn, mg})
|
||||
for (int k = 0; k < 3; k++)
|
||||
trial[a][k] -= step * grad[a][k];
|
||||
const double e_trial = EvaluateRestraints(metal_bonds, trial, nullptr);
|
||||
if (e_trial < e) {
|
||||
xyzb = trial;
|
||||
e = e_trial;
|
||||
step *= 1.5;
|
||||
} else
|
||||
step *= 0.5;
|
||||
}
|
||||
for (const RestraintRecord &r : metal_bonds.records)
|
||||
CHECK(RestraintValue(metal_bonds, r, xyzb) == Catch::Approx(r.ideal).margin(0.005));
|
||||
}
|
||||
|
||||
TEST_CASE("RefinementRestraints_gradient", "[Refinement]") {
|
||||
const gemmi::UnitCell cell(14, 15, 16, 90, 90, 90);
|
||||
gemmi::Structure st = residue_crystal({"TYR", "DG"}, cell, "P 21 21 21", 5);
|
||||
|
||||
Reference in new issue
Block a user