Refinement: common cofactors and modified RNA nucleotides in the embedded dictionary; heme iron restrained from LINK records

The embedded CCP4 Monomer Library subset grows from 62 to 84 compounds:
- the modified tRNA/rRNA nucleotides PSU, 5MC, 1MA, OMG, OMC, OMU, H2U, M2G, 7MG, 2MG, 5MU (RNA group,
  joined by the library's backbone link "p");
- the cofactors NAD, NAI, NAP, NDP, FAD, FMN, HEM, HEC, SAM, SAH, and dioxygen (OXY). CMO is left out:
  its dictionary has C-O at 1.25 A, where bound CO is near 1.15 A.
links_and_mods.cif gains the links these compounds use: HEC-CYS1/HEC-CYS2 (heme C thioethers),
CYS-HEMB/CYS-HEMC, CYS-FAD and HIS-FAD1/2, with their modifications.

AddMetalLinks now takes a metal atom of any residue, not only a one-atom ion, so a heme iron's LINK records
to His NE2 / Met SD / water become bonds at the library's metal-site distances; the site's coordination counts
the metal's bonds in its own dictionary (a heme's four pyrrole N) as well as the records. A metal within a
ligand is also a metal for the repulsions (1.9 A to N, O, S) and its dictionary bonds are kept with the metal
bonds. Note HEC.cif's own Fe-N(pyrrole) ideal is 1.90 A (HEM: 2.04 A), so a cytochrome c reports a metal-bond
rmsd near 0.1 A.

Test RefinementRestraints_deposited_ligands builds the topology of eleven deposited models cut down to the
ligand and its surroundings (tests/test_data/ligands, 66 KB, from PDB 1EHZ, 8BKH, 6K9I, 2W3V, 5VML, 6F3P,
8CON, 3TK0, 7TCD, 6W4H, 5EPE): every ligand restrained, bond rmsd < 0.03 A, the RNA backbone bonded to each
modified nucleotide, the heme Fe-His bonds, and the HEC thioethers. rugnux --mode refine on the full entries
against their deposited structure factors: R-free within +-0.003 of the build where these were held fixed,
ligand bond rmsd vs the dictionary from 0.004-0.058 A down to 0.002-0.042 A. rugnux grows by 425 KiB.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit f4d407c8de6cb462320edb21a70665350e436d28)
This commit is contained in:
leonarski_f committed 2026-10-26 13:56:29 +01:00
1 parent d90bc68085
commit 00662c4110
41 files changed
+8953 -36

No files matched your search

@@ -100,9 +100,15 @@ V3<double> FromImage(const RestraintRecord &r, V3<double> g) {
r.rot[2] * g.x + r.rot[5] * g.y + r.rot[8] * g.z};
}
// A metal ion: a residue of one atom, of a metal element.
bool IsMetalIon(const gemmi::CRA &cra) {
return cra.residue->atoms.size() == 1 && IsMetal(cra.atom->element);
// The bonds the dictionary gives a metal atom within its own residue: none for an ion, four for a heme's iron.
int DictionaryMetalBonds(const gemmi::MonLib &monlib, const gemmi::CRA &metal) {
auto cc = monlib.monomers.find(metal.residue->name);
if (cc == monlib.monomers.end())
return 0;
int n = 0;
for (const gemmi::Restraints::Bond &b : cc->second.rt.bonds)
n += b.id1.atom == metal.atom->name || b.id2.atom == metal.atom->name;
return n;
}
bool IsMetalDonor(const gemmi::Element &el) {
@@ -121,11 +127,12 @@ std::string MetalDonorModification(const std::string &resname, const std::string
return "";
}
// The model's LINK / struct_conn records between a metal ion and an O, N or S that no link of the dictionary
// describes (its ZN-CYS, ZN-HISND, ZN-HISNE, FE-CYS, FE-HISND and FE-HISNE are taken as they are): a link of
// one bond is made for each in `monlib`, at the distance of the library's statistics of metal sites
// (EmbeddedMetalDistance) for the two elements and the site's coordination - the number of records between the
// ion and an O, N or S - and the record is pointed at it, so that prepare_topology() restrains it like any other link.
// The model's LINK / struct_conn records between a metal atom (an ion, or the iron of a heme) and an O, N or S
// that no link of the dictionary describes (its ZN-CYS, ZN-HISND, ZN-HISNE, FE-CYS, FE-HISND and FE-HISNE are
// taken as they are): a link of one bond is made for each in `monlib`, at the distance of the library's statistics
// of metal sites (EmbeddedMetalDistance) for the two elements and the site's coordination - the number of records
// between the metal and an O, N or S, plus the bonds its own residue's dictionary gives it (a heme's four pyrrole
// N) - and the record is pointed at it, so that prepare_topology() restrains it like any other link.
// Only the bond: no angles at the metal. Sites are taken from the records alone, as REFMAC takes links from
// the coordinate file; a contact without a record stays a repulsion (BuildRepulsions, METAL_CONTACT).
// Records to a symmetry mate are left out, as every bond to a symmetry mate is (BuildRefinementTopology).
@@ -145,13 +152,15 @@ int AddMetalLinks(gemmi::Structure &st, gemmi::MonLib &monlib) {
gemmi::CRA a = model.find_cra(conn.partner1, true), b = model.find_cra(conn.partner2, true);
if (!a.atom || !b.atom)
continue;
const bool a_metal = IsMetalIon(a), b_metal = IsMetalIon(b);
const bool a_metal = IsMetal(a.atom->element), b_metal = IsMetal(b.atom->element);
if (a_metal == b_metal)
continue;
const gemmi::CRA &metal = a_metal ? a : b, &donor = a_metal ? b : a;
if (!IsMetalDonor(donor.atom->element))
continue;
// the site's coordination: every record of the ion, a symmetry mate's or one the dictionary describes too
// the site's coordination: every record of the metal, a symmetry mate's or one the dictionary describes too
if (coordination.count(metal.atom) == 0)
coordination[metal.atom] = DictionaryMetalBonds(monlib, metal);
coordination[metal.atom]++;
if (!conn.link_id.empty() || conn.asu == gemmi::Asu::Different ||
std::get<0>(monlib.match_link(*a.residue, a.atom->name, conn.partner1.altloc, *b.residue, b.atom->name,
@@ -410,7 +419,7 @@ RefinementTopology BuildRefinementTopology(gemmi::Structure &st, bool hydrogens)
out.refinable[i] = known && a.occ > 0 && !a.is_hydrogen();
out.vdw_radius[i] = VdwRadius(monlib, res.name, a);
out.hbond[i] = res.is_water() || CanHydrogenBond(monlib, res.name, a);
out.metal[i] = res.atoms.size() == 1 && IsMetal(a.element);
out.metal[i] = IsMetal(a.element);
out.altloc[i] = a.altloc;
out.element[i] = a.element.elem;
}