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)
97 lines
3.6 KiB
C++
97 lines
3.6 KiB
C++
// SPDX-FileCopyrightText: 2026 Filip Leonarski, Paul Scherrer Institute <filip.leonarski@psi.ch>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "MonomerLibrary.h"
|
|
|
|
#include <algorithm>
|
|
#include <cstring>
|
|
#include <vector>
|
|
|
|
#include "gemmi/numb.hpp" // as_number
|
|
#include "gemmi/read_cif.hpp" // read_cif_from_memory
|
|
|
|
// Generated by CMakeLists.txt from monomers/*.cif (MonomerLibraryData.cpp in the build tree).
|
|
extern const EmbeddedMonomerFile embedded_monomer_files[];
|
|
extern const size_t embedded_monomer_file_count;
|
|
|
|
namespace {
|
|
|
|
struct MetalRow {
|
|
gemmi::El metal, ligand;
|
|
int coordination;
|
|
double distance, std;
|
|
int count;
|
|
};
|
|
|
|
struct EmbeddedLibrary {
|
|
gemmi::MonLib monlib;
|
|
std::vector<MetalRow> metals; // metals.cif
|
|
};
|
|
|
|
EmbeddedLibrary ReadEmbeddedMonomerLibrary() {
|
|
EmbeddedLibrary lib;
|
|
gemmi::MonLib &monlib = lib.monlib;
|
|
for (size_t i = 0; i < embedded_monomer_file_count; i++) {
|
|
const EmbeddedMonomerFile &f = embedded_monomer_files[i];
|
|
std::string text;
|
|
for (size_t k = 0; k < f.n_pieces; k++)
|
|
text += f.pieces[k];
|
|
gemmi::cif::Document doc = gemmi::read_cif_from_memory(text.data(), text.size(), f.name);
|
|
if (std::strcmp(f.name, "ener_lib.cif") == 0)
|
|
monlib.ener_lib.read(doc);
|
|
else if (std::strcmp(f.name, "metals.cif") == 0) {
|
|
for (const auto &row : doc.blocks.at(0).find("_metal_coordination.",
|
|
{"metal", "ligand", "coordination", "distance", "std", "count"}))
|
|
lib.metals.push_back({gemmi::find_element(row[0].c_str()), gemmi::find_element(row[1].c_str()),
|
|
gemmi::cif::as_int(row[2]), gemmi::cif::as_number(row[3]),
|
|
gemmi::cif::as_number(row[4]), gemmi::cif::as_int(row[5])});
|
|
} else
|
|
monlib.read_monomer_doc(doc);
|
|
}
|
|
return lib;
|
|
}
|
|
|
|
const EmbeddedLibrary &Library() {
|
|
static const EmbeddedLibrary lib = ReadEmbeddedMonomerLibrary();
|
|
return lib;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
const gemmi::MonLib &EmbeddedMonomerLibrary() {
|
|
return Library().monlib;
|
|
}
|
|
|
|
bool EmbeddedMetalDistance(gemmi::El metal, gemmi::El ligand, int coordination, double &distance, double &sigma) {
|
|
// the rows of the pair resting on enough sites: the nearest at or below the coordination and above it
|
|
const MetalRow *lo = nullptr, *hi = nullptr;
|
|
for (const MetalRow &r : Library().metals) {
|
|
if (r.metal != metal || r.ligand != ligand || r.count < METAL_MIN_SITES)
|
|
continue;
|
|
if (r.coordination <= coordination && (lo == nullptr || r.coordination > lo->coordination))
|
|
lo = &r;
|
|
if (r.coordination > coordination && (hi == nullptr || r.coordination < hi->coordination))
|
|
hi = &r;
|
|
}
|
|
if (lo == nullptr && hi == nullptr)
|
|
return false;
|
|
if (lo != nullptr && (lo->coordination == coordination || hi == nullptr)) {
|
|
distance = lo->distance;
|
|
sigma = lo->std;
|
|
} else if (lo == nullptr) {
|
|
distance = hi->distance;
|
|
sigma = hi->std;
|
|
} else {
|
|
// between two coordination numbers: linear in the coordination
|
|
const double t = static_cast<double>(coordination - lo->coordination) / (hi->coordination - lo->coordination);
|
|
distance = lo->distance + t * (hi->distance - lo->distance);
|
|
sigma = lo->std + t * (hi->std - lo->std);
|
|
}
|
|
sigma = std::max(sigma, METAL_MIN_SIGMA);
|
|
return true;
|
|
}
|
|
|
|
bool InEmbeddedMonomerLibrary(const std::string &code) {
|
|
return EmbeddedMonomerLibrary().monomers.count(code) > 0;
|
|
}
|