Files
Jungfraujoch/gemmi_gph/monlib.cpp
T
leonarski_f e58453a693 Refinement restraints: the CCP4 Monomer Library subset embedded in rugnux, read through gemmi's MonLib
A restraint dictionary carried in the binary, so refinement reads nothing from disk and needs no
CLIBD_MON: the 20 amino acids and MSE, the DNA/RNA nucleotides, ATP/ADP/AMP/GTP/GDP/GMP, DMSO, PEG
fragments (PEG, PGE, PG4, 1PE), GOL, EDO, SO4, PO4, ACT, MPD, water and common ions - copied verbatim
from the CCP4 Monomer Library (LGPL-3.0) - plus links_and_mods.cif cut down to the links and
modifications those compounds can take (peptide, cis-peptide, proline, N-methyl, disulfide,
nucleic-acid backbone, termini, metal-Cys/His) and ener_lib.cif cut to its atom-type table.
monomers/update_monomers.py holds the compound list and regenerates the subset; CMake embeds every
.cif of the directory as string pieces under MSVC's literal limit; EmbeddedMonomerLibrary() parses
them from memory into a gemmi::MonLib once per process. About 0.7 MB of text in the binary.

gemmi v0.7.5's monlib.cpp, topo.cpp and riding_h.cpp vendored verbatim (prepare_topology). Licence
text in licenses/, rows and a note in THIRD_PARTY_NOTICES.md.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015CeEGgpKdfkCTJb3YNMJcu
(cherry picked from commit 38034718052c4a300691393b5b817f476c83dfc2)
2026-10-25 07:20:32 +01:00

634 lines
24 KiB
C++

// Copyright 2018-2023 Global Phasing Ltd.
#include <gemmi/monlib.hpp>
#include <gemmi/calculate.hpp> // for calculate_chiral_volume
#include <gemmi/modify.hpp> // for rename_atom_names
#include <gemmi/read_cif.hpp> // for read_cif_gz
#include <gemmi/numb.hpp> // for as_number
namespace gemmi {
int ChemLink::calculate_score(const Residue& res1, const Residue* res2,
char alt, char alt2,
const ChemComp::Aliasing* aliasing1,
const ChemComp::Aliasing* aliasing2) const {
int link_score = side1.specificity() + side2.specificity();
auto get_from = [&](const Restraints::AtomId& atomid) {
const auto* aliasing = (atomid.comp != 2 || res2 == nullptr) ? aliasing1 : aliasing2;
if (aliasing)
if (const std::string* real_id = aliasing->name_from_alias(atomid.atom))
return Restraints::AtomId{atomid.comp, *real_id}.get_from(res1, res2, alt, alt2);
return atomid.get_from(res1, res2, alt, alt2);
};
// check chirality
for (const Restraints::Chirality& chirality : rt.chirs)
if (chirality.sign != ChiralityType::Both) {
const Atom* a1 = get_from(chirality.id_ctr);
const Atom* a2 = get_from(chirality.id1);
const Atom* a3 = get_from(chirality.id2);
const Atom* a4 = get_from(chirality.id3);
if (a1 && a2 && a3 && a4) {
double vol = calculate_chiral_volume(a1->pos, a2->pos,
a3->pos, a4->pos);
if (chirality.is_wrong(vol))
link_score -= 10;
}
}
// check fixed torsion angle (_chem_link_tor.period == 0)
for (const Restraints::Torsion& tor : rt.torsions)
if (tor.period == 0) {
const Atom* a1 = get_from(tor.id1);
const Atom* a2 = get_from(tor.id2);
const Atom* a3 = get_from(tor.id3);
const Atom* a4 = get_from(tor.id4);
double z = 10.;
if (a1 && a2 && a3 && a4)
z = angle_z(calculate_dihedral(a1->pos, a2->pos, a3->pos, a4->pos),
tor);
link_score -= (int) z;
}
return link_score;
}
namespace {
Restraints read_link_restraints(const cif::Block& block_) {
auto read_aid = [](cif::Table::Row& row, int n) {
return Restraints::AtomId{cif::as_int(row[n]), row.str(n+1)};
};
Restraints rt;
cif::Block& block = const_cast<cif::Block&>(block_);
for (auto row : block.find("_chem_link_bond.",
{"atom_1_comp_id", "atom_id_1",
"atom_2_comp_id", "atom_id_2",
"type",
"value_dist", "value_dist_esd"}))
rt.bonds.push_back({read_aid(row, 0), read_aid(row, 2),
bond_type_from_string(row[4]), false,
cif::as_number(row[5]), cif::as_number(row[6]),
NAN, NAN});
for (auto row : block.find("_chem_link_angle.",
{"atom_1_comp_id", "atom_id_1",
"atom_2_comp_id", "atom_id_2",
"atom_3_comp_id", "atom_id_3",
"value_angle", "value_angle_esd"}))
rt.angles.push_back({read_aid(row, 0), read_aid(row, 2), read_aid(row, 4),
cif::as_number(row[6]), cif::as_number(row[7])});
for (auto row : block.find("_chem_link_tor.",
{"id",
"atom_1_comp_id", "atom_id_1",
"atom_2_comp_id", "atom_id_2",
"atom_3_comp_id", "atom_id_3",
"atom_4_comp_id", "atom_id_4",
"value_angle", "value_angle_esd",
"period"}))
rt.torsions.push_back({row.str(0),
read_aid(row, 1), read_aid(row, 3),
read_aid(row, 5), read_aid(row, 7),
cif::as_number(row[9]), cif::as_number(row[10]),
cif::as_int(row[11])});
for (auto row : block.find("_chem_link_chir.",
{"atom_centre_comp_id", "atom_id_centre",
"atom_1_comp_id", "atom_id_1",
"atom_2_comp_id", "atom_id_2",
"atom_3_comp_id", "atom_id_3",
"volume_sign"}))
if (row[4][0] != 'c') // ignore crossN
rt.chirs.push_back({read_aid(row, 0), read_aid(row, 2),
read_aid(row, 4), read_aid(row, 6),
chirality_from_string(row[8])});
for (auto row : block.find("_chem_link_plane.",
{"plane_id", "atom_comp_id", "atom_id",
"dist_esd"})) {
Restraints::Plane& plane = rt.get_or_add_plane(row.str(0));
if (plane.esd == 0.0)
plane.esd = cif::as_number(row[3]);
plane.ids.push_back(read_aid(row, 1));
}
return rt;
}
void insert_chemlinks_into(const cif::Document& doc, std::map<std::string,ChemLink>& links) {
const cif::Block* list_block = doc.find_block("link_list");
auto use_chem_link = [](const cif::Block& block, ChemLink& link) {
for (auto row : const_cast<cif::Block&>(block).find("_chem_link.",
{"id", "?name",
"?comp_id_1", "?mod_id_1", "?group_comp_1",
"?comp_id_2", "?mod_id_2", "?group_comp_2"})) {
if (row.str(0) == link.id) {
if (row.has2(1))
link.name = row.str(1);
if (row.has2(2))
link.side1.comp = row.str(2);
if (row.has2(3))
link.side1.mod = row.str(3);
if (row.has2(4))
link.side1.group = ChemComp::read_group(row[4]);
if (row.has2(5))
link.side2.comp = row.str(5);
if (row.has2(6))
link.side2.mod = row.str(6);
link.side2.group = ChemComp::read_group(row[7]);
break;
}
}
};
for (const cif::Block& block : doc.blocks)
if (starts_with(block.name, "link_") && block.name != "link_list") {
ChemLink link;
link.id = block.name.substr(5);
if (list_block)
use_chem_link(*list_block, link);
use_chem_link(block, link);
link.rt = read_link_restraints(block);
link.block = block;
links.emplace(link.id, link);
}
}
// Helper function. str is one of "add", "delete", "change".
int chem_mod_type(const std::string& str) {
char c = str[0] | 0x20;
if (c != 'a' && c != 'd' && c != 'c')
fail("Unexpected value of _chem_mod_*.function: " + str);
return c;
}
Restraints read_restraint_modifications(const cif::Block& block_) {
Restraints rt;
cif::Block& block = const_cast<cif::Block&>(block_);
for (auto row : block.find("_chem_mod_bond.",
{"function", "atom_id_1", "atom_id_2",
"new_type",
"new_value_dist", "new_value_dist_esd",
"?new_value_dist_nucleus", "?new_value_dist_nucleus_esd"}))
rt.bonds.push_back({Restraints::AtomId{chem_mod_type(row[0]), row.str(1)},
Restraints::AtomId{1, row.str(2)},
bond_type_from_string(row[3]), false,
cif::as_number(row[4]), cif::as_number(row[5]),
row.has(6) ? cif::as_number(row[6]) : NAN,
row.has(7) ? cif::as_number(row[7]) : NAN});
for (auto row : block.find("_chem_mod_angle.",
{"function", "atom_id_1",
"atom_id_2", "atom_id_3",
"new_value_angle", "new_value_angle_esd"}))
rt.angles.push_back({{chem_mod_type(row[0]), row.str(1)},
{1, row.str(2)}, {1, row.str(3)},
cif::as_number(row[4]), cif::as_number(row[5])});
for (auto row : block.find("_chem_mod_tor.",
{"function", "?id", "atom_id_1",
"atom_id_2", "atom_id_3", "atom_id_4",
"new_value_angle", "new_value_angle_esd",
"?new_period"}))
rt.torsions.push_back({row.has(1) ? row.str(1) : "",
{chem_mod_type(row[0]), row.str(2)},
{1, row.str(3)}, {1, row.str(4)}, {1, row.str(5)},
cif::as_number(row[6]), cif::as_number(row[7]),
row.has(8) ? cif::as_int(row[8], 0) : -1});
for (auto row : block.find("_chem_mod_chir.",
{"function", "atom_id_centre", "atom_id_1",
"atom_id_2", "atom_id_3",
"new_volume_sign"}))
rt.chirs.push_back({{1, row.str(1)}, {chem_mod_type(row[0]), row.str(2)},
{1, row.str(3)}, {1, row.str(4)},
chirality_from_string(row[5])});
for (auto row : block.find("_chem_mod_plane_atom.",
{"function", "plane_id", "atom_id" ,
"new_dist_esd"})) {
Restraints::Plane& plane = rt.get_or_add_plane(row.str(1));
if (plane.esd == 0.0)
plane.esd = cif::as_number(row[3]);
plane.ids.push_back({chem_mod_type(row[0]), row.str(2)});
}
return rt;
}
void insert_chemmods_into(const cif::Document& doc, std::map<std::string, ChemMod>& mods) {
const cif::Block* list_block = doc.find_block("mod_list");
auto use_chem_mod = [](const cif::Block& block, ChemMod& mod) {
for (auto row : const_cast<cif::Block&>(block).find("_chem_mod.",
{"id", "?name", "?comp_id", "?group_id"}))
if (row.str(0) == mod.id) {
if (row.has2(1))
mod.name = row.str(1);
if (row.has2(2))
mod.comp_id = row.str(2);
if (row.has2(3))
mod.group_id = row.str(3);
break;
}
};
for (const cif::Block& block : doc.blocks)
if (starts_with(block.name, "mod_") && block.name != "mod_list") {
ChemMod mod;
mod.id = block.name.substr(4);
if (list_block)
use_chem_mod(*list_block, mod);
use_chem_mod(block, mod);
for (auto ra : const_cast<cif::Block&>(block).find("_chem_mod_atom.",
{"function", "atom_id", "new_atom_id",
"new_type_symbol", "new_type_energy",
"?new_charge", "?new_partial_charge"}))
mod.atom_mods.push_back({chem_mod_type(ra[0]), ra.str(1), ra.str(2),
Element(ra.str(3)),
(float) cif::as_number(ra.one_of(5, 6)),
ra.str(4)});
mod.rt = read_restraint_modifications(block);
mod.block = block;
mods.emplace(mod.id, mod);
}
}
} // anonymous namespace
void ChemMod::apply_to(ChemComp& chemcomp, ChemComp::Group alias_group) const {
auto real = [&chemcomp, alias_group](const std::string& atom_id) -> const std::string& {
if (alias_group != ChemComp::Group::Null) {
const ChemComp::Aliasing& aliasing = chemcomp.get_aliasing(alias_group);
if (const std::string* real_id = aliasing.name_from_alias(atom_id))
return *real_id;
}
return atom_id; // NOLINT(bugprone-return-const-ref-from-parameter)
};
cat_to(chemcomp.name, '+', id);
// _chem_mod_atom
for (const AtomMod& mod : atom_mods) {
if (mod.func == 'a') {
if (mod.new_id.empty())
fail("New atom id is not given");
if (!chemcomp.has_atom(real(mod.new_id)))
chemcomp.atoms.push_back({mod.new_id, "", mod.el,
std::isnan(mod.charge) ? mod.charge : 0,
mod.chem_type, "", Position()});
continue;
}
const std::string& old = real(mod.old_id);
auto it = chemcomp.find_atom(old);
switch (mod.func) {
case 'd':
if (it != chemcomp.atoms.end()) {
chemcomp.atoms.erase(it);
// delete restraints containing mod.old_id
vector_remove_if(chemcomp.rt.bonds, [&](const Restraints::Bond& b) {
return b.id1 == old || b.id2 == old;
});
vector_remove_if(chemcomp.rt.angles, [&](const Restraints::Angle& a) {
return a.id1 == old || a.id2 == old || a.id3 == old;
});
vector_remove_if(chemcomp.rt.torsions,
[&](const Restraints::Torsion& t) {
return t.id1 == old || t.id2 == old || t.id3 == old ||
t.id4 == old;
});
vector_remove_if(chemcomp.rt.chirs,
[&](const Restraints::Chirality& c) {
return c.id_ctr == old || c.id1 == old || c.id2 == old ||
c.id3 == old;
});
for (Restraints::Plane& plane : chemcomp.rt.planes)
vector_remove_if(plane.ids, [&](const Restraints::AtomId& a) {
return a.atom == old;
});
}
break;
case 'c':
if (it != chemcomp.atoms.end()) {
// the modification shouldn't change the atom name, so we don't do:
// if (!mod.new_id.empty())
// it->id = mod.new_id;
if (mod.el != El::X)
it->el = mod.el;
if (!std::isnan(mod.charge))
it->charge = mod.charge;
if (!mod.chem_type.empty())
it->chem_type = mod.chem_type;
}
break;
}
}
// _chem_mod_bond
for (const Restraints::Bond& mod : rt.bonds) {
auto it = chemcomp.rt.find_bond(real(mod.id1.atom), real(mod.id2.atom));
switch (mod.id1.comp) {
case 'a':
if (it == chemcomp.rt.bonds.end()) {
chemcomp.rt.bonds.push_back(mod);
// id1.comp was temporarily set to 'a', set it back to 1
chemcomp.rt.bonds.back().id1.comp = 1;
}
break;
case 'd':
if (it != chemcomp.rt.bonds.end())
chemcomp.rt.bonds.erase(it);
break;
case 'c':
if (it != chemcomp.rt.bonds.end()) {
if (mod.type != BondType::Unspec)
it->type = mod.type;
if (!std::isnan(mod.value))
it->value = mod.value;
if (!std::isnan(mod.esd))
it->esd = mod.esd;
if (!std::isnan(mod.value_nucleus))
it->value_nucleus = mod.value_nucleus;
if (!std::isnan(mod.esd_nucleus))
it->esd_nucleus = mod.esd_nucleus;
}
break;
}
}
// _chem_mod_angle
for (const Restraints::Angle& mod : rt.angles) {
auto it = chemcomp.rt.find_angle(real(mod.id1.atom),
real(mod.id2.atom),
real(mod.id3.atom));
switch (mod.id1.comp) {
case 'a':
if (it == chemcomp.rt.angles.end()) {
chemcomp.rt.angles.push_back(mod);
chemcomp.rt.angles.back().id1.comp = 1;
}
break;
case 'd':
if (it != chemcomp.rt.angles.end())
chemcomp.rt.angles.erase(it);
break;
case 'c':
if (it != chemcomp.rt.angles.end()) {
if (!std::isnan(mod.value))
it->value = mod.value;
if (!std::isnan(mod.esd))
it->esd = mod.esd;
}
break;
}
}
// _chem_mod_tor
for (const Restraints::Torsion& mod : rt.torsions) {
auto it = chemcomp.rt.find_torsion(real(mod.id1.atom), real(mod.id2.atom),
real(mod.id3.atom), real(mod.id4.atom));
switch (mod.id1.comp) {
case 'a':
if (it == chemcomp.rt.torsions.end()) {
chemcomp.rt.torsions.push_back(mod);
chemcomp.rt.torsions.back().id1.comp = 1;
}
break;
case 'd':
if (it != chemcomp.rt.torsions.end())
chemcomp.rt.torsions.erase(it);
break;
case 'c':
if (it != chemcomp.rt.torsions.end()) {
if (!mod.label.empty())
it->label = mod.label;
if (!std::isnan(mod.value))
it->value = mod.value;
if (!std::isnan(mod.esd))
it->esd = mod.esd;
if (mod.period != -1)
it->period = mod.period;
}
break;
}
}
// _chem_mod_chir
for (const Restraints::Chirality& mod : rt.chirs) {
auto it = chemcomp.rt.find_chir(real(mod.id_ctr.atom), real(mod.id1.atom),
real(mod.id2.atom), real(mod.id3.atom));
switch (mod.id1.comp) {
case 'a':
if (it == chemcomp.rt.chirs.end()) {
chemcomp.rt.chirs.push_back(mod);
chemcomp.rt.chirs.back().id1.comp = 1;
}
break;
case 'd':
if (it != chemcomp.rt.chirs.end())
chemcomp.rt.chirs.erase(it);
break;
case 'c':
if (it != chemcomp.rt.chirs.end())
it->sign = mod.sign;
break;
}
}
// _chem_mod_plane_atom
for (const Restraints::Plane& mod : rt.planes)
for (const Restraints::AtomId& atom_id : mod.ids) {
const std::string& real_id = real(atom_id.atom);
if (atom_id.comp == 'a') {
Restraints::Plane& plane = chemcomp.rt.get_or_add_plane(mod.label);
if (plane.esd == 0.0 && !std::isnan(mod.esd))
plane.esd = mod.esd;
auto it = std::find(plane.ids.begin(), plane.ids.end(), real_id);
if (it == plane.ids.end())
plane.ids.push_back({1, real_id});
} else if (atom_id.comp == 'd') {
auto it = chemcomp.rt.get_plane(mod.label);
if (it != chemcomp.rt.planes.end()) {
auto item = std::find(it->ids.begin(), it->ids.end(), real_id);
if (item != it->ids.end())
it->ids.erase(item);
}
}
}
}
void EnerLib::read(const cif::Document& doc) {
cif::Block& block = const_cast<cif::Block&>(doc.blocks[0]);
for (const auto& row : block.find("_lib_atom.",
{"type", "hb_type", "vdw_radius", "vdwh_radius",
"ion_radius", "element", "valency", "sp"}))
atoms.emplace(row[0], Atom{Element(row[5]), row[1][0], cif::as_number(row[2]),
cif::as_number(row[3]), cif::as_number(row[4]),
cif::as_int(row[6], -1), cif::as_int(row[7], -1)});
for (const auto& row : block.find("_lib_bond.",
{"atom_type_1", "atom_type_2", "type", "length", "value_esd"}))
bonds.emplace(row.str(0), Bond{row.str(1), bond_type_from_string(row[2]),
cif::as_number(row[3]), cif::as_number(row[4])});
}
std::string MonLib::relative_monomer_path(const std::string& code) {
std::string path;
if (!code.empty()) {
path += lower(code[0]);
path += '/'; // works also on Windows
path += code;
// On Windows several names are reserved (CON, PRN, AUX, ...), see
// https://docs.microsoft.com/en-us/windows/win32/fileio/naming-a-file
// The workaround in CCP4 monomer library is to use CON_CON.cif, etc.
if (code.size() == 3)
switch (ialpha3_id(code.c_str())) {
case ialpha3_id("AUX"):
case ialpha3_id("COM"):
case ialpha3_id("CON"):
case ialpha3_id("LPT"):
case ialpha3_id("PRN"):
path += '_';
path += code;
}
path += ".cif";
}
return path;
}
void MonLib::read_monomer_doc(const cif::Document& doc) {
// ChemComp
if (const cif::Block* block = doc.find_block("comp_list"))
for (auto row : const_cast<cif::Block*>(block)->find("_chem_comp.", {"id", "group"}))
cc_groups.emplace(row.str(0), ChemComp::read_group(row.str(1)));
for (const cif::Block& block : doc.blocks)
add_monomer_if_present(block);
// ChemLink
insert_chemlinks_into(doc, links);
// ChemMod
insert_chemmods_into(doc, modifications);
}
namespace {
const std::string* find_chemtype(const std::map<std::string, ChemComp>& monomers,
const const_CRA& cra) {
auto cc = monomers.find(cra.residue->name);
if (cc != monomers.end()) {
auto cc_atom = cc->second.find_atom(cra.atom->name);
if (cc_atom != cc->second.atoms.end())
return &cc_atom->chem_type;
}
return nullptr;
}
// Searches data from _lib_atom in ener_lib.cif.
double find_radius(const EnerLib& ener_lib, const std::string& chemtype,
EnerLib::RadiusType type) {
double r = 0;
auto it = ener_lib.atoms.find(chemtype);
if (it != ener_lib.atoms.end()) {
if (type == EnerLib::RadiusType::Ion)
r = it->second.ion_radius;
else if (type == EnerLib::RadiusType::Vdw)
r = it->second.vdw_radius;
else if (type == EnerLib::RadiusType::Vdwh)
r = it->second.vdwh_radius;
}
return std::isnan(r) ? 0 : r;
}
} // anonymous namespace
void MonLib::read_monomer_cif(const std::string& path_) {
read_monomer_doc(read_cif_gz(path_));
}
bool MonLib::read_monomer_lib(const std::string& monomer_dir_,
const std::vector<std::string>& resnames,
const Logger& logger) {
if (monomer_dir_.empty())
fail("read_monomer_lib: monomer_dir not specified.");
set_monomer_dir(monomer_dir_);
// Only recent versions of CCP4 Monomer Library have links_and_mods.cif
try {
read_monomer_cif(monomer_dir + "links_and_mods.cif");
} catch (std::system_error&) {
read_monomer_cif(monomer_dir + "list/mon_lib_list.cif");
}
ener_lib.read(read_cif_gz(monomer_dir + "ener_lib.cif"));
bool ok = true;
for (const std::string& name : resnames) {
if (monomers.find(name) != monomers.end())
continue;
try {
const cif::Document& doc = read_cif_gz(path(name));
read_monomer_doc(doc);
} catch (std::system_error& err) {
if (err.code().value() == ENOENT)
logger.mesg("Monomer not in the library: ", name, '.');
else
logger.mesg("Failed to read ", name, ": ", err.what(), '.');
ok = false;
} catch (std::runtime_error& err) {
logger.mesg("Failed to read ", name, ": ", err.what(), '.');
ok = false;
}
}
return ok;
}
double MonLib::find_ideal_distance(const const_CRA& cra1, const const_CRA& cra2) const {
std::string types[2] = {cra1.atom->element.uname(),
cra2.atom->element.uname()};
if (const std::string* tmp = find_chemtype(monomers, cra1))
types[0] = *tmp;
if (const std::string* tmp = find_chemtype(monomers, cra2))
types[1] = *tmp;
// if either one is metal, use ion radius + ion radius
if (cra1.atom->element.is_metal() != cra2.atom->element.is_metal()) {
// return if ion radius is found for both of them
double r1 = find_radius(ener_lib, types[0], EnerLib::RadiusType::Ion);
double r2 = find_radius(ener_lib, types[1], EnerLib::RadiusType::Ion);
if (r1 > 0 && r2 > 0)
return r1 + r2;
}
// otherwise, look for defined distance or use average of distances
double r[2] = {0, 0};
for (int j = 0; j < 2; ++j) {
auto range = ener_lib.bonds.equal_range(types[j]);
for (auto i = range.first; i != range.second; ++i)
if (i->second.atom_type_2 == types[1-j] && !std::isnan(i->second.length))
return i->second.length;
else if (i->second.atom_type_2.empty() && i->second.type == BondType::Single)
r[j] = i->second.length / 2;
if ((r[j] == 0 || std::isnan(r[j])) && range.first != range.second)
r[j] = range.first->second.length / 2;
if (r[j] == 0 || std::isnan(r[j]))
r[j] = (j==0 ? cra1 : cra2).atom->element.covalent_r();
}
return r[0] + r[1];
}
void MonLib::update_old_atom_names(Structure& st, const Logger& logger) const {
for (const auto& it : monomers)
// monomers should have only monomers needed for this structure.
// Few of them (usually none or one) have old names defined.
if (it.second.has_old_names()) {
const std::string& resname = it.first;
const ChemComp& cc = it.second;
int old_vs_new = 0;
for (const Model& model : st.models)
for (const Chain& chain : model.chains)
for (const Residue& res : chain.residues)
if (res.name == resname) {
for (const Atom& atom : res.atoms) {
if (cc.find_atom(atom.name) == cc.atoms.end())
++old_vs_new;
if (cc.find_atom_by_old_name(atom.name) == cc.atoms.end())
--old_vs_new;
}
}
if (old_vs_new > 0) {
std::string msg = cat("Updating atom names in ", resname, ':');
std::map<std::string, std::string> mapping;
for (const ChemComp::Atom& a : cc.atoms)
if (!a.old_id.empty() && a.old_id != a.id) {
mapping.emplace(a.old_id, a.id);
cat_to(msg, ' ', a.old_id, "->", a.id);
}
logger.mesg(msg);
rename_atom_names(st, resname, mapping);
}
}
}
} // namespace gemmi